Beispiel #1
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))
Beispiel #2
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}
Beispiel #3
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()}
Beispiel #4
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
Beispiel #5
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
Beispiel #6
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()
Beispiel #7
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)
Beispiel #8
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]
Beispiel #9
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]
Beispiel #10
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}
Beispiel #11
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()
Beispiel #12
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()}
Beispiel #13
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]
Beispiel #14
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
Beispiel #15
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()
Beispiel #16
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()}
Beispiel #17
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() }
Beispiel #18
0
def set_idea_criteria(request):
    ctx = request.context
    widget = ctx.parent_instance
    ideas = [Idea.get_instance(idea['@id']) for idea in request.json_body]
    widget.set_criteria(ideas)
    return HTTPOk()
Beispiel #19
0
def test_creativity_session_widget(
        discussion, test_app, subidea_1, subidea_1_1,
        participant1_user, test_session, request):
    # Post the initial configuration
    format = lambda x: x.strftime('%Y-%m-%dT%H:%M:%S')
    new_widget_loc = test_app.post_json(
        '/data/Discussion/%d/widgets' % (discussion.id,), {
            '@type': 'CreativitySessionWidget',
            'settings': {
                'idea': 'local:Idea/%d' % (subidea_1.id),
                'notifications': [
                    {
                        'start': '2014-01-01T00:00:00',
                        'end': format(datetime.utcnow() + timedelta(1)),
                        'message': 'creativity_session'
                    },
                    {
                        'start': format(datetime.utcnow() + timedelta(1)),
                        'end': format(datetime.utcnow() + timedelta(2)),
                        'message': 'creativity_session'
                    }
                ]
            }
        })
    assert new_widget_loc.status_code == 201
    # Get the widget from the db
    discussion.db.flush()
    new_widget = Widget.get_instance(new_widget_loc.location)
    assert new_widget
    assert new_widget.base_idea == subidea_1
    assert not new_widget.generated_ideas
    widget_id = new_widget.id
    # There should be a link
    widget_uri = new_widget.uri()
    widget_link = discussion.db.query(BaseIdeaWidgetLink).filter_by(
        idea_id=subidea_1.id, widget_id=widget_id).all()
    assert widget_link
    assert len(widget_link) == 1
    # Get the widget from the api
    widget_rep = test_app.get(
        local_to_absolute(widget_uri),
        headers={"Accept": "application/json"}
    )
    assert widget_rep.status_code == 200
    widget_rep = widget_rep.json
    print widget_rep
    assert 'messages_url' in widget_rep
    assert 'ideas_url' in widget_rep
    assert 'user' in widget_rep
    # Get the list of new ideas
    # should be empty, despite the idea having a non-widget child
    idea_endpoint = local_to_absolute(widget_rep['ideas_url'])
    idea_hiding_endpoint = local_to_absolute(widget_rep['ideas_hiding_url'])
    test = test_app.get(idea_endpoint)
    assert test.status_code == 200
    assert test.json == []

    discussion.db.flush()
    assert new_widget.base_idea == subidea_1
    ctx_url = "http://example.com/cardgame.xml#card_1"
    # Create a new sub-idea
    new_idea_create = test_app.post_json(idea_hiding_endpoint, {
        "@type": "Idea", "short_title": "This is a brand new idea",
        "context_url": ctx_url
    })
    assert new_idea_create.status_code == 201
    # Get the sub-idea from the db
    discussion.db.flush()
    assert new_widget.base_idea == subidea_1
    new_idea1_id = new_idea_create.location
    new_idea1 = Idea.get_instance(new_idea1_id)
    assert new_idea1.proposed_in_post
    assert new_idea1 in new_widget.generated_ideas
    assert new_idea1.hidden
    assert new_idea1.proposed_in_post.hidden
    assert not subidea_1.hidden

    # Get the sub-idea from the api
    new_idea1_rep = test_app.get(
        local_to_absolute(new_idea_create.location),
        headers={"Accept": "application/json"}
    )
    assert new_idea1_rep.status_code == 200
    new_idea1_rep = new_idea1_rep.json

    # It should have a link to the root idea
    idea_link = discussion.db.query(IdeaLink).filter_by(
        source_id=subidea_1.id, target_id=new_idea1.id).one()
    assert idea_link

    # It should have a link to the widget
    widget_link = discussion.db.query(GeneratedIdeaWidgetLink).filter_by(
        idea_id=new_idea1.id, widget_id=widget_id).all()
    assert widget_link
    assert len(widget_link) == 1

    # It should be linked to its creating post.
    content_link = discussion.db.query(IdeaContentWidgetLink).filter_by(
        idea_id=new_idea1.id, content_id=new_idea1.proposed_in_post.id).first()
    assert content_link

    # The new idea should now be in the collection api
    test = test_app.get(idea_endpoint)
    assert test.status_code == 200
    test = test.json
    assert new_idea1_id in test or new_idea1_id in [
        x['@id'] for x in test]

    # We should find the context in the new idea
    assert ctx_url in test[0].get('creation_ctx_url', [])
    # TODO: The root idea is included in the above, that's a bug.
    # get the new post endpoint from the idea data
    post_endpoint = new_idea1_rep.get('widget_add_post_endpoint', None)
    assert (post_endpoint and widget_rep["@id"] and
            post_endpoint[widget_rep["@id"]])
    post_endpoint = post_endpoint[widget_rep["@id"]]

    # Create a new post attached to the sub-idea
    new_post_create = test_app.post_json(local_to_absolute(post_endpoint), {
        "@type": "AssemblPost",
        "body": {"@type": "LangString", "entries": [{
            "@type": "LangStringEntry", "value": "body",
            "@language": "en"
        }]}, "idCreator": participant1_user.uri()})
    assert new_post_create.status_code == 201

    # Get the new post from the db
    discussion.db.flush()
    new_post1_id = new_post_create.location
    post = Post.get_instance(new_post1_id)
    assert post.hidden

    # It should have a widget link to the idea.
    post_widget_link = discussion.db.query(IdeaContentWidgetLink).filter_by(
        content_id=post.id, idea_id=new_idea1.id).one()

    # It should be linked to the idea.
    content_link = discussion.db.query(IdeaContentWidgetLink).filter_by(
        idea_id=new_idea1.id, content_id=post.id).first()
    assert content_link

    # TODO: get the semantic data in tests.
    # assert subidea_1.id in Idea.get_idea_ids_showing_post(new_post1_id)
    # It should be a child of the proposing post
    assert post.parent == new_idea1.proposed_in_post

    # The new post should now be in the collection api
    test = test_app.get(local_to_absolute(post_endpoint))
    assert test.status_code == 200
    assert new_post1_id in test.json or new_post1_id in [
        x['@id'] for x in test.json]

    # Get the new post from the api
    new_post1_rep = test_app.get(
        local_to_absolute(new_post_create.location),
        headers={"Accept": "application/json"}
    )
    assert new_post1_rep.status_code == 200

    # It should mention its idea
    print new_post1_rep.json
    assert new_idea1_id in new_post1_rep.json['widget_ideas']

    new_post1 = Post.get_instance(new_post1_id)
    assert new_post1.hidden
    new_idea1 = Idea.get_instance(new_idea1_id)
    assert new_idea1.hidden

    # Create a second idea
    new_idea_create = test_app.post_json(idea_hiding_endpoint, {
        "@type": "Idea", "short_title": "This is another new idea"})
    assert new_idea_create.status_code == 201
    # Get the sub-idea from the db
    discussion.db.flush()
    new_idea2_id = new_idea_create.location

    # Approve the first but not the second idea
    confirm_idea_url = local_to_absolute(widget_rep['confirm_ideas_url'])
    confirm = test_app.post_json(confirm_idea_url, {
        "ids": [new_idea1_id]})
    assert confirm.status_code == 200
    discussion.db.flush()

    # Get it back
    get_back = test_app.get(confirm_idea_url)
    assert get_back.status_code == 200

    # The first idea should now be unhidden, but not the second
    assert get_back.json == [new_idea1_id]
    new_idea1 = Idea.get_instance(new_idea1_id)
    assert not new_idea1.hidden
    new_idea2 = Idea.get_instance(new_idea2_id)
    assert new_idea2.hidden
    assert new_idea2.proposed_in_post

    # The second idea was not proposed in public
    assert new_idea2.proposed_in_post.hidden

    # The root ideas should not be hidden.
    subidea_1 = Idea.get_instance(subidea_1.id)
    assert not subidea_1.hidden

    # Create a second post.
    new_post_create = test_app.post_json(local_to_absolute(post_endpoint), {
        "@type": "AssemblPost",
        "body": {"@type": "LangString", "entries": [{
            "@type": "LangStringEntry", "value": "body",
            "@language": "en"
        }]}, "idCreator": participant1_user.uri()})
    assert new_post_create.status_code == 201
    discussion.db.flush()
    new_post2_id = new_post_create.location

    # Approve the first but not the second idea
    confirm_messages_url = local_to_absolute(
        widget_rep['confirm_messages_url'])
    confirm = test_app.post_json(confirm_messages_url, {
        "ids": [new_post1_id]})
    assert confirm.status_code == 200
    discussion.db.flush()

    # Get it back
    get_back = test_app.get(confirm_messages_url)
    assert get_back.status_code == 200
    assert get_back.json == [new_post1_id]

    # The first idea should now be unhidden, but not the second
    new_post1 = Post.get_instance(new_post1_id)
    assert not new_post1.hidden
    new_post2 = Post.get_instance(new_post2_id)

    def clear_data():
        print "finalizing test data"
        test_session.delete(new_post1)
        test_session.delete(new_post2)
        test_session.delete(new_idea1.proposed_in_post)
        test_session.delete(new_idea2.proposed_in_post)
        test_session.flush()
    request.addfinalizer(clear_data)
    assert new_post2.hidden

    # Get the notifications
    notifications = test_app.get(
        '/data/Discussion/%d/notifications' % discussion.id)
    assert notifications.status_code == 200
    notifications = notifications.json

    # Only one active session
    assert len(notifications) == 1
    notification = notifications[0]
    print notification
    assert notification['widget_url']
    assert notification['time_to_end'] > 23 * 60 * 60
    assert notification['num_participants'] == 2  # participant and admin
    assert notification['num_ideas'] == 2
