def upgrade(pyramid_env): # Do stuff with the app's models here. from assembl import models as m from assembl.lib.clean_input import sanitize_html, sanitize_text db = m.get_session_maker()() with transaction.manager: # sanitize body of assemblposts (not imported posts) for lse in db.query(m.LangStringEntry ).join(m.AssemblPost, m.Content.body_id == m.LangStringEntry.langstring_id ).filter(m.LangStringEntry.value.like('%<%')): lse.value = sanitize_html(lse.value) # sanitize subject of all posts for lse in db.query(m.LangStringEntry ).join(m.Content, m.Content.subject_id == m.LangStringEntry.langstring_id ).filter(m.LangStringEntry.value.like('%<%')): lse.value = sanitize_text(lse.value)
def upgrade(pyramid_env): # Do stuff with the app's models here. from assembl import models as m from assembl.lib.clean_input import sanitize_html, sanitize_text db = m.get_session_maker()() with transaction.manager: # sanitize body of assemblposts (not imported posts) for lse in db.query(m.LangStringEntry ).join(m.AssemblPost, m.Content.body_id == m.LangStringEntry.langstring_id ).filter(m.LangStringEntry.value.like('%<%')): lse.value = sanitize_html(lse.value) # sanitize subject of all posts for lse in db.query(m.LangStringEntry ).join(m.Content, m.Content.subject_id == m.LangStringEntry.langstring_id ).filter(m.LangStringEntry.value.like('%<%')): lse.value = sanitize_text(lse.value)
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 mutate(root, args, context, info): EMBED_ATTACHMENT = models.AttachmentPurpose.EMBED_ATTACHMENT.value discussion_id = context.matchdict['discussion_id'] user_id = context.authenticated_userid or Everyone discussion = models.Discussion.get(discussion_id) post_id = args.get('post_id') post_id = int(Node.from_global_id(post_id)[1]) post = models.Post.get(post_id) cls = models.Post permissions = get_permissions(user_id, discussion_id) allowed = post.user_can(user_id, CrudPermissions.UPDATE, permissions) if not allowed: raise HTTPUnauthorized() changed = False subject = args.get('subject') if subject: subject = sanitize_text(subject) body = args.get('body') if body: body = sanitize_html(body) # TODO: Here, an assumption that the modification uses the same # language as the original. May need revisiting. original_subject_entry = post.subject.first_original() # subject is not required, be careful to not remove it if not specified if subject and subject != original_subject_entry.value: changed = True post.subject.add_value(subject, original_subject_entry.locale_code) # Edit subject for all descendants children = post.children[:] new_subject = u'Re: ' + restrip_pat.sub('', subject).strip() while children: child = children.pop() children.extend(child.children) child.subject.add_value( new_subject, child.subject.first_original().locale_code) original_body_entry = post.body.first_original() if body != original_body_entry.value: post.body.add_value(body, original_body_entry.locale_code) changed = True original_attachments = post.attachments original_attachments_doc_ids = [] if original_attachments: original_attachments_doc_ids = [ str(a.document_id) for a in original_attachments ] attachments = args.get('attachments', []) for document_id in attachments: if document_id not in original_attachments_doc_ids: document = models.Document.get(document_id) models.PostAttachment( document=document, discussion=discussion, creator_id=context.authenticated_userid, post=post, title=document.title, attachmentPurpose=EMBED_ATTACHMENT) # delete attachments that has been removed documents_to_delete = set(original_attachments_doc_ids) - set( attachments) # noqa: E501 for document_id in documents_to_delete: with cls.default_db.no_autoflush: document = models.Document.get(document_id) post_attachment = post.db.query( models.PostAttachment).filter_by( discussion_id=discussion_id, post_id=post_id, document_id=document_id).first() document.delete_file() post.db.delete(document) post.attachments.remove(post_attachment) post.db.flush() publication_state = models.PublicationStates.from_string( args.get('publication_state')) if args.get( 'publication_state') in models.PublicationStates.values( ) else None if publication_state and publication_state != post.publication_state: post.publication_state = publication_state changed = True # Update the creation date when switching from draft to published if post.publication_state == models.PublicationStates.DRAFT and publication_state == models.PublicationStates.PUBLISHED: post.creation_date = datetime.utcnow() if changed: post.modification_date = datetime.utcnow() post.body_mime_type = u'text/html' post.db.flush() post.db.expire(post.subject, ["entries"]) post.db.expire(post.body, ["entries"]) return UpdatePost(post=post)
def mutate(root, args, context, info): EMBED_ATTACHMENT = models.AttachmentPurpose.EMBED_ATTACHMENT.value discussion_id = context.matchdict['discussion_id'] user_id = context.authenticated_userid or Everyone discussion = models.Discussion.get(discussion_id) idea_id = args.get('idea_id') idea_id = int(Node.from_global_id(idea_id)[1]) in_reply_to_idea = models.Idea.get(idea_id) if isinstance(in_reply_to_idea, models.Question): cls = models.PropositionPost else: cls = models.AssemblPost extract_id = args.get('extract_id') if extract_id: extract_id_global = int(Node.from_global_id(extract_id)[1]) extract = models.Extract.get(extract_id_global) cls = models.ExtractComment in_reply_to_post = None if (cls == models.AssemblPost) or (cls == models.ExtractComment): in_reply_to_post_id = args.get('parent_id') if in_reply_to_post_id: in_reply_to_post_id = int( Node.from_global_id(in_reply_to_post_id)[1]) if in_reply_to_post_id: in_reply_to_post = models.Post.get(in_reply_to_post_id) permissions = get_permissions(user_id, discussion_id) allowed = cls.user_can_cls(user_id, CrudPermissions.CREATE, permissions) if not allowed: raise HTTPUnauthorized() with cls.default_db.no_autoflush: subject = args.get('subject') body = args.get('body') classifier = args.get('message_classifier', None) body = sanitize_html(body) body_langstring = models.LangString.create(body) publication_state = models.PublicationStates.from_string( args.get('publication_state')) if args.get( 'publication_state') in models.PublicationStates.values( ) else models.PublicationStates.PUBLISHED if subject: subject = sanitize_text(subject) subject_langstring = models.LangString.create(subject) elif issubclass(cls, models.PropositionPost): # Specific case first. Respect inheritance. Since we are using # a specific value, construct it with localization machinery. subject_langstring = models.LangString.create_localized_langstring( # noqa: E501 _('Proposal'), discussion.discussion_locales, {'fr': 'Proposition'}) else: # We apply the same logic than in views/api/post.py::create_post # noqa: E501 locale = models.Locale.UNDEFINED if in_reply_to_post and in_reply_to_post.get_title(): original_subject = in_reply_to_post.get_title( ).first_original() locale = original_subject.locale_code subject = original_subject.value elif in_reply_to_idea: # TODO: some ideas have extra langstring titles # we try to guess the locale of the body to use the same locale for post's subject body_lang, data = discussion.translation_service( ).identify(body_langstring.entries[0].value, discussion.discussion_locales) closest_subject = in_reply_to_idea.title.closest_entry( body_lang) if closest_subject: subject = closest_subject.value locale = closest_subject.locale.code else: # rather no subject than one in a random locale subject = u'' locale = discussion.main_locale else: subject = discussion.topic if discussion.topic else '' locale = discussion.main_locale if subject is not None: if in_reply_to_idea and in_reply_to_idea.message_view_override == u'messageColumns': new_subject = subject else: new_subject = u'Re: ' + restrip_pat.sub( '', subject).strip() # noqa: E501 if (in_reply_to_post and new_subject == subject and in_reply_to_post.get_title()): # reuse subject and translations subject_langstring = in_reply_to_post.get_title( ).clone(discussion.db) else: subject_langstring = models.LangString.create( new_subject, locale) if cls == models.ExtractComment: new_post = cls(discussion=discussion, subject=subject_langstring, body=body_langstring, creator_id=user_id, body_mime_type=u'text/html', message_classifier=classifier, creation_date=datetime.utcnow(), publication_state=publication_state, parent_extract_id=extract.id) else: new_post = cls(discussion=discussion, subject=subject_langstring, body=body_langstring, creator_id=user_id, body_mime_type=u'text/html', message_classifier=classifier, creation_date=datetime.utcnow(), publication_state=publication_state) new_post.guess_languages() db = new_post.db db.add(new_post) db.flush() if in_reply_to_post: new_post.set_parent(in_reply_to_post) elif in_reply_to_idea and cls != models.ExtractComment: # don't create IdeaRelatedPostLink when we have both # in_reply_to_post and in_reply_to_idea or if it's a comment # for an extract idea_post_link = models.IdeaRelatedPostLink( creator_id=user_id, content=new_post, idea=in_reply_to_idea) db.add(idea_post_link) db.flush() attachments = args.get('attachments', []) for document_id in attachments: document = models.Document.get(document_id) models.PostAttachment(document=document, discussion=discussion, creator_id=context.authenticated_userid, post=new_post, title=document.title, attachmentPurpose=EMBED_ATTACHMENT) db.flush() return CreatePost(post=new_post)
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 mutate(root, args, context, info): EMBED_ATTACHMENT = models.AttachmentPurpose.EMBED_ATTACHMENT.value user_id = context.authenticated_userid or Everyone discussion_id = context.matchdict['discussion_id'] discussion = models.Discussion.get(discussion_id) post_id = args.get('post_id') post_id = int(Node.from_global_id(post_id)[1]) post = models.Post.get(post_id) cls = models.Post permissions = get_permissions(user_id, discussion_id) allowed = post.user_can(user_id, CrudPermissions.UPDATE, permissions) if not allowed: raise HTTPUnauthorized() if (post.publication_state == models.PublicationStates.PUBLISHED and P_MODERATE not in permissions and discussion.preferences['with_moderation']): raise HTTPUnauthorized() changed = False subject = args.get('subject') if subject: subject = sanitize_text(subject) body = args.get('body') if body: body = sanitize_html(body) # TODO: Here, an assumption that the modification uses the same # language as the original. May need revisiting. original_subject_entry = post.subject.first_original() # subject is not required, be careful to not remove it if not specified if subject and subject != original_subject_entry.value: changed = True post.subject.add_value(subject, original_subject_entry.locale_code) # Edit subject for all descendants children = post.children[:] new_subject = u'Re: ' + restrip_pat.sub('', subject).strip() while children: child = children.pop() children.extend(child.children) child.subject.add_value( new_subject, child.subject.first_original().locale_code) original_body_entry = post.body.first_original() if body != original_body_entry.value: post.body.add_value(body, original_body_entry.locale_code) changed = True original_attachments = post.attachments original_attachments_doc_ids = [] if original_attachments: original_attachments_doc_ids = [ str(a.document_id) for a in original_attachments] attachments = args.get('attachments', []) for document_id in attachments: if document_id not in original_attachments_doc_ids: document = models.Document.get(document_id) models.PostAttachment( document=document, discussion=discussion, creator_id=context.authenticated_userid, post=post, title=document.title, attachmentPurpose=EMBED_ATTACHMENT ) # delete attachments that has been removed documents_to_delete = set(original_attachments_doc_ids) - set(attachments) # noqa: E501 for document_id in documents_to_delete: with cls.default_db.no_autoflush: document = models.Document.get(document_id) post_attachment = post.db.query( models.PostAttachment ).filter_by( discussion_id=discussion_id, post_id=post_id, document_id=document_id ).first() document.delete_file() post.db.delete(document) post.attachments.remove(post_attachment) post.db.flush() publication_state = models.PublicationStates.from_string(args.get('publication_state')) if args.get('publication_state') in models.PublicationStates.values() else None if publication_state and publication_state != post.publication_state: post.publication_state = publication_state changed = True # Update the creation date when switching from draft to published if post.publication_state == models.PublicationStates.DRAFT and publication_state == models.PublicationStates.PUBLISHED: post.creation_date = datetime.utcnow() if changed: post.modification_date = datetime.utcnow() post.body_mime_type = u'text/html' post.db.flush() post.db.expire(post.subject, ["entries"]) post.db.expire(post.body, ["entries"]) return UpdatePost(post=post)
def mutate(root, args, context, info): EMBED_ATTACHMENT = models.AttachmentPurpose.EMBED_ATTACHMENT.value discussion_id = context.matchdict['discussion_id'] user_id = context.authenticated_userid or Everyone discussion = models.Discussion.get(discussion_id) idea_id = args.get('idea_id') idea_id = int(Node.from_global_id(idea_id)[1]) in_reply_to_idea = models.Idea.get(idea_id) if isinstance(in_reply_to_idea, models.Question): cls = models.PropositionPost else: cls = models.AssemblPost extract_id = args.get('extract_id') if extract_id: extract_id_global = int(Node.from_global_id(extract_id)[1]) extract = models.Extract.get(extract_id_global) cls = models.ExtractComment in_reply_to_post = None if (cls == models.AssemblPost) or (cls == models.ExtractComment): in_reply_to_post_id = args.get('parent_id') if in_reply_to_post_id: in_reply_to_post_id = int( Node.from_global_id(in_reply_to_post_id)[1]) if in_reply_to_post_id: in_reply_to_post = models.Post.get(in_reply_to_post_id) require_cls_permission(CrudPermissions.CREATE, cls, context) with cls.default_db.no_autoflush: subject = args.get('subject') body = args.get('body') classifier = args.get('message_classifier', None) body = sanitize_html(body) body_langstring = models.LangString.create(body) publication_state = models.PublicationStates.from_string(args.get('publication_state')) if args.get('publication_state') in models.PublicationStates.values() else models.PublicationStates.PUBLISHED if subject: subject = sanitize_text(subject) subject_langstring = models.LangString.create(subject) elif issubclass(cls, models.PropositionPost): # Specific case first. Respect inheritance. Since we are using # a specific value, construct it with localization machinery. subject_langstring = models.LangString.create_localized_langstring( # noqa: E501 _('Proposal'), discussion.discussion_locales, {'fr': 'Proposition'}) else: # We apply the same logic than in views/api/post.py::create_post # noqa: E501 locale = models.Locale.UNDEFINED if in_reply_to_post and in_reply_to_post.get_title(): original_subject = in_reply_to_post.get_title().first_original() locale = original_subject.locale_code subject = original_subject.value elif in_reply_to_idea: # TODO: some ideas have extra langstring titles # we try to guess the locale of the body to use the same locale for post's subject body_lang, data = discussion.translation_service().identify( body_langstring.entries[0].value, discussion.discussion_locales) closest_subject = in_reply_to_idea.title.closest_entry(body_lang) if closest_subject: subject = closest_subject.value locale = closest_subject.locale.code else: # rather no subject than one in a random locale subject = u'' locale = discussion.main_locale else: subject = discussion.topic if discussion.topic else '' locale = discussion.main_locale if subject is not None: if in_reply_to_idea and in_reply_to_idea.message_view_override == u'messageColumns': new_subject = subject else: new_subject = u'Re: ' + restrip_pat.sub('', subject).strip() # noqa: E501 if (in_reply_to_post and new_subject == subject and in_reply_to_post.get_title()): # reuse subject and translations subject_langstring = in_reply_to_post.get_title().clone(discussion.db) else: subject_langstring = models.LangString.create( new_subject, locale) if cls == models.ExtractComment: new_post = cls( discussion=discussion, subject=subject_langstring, body=body_langstring, creator_id=user_id, body_mime_type=u'text/html', message_classifier=classifier, creation_date=datetime.utcnow(), publication_state=publication_state, parent_extract_id=extract.id ) else: new_post = cls( discussion=discussion, subject=subject_langstring, body=body_langstring, creator_id=user_id, body_mime_type=u'text/html', message_classifier=classifier, creation_date=datetime.utcnow(), publication_state=publication_state ) new_post.guess_languages() db = new_post.db db.add(new_post) db.flush() if in_reply_to_post: new_post.set_parent(in_reply_to_post) elif in_reply_to_idea and cls != models.ExtractComment: # don't create IdeaRelatedPostLink when we have both # in_reply_to_post and in_reply_to_idea or if it's a comment # for an extract idea_post_link = models.IdeaRelatedPostLink( creator_id=user_id, content=new_post, idea=in_reply_to_idea ) db.add(idea_post_link) db.flush() attachments = args.get('attachments', []) for document_id in attachments: document = models.Document.get(document_id) models.PostAttachment( document=document, discussion=discussion, creator_id=context.authenticated_userid, post=new_post, title=document.title, attachmentPurpose=EMBED_ATTACHMENT ) db.flush() return CreatePost(post=new_post)