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