Exemplo n.º 1
0
def _get_ideas_real(discussion, view_def=None, ids=None, user_id=None):
    user_id = user_id or Everyone
    # optimization: Recursive widget links.
    from assembl.models import (
        Widget, IdeaWidgetLink, IdeaDescendantsShowingWidgetLink)
    universal_widget_links = []
    by_idea_widget_links = defaultdict(list)
    widget_links = discussion.db.query(IdeaWidgetLink
        ).join(Widget).join(Discussion).filter(
        Widget.test_active(), Discussion.id == discussion.id,
        IdeaDescendantsShowingWidgetLink.polymorphic_filter()
        ).options(joinedload_all(IdeaWidgetLink.idea)).all()
    for wlink in widget_links:
        if isinstance(wlink.idea, RootIdea):
            universal_widget_links.append({
                '@type': wlink.external_typename(),
                'widget': Widget.uri_generic(wlink.widget_id)})
        else:
            for id in wlink.idea.get_all_descendants(True):
                by_idea_widget_links[Idea.uri_generic(id)].append({
                    '@type': wlink.external_typename(),
                    'widget': Widget.uri_generic(wlink.widget_id)})

    next_synthesis = discussion.get_next_synthesis()
    ideas = discussion.db.query(Idea).filter_by(
        discussion_id=discussion.id
    )

    ideas = ideas.outerjoin(SubGraphIdeaAssociation,
                    and_(SubGraphIdeaAssociation.sub_graph_id==next_synthesis.id, SubGraphIdeaAssociation.idea_id==Idea.id)
        )
    
    ideas = ideas.outerjoin(IdeaLink,
                    and_(IdeaLink.target_id==Idea.id)
        )
    
    ideas = ideas.order_by(IdeaLink.order, Idea.creation_date)
    
    if ids:
        ids = [get_database_id("Idea", id) for id in ids]
        ideas = ideas.filter(Idea.id.in_(ids))
    # remove tombstones
    ideas = ideas.filter(and_(*Idea.base_conditions()))
    ideas = ideas.options(
        joinedload_all(Idea.source_links),
        joinedload_all(Idea.has_showing_widget_links),
        undefer(Idea.num_children))

    permissions = get_permissions(user_id, discussion.id)
    Idea.prepare_counters(discussion.id, True)
    retval = [idea.generic_json(view_def, user_id, permissions)
              for idea in ideas]
    retval = [x for x in retval if x is not None]
    for r in retval:
        if r.get('widget_links', None) is not None:
            links = r['widget_links'][:]
            links.extend(universal_widget_links)
            links.extend(by_idea_widget_links[r['@id']])
            r['active_widget_links'] = links
    return retval
Exemplo n.º 2
0
def _get_ideas_real(discussion, view_def=None, ids=None, user_id=None):
    user_id = user_id or Everyone
    # optimization: Recursive widget links.
    from assembl.models import (
        Widget, IdeaWidgetLink, IdeaDescendantsShowingWidgetLink)
    universal_widget_links = []
    by_idea_widget_links = defaultdict(list)
    widget_links = discussion.db.query(IdeaWidgetLink
        ).join(Widget).join(Discussion).filter(
        Widget.test_active(), Discussion.id == discussion.id,
        IdeaDescendantsShowingWidgetLink.polymorphic_filter()
        ).options(joinedload_all(IdeaWidgetLink.idea)).all()
    for wlink in widget_links:
        if isinstance(wlink.idea, RootIdea):
            universal_widget_links.append({
                '@type': wlink.external_typename(),
                'widget': Widget.uri_generic(wlink.widget_id)})
        else:
            for id in wlink.idea.get_all_descendants(True):
                by_idea_widget_links[Idea.uri_generic(id)].append({
                    '@type': wlink.external_typename(),
                    'widget': Widget.uri_generic(wlink.widget_id)})

    next_synthesis = discussion.get_next_synthesis()
    ideas = discussion.db.query(Idea).filter_by(
        discussion_id=discussion.id
    )

    ideas = ideas.outerjoin(SubGraphIdeaAssociation,
                    and_(SubGraphIdeaAssociation.sub_graph_id==next_synthesis.id, SubGraphIdeaAssociation.idea_id==Idea.id)
        )
    
    ideas = ideas.outerjoin(IdeaLink,
                    and_(IdeaLink.target_id==Idea.id)
        )
    
    ideas = ideas.order_by(IdeaLink.order, Idea.creation_date)
    
    if ids:
        ids = [get_database_id("Idea", id) for id in ids]
        ideas = ideas.filter(Idea.id.in_(ids))
    # remove tombstones
    ideas = ideas.filter(and_(*Idea.base_conditions()))
    ideas = ideas.options(
        joinedload_all(Idea.source_links),
        joinedload_all(Idea.has_showing_widget_links),
        undefer(Idea.num_children))

    permissions = get_permissions(user_id, discussion.id)
    Idea.prepare_counters(discussion.id, True)
    retval = [idea.generic_json(view_def, user_id, permissions)
              for idea in ideas]
    retval = [x for x in retval if x is not None]
    for r in retval:
        if r.get('widget_links', None) is not None:
            links = r['widget_links'][:]
            links.extend(universal_widget_links)
            links.extend(by_idea_widget_links[r['@id']])
            r['active_widget_links'] = links
    return retval
Exemplo n.º 3
0
def test_get_ideas(discussion, test_app, synthesis_1,
                   subidea_1_1_1, test_session):
    all_ideas = test_app.get('/data/Idea')
    assert all_ideas.status_code == 200
    all_ideas = all_ideas.json
    disc_ideas = test_app.get('/data/Discussion/%d/ideas?view=id_only' %
                              (discussion.id,))
    assert disc_ideas.status_code == 200
    disc_ideas = disc_ideas.json
    assert set(all_ideas) == set(disc_ideas)
    synthesis_ideasassocs = test_app.get(
        '/data/Discussion/%d/views/%d/idea_assocs?view=id_only' % (
            discussion.id, synthesis_1.id))
    assert synthesis_ideasassocs.status_code == 200
    synthesis_ideasassocs = synthesis_ideasassocs.json
    syn_ideas = set()
    for assoc_id in synthesis_ideasassocs:
        a = SubGraphIdeaAssociation.get_instance(assoc_id)
        syn_ideas.add(Idea.uri_generic(a.idea_id))
    assert syn_ideas < set(disc_ideas)
    subidea_1_1_1_id = Idea.uri_generic(subidea_1_1_1.id)
    assert subidea_1_1_1_id in disc_ideas
    assert subidea_1_1_1_id not in syn_ideas
    syn_ideas = test_app.get(
        '/data/Discussion/%d/views/%d/ideas?view=id_only' % (
            discussion.id, synthesis_1.id))
    assert syn_ideas.status_code == 200
    syn_ideas = syn_ideas.json
    assert set(syn_ideas) < set(disc_ideas)
    subidea_1_1_1_id = Idea.uri_generic(subidea_1_1_1.id)
    assert subidea_1_1_1_id in disc_ideas
    assert subidea_1_1_1_id not in syn_ideas
Exemplo n.º 4
0
def create_idea(request):
    discussion_id = int(request.matchdict['discussion_id'])
    session = Discussion.default_db
    discussion = session.query(Discussion).get(int(discussion_id))
    user_id = request.authenticated_userid
    permissions = get_permissions(user_id, discussion.id)
    idea_data = json.loads(request.body)
    kwargs = {
        "discussion": discussion
    }

    for key, attr_name in langstring_fields.iteritems():
        if key in idea_data:
            ls_data = idea_data[key]
            if ls_data is None:
                continue
            assert isinstance(ls_data, dict)
            current = LangString.create_from_json(
                ls_data, user_id, permissions=permissions)
            kwargs[attr_name] = current

    new_idea = Idea(**kwargs)

    session.add(new_idea)

    if idea_data['parentId']:
        parent = Idea.get_instance(idea_data['parentId'])
    else:
        parent = discussion.root_idea
    session.add(IdeaLink(source=parent, target=new_idea, order=idea_data.get('order', 0.0)))

    session.flush()

    return {'ok': True, '@id': new_idea.uri()}
Exemplo n.º 5
0
def test_get_ideas(discussion, test_app, synthesis_1, subidea_1_1_1,
                   test_session):
    all_ideas = test_app.get('/data/Idea')
    assert all_ideas.status_code == 200
    all_ideas = all_ideas.json
    disc_ideas = test_app.get('/data/Discussion/%d/ideas?view=id_only' %
                              (discussion.id, ))
    assert disc_ideas.status_code == 200
    disc_ideas = disc_ideas.json
    assert set(all_ideas) == set(disc_ideas)
    synthesis_ideasassocs = test_app.get(
        '/data/Discussion/%d/views/%d/idea_assocs?view=id_only' %
        (discussion.id, synthesis_1.id))
    assert synthesis_ideasassocs.status_code == 200
    synthesis_ideasassocs = synthesis_ideasassocs.json
    syn_ideas = set()
    for assoc_id in synthesis_ideasassocs:
        a = SubGraphIdeaAssociation.get_instance(assoc_id)
        syn_ideas.add(Idea.uri_generic(a.idea_id))
    assert syn_ideas < set(disc_ideas)
    subidea_1_1_1_id = Idea.uri_generic(subidea_1_1_1.id)
    assert subidea_1_1_1_id in disc_ideas
    assert subidea_1_1_1_id not in syn_ideas
    syn_ideas = test_app.get(
        '/data/Discussion/%d/views/%d/ideas?view=id_only' %
        (discussion.id, synthesis_1.id))
    assert syn_ideas.status_code == 200
    syn_ideas = syn_ideas.json
    assert set(syn_ideas) < set(disc_ideas)
    subidea_1_1_1_id = Idea.uri_generic(subidea_1_1_1.id)
    assert subidea_1_1_1_id in disc_ideas
    assert subidea_1_1_1_id not in syn_ideas
