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 save_synthesis(request): synthesis_id = request.matchdict['id'] discussion_id = int(request.matchdict['discussion_id']) if synthesis_id == 'next_synthesis': discussion = Discussion.get_instance(discussion_id) synthesis = discussion.get_next_synthesis() else: synthesis = Synthesis.get_instance(synthesis_id) if not synthesis: raise HTTPBadRequest("Synthesis with id '%s' not found." % synthesis_id) synthesis_data = json.loads(request.body) user_id = request.authenticated_userid permissions = get_permissions(user_id, discussion_id) for key in ('subject', 'introduction', 'conclusion'): if key in synthesis_data: ls_data = synthesis_data[key] if ls_data is None: continue assert isinstance(ls_data, dict) current = LangString.create_from_json(ls_data, user_id, permissions=permissions) setattr(synthesis, key, current) Synthesis.default_db.add(synthesis) Synthesis.default_db.flush() return {'ok': True, 'id': synthesis.uri()}
def save_synthesis(request): synthesis_id = request.matchdict['id'] discussion_id = int(request.matchdict['discussion_id']) if synthesis_id == 'next_synthesis': discussion = Discussion.get_instance(discussion_id) synthesis = discussion.get_next_synthesis() else: synthesis = Synthesis.get_instance(synthesis_id) if not synthesis: raise HTTPBadRequest("Synthesis with id '%s' not found." % synthesis_id) synthesis_data = json.loads(request.body) user_id = request.authenticated_userid permissions = get_permissions(user_id, discussion_id) for key in ('subject', 'introduction', 'conclusion'): if key in synthesis_data: ls_data = synthesis_data[key] if ls_data is None: continue assert isinstance(ls_data, dict) current = LangString.create_from_json( ls_data, user_id, permissions=permissions) setattr(synthesis, key, current) Synthesis.default_db.add(synthesis) Synthesis.default_db.flush() return {'ok': True, 'id': synthesis.uri()}
def create_idea(request): discussion = request.context session = discussion.db user_id = authenticated_userid(request) permissions = request.permissions idea_data = request.json_body now = datetime.utcnow() pub_state = None pub_flow = discussion.idea_publication_flow if pub_flow: pub_state_name = discussion.preferences['default_idea_pub_state'] pub_state = pub_flow.state_by_label(pub_state_name) kwargs = { "discussion": discussion, "creation_date": now, "pub_state": pub_state, "creator_id": user_id, } new_idea = Idea(**kwargs) session.add(new_idea) context = new_idea.get_instance_context(request=request) for key, attr_name in langstring_fields.items(): if key in idea_data: ls_data = idea_data[key] if ls_data is None: continue subcontext = new_idea.get_collection_context(key, context) current = LangString.create_from_json(ls_data, context=subcontext) setattr(new_idea, attr_name, current._instance) if idea_data['parentId']: parent = Idea.get_instance(idea_data['parentId']) else: parent = discussion.root_idea session.add( IdeaLink(source=parent, target=new_idea, creation_date=now, order=idea_data.get('order', 0.0))) session.flush() return {'ok': True, '@id': new_idea.uri()}
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 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) 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(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, '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) 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) 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_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: " + 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, '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 = request.permissions 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) 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) 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() if 'subtype' in idea_data: idea.rdf_type = idea_data['subtype'] 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()}