Beispiel #20
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)
Beispiel #21
0
def save_idea(request):
    """Update this idea.

    In case the ``parentId`` is changed, handle all
    ``IdeaLink`` changes and send relevant ideas on the socket."""
    discussion_id = int(request.matchdict['discussion_id'])
    user_id = request.authenticated_userid
    permissions = get_permissions(user_id, discussion_id)
    idea_id = request.matchdict['id']
    idea_data = json.loads(request.body)
    #Idea.default_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 ))
    simple_fields = {
        'message_view_override': 'message_view_override',
        'messages_in_parent': 'messages_in_parent',
    }

    for key, attr_name in simple_fields.iteritems():
        if key in idea_data:
            setattr(idea, attr_name, idea_data[key])

    for key, attr_name in langstring_fields.iteritems():
        if key in idea_data:
            current = getattr(idea, attr_name)
            ls_data = idea_data[key]
            # TODO: handle legacy string instance?
            assert isinstance(ls_data, (dict, NoneType))
            if current:
                if ls_data:
                    current.update_from_json(
                        ls_data, user_id, permissions=permissions)
                else:
                    current.delete()
            elif ls_data:
                current = LangString.create_from_json(
                    ls_data, user_id, permissions=permissions)
                setattr(idea, attr_name, current)

    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'])

        if not parent:
            raise HTTPNotFound("Missing parentId %s" % (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)

        for parent_link in idea.source_links:
            # still assuming there's only one.
            pl_parent = parent_link.source
            pl_ancestors = pl_parent.get_all_ancestors()
            new_ancestors.update(pl_ancestors)
            if parent_link.source != parent:
                parent_link.copy(True)
                parent_link.source = parent
                parent.db.expire(parent, ['target_links'])
                parent.db.expire(pl_parent, ['target_links'])
                for ancestor in pl_ancestors:
                    if ancestor in prev_ancestors:
                        break
                    ancestor.send_to_changes()
                for ancestor in prev_ancestors:
                    if ancestor in new_ancestors:
                        break
                    ancestor.send_to_changes()
            parent_link.order = order
            parent_link.db.expire(parent_link.source, ['target_links'])
            parent_link.source.send_to_changes()
            parent_link.db.flush()

    idea.send_to_changes()

    return {'ok': True, 'id': idea.uri() }
Beispiel #22
0
def create_post(request):
    """
    Create a new post in this discussion.

    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()

    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 = request.context

    ctx = discussion.get_instance_context(request=request)
    if html:
        log.warning("Still using html")
        # how to guess locale in this case?
        body = LangString.create(sanitize_html(html))
        # TODO: LocalPosts are pure text right now.
        # Allowing HTML requires changes to the model.
    elif body:
        # TODO: Accept HTML body.
        for e in body['entries']:
            e['value'] = sanitize_text(e['value'])
        body_ctx = LangString.create_from_json(body, context=ctx)
        body = body_ctx._instance
    else:
        body = LangString.EMPTY(discussion.db)

    if subject:
        for e in subject['entries']:
            e['value'] = sanitize_text(e['value'])
        subject_ctx = LangString.create_from_json(subject, context=ctx)
        subject = subject_ctx._instance
    else:
        from assembl.models import LocaleLabel
        locale = LocaleLabel.UNDEFINED
        # print(in_reply_to_post.subject, discussion.topic)
        if in_reply_to_post and in_reply_to_post.get_title():
            original_subject = in_reply_to_post.get_title().first_original()
            if original_subject:
                locale = original_subject.locale_code
                subject = (original_subject.value or ''
                           if in_reply_to_post.get_title() else '')
        elif in_reply_to_idea:
            # TODO:  THis should use a cascade like the frontend
            # also, some ideas have extra langstring titles
            subject = (in_reply_to_idea.short_title
                       if in_reply_to_idea.short_title else '')
            locale = discussion.main_locale
        else:
            subject = discussion.topic if discussion.topic else ''
            locale = discussion.main_locale
        # print subject
        if subject is not None and len(subject):
            new_subject = "Re: " + SUBJECT_RE.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().clone(discussion.db)
            else:
                # how to guess locale in this case?
                subject = LangString.create(new_subject, locale)
        else:
            capture_message(
                "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)
        new_post.finalize_publish()
    else:
        new_post = LocalPost(**post_constructor_args)
    new_post.guess_languages()

    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 = request.permissions

    return new_post.generic_json('default', user_id, permissions)
Beispiel #23
0
def post_extract(request):
    """
    Create a new extract.
    """
    extract_data = json.loads(request.body)
    discussion_id = int(request.matchdict['discussion_id'])
    user_id = authenticated_userid(request)
    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
    if not user_has_permission(discussion_id, user_id, P_ADD_EXTRACT):
        #TODO: maparent:  restore this code once it works:
        #return HTTPForbidden(result=ACLDenied(permission=P_ADD_EXTRACT))
        return HTTPForbidden()
    if not user_id or user_id == Everyone:
        # TODO: Create an anonymous user.
        raise HTTPServerError("Anonymous extracts are not implemeted yet.")
    content = None
    uri = extract_data.get('uri')
    important = extract_data.get('important', False)
    annotation_text = None
    if uri:
        # Straight from annotator
        annotation_text = extract_data.get('text')
    else:
        target = extract_data.get('target')
        if not (target or uri):
            raise HTTPBadRequest("No target")

        target_class = sqla.get_named_class(target.get('@type'))
        if issubclass(target_class, Post):
            post_id = target.get('@id')
            post = Post.get_instance(post_id)
            if not post:
                raise HTTPNotFound(
                    "Post with id '%s' not found." % post_id)
            content = post
        elif issubclass(target_class, Webpage):
            uri = target.get('url')
    if uri and not content:
        content = Webpage.get_instance(uri)
        if not content:
            # TODO: maparent:  This is actually a singleton pattern, should be
            # handled by the AnnotatorSource now that it exists...
            source = AnnotatorSource.default_db.query(AnnotatorSource).filter_by(
                discussion_id=discussion_id).filter(
                cast(AnnotatorSource.name, Unicode) == 'Annotator').first()
            if not source:
                source = AnnotatorSource(
                    name='Annotator', discussion_id=discussion_id,
                    type='source')
            content = Webpage(url=uri, discussion_id=discussion_id)
    extract_body = extract_data.get('quote', '')

    idea_id = extract_data.get('idIdea', None)
    if idea_id:
        idea = Idea.get_instance(idea_id)
        if(idea.discussion.id != discussion_id):
            raise HTTPBadRequest(
                "Extract from discussion %s cannot be associated with an idea from a different discussion." % extract.get_discussion_id())
    else:
        idea = None


    new_extract = Extract(
        creator_id=user_id,
        owner_id=user_id,
        discussion_id=discussion_id,
        body=extract_body,
        idea=idea,
        important=important,
        annotation_text=annotation_text,
        content=content
    )
    Extract.default_db.add(new_extract)

    for range_data in extract_data.get('ranges', []):
        range = TextFragmentIdentifier(
            extract=new_extract,
            xpath_start=range_data['start'],
            offset_start=range_data['startOffset'],
            xpath_end=range_data['end'],
            offset_end=range_data['endOffset'])
        TextFragmentIdentifier.default_db.add(range)
    Extract.default_db.flush()

    return {'ok': True, '@id': new_extract.uri()}
Beispiel #24
0
def create_post(request):
    """
    We use post, not put, because we don't know the id of the post
    """
    localizer = get_localizer(request)
    request_body = json.loads(request.body)
    user_id = authenticated_userid(request)
    user = Post.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 user_id:
        raise HTTPUnauthorized()

    if not message:
        raise HTTPUnauthorized()

    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 = 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
    elif in_reply_to_post:
        subject = in_reply_to_post.subject
    elif in_reply_to_idea:
        subject = in_reply_to_idea.short_title
    else:
        subject = discussion.topic
    subject = "Re: " + restrip_pat.sub("", subject)

    post_constructor_args = {
        "discussion": discussion,
        "message_id": uuid.uuid1().urn,
        "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)

    new_post.db.add(new_post)
    new_post.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)
        IdeaRelatedPostLink.db.add(idea_post_link)

    for source in discussion.sources:
        source.send_post(new_post)

    return {"ok": True}
Beispiel #25
0
def save_idea(request):
    discussion_id = 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(id=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 ))

    idea.short_title = idea_data['shortTitle']
    idea.long_title = idea_data['longTitle']
    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.
        ancestors = parent.get_all_ancestors()

        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()
            if parent_link.source != parent:
                parent_link.is_tombstone=True
            else:
                parent_link.order = order
                current_parent = parent_link
            Idea.db.expire(parent_link.source, ['target_links'])
            parent_link.source.send_to_changes()
            for ancestor in pl_ancestors:
                ancestor.send_to_changes()
            
        if current_parent is None:
            link = IdeaLink(source=parent, target=idea, order=order)
            idea.source_links.append(link)
            Idea.db.expire(parent, ['target_links'])
            parent.send_to_changes()
            for ancestor in ancestors:
                ancestor.send_to_changes()
        Idea.db.expire(idea, ['source_links'])
        
    next_synthesis = discussion.get_next_synthesis()
    if idea_data['inNextSynthesis']:
        if idea not in next_synthesis.ideas:
            next_synthesis.ideas.append(idea)
    else:
        if idea in next_synthesis.ideas:
            next_synthesis.ideas.remove(idea)
    idea.send_to_changes()

    return {'ok': True, 'id': idea.uri() }
Beispiel #26
0
def create_post(request):
    """
    Create a new post in this discussion.

    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 = request.authenticated_userid
    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)
    message_classifier = request_body.get('message_classifier', 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(sanitize_html(html))
        # TODO: AssemblPosts are pure text right now.
        # Allowing HTML requires changes to the model.
    elif body:
        # TODO: Accept HTML body.
        for e in body['entries']:
            e['value'] = sanitize_text(e['value'])
        body = LangString.create_from_json(
            body, context=ctx, user_id=user_id)
    else:
        body = LangString.EMPTY(discussion.db)

    if subject:
        for e in subject['entries']:
            e['value'] = sanitize_text(e['value'])
        subject = LangString.create_from_json(
            subject, context=ctx, user_id=user_id)
    else:
        from assembl.models import Locale
        locale = Locale.UNDEFINED
        # print(in_reply_to_post.subject, discussion.topic)
        if in_reply_to_post and in_reply_to_post.get_title():
            original_subject = in_reply_to_post.get_title().first_original()
            if original_subject:
                locale = original_subject.locale_code
                subject = (
                    original_subject.value or ''
                    if in_reply_to_post.get_title() else '')
        elif in_reply_to_idea:
            # TODO:  THis should use a cascade like the frontend
            # also, some ideas have extra langstring titles
            subject = (in_reply_to_idea.short_title
                       if in_reply_to_idea.short_title else '')
            locale = discussion.main_locale
        else:
            subject = discussion.topic if discussion.topic else ''
            locale = discussion.main_locale
        # 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().clone(discussion.db)
            else:
                # how to guess locale in this case?
                subject = LangString.create(new_subject, locale)
        else:
            capture_message(
                "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,
        'message_classifier': message_classifier,
        '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)
        new_post.finalize_publish()
    else:
        new_post = AssemblPost(**post_constructor_args)
    new_post.guess_languages()

    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)
Beispiel #27
0
def save_idea(request):
    """Update this idea.

    In case the ``parentId`` is changed, handle all
    ``IdeaLink`` changes and send relevant ideas on the socket."""
    discussion = request.context
    user_id = authenticated_userid(request)
    permissions = request.permissions
    idea_id = request.matchdict['id']
    idea_data = json.loads(request.body)
    # Idea.default_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)
    db = idea.db
    if not idea:
        raise HTTPNotFound("No such idea: %s" % (idea_id))
    if isinstance(idea, RootIdea):
        raise HTTPBadRequest("Cannot edit root idea.")
    if (idea.discussion_id != discussion.id):
        raise HTTPBadRequest(
            "Idea from discussion %s cannot be saved from different discussion (%s)."
            % (idea.discussion_id, discussion.id))

    context = idea.get_instance_context(request=request)
    for key, attr_name in langstring_fields.items():
        if key in idea_data:
            current = getattr(idea, attr_name)
            ls_data = idea_data[key]
            # TODO: handle legacy string instance?
            subcontext = idea.get_collection_context(key, context)
            if current:
                if ls_data:
                    current.update_from_json(ls_data,
                                             context=subcontext,
                                             permissions=permissions)
                else:
                    current.delete()
            elif ls_data:
                current = LangString.create_from_json(ls_data,
                                                      context=subcontext)
                setattr(idea, attr_name, current._instance)

    new_parent_id = idea_data.get('parentId', None)
    if new_parent_id:
        # TODO: Make sure this is sent as a list!
        # Actually, use embedded links to do this properly...
        new_parent_ids = {new_parent_id}
        old_parent_ids = {
            Idea.uri_generic(l.source_id)
            for l in idea.source_links
        }
        if new_parent_ids != old_parent_ids:
            added_parent_ids = new_parent_ids - old_parent_ids
            removed_parent_ids = old_parent_ids - new_parent_ids
            added_parents = [Idea.get_instance(id) for id in added_parent_ids]
            current_parents = idea.get_parents()
            removed_parents = [
                p for p in current_parents if p.uri() in removed_parent_ids
            ]
            if None in added_parents:
                missing = [
                    id for id in added_parent_ids if not Idea.get_instance(id)
                ]
                raise HTTPNotFound("Missing parentId %s" % (','.join(missing)))
            if not idea.has_permission_req(P_ASSOCIATE_IDEA):
                raise HTTPUnauthorized("Cannot associate idea " + idea.uri())
            for parent in added_parents + removed_parents:
                if not parent.has_permission_req(P_ASSOCIATE_IDEA):
                    raise HTTPUnauthorized("Cannot associate parent idea " +
                                           idea.uri())
            old_ancestors = set()
            new_ancestors = set()
            for parent in current_parents:
                old_ancestors.add(parent)
                old_ancestors.update(parent.get_all_ancestors())
            kill_links = {
                l
                for l in idea.source_links
                if Idea.uri_generic(l.source_id) in removed_parent_ids
            }
            order = idea_data.get('order', 0.0)
            for parent in added_parents:
                if kill_links:
                    link = kill_links.pop()
                    db.expire(link.source, ['target_links'])
                    link.copy(True)
                    link.order = order
                    link.source = parent
                else:
                    link = IdeaLink(source=source, target=idea, order=order)
                    db.add(link)
                db.expire(parent, ['target_links'])
                order += 1.0
            for link in kill_links:
                db.expire(link.source, ['target_links'])
                kill_links.is_tombstone = True
            db.expire(idea, ['source_links'])
            db.flush()
            for parent in idea.get_parents():
                new_ancestors.add(parent)
                new_ancestors.update(parent.get_all_ancestors())
            for ancestor in new_ancestors ^ old_ancestors:
                ancestor.send_to_changes()
        else:
            order = idea_data.get('order', None)
            if order is not None:
                new_parent_id = Idea.get_database_id(new_parent_id)
                parent_links = [
                    link for link in idea.source_links
                    if link.source_id == new_parent_id
                ]
                assert len(parent_links) == 1
                parent_links[0].order = idea_data.get('order', 0.0)

    if 'subtype' in idea_data:
        idea.rdf_type = idea_data['subtype']
    idea.send_to_changes()

    return {'ok': True, 'id': idea.uri()}
Beispiel #28
0
def set_vote_votables(request):
    ctx = request.context
    widget = ctx.parent_instance
    ideas = [Idea.get_instance(idea['@id']) for idea in request.json]
    widget.set_votables(ideas)
    return HTTPOk()
Beispiel #29
0
def save_idea(request):
    """Update this idea.

    In case the ``parentId`` is changed, handle all
    ``IdeaLink`` changes and send relevant ideas on the socket."""
    discussion_id = int(request.matchdict['discussion_id'])
    user_id = request.authenticated_userid
    permissions = get_permissions(user_id, discussion_id)
    idea_id = request.matchdict['id']
    idea_data = json.loads(request.body)
    #Idea.default_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))
    simple_fields = {
        'message_view_override': 'message_view_override',
        'messages_in_parent': 'messages_in_parent',
    }

    for key, attr_name in simple_fields.iteritems():
        if key in idea_data:
            setattr(idea, attr_name, idea_data[key])

    for key, attr_name in langstring_fields.iteritems():
        if key in idea_data:
            current = getattr(idea, attr_name)
            ls_data = idea_data[key]
            # TODO: handle legacy string instance?
            assert isinstance(ls_data, (dict, NoneType))
            if current:
                if ls_data:
                    current.update_from_json(ls_data,
                                             user_id,
                                             permissions=permissions)
                else:
                    current.delete()
            elif ls_data:
                current = LangString.create_from_json(ls_data,
                                                      user_id,
                                                      permissions=permissions)
                setattr(idea, attr_name, current)

    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'])

        if not parent:
            raise HTTPNotFound("Missing parentId %s" % (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)

        for parent_link in idea.source_links:
            # still assuming there's only one.
            pl_parent = parent_link.source
            pl_ancestors = pl_parent.get_all_ancestors()
            new_ancestors.update(pl_ancestors)
            if parent_link.source != parent:
                parent_link.copy(True)
                parent_link.source = parent
                parent.db.expire(parent, ['target_links'])
                parent.db.expire(pl_parent, ['target_links'])
                for ancestor in pl_ancestors:
                    if ancestor in prev_ancestors:
                        break
                    ancestor.send_to_changes()
                for ancestor in prev_ancestors:
                    if ancestor in new_ancestors:
                        break
                    ancestor.send_to_changes()
            parent_link.order = order
            parent_link.db.expire(parent_link.source, ['target_links'])
            parent_link.source.send_to_changes()
            parent_link.db.flush()

    idea.send_to_changes()

    return {'ok': True, 'id': idea.uri()}
Beispiel #30
0
def save_idea(request):
    discussion_id = int(request.matchdict['discussion_id'])
    idea_id = request.matchdict['id']
    idea_data = json.loads(request.body)
    #Idea.default_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']))

        for parent_link in idea.source_links:
            # still assuming there's only one.
            pl_parent = parent_link.source
            pl_ancestors = pl_parent.get_all_ancestors()
            new_ancestors.update(pl_ancestors)
            if parent_link.source != parent:
                parent_link.copy(True)
                parent_link.source = parent
                parent.db.expire(parent, ['target_links'])
                parent.db.expire(pl_parent, ['target_links'])
                for ancestor in pl_ancestors:
                    if ancestor in prev_ancestors:
                        break
                    ancestor.send_to_changes()
                for ancestor in prev_ancestors:
                    if ancestor in new_ancestors:
                        break
                    ancestor.send_to_changes()
            parent_link.order = order
            parent_link.db.expire(parent_link.source, ['target_links'])
            parent_link.source.send_to_changes()
            parent_link.db.flush()

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

    return {'ok': True, 'id': idea.uri() }
Beispiel #31
0
def post_extract(request):
    """
    Create a new extract.
    """
    extract_data = json.loads(request.body)
    discussion_id = int(request.matchdict['discussion_id'])
    user_id = authenticated_userid(request)
    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
    if not user_has_permission(discussion_id, user_id, P_ADD_EXTRACT):
        #TODO: maparent:  restore this code once it works:
        #return HTTPForbidden(result=ACLDenied(permission=P_ADD_EXTRACT))
        return HTTPForbidden()
    if not user_id or user_id == Everyone:
        # TODO: Create an anonymous user.
        raise HTTPServerError("Anonymous extracts are not implemeted yet.")
    content = None
    uri = extract_data.get('uri')
    important = extract_data.get('important', False)
    annotation_text = None
    if uri:
        # Straight from annotator
        annotation_text = extract_data.get('text')
    else:
        target = extract_data.get('target')
        if not (target or uri):
            raise HTTPBadRequest("No target")

        target_class = sqla.get_named_class(target.get('@type'))
        if issubclass(target_class, Post):
            post_id = target.get('@id')
            post = Post.get_instance(post_id)
            if not post:
                raise HTTPNotFound("Post with id '%s' not found." % post_id)
            content = post
        elif issubclass(target_class, Webpage):
            uri = target.get('url')
    if uri and not content:
        content = Webpage.get_instance(uri)
        if not content:
            # TODO: maparent:  This is actually a singleton pattern, should be
            # handled by the AnnotatorSource now that it exists...
            source = AnnotatorSource.default_db.query(
                AnnotatorSource).filter_by(discussion_id=discussion_id).filter(
                    cast(AnnotatorSource.name, Unicode) ==
                    'Annotator').first()
            if not source:
                source = AnnotatorSource(name='Annotator',
                                         discussion_id=discussion_id,
                                         type='source')
            content = Webpage(url=uri, discussion_id=discussion_id)
    extract_body = extract_data.get('quote', '')

    idea_id = extract_data.get('idIdea', None)
    if idea_id:
        idea = Idea.get_instance(idea_id)
        if (idea.discussion.id != discussion_id):
            raise HTTPBadRequest(
                "Extract from discussion %s cannot be associated with an idea from a different discussion."
                % extract.get_discussion_id())
    else:
        idea = None

    new_extract = Extract(creator_id=user_id,
                          owner_id=user_id,
                          discussion_id=discussion_id,
                          body=extract_body,
                          idea=idea,
                          important=important,
                          annotation_text=annotation_text,
                          content=content)
    Extract.default_db.add(new_extract)

    for range_data in extract_data.get('ranges', []):
        range = TextFragmentIdentifier(extract=new_extract,
                                       xpath_start=range_data['start'],
                                       offset_start=range_data['startOffset'],
                                       xpath_end=range_data['end'],
                                       offset_end=range_data['endOffset'])
        TextFragmentIdentifier.default_db.add(range)
    Extract.default_db.flush()

    return {'ok': True, '@id': new_extract.uri()}
Beispiel #32
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)
Beispiel #33
0
def create_post(request):
    """
    Create a new post in this discussion.

    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
                       or '' 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().clone(discussion.db)
            else:
                # how to guess locale in this case?
                subject = LangString.create(new_subject)
        else:
            capture_message(
                "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)
        new_post.finalize_publish()
    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)