Exemplo n.º 6
0
def create_idea(request):
    discussion_id = int(request.matchdict['discussion_id'])
    session = Discussion.default_db
    discussion = session.query(Discussion).get(int(discussion_id))
    idea_data = json.loads(request.body)

    new_idea = Idea(
        short_title=idea_data['shortTitle'],
        long_title=idea_data['longTitle'],
        discussion=discussion,
    )

    session.add(new_idea)

    if idea_data['parentId']:
        parent = Idea.get_instance(idea_data['parentId'])
    else:
        parent = discussion.root_idea
    session.add(
        IdeaLink(source=parent,
                 target=new_idea,
                 order=idea_data.get('order', 0.0)))

    session.flush()

    return {'ok': True, '@id': new_idea.uri()}
Exemplo n.º 7
0
def test_get_long_title_on_idea(graphql_request, idea_in_thread_phase):
    # This is the "What you need to know"
    idea_id = idea_in_thread_phase
    from graphene.relay import Node
    raw_id = int(Node.from_global_id(idea_id)[1])
    from assembl.models import Idea
    idea = Idea.get(raw_id)
    idea.long_title = u'What you need to know'
    idea.db.flush()
    res = schema.execute(u"""
query Idea($lang: String!, $id: ID!) {
  idea: node(id: $id) {
    ... on Idea {
      title(lang: $lang)
      longTitle
      description(lang: $lang)
      imgUrl
    }
  }
}
""",
                         context_value=graphql_request,
                         variable_values={
                             "id": idea_id,
                             "lang": u'en',
                         })
    assert json.loads(json.dumps(res.data)) == {
        u'idea': {
            u'title': u'Understanding the dynamics and issues',
            u'longTitle': u'What you need to know',
            u'description': u'',
            u'imgUrl': None
        }
    }
Exemplo n.º 8
0
def subidea_1_1_1_1_2_2(request, discussion, subidea_1_1_1_1_2, test_session):
    """An Idea fixture with a idealink to subidea_1_1_1_1_2 fixture -
    root_idea
        |-> subidea_1
            |-> subidea_1_1
                |-> subidea_1_1_1
                    |-> subidea_1_1_1_1
                        |-> subidea_1_1_1_1
                        |-> subidea_1_1_1_2
                            |-> subidea_1_1_1_1_2_1
                            |-> subidea_1_1_1_1_2_2"""

    from assembl.models import Idea, IdeaLink
    i = Idea(short_title=u"Federal programs are ineffective",
             discussion=discussion)
    test_session.add(i)
    l_11112_111122 = IdeaLink(source=subidea_1_1_1_1_2, target=i)
    test_session.add(l_11112_111122)
    test_session.flush()

    def fin():
        print "finalizer subidea_1_1_1_1_2_2"
        test_session.delete(l_11112_111122)
        test_session.delete(i)
        test_session.flush()

    request.addfinalizer(fin)
    return i
Exemplo n.º 9
0
def subidea_1_1_1_1_2_1(request, discussion, subidea_1_1_1_1_2, test_session):
    """An Idea fixture with a idealink to subidea_1_1_1_1_2 fixture -
    root_idea
        |-> subidea_1
            |-> subidea_1_1
                |-> subidea_1_1_1
                    |-> subidea_1_1_1_1
                        |-> subidea_1_1_1_1
                        |-> subidea_1_1_1_2
                            |-> subidea_1_1_1_1_2_1"""

    from assembl.models import Idea, IdeaLink
    i = Idea(short_title=u"Bad for the environment", discussion=discussion)
    test_session.add(i)
    l_11112_111121 = IdeaLink(source=subidea_1_1_1_1_2, target=i)
    test_session.add(l_11112_111121)
    test_session.flush()

    def fin():
        print "finalizer subidea_1_1_1_1_2_1"
        test_session.delete(l_11112_111121)
        test_session.delete(i)
        test_session.flush()

    request.addfinalizer(fin)
    return i
Exemplo n.º 10
0
def subidea_1_1_1_1_2(request, discussion, subidea_1_1_1_1, test_session):
    """An Idea fixture with a idealink to subidea_1_1_1_1 fixture -
    root_idea
        |-> subidea_1
            |-> subidea_1_1
                |-> subidea_1_1_1
                    |-> subidea_1_1_1_1
                        |-> subidea_1_1_1_1
                        |-> subidea_1_1_1_2"""

    from assembl.models import Idea, IdeaLink
    i = Idea(short_title=u"Environmental program cuts", discussion=discussion)
    test_session.add(i)
    l_1111_11112 = IdeaLink(source=subidea_1_1_1_1, target=i)
    test_session.add(l_1111_11112)
    test_session.flush()

    def fin():
        print "finalizer subidea_1_1_1_1_2"
        test_session.delete(l_1111_11112)
        test_session.delete(i)
        test_session.flush()

    request.addfinalizer(fin)
    return i
Exemplo n.º 11
0
def subidea_1_1_1(request, discussion, subidea_1_1, test_session):
    """An Idea fixture with a idealink to subidea_1_1 fixture::

        root_idea
            |-> subidea_1
                |-> subidea_1_1
                    |-> subidea_1_1_1"""

    from assembl.models import Idea, IdeaLink, LangString
    title = LangString.create(u"Lower government revenue", 'en')
    title.add_value(u'Moins de revenus pour le gouvernement', 'fr')
    i = Idea(title=title,
             discussion=discussion,
             description=LangString.create("Some definition of an idea", 'en'))
    test_session.add(i)
    l_11_111 = IdeaLink(source=subidea_1_1, target=i)
    test_session.add(l_11_111)
    test_session.flush()

    def fin():
        print("finalizer subidea_1_1_1")
        test_session.delete(l_11_111)
        test_session.delete(i)
        test_session.flush()

    request.addfinalizer(fin)
    return i
Exemplo n.º 12
0
def put_extract(request):
    """
    Updating an Extract
    """
    extract_id = request.matchdict['id']
    user_id = authenticated_userid(request)

    updated_extract_data = json.loads(request.body)
    extract = Extract.get_instance(extract_id)
    if not extract:
        raise HTTPNotFound("Extract with id '%s' not found." % extract_id)

    extract.owner_id = user_id or get_database_id("User", extract.owner_id)
    extract.order = updated_extract_data.get('order', extract.order)
    idea_id = updated_extract_data.get('idIdea', None)
    if idea_id:
        idea = Idea.get_instance(idea_id)
        if(idea.get_discussion_id() != extract.get_discussion_id()):
            raise HTTPBadRequest(
                "Extract from discussion %s cannot be associated with an idea from a different discussion." % extract.get_discussion_id())
        extract.idea = idea
    else:
        extract.idea = None

    Extract.db.add(extract)
    #TODO: Merge ranges. Sigh.

    return {'ok': True}
Exemplo n.º 13
0
def get_indexable_contents(session):
    from assembl.models import AgentProfile, Idea, Post
    from assembl.models.post import PublicationStates

    query = session.query(Idea
        ).filter(Idea.tombstone_condition()
        ).filter(Idea.hidden==False
        ).options(
            joinedload(Idea.title).joinedload("entries"),
            joinedload(Idea.synthesis_title).joinedload("entries"),
            joinedload(Idea.description).joinedload("entries")
        )

    for idea in query:
        yield idea

    query = session.query(AgentProfile)
    for user in query:
        yield user

    AllPost = with_polymorphic(Post, '*')
    query = session.query(AllPost
        ).filter(AllPost.tombstone_condition()
        ).filter(AllPost.hidden==False
        ).filter(AllPost.publication_state == PublicationStates.PUBLISHED
        ).options(
            joinedload(AllPost.subject).joinedload("entries"),
            joinedload(AllPost.body).joinedload("entries")
        )
    for post in query:
        for extract in post.extracts:
            yield extract

        yield post
Exemplo n.º 14
0
def subidea_1_1_1_1_1(request, discussion, subidea_1_1_1_1, test_session):
    """An Idea fixture with a idealink to subidea_1_1_1_1 fixture -
    root_idea
        |-> subidea_1
            |-> subidea_1_1
                |-> subidea_1_1_1
                    |-> subidea_1_1_1_1
                        |-> subidea_1_1_1_1"""

    from assembl.models import Idea, IdeaLink
    i = Idea(short_title=u"Job loss",
             discussion=discussion,
             definition="Some definition of an idea")
    test_session.add(i)
    l_1111_11111 = IdeaLink(source=subidea_1_1_1_1, target=i)
    test_session.add(l_1111_11111)
    test_session.flush()

    def fin():
        print "finalizer subidea_1_1_1_1_1_1"
        test_session.delete(l_1111_11111)
        test_session.delete(i)
        test_session.flush()

    request.addfinalizer(fin)
    return i
Exemplo n.º 15
0
def subidea_1_1_1_1_2(request, discussion, subidea_1_1_1_1, test_session):
    """An Idea fixture with a idealink to subidea_1_1_1_1 fixture -
    root_idea
        |-> subidea_1
            |-> subidea_1_1
                |-> subidea_1_1_1
                    |-> subidea_1_1_1_1
                        |-> subidea_1_1_1_1
                        |-> subidea_1_1_1_2"""

    from assembl.models import Idea, IdeaLink, LangString
    i = Idea(title=LangString.create(u"Environmental program cuts", 'en'),
             discussion=discussion,
             description=LangString.create("Some definition of an idea", 'en'))
    test_session.add(i)
    l_1111_11112 = IdeaLink(source=subidea_1_1_1_1, target=i)
    test_session.add(l_1111_11112)
    test_session.flush()

    def fin():
        print "finalizer subidea_1_1_1_1_2"
        test_session.delete(l_1111_11112)
        test_session.delete(i)
        test_session.flush()
    request.addfinalizer(fin)
    return i
Exemplo n.º 16
0
def widget_view(request):
    # IF_OWNED not applicable for widgets... so far
    ctx = request.context
    user_id = authenticated_userid(request) or Everyone
    permissions = get_permissions(user_id, ctx.get_discussion_id())
    check_permissions(ctx, user_id, permissions, CrudPermissions.READ)
    view = (request.matchdict or {}).get('view', None)\
        or ctx.get_default_view() or 'default'
    json = ctx._instance.generic_json(view, user_id, permissions)
    # json['discussion'] = ...
    if user_id != Everyone:
        user = User.get(user_id)
        user_state = ctx._instance.get_user_state(user_id)
        json['user'] = user.generic_json(view, user_id, permissions)
        json['user_permissions'] = get_permissions(
            user_id, ctx._instance.get_discussion_id())
        if user_state is not None:
            json['user_state'] = user_state
    target_id = request.GET.get('target', None)
    if target_id:
        idea = Idea.get_instance(target_id)
        if idea:
            json['target'] = idea.generic_json(view, user_id, permissions)
        else:
            return HTTPNotFound("No idea " + target_id)
    return json
Exemplo n.º 17
0
def voting_widget_view(request):
    user_id = authenticated_userid(request) or Everyone
    ctx = request.context
    view = (request.matchdict or {}).get('view', None)\
        or ctx.get_default_view() or 'default'
    widget = ctx._instance
    permissions = get_permissions(user_id, ctx.get_discussion_id())
    json = widget.generic_json(view, user_id, permissions)
    #json['discussion'] = ...
    if user_id != Everyone:
        user = User.get(user_id)
        json['user'] = user.generic_json(view, user_id, permissions)
        json['user_permissions'] = get_permissions(user_id,
                                                   widget.get_discussion_id())
        user_state = widget.get_user_state(user_id)
        if user_state is not None:
            json['user_state'] = user_state
    target_id = request.GET.get('target', None)
    if target_id and Idea.get_database_id(target_id):
        json['user_votes_url'] = widget.get_user_votes_url(target_id)
        json['voting_urls'] = widget.get_voting_urls(target_id)
    json['criteria'] = [
        idea.generic_json(view, user_id, permissions)
        for idea in widget.criteria
    ]
    return json
Exemplo n.º 18
0
def add_idea_to_synthesis(request):
    """Add an idea to an ExplictSubgraphView"""
    ctx = request.context
    graph_view = ctx.parent_instance
    if isinstance(graph_view, Synthesis) and not graph_view.is_next_synthesis:
        raise HTTPBadRequest("Synthesis is published")
    content = request.json
    idea_id = content.get('@id', None)
    if not idea_id:
        raise HTTPBadRequest("Post an idea with its @id")
    idea = Idea.get_instance(idea_id)
    if not idea:
        raise HTTPNotFound("Unknown idea")
    link = SubGraphIdeaAssociation(idea=idea, sub_graph=graph_view)
    duplicate = link.find_duplicate(False)
    if duplicate:
        link.delete()
        return duplicate.idea.generic_json()
    graph_view.db.add(link)
    graph_view.db.expire(graph_view, ["idea_assocs"])
    graph_view.send_to_changes()
    # special location
    return Response(
        json.dumps(idea.generic_json()), 201, content_type='application/json',
        location=request.url + "/" + str(idea.id))
Exemplo n.º 19
0
def voting_widget_view(request):
    user_id = authenticated_userid(request) or Everyone
    ctx = request.context
    view = (request.matchdict or {}).get('view', None)\
        or ctx.get_default_view() or 'default'
    widget = ctx._instance
    permissions = get_permissions(
        user_id, ctx.get_discussion_id())
    json = widget.generic_json(view, user_id, permissions)
    #json['discussion'] = ...
    if user_id != Everyone:
        user = User.get(user_id)
        json['user'] = user.generic_json(view, user_id, permissions)
        json['user_permissions'] = get_permissions(
            user_id, widget.get_discussion_id())
        user_state = widget.get_user_state(user_id)
        if user_state is not None:
            json['user_state'] = user_state
    target_id = request.GET.get('target', None)
    if target_id and Idea.get_database_id(target_id):
        json['user_votes_url'] = widget.get_user_votes_url(target_id)
        json['voting_urls'] = widget.get_voting_urls(target_id)
    json['criteria'] = [idea.generic_json(view, user_id, permissions)
                        for idea in widget.criteria]
    return json
Exemplo n.º 20
0
def post_draft_for_bright_mirror(
        request, test_session, discussion, moderator_user,
        bright_mirror):
    from assembl.models import Post, Idea, LangString, IdeaRelatedPostLink, PublicationStates
    from graphene.relay import Node
    idea_id = bright_mirror
    raw_id = int(Node.from_global_id(idea_id)[1])
    idea = Idea.get(raw_id)
    p = Post(
        discussion=discussion, creator=moderator_user,
        subject=LangString.create(u"Draft"),
        body=LangString.create(u"A simple draft fiction"),
        type='post', publication_state=PublicationStates.DRAFT,
        message_id="*****@*****.**",
        creation_date = datetime.utcnow() - timedelta(days=7))

    idc = IdeaRelatedPostLink(
        idea=idea,
        creator=moderator_user,
        content=p)

    test_session.add(p)
    test_session.add(idc)
    test_session.flush()

    def fin():
        print "finalizer post_draft_for_bright_mirror"
        test_session.delete(p)
        test_session.delete(idc)
        test_session.flush()

    request.addfinalizer(fin)
    return p
Exemplo n.º 21
0
def post_published_for_bright_mirror_participant(
        request, test_session, discussion, admin_user, participant1_user,
        bright_mirror):
    from assembl.models import Post, Idea, LangString, IdeaRelatedPostLink, PublicationStates
    from graphene.relay import Node
    idea_id = bright_mirror
    raw_id = int(Node.from_global_id(idea_id)[1])
    idea = Idea.get(raw_id)
    p = Post(
        discussion=discussion, creator=participant1_user,
        subject=LangString.create(u"Published by participant"),
        body=LangString.create(u"A simple published fiction by participant"),
        type='post', publication_state=PublicationStates.PUBLISHED,
        message_id="*****@*****.**",
        creation_date = datetime.utcnow())

    idc = IdeaRelatedPostLink(
        idea=idea,
        creator=admin_user,
        content=p)

    test_session.add(p)
    test_session.add(idc)
    test_session.flush()

    def fin():
        print "finalizer post_published_for_bright_mirror"
        test_session.delete(p)
        test_session.delete(idc)
        test_session.flush()

    request.addfinalizer(fin)
    return p
Exemplo n.º 22
0
def widget_view(request):
    # IF_OWNED not applicable for widgets... so far
    ctx = request.context
    user_id = authenticated_userid(request) or Everyone
    permissions = get_permissions(
        user_id, ctx.get_discussion_id())
    check_permissions(ctx, user_id, permissions, CrudPermissions.READ)
    view = (request.matchdict or {}).get('view', None)\
        or ctx.get_default_view() or 'default'
    json = ctx._instance.generic_json(view, user_id, permissions)
    # json['discussion'] = ...
    if user_id != Everyone:
        user = User.get(user_id)
        user_state = ctx._instance.get_user_state(user_id)
        json['user'] = user.generic_json(view, user_id, permissions)
        json['user_permissions'] = get_permissions(
            user_id, ctx._instance.get_discussion_id())
        if user_state is not None:
            json['user_state'] = user_state
    target_id = request.GET.get('target', None)
    if target_id:
        idea = Idea.get_instance(target_id)
        if idea:
            json['target'] = idea.generic_json(view, user_id, permissions)
        else:
            return HTTPNotFound("No idea "+target_id)
    return json
Exemplo n.º 23
0
def subidea_1_1_1_1_2_2(request, discussion, subidea_1_1_1_1_2, test_session):
    """An Idea fixture with a idealink to subidea_1_1_1_1_2 fixture::

        root_idea
            |-> subidea_1
                |-> subidea_1_1
                    |-> subidea_1_1_1
                        |-> subidea_1_1_1_1
                            |-> subidea_1_1_1_1
                            |-> subidea_1_1_1_2
                                |-> subidea_1_1_1_1_2_1
                                |-> subidea_1_1_1_1_2_2"""

    from assembl.models import Idea, IdeaLink, LangString
    i = Idea(title=LangString.create(u"Federal programs are ineffective",
                                     'en'),
             discussion=discussion,
             description=LangString.create("Some definition of an idea", 'en'))
    test_session.add(i)
    l_11112_111122 = IdeaLink(source=subidea_1_1_1_1_2, target=i)
    test_session.add(l_11112_111122)
    test_session.flush()

    def fin():
        print("finalizer subidea_1_1_1_1_2_2")
        test_session.delete(l_11112_111122)
        test_session.delete(i)
        test_session.flush()

    request.addfinalizer(fin)
    return i
Exemplo n.º 24
0
def get_indexable_contents(session):
    from assembl.models import AgentProfile, Idea, Post
    from assembl.models.post import PublicationStates

    query = session.query(Idea).filter(
        Idea.tombstone_condition()).filter(Idea.hidden == False).options(
            joinedload(Idea.title).joinedload("entries"),
            joinedload(Idea.synthesis_title).joinedload("entries"),
            joinedload(Idea.description).joinedload("entries"))

    for idea in query:
        yield idea

    query = session.query(AgentProfile)
    for user in query:
        yield user

    AllPost = with_polymorphic(Post, '*')
    query = session.query(AllPost).filter(
        AllPost.tombstone_condition()).filter(AllPost.hidden == False).filter(
            AllPost.publication_state == PublicationStates.PUBLISHED).options(
                joinedload(AllPost.subject).joinedload("entries"),
                joinedload(AllPost.body).joinedload("entries"))
    for post in query:
        for extract in post.extracts:
            yield extract

        yield post
Exemplo n.º 25
0
def creativity_session_widget_new_idea(
        request, test_session, discussion, subidea_1,
        creativity_session_widget, participant1_user):
    from assembl.models import (Idea, IdeaLink, GeneratedIdeaWidgetLink,
                                IdeaProposalPost)
    i = Idea(
        discussion=discussion,
        short_title="generated idea")
    test_session.add(i)
    l_1_wi = IdeaLink(source=subidea_1, target=i)
    test_session.add(l_1_wi)
    l_w_wi = GeneratedIdeaWidgetLink(
        widget=creativity_session_widget,
        idea=i)
    ipp = IdeaProposalPost(
        proposes_idea=i, creator=participant1_user, discussion=discussion,
        message_id='proposal', subject=u"propose idea", body="")
    test_session.add(ipp)
    def fin():
        print "finalizer creativity_session_widget_new_idea"
        test_session.delete(ipp)
        test_session.delete(l_w_wi)
        test_session.delete(l_1_wi)
        test_session.delete(i)
        test_session.flush()
    request.addfinalizer(fin)

    return i
Exemplo n.º 26
0
def test_add_subidea_in_synthesis(discussion, test_app, synthesis_1,
                                  subidea_1_1, test_session):
    new_idea_r = test_app.post_json(
        '/data/Conversation/%d/views/%d/ideas/%d/children' %
        (discussion.id, synthesis_1.id, subidea_1_1.id), {
            "shortTitle": {
                "@type":
                "LangString",
                "entries": [{
                    "@type": "LangStringEntry",
                    "@language": "en",
                    "value": "New subidea"
                }]
            }
        })
    assert new_idea_r.status_code == 201
    link = new_idea_r.location
    new_idea = Idea.get_instance(link)
    assert new_idea
    db = discussion.db
    idea_link = db.query(IdeaLink).filter_by(target=new_idea,
                                             source=subidea_1_1).first()
    assert idea_link
    idea_assoc = db.query(SubGraphIdeaAssociation).filter_by(
        idea=new_idea, sub_graph=synthesis_1).first()
    assert idea_assoc
    idealink_assoc = db.query(SubGraphIdeaLinkAssociation).filter_by(
        sub_graph=synthesis_1, idea_link=idea_link).first()
    assert idealink_assoc

    idealink_assoc.delete()
    idea_assoc.delete()
    idea_link.delete()
    new_idea.delete()
    new_idea.db.commit()
Exemplo n.º 27
0
def subidea_1(
    request,
    discussion,
    root_idea,
    test_session,
):
    """An Idea fixture with a idealink to root idea fixture -
    root_idea
        |-> subidea_1"""

    from assembl.models import Idea, IdeaLink, LangString
    i = Idea(title=LangString.create(u"Favor economic growth", 'en'),
             discussion=discussion,
             description=LangString.create("Some definition of an idea", 'en'))
    test_session.add(i)
    l_r_1 = IdeaLink(source=root_idea, target=i)
    test_session.add(l_r_1)
    test_session.flush()

    def fin():
        print("finalizer subidea_1")
        test_session.delete(l_r_1)
        test_session.delete(i)
        test_session.flush()

    request.addfinalizer(fin)
    return i
Exemplo n.º 28
0
def subidea_1_2_1(request, discussion, subidea_1_2, test_session):
    """An Idea fixture with a idealink to subidea_1 fixture::

        root_idea
            |-> subidea_1
                |-> subidea_1_2
                    |-> subidea_1_2_1"""

    from assembl.models import Idea, IdeaLink, LangString
    i = Idea(title=LangString.create(u"Bad for the environment", 'en'),
             discussion=discussion,
             description=LangString.create("Some definition of an idea", 'en'))
    test_session.add(i)
    l_12_121 = IdeaLink(source=subidea_1_2, target=i)
    test_session.add(l_12_121)
    test_session.flush()

    def fin():
        print("finalizer subidea_1_2_1")
        test_session.delete(l_12_121)
        test_session.delete(i)
        test_session.flush()

    request.addfinalizer(fin)
    return i
Exemplo n.º 29
0
def creativity_session_widget_new_idea(request, test_session, discussion,
                                       subidea_1, creativity_session_widget,
                                       participant1_user):
    """An Idea fixture with an bound ideaLink,
    GeneratedIdeaWidgetLink, and a IdeaProposalPost"""

    from assembl.models import (Idea, IdeaLink, GeneratedIdeaWidgetLink,
                                IdeaProposalPost, LangString)
    i = Idea(discussion=discussion,
             title=LangString.create(u"generated idea", 'en'))
    test_session.add(i)
    l_1_wi = IdeaLink(source=subidea_1, target=i)
    test_session.add(l_1_wi)
    l_w_wi = GeneratedIdeaWidgetLink(widget=creativity_session_widget, idea=i)
    ipp = IdeaProposalPost(proposes_idea=i,
                           creator=participant1_user,
                           discussion=discussion,
                           message_id='*****@*****.**',
                           subject=LangString.create(u"propose idea"),
                           body=LangString.EMPTY(test_session))
    test_session.add(ipp)

    def fin():
        print "finalizer creativity_session_widget_new_idea"
        test_session.delete(ipp)
        test_session.delete(l_w_wi)
        test_session.delete(l_1_wi)
        test_session.delete(i)
        test_session.flush()

    request.addfinalizer(fin)

    return i
Exemplo n.º 30
0
def subidea_1_1_1_1(request, discussion, subidea_1_1_1, test_session):
    """An Idea fixture with a idealink to subidea_1_1_1 fixture::

        root_idea
            |-> subidea_1
                |-> subidea_1_1
                    |-> subidea_1_1_1
                        |->subidea_1_1_1_1"""

    from assembl.models import Idea, IdeaLink, LangString
    i = Idea(title=LangString.create(u"Austerity yields contraction", 'en'),
             discussion=discussion,
             description=LangString.create("Some definition of an idea", 'en'))
    test_session.add(i)
    l_111_1111 = IdeaLink(source=subidea_1_1_1, target=i)
    test_session.add(l_111_1111)
    test_session.flush()

    def fin():
        print "finalizer subidea_1_1_1_1"
        test_session.delete(l_111_1111)
        test_session.delete(i)
        test_session.flush()

    request.addfinalizer(fin)
    return i
Exemplo n.º 31
0
def add_idea_to_synthesis(request):
    """Add an idea to an ExplictSubgraphView"""
    ctx = request.context
    graph_view = ctx.parent_instance
    if isinstance(graph_view, Synthesis) and not graph_view.is_next_synthesis:
        raise HTTPBadRequest("Synthesis is published")
    content = request.json
    idea_id = content.get('@id', None)
    if not idea_id:
        raise HTTPBadRequest("Post an idea with its @id")
    idea = Idea.get_instance(idea_id)
    if not idea:
        raise HTTPNotFound("Unknown idea")
    link = SubGraphIdeaAssociation(idea=idea, sub_graph=graph_view)
    duplicate = link.find_duplicate(False)
    if duplicate:
        link.delete()
        return duplicate.idea.generic_json()
    graph_view.db.add(link)
    graph_view.db.expire(graph_view, ["idea_assocs"])
    graph_view.send_to_changes()
    # special location
    return Response(json.dumps(idea.generic_json()),
                    201,
                    content_type='application/json',
                    location=request.url + "/" + str(idea.id))
Exemplo n.º 32
0
def delete_idea(request):
    idea_id = request.matchdict['id']
    idea = Idea.get_instance(idea_id)

    if not idea:
        raise HTTPNotFound("Idea with id '%s' not found." % idea_id)
    if isinstance(idea, RootIdea):
        raise HTTPBadRequest("Cannot delete root idea.")
    num_childrens = len(idea.children)
    if num_childrens > 0:
        raise HTTPBadRequest("Idea cannot be deleted because it still has %d child ideas." % num_childrens)
    num_extracts = len(idea.extracts)
    if num_extracts > 0:
        raise HTTPBadRequest("Idea cannot be deleted because it still has %d extracts." % num_extracts)
    db = Idea.db()
    db.delete(idea)
    request.response.status = HTTPNoContent.code
    return HTTPNoContent()
Exemplo n.º 33
0
def post_ids_of(idea):
    related = text(
        Idea._get_related_posts_statement(),
        bindparams=[bindparam('root_idea_id', idea.id),
                    bindparam('discussion_id', idea.discussion_id)]
        ).columns(column('post_id')).alias('related')
    post_ids = idea.db.query(Content.id).join(
        related, Content.id == related.c.post_id)
    return [x for (x,) in post_ids]
Exemplo n.º 34
0
def create_idea(request):
    discussion_id = int(request.matchdict["discussion_id"])
    session = Discussion.default_db
    discussion = session.query(Discussion).get(int(discussion_id))
    idea_data = json.loads(request.body)

    new_idea = Idea(short_title=idea_data["shortTitle"], long_title=idea_data["longTitle"], discussion=discussion)

    session.add(new_idea)

    if idea_data["parentId"]:
        parent = Idea.get_instance(idea_data["parentId"])
    else:
        parent = discussion.root_idea
    session.add(IdeaLink(source=parent, target=new_idea, order=idea_data.get("order", 0.0)))

    session.flush()

    return {"ok": True, "@id": new_idea.uri()}
Exemplo n.º 35
0
def idea_connexions(request):
    # Returns the ideas this post is connected to.
    # This is a list of tuples of the form:
    # [idea, connexion_idea, connexion_post, extract?]
    # the idea is an ancestor or equal to connexion_idea
    # the connexion_post is an ancestor or equal to current post
    from assembl.models import Idea
    ctx = request.context
    post = ctx._instance
    return Idea.get_idea_ids_showing_post(post.id, True, True)
Exemplo n.º 36
0
def mark_post_read(request):
    """Mark this post as un/read. Return the read post count for all affected ideas."""
    discussion_id = int(request.matchdict['discussion_id'])
    discussion = Discussion.get_instance(discussion_id)
    post_id = request.matchdict['id']
    post = Post.get_instance(post_id)
    if not post:
        raise HTTPNotFound("Post with id '%s' not found." % post_id)
    post_id = post.id
    user_id = authenticated_userid(request)
    if not user_id:
        raise HTTPUnauthorized()
    read_data = json.loads(request.body)
    db = discussion.db
    change = False
    with transaction.manager:
        if read_data.get('read', None) is False:
            view = db.query(ViewPost).filter_by(post_id=post_id,
                                                actor_id=user_id,
                                                tombstone_date=None).first()
            if view:
                change = True
                view.is_tombstone = True
        else:
            count = db.query(ViewPost).filter_by(post_id=post_id,
                                                 actor_id=user_id,
                                                 tombstone_date=None).count()
            if not count:
                change = True
                db.add(ViewPost(post=post, actor_id=user_id))

    new_counts = []
    if change:
        new_counts = Idea.idea_read_counts(discussion_id, post_id, user_id)

    return {
        "ok":
        True,
        "ideas": [{
            "@id": Idea.uri_generic(idea_id),
            "num_read_posts": read_posts
        } for (idea_id, read_posts) in new_counts]
    }
Exemplo n.º 37
0
def mark_post_read(request):
    discussion_id = int(request.matchdict["discussion_id"])
    discussion = Discussion.get_instance(discussion_id)
    post_id = request.matchdict["id"]
    post = Post.get_instance(post_id)
    if not post:
        raise HTTPNotFound("Post with id '%s' not found." % post_id)
    post_id = post.id
    user_id = authenticated_userid(request)
    if not user_id:
        raise HTTPUnauthorized()
    read_data = json.loads(request.body)
    db = Discussion.db()
    change = False
    with transaction.manager:
        if read_data.get("read", None) is False:
            view = db.query(ViewPost).filter(ViewPost.post_id == post_id).filter(Action.actor_id == user_id).first()
            if view:
                change = True
                db.delete(view)
        else:
            count = db.query(ViewPost).filter(ViewPost.post_id == post_id).filter(Action.actor_id == user_id).count()
            if not count:
                change = True
                db.add(ViewPost(post=post, actor_id=user_id))

    new_counts = []
    if change:
        new_counts = Idea.idea_counts(discussion_id, post_id, user_id)

    return {
        "ok": True,
        "ideas": [
            {
                "@id": Idea.uri_generic(idea_id),
                "@type": db.query(Idea).get(idea_id).external_typename(),
                "num_posts": total_posts,
                "num_read_posts": read_posts,
            }
            for (idea_id, total_posts, read_posts) in new_counts
        ],
    }
Exemplo n.º 38
0
def post_to_vote_votables(request):
    ctx = request.context
    target_id = request.POST.get('id', None)
    idea = None
    if target_id:
        idea = Idea.get_instance(target_id)
    if not idea:
        raise HTTPNotFound
    widget = ctx.parent_instance
    widget.add_votable(idea)
    return HTTPOk()  # Not sure this can be called a creation
Exemplo n.º 39
0
def post_to_vote_votables(request):
    ctx = request.context
    target_id = request.POST.get('id', None)
    idea = None
    if target_id:
        idea = Idea.get_instance(target_id)
    if not idea:
        raise HTTPNotFound
    widget = ctx.parent_instance
    widget.add_votable(idea)
    return HTTPOk()  # Not sure this can be called a creation
Exemplo n.º 40
0
def get_idea(request):
    idea_id = request.matchdict['id']
    idea = Idea.get_instance(idea_id)
    view_def = request.GET.get('view')
    discussion_id = int(request.matchdict['discussion_id'])
    user_id = authenticated_userid(request) or Everyone
    permissions = get_permissions(user_id, discussion_id)

    if not idea:
        raise HTTPNotFound("Idea with id '%s' not found." % idea_id)

    return idea.generic_json(view_def, user_id, permissions)
Exemplo n.º 41
0
def get_idea(request):
    idea_id = request.matchdict['id']
    idea = Idea.get_instance(idea_id)
    view_def = request.GET.get('view')
    discussion_id = int(request.matchdict['discussion_id'])
    user_id = authenticated_userid(request)
    permissions = get_permissions(user_id, discussion_id)

    if not idea:
        raise HTTPNotFound("Idea with id '%s' not found." % idea_id)

    return idea.generic_json(view_def, user_id, permissions)
Exemplo n.º 42
0
def get_idea(request):
    idea_id = request.matchdict['id']
    idea = Idea.get_instance(idea_id)
    view_def = request.GET.get('view')

    if not idea:
        raise HTTPNotFound("Idea with id '%s' not found." % idea_id)

    if view_def:
        return idea.generic_json(view_def)
    else:
        return idea.generic_json()
Exemplo n.º 43
0
def mark_post_read(request):
    """Mark this post as un/read. Return the read post count for all affected ideas."""
    discussion_id = int(request.matchdict['discussion_id'])
    discussion = Discussion.get_instance(discussion_id)
    post_id = request.matchdict['id']
    post = Post.get_instance(post_id)
    if not post:
        raise HTTPNotFound("Post with id '%s' not found." % post_id)
    post_id = post.id
    user_id = request.authenticated_userid
    if not user_id:
        raise HTTPUnauthorized()
    read_data = json.loads(request.body)
    db = discussion.db
    change = False
    with transaction.manager:
        if read_data.get('read', None) is False:
            view = db.query(ViewPost).filter_by(
                post_id=post_id, actor_id=user_id,
                tombstone_date=None).first()
            if view:
                change = True
                view.is_tombstone = True
        else:
            count = db.query(ViewPost).filter_by(
                post_id=post_id, actor_id=user_id,
                tombstone_date=None).count()
            if not count:
                change = True
                db.add(ViewPost(post=post, actor_id=user_id))

    new_counts = []
    if change:
        new_counts = Idea.idea_read_counts(discussion_id, post_id, user_id)

    return { "ok": True, "ideas": [
        {"@id": Idea.uri_generic(idea_id),
         "num_read_posts": read_posts
        } for (idea_id, read_posts) in new_counts] }
Exemplo n.º 44
0
def create_idea(request):
    discussion_id = int(request.matchdict['discussion_id'])
    session = Discussion.db()
    discussion = session.query(Discussion).get(int(discussion_id))
    idea_data = json.loads(request.body)

    new_idea = Idea(
        short_title=idea_data['shortTitle'],
        long_title=idea_data['longTitle'],
        discussion=discussion,
        )

    session.add(new_idea)

    if idea_data['parentId']:
        parent = Idea.get_instance(idea_data['parentId'])
    else:
        parent = discussion.root_idea
    session.add(IdeaLink(source=parent, target=new_idea, order=idea_data.get('order', 0.0)))

    session.flush()

    return {'ok': True, '@id': new_idea.uri()}
Exemplo n.º 45
0
def put_extract(request):
    """
    Updating an Extract
    """
    extract_id = request.matchdict['id']
    user_id = authenticated_userid(request)
    discussion = request.context

    if not user_id:
        # Straight from annotator
        token = request.headers.get('X-Annotator-Auth-Token')
        if token:
            token = decode_token(token,
                                 request.registry.settings['session.secret'])
            if token:
                user_id = token['userId']
        user_id = user_id or Everyone

    extract = Extract.get_instance(extract_id)
    if not extract:
        raise HTTPNotFound("Extract with id '%s' not found." % extract_id)
    permissions = get_permissions(user_id, discussion.id, extract)

    if P_EDIT_EXTRACT not in permissions:
        raise HTTPForbidden()

    updated_extract_data = json.loads(request.body)

    extract.owner_id = user_id or AgentProfile.get_database_id(
        extract.owner_id)
    extract.order = updated_extract_data.get('order', extract.order)
    extract.important = updated_extract_data.get('important',
                                                 extract.important)
    idea_id = updated_extract_data.get('idIdea', None)
    if idea_id:
        idea = Idea.get_instance(idea_id)
        if (idea.discussion != extract.discussion):
            raise HTTPBadRequest(
                "Extract from discussion %s cannot be associated with an idea from a different discussion."
                % extract.get_discussion_id())
        if not idea.has_permission_req(P_ASSOCIATE_EXTRACT):
            raise HTTPForbidden("Cannot associate extact with this idea")
        extract.idea = idea
    else:
        extract.idea = None

    Extract.default_db.add(extract)
    #TODO: Merge ranges. Sigh.

    return {'ok': True}
Exemplo n.º 46
0
def get_idea_extracts(request):
    idea_id = request.matchdict["id"]
    idea = Idea.get_instance(idea_id)
    view_def = request.GET.get("view") or "default"
    discussion_id = int(request.matchdict["discussion_id"])
    user_id = authenticated_userid(request) or Everyone
    permissions = get_permissions(user_id, discussion_id)

    if not idea:
        raise HTTPNotFound("Idea with id '%s' not found." % idea_id)

    extracts = Extract.default_db.query(Extract).filter(Extract.idea_id == idea.id).order_by(Extract.order.desc())

    return [extract.generic_json(view_def, user_id, permissions) for extract in extracts]
Exemplo n.º 47
0
def create_idea(request):
    discussion_id = int(request.matchdict['discussion_id'])
    session = Discussion.default_db
    discussion = session.query(Discussion).get(int(discussion_id))
    user_id = request.authenticated_userid
    permissions = get_permissions(user_id, discussion.id)
    idea_data = json.loads(request.body)
    kwargs = {"discussion": discussion}

    for key, attr_name in langstring_fields.iteritems():
        if key in idea_data:
            ls_data = idea_data[key]
            if ls_data is None:
                continue
            assert isinstance(ls_data, dict)
            current = LangString.create_from_json(ls_data,
                                                  user_id,
                                                  permissions=permissions)
            kwargs[attr_name] = current

    new_idea = Idea(**kwargs)

    session.add(new_idea)

    if idea_data['parentId']:
        parent = Idea.get_instance(idea_data['parentId'])
    else:
        parent = discussion.root_idea
    session.add(
        IdeaLink(source=parent,
                 target=new_idea,
                 order=idea_data.get('order', 0.0)))

    session.flush()

    return {'ok': True, '@id': new_idea.uri()}
Exemplo n.º 48
0
def subidea_1_1_1(request, discussion, subidea_1_1, test_session):
    from assembl.models import Idea, IdeaLink
    i = Idea(short_title="idea 1.1.1", discussion=discussion)
    test_session.add(i)
    l_11_111 = IdeaLink(source=subidea_1_1, target=i)
    test_session.add(l_11_111)
    test_session.flush()

    def fin():
        print "finalizer subidea_1_1_1"
        test_session.delete(l_11_111)
        test_session.delete(i)
        test_session.flush()
    request.addfinalizer(fin)
    return i
Exemplo n.º 49
0
def get_idea_extracts(request):
    idea_id = request.matchdict['id']
    idea = Idea.get_instance(idea_id)
    view_def = request.GET.get('view')

    if not idea:
        raise HTTPNotFound("Idea with id '%s' not found." % idea_id)

    extracts = Extract.db.query(Extract).filter(
        Extract.idea_id == idea.id
    ).order_by(Extract.order.desc())

    if view_def:
        return [extract.generic_json(view_def) for extract in extracts]
    else:
        return [extract.serializable() for extract in extracts]
Exemplo n.º 50
0
def put_extract(request):
    """
    Updating an Extract
    """
    extract_id = request.matchdict['id']
    user_id = authenticated_userid(request)
    discussion_id = int(request.matchdict['discussion_id'])

    if not user_id:
        # Straight from annotator
        token = request.headers.get('X-Annotator-Auth-Token')
        if token:
            token = decode_token(
                token, request.registry.settings['session.secret'])
            if token:
                user_id = token['userId']
    if not user_id:
        user_id = Everyone

    updated_extract_data = json.loads(request.body)
    extract = Extract.get_instance(extract_id)
    if not extract:
        raise HTTPNotFound("Extract with id '%s' not found." % extract_id)

    if not (user_has_permission(discussion_id, user_id, P_EDIT_EXTRACT)
        or (user_has_permission(discussion_id, user_id, P_EDIT_MY_EXTRACT)
            and user_id == extract.owner_id)):
        return HTTPForbidden()

    extract.owner_id = user_id or get_database_id("User", extract.owner_id)
    extract.order = updated_extract_data.get('order', extract.order)
    extract.important = updated_extract_data.get('important', extract.important)
    idea_id = updated_extract_data.get('idIdea', None)
    if idea_id:
        idea = Idea.get_instance(idea_id)
        if(idea.discussion != extract.discussion):
            raise HTTPBadRequest(
                "Extract from discussion %s cannot be associated with an idea from a different discussion." % extract.get_discussion_id())
        extract.idea = idea
    else:
        extract.idea = None

    Extract.db.add(extract)
    #TODO: Merge ranges. Sigh.

    return {'ok': True}
Exemplo n.º 51
0
def test_add_subidea_in_synthesis(
        discussion, test_app, synthesis_1, subidea_1_1, test_session):
    new_idea_r = test_app.post(
        '/data/Discussion/%d/views/%d/ideas/%d/children' % (
            discussion.id, synthesis_1.id, subidea_1_1.id),
        {"short_title": "New subidea"})
    assert new_idea_r.status_code == 201
    link = new_idea_r.location
    new_idea = Idea.get_instance(link)
    assert new_idea
    db = discussion.db
    idea_link = db.query(IdeaLink).filter_by(
        target=new_idea, source=subidea_1_1).first()
    assert idea_link
    idea_assoc = db.query(SubGraphIdeaAssociation).filter_by(
        idea=new_idea, sub_graph=synthesis_1).first()
    assert idea_assoc
    idealink_assoc = db.query(SubGraphIdeaLinkAssociation).filter_by(
        sub_graph=synthesis_1, idea_link=idea_link).first()
    assert idealink_assoc
Exemplo n.º 52
0
def delete_idea(request):
    idea_id = request.matchdict['id']
    idea = Idea.get_instance(idea_id)

    if not idea:
        raise HTTPNotFound("Idea with id '%s' not found." % idea_id)
    if isinstance(idea, RootIdea):
        raise HTTPBadRequest("Cannot delete root idea.")
    num_childrens = len(idea.children)
    if num_childrens > 0:
        raise HTTPBadRequest("Idea cannot be deleted because it still has %d child ideas." % num_childrens)
    num_extracts = len(idea.extracts)
    if num_extracts > 0:
        raise HTTPBadRequest("Idea cannot be deleted because it still has %d extracts." % num_extracts)
    for link in idea.source_links:
        link.is_tombstone = True
    idea.is_tombstone = True
    # Maybe return tombstone() ?
    request.response.status = HTTPNoContent.code
    return HTTPNoContent()
Exemplo n.º 53
0
def get_idea_extracts(request):
    idea_id = request.matchdict['id']
    idea = Idea.get_instance(idea_id)
    view_def = request.GET.get('view')
    discussion_id = int(request.matchdict['discussion_id'])
    user_id = authenticated_userid(request)
    permissions = get_permissions(user_id, discussion_id)

    if not idea:
        raise HTTPNotFound("Idea with id '%s' not found." % idea_id)

    extracts = Extract.db.query(Extract).filter(
        Extract.idea_id == idea.id
    ).order_by(Extract.order.desc())

    if view_def:
        return [extract.generic_json(view_def, user_id, permissions)
                for extract in extracts]
    else:
        return [extract.serializable() for extract in extracts]
Exemplo n.º 54
0
def get_posts(request):
    """
    Query interface on posts
    Filters have two forms:
    only_*, is for filters that cannot be reversed (ex: only_synthesis)
    is_*, is for filters that can be reversed (ex:is_unread=true returns only unread
    order can be chronological, reverse_chronological
    message, is_unread=false returns only read messages)
    """
    localizer = request.localizer
    discussion_id = int(request.matchdict['discussion_id'])
    discussion = Discussion.get(int(discussion_id))
    if not discussion:
        raise HTTPNotFound(localizer.translate(
            _("No discussion found with id=%s")) % discussion_id)

    discussion.import_from_sources()

    user_id = authenticated_userid(request) or Everyone
    permissions = get_permissions(user_id, discussion_id)

    DEFAULT_PAGE_SIZE = 25
    page_size = DEFAULT_PAGE_SIZE

    filter_names = [ 
        filter_name for filter_name \
        in request.GET.getone('filters').split(',') \
        if filter_name
    ] if request.GET.get('filters') else []

    try:
        page = int(request.GET.getone('page'))
    except (ValueError, KeyError):
        page = 1

    text_search = request.GET.get('text_search', None)

    order = request.GET.get('order')
    if order == None:
        order = 'chronological'
    assert order in ('chronological', 'reverse_chronological', 'score')
    if order == 'score':
        assert text_search is not None

    if page < 1:
        page = 1

    root_post_id = request.GET.getall('root_post_id')
    if root_post_id:
        root_post_id = get_database_id("Post", root_post_id[0])
    family_post_id = request.GET.getall('family_post_id')
    if family_post_id:
        family_post_id = get_database_id("Post", family_post_id[0])

    root_idea_id = request.GET.getall('root_idea_id')
    if root_idea_id:
        root_idea_id = get_database_id("Idea", root_idea_id[0])

    ids = request.GET.getall('ids[]')
    if ids:
        ids = [get_database_id("Post", id) for id in ids]

    view_def = request.GET.get('view') or 'default'

    only_synthesis = request.GET.get('only_synthesis')

    post_author_id = request.GET.get('post_author')
    if post_author_id:
        post_author_id = get_database_id("AgentProfile", post_author_id)
        assert AgentProfile.get(post_author_id), "Unable to find agent profile with id " + post_author_id

    post_replies_to = request.GET.get('post_replies_to')
    if post_replies_to:
        post_replies_to = get_database_id("AgentProfile", post_replies_to)
        assert AgentProfile.get(post_replies_to), "Unable to find agent profile with id " + post_replies_to

    posted_after_date = request.GET.get('posted_after_date')
    posted_before_date = request.GET.get('posted_before_date')

    PostClass = SynthesisPost if only_synthesis == "true" else Post
    ideaContentLinkQuery = discussion.db.query(
        PostClass.id, PostClass.idea_content_links_above_post)
    if order == 'score':
        posts = discussion.db.query(PostClass, Content.body_text_index.score_name)
    else:
        posts = discussion.db.query(PostClass)

    posts = posts.filter(
        PostClass.discussion_id == discussion_id,
    )
    ideaContentLinkQuery = ideaContentLinkQuery.filter(
        PostClass.discussion_id == discussion_id)
    ##no_of_posts_to_discussion = posts.count()

    post_data = []

    only_orphan = request.GET.get('only_orphan')
    if only_orphan == "true":
        if root_idea_id:
            raise HTTPBadRequest(localizer.translate(
                _("Getting orphan posts of a specific idea isn't supported.")))
        orphans = text(Idea._get_orphan_posts_statement(),
                        bindparams=[bindparam('discussion_id', discussion_id)]
                        ).columns(column('post_id')).alias('orphans')
        posts = posts.join(orphans, PostClass.id==orphans.c.post_id)
        ideaContentLinkQuery = ideaContentLinkQuery.join(
            orphans, PostClass.id==orphans.c.post_id)
    elif only_orphan == "false":
        raise HTTPBadRequest(localizer.translate(
            _("Getting non-orphan posts isn't supported.")))

    # "true" means hidden only, "false" (default) means visible only. "any" means both.
    hidden = request.GET.get('hidden_messages', "false")
    if hidden != 'any':
        posts = posts.filter(PostClass.hidden==asbool(hidden))
        ideaContentLinkQuery = ideaContentLinkQuery.filter(
            PostClass.hidden==asbool(hidden))

    if root_idea_id:
        related = text(Idea._get_related_posts_statement(),
                    bindparams=[bindparam('root_idea_id', root_idea_id),
                    bindparam('discussion_id', discussion_id)]
                    ).columns(column('post_id')).alias('related')
        #Virtuoso bug: This should work...
        #posts = posts.join(related, PostClass.id==related.c.post_id)
        posts = posts.join(related, PostClass.id == related.c.post_id)
        ideaContentLinkQuery = ideaContentLinkQuery.join(
            related, PostClass.id == related.c.post_id)
    if root_post_id:
        root_post = Post.get(root_post_id)

        posts = posts.filter(
            (Post.ancestry.like(
            root_post.ancestry + cast(root_post.id, String) + ',%'
            ))
            |
            (PostClass.id==root_post.id)
            )
    elif family_post_id:
        root_post = Post.get(family_post_id)
        ancestor_ids = root_post.ancestor_ids()
        posts = posts.filter(
            (Post.ancestry.like(
            root_post.ancestry + cast(root_post.id, String) + ',%'
            ))
            |
            (PostClass.id==root_post.id)
            |
            (PostClass.id.in_(ancestor_ids))
            )
        ideaContentLinkQuery = ideaContentLinkQuery.filter(
            (Post.ancestry.like(
            root_post.ancestry + cast(root_post.id, String) + ',%'
            ))
            |
            (PostClass.id==root_post.id)
            |
            (PostClass.id.in_(ancestor_ids))
            )
    else:
        root_post = None

    if ids:
        posts = posts.filter(Post.id.in_(ids))
        ideaContentLinkQuery = ideaContentLinkQuery.filter(Post.id.in_(ids))

    if posted_after_date:
        posted_after_date = parse_datetime(posted_after_date)
        if posted_after_date:
            posts = posts.filter(PostClass.creation_date >= posted_after_date)
            ideaContentLinkQuery = ideaContentLinkQuery.filter(
                PostClass.creation_date >= posted_after_date)
        #Maybe we should do something if the date is invalid.  benoitg

    if posted_before_date:
        posted_before_date = parse_datetime(posted_before_date)
        if posted_before_date:
            posts = posts.filter(PostClass.creation_date <= posted_before_date)
            ideaContentLinkQuery = posts.filter(
                ideaContentLinkQuery.creation_date <= posted_before_date)
        #Maybe we should do something if the date is invalid.  benoitg

    if post_author_id:
        posts = posts.filter(PostClass.creator_id == post_author_id)
        ideaContentLinkQuery = ideaContentLinkQuery.filter(
            PostClass.creator_id == post_author_id)

    if post_replies_to:
        parent_alias = aliased(PostClass)
        posts = posts.join(parent_alias, PostClass.parent)
        posts = posts.filter(parent_alias.creator_id == post_replies_to)
        ideaContentLinkQuery = ideaContentLinkQuery.join(
            parent_alias, PostClass.parent)
        ideaContentLinkQuery = ideaContentLinkQuery.filter(
            parent_alias.creator_id == post_replies_to)
    # Post read/unread management
    is_unread = request.GET.get('is_unread')
    translations = None
    if user_id != Everyone:
        # This is horrible, but the join creates complex subqueries that
        # virtuoso cannot decode properly.
        read_posts = {v.post_id for v in discussion.db.query(
            ViewPost).filter(
                ViewPost.tombstone_condition(),
                ViewPost.actor_id == user_id,
                *ViewPost.get_discussion_conditions(discussion_id))}
        liked_posts = {l.post_id: l.id for l in discussion.db.query(
            LikedPost).filter(
                LikedPost.tombstone_condition(),
                LikedPost.actor_id == user_id,
                *LikedPost.get_discussion_conditions(discussion_id))}
        if is_unread != None:
            posts = posts.outerjoin(
                ViewPost, and_(
                    ViewPost.actor_id==user_id,
                    ViewPost.post_id==PostClass.id,
                    ViewPost.tombstone_date == None))
            if is_unread == "true":
                posts = posts.filter(ViewPost.id == None)
            elif is_unread == "false":
                posts = posts.filter(ViewPost.id != None)
        user = AgentProfile.get(user_id)
        service = discussion.translation_service()
        if service:
            translations = user_pref_as_translation_table(user, service)
    else:
        #If there is no user_id, all posts are always unread
        if is_unread == "false":
            raise HTTPBadRequest(localizer.translate(
                _("You must be logged in to view which posts are read")))

    if text_search is not None:
        # another Virtuoso bug: offband kills score. but it helps speed.
        offband = () if (order == 'score') else None
        posts = posts.filter(Post.body_text_index.contains(
            text_search.encode('utf-8'), offband=offband))
        ideaContentLinkQuery = ideaContentLinkQuery.filter(
            Post.body_text_index.contains(
                text_search.encode('utf-8'), offband=offband))

    # posts = posts.options(contains_eager(Post.source))
    # Horrible hack... But useful for structure load
    if view_def == 'id_only':
        pass  # posts = posts.options(defer(Post.body))
    else:
        posts = posts.options(
            # undefer(Post.idea_content_links_above_post),
            joinedload_all(Post.creator),
            joinedload_all(Post.extracts),
            joinedload_all(Post.widget_idea_links),
            joinedload_all(SynthesisPost.publishes_synthesis),
            subqueryload_all(Post.attachments))
        if len(discussion.discussion_locales) > 1:
            posts = posts.options(*Content.subqueryload_options())
        else:
            posts = posts.options(*Content.joinedload_options())
        ideaContentLinkCache = dict(ideaContentLinkQuery.all())

    if order == 'chronological':
        posts = posts.order_by(Content.creation_date)
    elif order == 'reverse_chronological':
        posts = posts.order_by(Content.creation_date.desc())
    elif order == 'score':
        posts = posts.order_by(Content.body_text_index.score_name.desc())
    else:
        posts = posts.order_by(Content.id)
    print str(posts)

    no_of_posts = 0
    no_of_posts_viewed_by_user = 0

    for query_result in posts:
        score, viewpost, likedpost = None, None, None
        if not isinstance(query_result, (list, tuple)):
            query_result = [query_result]
        post = query_result[0]
        if user_id != Everyone:
            viewpost = post.id in read_posts
            likedpost = liked_posts.get(post.id, None)
            if view_def != "id_only":
                translate_content(
                    post, translation_table=translations, service=service)
        no_of_posts += 1
        serializable_post = post.generic_json(
            view_def, user_id, permissions) or {}
        if order == 'score':
            score = query_result[1]
            serializable_post['score'] = score

        if viewpost:
            serializable_post['read'] = True
            no_of_posts_viewed_by_user += 1
        elif user_id != Everyone and root_post is not None and root_post.id == post.id:
            # Mark post read, we requested it explicitely
            viewed_post = ViewPost(
                actor_id=user_id,
                post=root_post
                )
            discussion.db.add(viewed_post)
            serializable_post['read'] = True
        else:
            serializable_post['read'] = False
        # serializable_post['liked'] = likedpost.uri() if likedpost else False
        serializable_post['liked'] = (
            LikedPost.uri_generic(likedpost) if likedpost else False)
        if view_def != "id_only":
            serializable_post['indirect_idea_content_links'] = (
                post.indirect_idea_content_links_with_cache(
                    ideaContentLinkCache.get(post.id, None)))

        post_data.append(serializable_post)

    # Benoitg:  For now, this completely garbles threading without intelligent
    #handling of pagination.  Disabling
    #posts = posts.limit(page_size).offset(data['startIndex']-1)
    # This code isn't up to date.  If limiting the query by page, we need to 
    # calculate the counts with a separate query to have the right number of 
    # results
    #no_of_messages_viewed_by_user = discussion.db.query(ViewPost).join(
    #    Post
    #).filter(
    #    Post.discussion_id == discussion_id,
    #    ViewPost.actor_id == user_id,
    #).count() if user_id else 0

    data = {}
    data["page"] = page
    data["unread"] = no_of_posts - no_of_posts_viewed_by_user
    data["total"] = no_of_posts
    data["maxPage"] = max(1, ceil(float(data["total"])/page_size))
    #TODO:  Check if we want 1 based index in the api
    data["startIndex"] = (page_size * page) - (page_size-1)

    if data["page"] == data["maxPage"]:
        data["endIndex"] = data["total"]
    else:
        data["endIndex"] = data["startIndex"] + (page_size-1)
    data["posts"] = post_data

    return data
Exemplo n.º 55
0
def create_post(request):
    """
    We use post, not put, because we don't know the id of the post
    """
    localizer = request.localizer
    request_body = json.loads(request.body)
    user_id = authenticated_userid(request)
    if not user_id:
        raise HTTPUnauthorized()

    user = Post.default_db.query(User).filter_by(id=user_id).one()

    body = request_body.get('body', None)
    html = request_body.get('html', None)  # BG: Is this used now? I cannot see it.
    reply_id = request_body.get('reply_id', None)
    idea_id = request_body.get('idea_id', None)
    subject = request_body.get('subject', None)
    publishes_synthesis_id = request_body.get('publishes_synthesis_id', None)

    if not body and not publishes_synthesis_id:
        # Should we allow empty messages otherwise?
        raise HTTPBadRequest(localizer.translate(
                _("Your message is empty")))

    if reply_id:
        in_reply_to_post = Post.get_instance(reply_id)
    else:
        in_reply_to_post = None

    if idea_id:
        in_reply_to_idea = Idea.get_instance(idea_id)
    else:
        in_reply_to_idea = None

    discussion_id = int(request.matchdict['discussion_id'])
    discussion = Discussion.get_instance(discussion_id)

    if not discussion:
        raise HTTPNotFound(localizer.translate(_(
            "No discussion found with id=%s")) % (discussion_id,)
        )

    ctx = DummyContext({Discussion: discussion})
    if html:
        log.warning("Still using html")
        # how to guess locale in this case?
        body = LangString.create(html)
    elif body:
        body = LangString.create_from_json(
            body, context=ctx, user_id=user_id)
    else:
        body = LangString.EMPTY(discussion.db)

    if subject:
        subject = LangString.create_from_json(
            subject, context=ctx, user_id=user_id)
    else:
        # print(in_reply_to_post.subject, discussion.topic)
        if in_reply_to_post:
            subject = (in_reply_to_post.get_title().first_original().value
                       if in_reply_to_post.get_title() else '')
        elif in_reply_to_idea:
            # TODO:  THis should use a cascade like the frontend
            subject = (in_reply_to_idea.short_title
                       if in_reply_to_idea.short_title else '')
        else:
            subject = discussion.topic if discussion.topic else ''
        # print subject
        if subject is not None and len(subject):
            new_subject = "Re: " + restrip_pat.sub('', subject).strip()
            if (in_reply_to_post and new_subject == subject and
                    in_reply_to_post.get_title()):
                # reuse subject and translations
                subject = in_reply_to_post.get_title()
            else:
                # how to guess locale in this case?
                subject = LangString.create(new_subject)
        else:
            raven_client = get_raven_client()
            if raven_client:
                raven_client.captureMessage(
                    "A message is about to be written to the database with an "
                    "empty subject.  This is not supposed to happen.")
            subject = LangString.EMPTY(discussion.db)

    post_constructor_args = {
        'discussion': discussion,
        'creator_id': user_id,
        'subject': subject,
        'body': body
        }

    if publishes_synthesis_id:
        published_synthesis = Synthesis.get_instance(publishes_synthesis_id)
        post_constructor_args['publishes_synthesis'] = published_synthesis
        new_post = SynthesisPost(**post_constructor_args)
    else:
        new_post = AssemblPost(**post_constructor_args)

    discussion.db.add(new_post)
    discussion.db.flush()

    if in_reply_to_post:
        new_post.set_parent(in_reply_to_post)
    if in_reply_to_idea:
        idea_post_link = IdeaRelatedPostLink(
            creator_id=user_id,
            content=new_post,
            idea=in_reply_to_idea
        )
        discussion.db.add(idea_post_link)
        idea = in_reply_to_idea
        while idea:
            idea.send_to_changes()
            parents = idea.get_parents()
            idea = next(iter(parents)) if parents else None
    else:
        discussion.root_idea.send_to_changes()
    for source in discussion.sources:
        if 'send_post' in dir(source):
            source.send_post(new_post)
    permissions = get_permissions(user_id, discussion_id)

    return new_post.generic_json('default', user_id, permissions)
Exemplo n.º 56
0
def create_post(request):
    """
    We use post, not put, because we don't know the id of the post
    """
    localizer = request.localizer
    request_body = json.loads(request.body)
    user_id = authenticated_userid(request)
    if not user_id:
        raise HTTPUnauthorized()

    user = Post.default_db.query(User).filter_by(id=user_id).one()

    message = request_body.get('message', None)
    html = request_body.get('html', None)
    reply_id = request_body.get('reply_id', None)
    idea_id = request_body.get('idea_id', None)
    subject = request_body.get('subject', None)
    publishes_synthesis_id = request_body.get('publishes_synthesis_id', None)

    if not message:
        raise HTTPBadRequest(localizer.translate(
                _("Your message is empty")))

    if reply_id:
        in_reply_to_post = Post.get_instance(reply_id)
    else:
        in_reply_to_post = None
    
    if idea_id:
        in_reply_to_idea = Idea.get_instance(idea_id)
    else:
        in_reply_to_idea = None

    discussion_id = int(request.matchdict['discussion_id'])
    discussion = Discussion.get_instance(discussion_id)

    if not discussion:
        raise HTTPNotFound(
            localizer.translate(_("No discussion found with id=%s" % discussion_id))
        )

    if subject:
        subject = subject
    else:
        #print(in_reply_to_post.subject, discussion.topic)
        if in_reply_to_post:
            subject = in_reply_to_post.get_title() if in_reply_to_post.get_title() else ''
        elif in_reply_to_idea:
            #TODO:  THis should use a cascade like the frontend   
            subject = in_reply_to_idea.short_title if in_reply_to_idea.short_title else ''
        else:
            subject = discussion.topic if discussion.topic else ''
        #print subject
        subject = "Re: " + restrip_pat.sub('', subject)

    post_constructor_args = {
        'discussion': discussion,
        'creator_id': user_id,
        'subject': subject,
        'body': html if html else message
        }

    if publishes_synthesis_id:
        published_synthesis = Synthesis.get_instance(publishes_synthesis_id)
        post_constructor_args['publishes_synthesis'] = published_synthesis
        new_post = SynthesisPost(**post_constructor_args)
    else:
        new_post = AssemblPost(**post_constructor_args)

    discussion.db.add(new_post)
    discussion.db.flush()

    if in_reply_to_post:
        new_post.set_parent(in_reply_to_post)
    if in_reply_to_idea:
        idea_post_link = IdeaRelatedPostLink(
            creator_id=user_id,
            content=new_post,
            idea=in_reply_to_idea
        )
        discussion.db.add(idea_post_link)
        idea = in_reply_to_idea
        while idea:
            idea.send_to_changes()
            parents = idea.get_parents()
            idea = next(iter(parents)) if parents else None
    else:
        discussion.root_idea.send_to_changes()
    for source in discussion.sources:
        if 'send_post' in dir(source):
            source.send_post(new_post)
    permissions = get_permissions(user_id, discussion_id)

    return new_post.generic_json('default', user_id, permissions)
Exemplo n.º 57
0
def save_idea(request):
    discussion_id = int(request.matchdict['discussion_id'])
    idea_id = request.matchdict['id']
    idea_data = json.loads(request.body)
    #Idea.db.execute('set transaction isolation level read committed')
    # Special items in TOC, like unsorted posts.
    if idea_id in ['orphan_posts']:
        return {'ok': False, 'id': Idea.uri_generic(idea_id)}

    idea = Idea.get_instance(idea_id)
    if not idea:
        raise HTTPNotFound("No such idea: %s" % (idea_id))
    if isinstance(idea, RootIdea):
        raise HTTPBadRequest("Cannot edit root idea.")
    discussion = Discussion.get(int(discussion_id))
    if not discussion:
        raise HTTPNotFound("Discussion with id '%s' not found." % discussion_id)
    if(idea.discussion_id != discussion.id):
        raise HTTPBadRequest(
            "Idea from discussion %s cannot saved from different discussion (%s)." % (idea.discussion_id,discussion.id ))
    if 'shortTitle' in idea_data:
        idea.short_title = idea_data['shortTitle']
    if 'longTitle' in idea_data:
        idea.long_title = idea_data['longTitle']
    if 'definition' in idea_data:
        idea.definition = idea_data['definition']
    
    if 'parentId' in idea_data and idea_data['parentId'] is not None:
        # TODO: Make sure this is sent as a list!
        parent = Idea.get_instance(idea_data['parentId'])
        # calculate it early to maximize contention.
        prev_ancestors = parent.get_all_ancestors()
        new_ancestors = set()

        order = idea_data.get('order', 0.0)
        if not parent:
            raise HTTPNotFound("Missing parentId %s" % (idea_data['parentId']))

        current_parent = None
        for parent_link in idea.source_links:
            pl_ancestors = parent_link.source.get_all_ancestors()
            new_ancestors.update(pl_ancestors)
            if parent_link.source != parent:
                parent_link.is_tombstone=True
                Idea.db.expire(idea, ['source_links'])
                for ancestor in pl_ancestors:
                    if ancestor in prev_ancestors:
                        break
                    ancestor.send_to_changes()
            else:
                parent_link.order = order
                current_parent = parent_link
            Idea.db.expire(parent_link.source, ['target_links'])
            parent_link.source.send_to_changes()
            
        if current_parent is None:
            link = IdeaLink(source=parent, target=idea, order=order)
            Idea.db.add(link)
            # None of these 3 calls should be necessary, but they do help with 
            # the parents being available (the "empty parent" bug).
            # The root cause is somewhere  IdeaLink, or in sqlalchemy proper
            # but I can't seem to find it - benoitg - 2014-05-27
            Idea.db.flush()
            Idea.db.expire(parent, ['target_links'])
            Idea.db.expire(idea, ['source_links'])
            parent.send_to_changes()
            for ancestor in prev_ancestors:
                if ancestor in new_ancestors:
                    break
                ancestor.send_to_changes()

    idea.is_in_next_synthesis = idea_data.get('inNextSynthesis', False)
    idea.send_to_changes()

    return {'ok': True, 'id': idea.uri() }