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 voting_widget_view(request): user_id = authenticated_userid(request) or Everyone ctx = request.context view = (request.matchdict or {}).get('view', None)\ or ctx.get_default_view() or 'default' widget = ctx._instance permissions = get_permissions( user_id, ctx.get_discussion_id()) json = widget.generic_json(view, user_id, permissions) #json['discussion'] = ... if user_id != Everyone: user = User.get(user_id) json['user'] = user.generic_json(view, user_id, permissions) json['user_permissions'] = get_permissions( user_id, widget.get_discussion_id()) user_state = widget.get_user_state(user_id) if user_state is not None: json['user_state'] = user_state target_id = request.GET.get('target', None) if target_id and Idea.get_database_id(target_id): json['user_votes_url'] = widget.get_user_votes_url(target_id) json['voting_urls'] = widget.get_voting_urls(target_id) json['criteria'] = [idea.generic_json(view, user_id, permissions) for idea in widget.criteria] return json
def add_local_role(request): # Do not use check_permissions, this is a special case ctx = request.context user_id = request.authenticated_userid if not user_id: raise HTTPUnauthorized() discussion_id = ctx.get_discussion_id() discussion = Discussion.get(discussion_id) user_uri = User.uri_generic(user_id) if discussion_id is None: raise HTTPBadRequest() permissions = get_permissions(user_id, discussion_id) json = request.json_body if "discussion" not in json: json["discussion"] = Discussion.uri_generic(discussion_id) requested_user = json.get('user', None) if not requested_user: json['user'] = requested_user = user_uri elif requested_user != user_uri and P_ADMIN_DISC not in permissions: raise HTTPUnauthorized() if P_ADMIN_DISC not in permissions: if P_SELF_REGISTER in permissions: json['requested'] = False json['role'] = R_PARTICIPANT req_user = User.get_instance(requested_user) if not discussion.check_authorized_email(req_user): raise HTTPForbidden() elif P_SELF_REGISTER_REQUEST in permissions: json['requested'] = True else: raise HTTPUnauthorized() try: instances = ctx.create_object("LocalUserRole", json, user_id) except HTTPClientError as e: raise e except Exception as e: raise HTTPBadRequest(e) if instances: first = instances[0] db = first.db for instance in instances: db.add(instance) db.flush() # Side effect: materialize subscriptions. if not first.requested: # relationship may not be initialized user = first.user or User.get(first.user_id) user.get_notification_subscriptions(discussion_id, True) # Update the user's AgentStatusInDiscussion user.update_agent_status_subscribe(discussion) view = request.GET.get('view', None) or 'default' permissions = get_permissions( user_id, ctx.get_discussion_id()) return CreationResponse(first, user_id, permissions, view)
def add_local_role(request): # Do not use check_permissions, this is a special case ctx = request.context user_id = authenticated_userid(request) if user_id == Everyone: raise HTTPUnauthorized() discussion_id = ctx.get_discussion_id() user_uri = User.uri_generic(user_id) if discussion_id is None: raise HTTPBadRequest() permissions = get_permissions(user_id, discussion_id) json = request.json_body if "discussion" not in json: json["discussion"] = Discussion.uri_generic(discussion_id) requested_user = json.get('user', None) if not requested_user: json['user'] = requested_user = user_uri elif requested_user != user_uri and P_ADMIN_DISC not in permissions: raise HTTPUnauthorized() if P_ADMIN_DISC not in permissions: if P_SELF_REGISTER in permissions: json['requested'] = False json['role'] = R_PARTICIPANT elif P_SELF_REGISTER_REQUEST in permissions: json['requested'] = True else: raise HTTPUnauthorized() try: instances = ctx.create_object("LocalUserRole", json, user_id) except HTTPClientError as e: raise e except Exception as e: raise HTTPBadRequest(e) if instances: first = instances[0] db = first.db() for instance in instances: db.add(instance) db.flush() # Side effect: materialize subscriptions. if not first.requested: # relationship may not be initialized user = first.user or User.get(first.user_id) user.get_notification_subscriptions(discussion_id, True) view = request.GET.get('view', None) or 'default' permissions = get_permissions( user_id, ctx.get_discussion_id()) return Response( dumps(first.generic_json(view, user_id, permissions)), location=first.uri_generic(first.id), status_code=201)
def discussion_edit(request): discussion_id = int(request.matchdict["discussion_id"]) discussion = Discussion.get_instance(discussion_id) user_id = authenticated_userid(request) assert user_id permissions = get_permissions(user_id, discussion_id) partners = json.dumps( [p.generic_json(user_id=user_id, permissions=permissions) for p in discussion.partner_organizations] ) if not discussion: raise HTTPNotFound("Discussion with id '%d' not found." % (discussion_id,)) context = dict( get_default_context(request), discussion=discussion, admin_discussion_permissions_url=request.route_url("discussion_permissions", discussion_id=discussion.id), partners=partners, ) if request.method == "POST": g = lambda x: request.POST.get(x, None) (topic, slug, objectives) = (g("topic"), g("slug"), g("objectives")) discussion.topic = topic discussion.slug = slug discussion.objectives = objectives return render_to_response("admin/discussion_edit.jinja2", context, request=request)
def votes_collection_add(request): ctx = request.context user_id = authenticated_userid(request) permissions = get_permissions( user_id, ctx.get_discussion_id()) check_permissions(ctx, user_id, permissions, CrudPermissions.CREATE) args = request.params if 'type' in args: args = dict(args) typename = args['type'] del args['type'] else: typename = ctx.collection_class.external_typename() args['voter_id'] = user_id try: instances = ctx.create_object(typename, None, user_id, **args) except Exception as e: raise HTTPBadRequest(e) if instances: first = instances[0] db = first.db() for instance in instances: db.add(instance) print "before flush" db.flush() print "after flush" return Response( dumps(first.generic_json('default', user_id, permissions)), location=first.uri_generic(first.id), status_code=201) raise HTTPBadRequest()
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 mutate(root, args, context, info): model = models.User discussion_id = context.matchdict['discussion_id'] discussion = models.Discussion.get(discussion_id) user_id = context.authenticated_userid or Everyone global_id = args.get('id') id_ = int(Node.from_global_id(global_id)[1]) user = model.get(id_) # Global permission check. Permission is checked in the preferences setter # Is it necessary? permissions = get_permissions(user_id, discussion_id) allowed = user.user_can( user_id, CrudPermissions.UPDATE, permissions) if not allowed: raise HTTPUnauthorized("You don't have the authorization to update this haversting locale. If you think it's an error, please reconnect to assembl.") with model.default_db.no_autoflush as db: preferences = user.get_preferences_for_discussion(discussion) # Permission check in the preferences setter # See models.user_key_value and models.preferences preference_data_list preferences['harvesting_translation'] = args.get('translation') db.flush() return UpdateHarvestingTranslationPreference( preferences=preferences)
def mutate(root, args, context, info): user_id = context.authenticated_userid or Everyone post_id = args.get('post_id') post_id = int(Node.from_global_id(post_id)[1]) post = models.Post.get(post_id) discussion_id = context.matchdict['discussion_id'] permissions = get_permissions(user_id, discussion_id) require_instance_permission(CrudPermissions.DELETE, post, context) # Same logic as in assembl/views/api2/post.py:delete_post_instance # Remove extracts associated to this post extracts_to_remove = post.db.query(models.Extract).filter( models.Extract.content_id == post.id).all() for extract in extracts_to_remove: extract.delete() if user_id == post.creator_id and P_DELETE_MY_POST in permissions: cause = models.PublicationStates.DELETED_BY_USER elif P_DELETE_POST in permissions: cause = models.PublicationStates.DELETED_BY_ADMIN post.delete_post(cause) post.db.flush() return DeletePost(post=post)
def require_instance_permission(permission_type, instance, request): user_id = request.authenticated_userid or Everyone discussion_id = request.matchdict['discussion_id'] permissions = get_permissions(user_id, discussion_id) allowed = instance.user_can(user_id, permission_type, permissions) if instance else False if not allowed: raise HTTPUnauthorized(request.localizer.translate(error))
def require_cls_permission(permission_type, cls, request): user_id = request.authenticated_userid or Everyone discussion_id = request.matchdict['discussion_id'] permissions = get_permissions(user_id, discussion_id) allowed = cls.user_can_cls(user_id, permission_type, permissions) if not allowed or (allowed == IF_OWNED and user_id == Everyone): raise HTTPUnauthorized(request.localizer.translate(error))
def get_token(request): user_id = authenticated_userid(request) discussion_id = request.context.get_discussion_id() if not user_id: raise HTTPUnauthorized() req_permissions = request.GET.getall('permission') or [ P_READ, P_READ_PUBLIC_CIF] random_seed = request.GET.get('seed', None) if random_seed: # We need some determinism import random random.seed(random_seed) random_str = ''.join([chr(random.randint(0,256)) for i in range(8)]) random.seed(urandom(8)) else: random_str = urandom(8) if isinstance(req_permissions, list): req_permissions = set(req_permissions) else: req_permissions = set((req_permissions,)) permissions = set(get_permissions(user_id, discussion_id)) if not req_permissions: req_permissions = permissions else: if P_READ in permissions: permissions.add(P_READ_PUBLIC_CIF) if P_SYSADMIN not in permissions: req_permissions = list(req_permissions.intersection(permissions)) req_permissions = list(req_permissions) data = [str(user_id), str(discussion_id)] data.extend([str(x) for (x,) in Permission.db.query(Permission.id).filter( Permission.name.in_(req_permissions)).all()]) data = ','.join(data) + '.' + base64.urlsafe_b64encode(random_str) return Response(body=data_token(data), content_type="text/text")
def resolve_email(self, args, context, info): user_id = context.authenticated_userid or Everyone discussion_id = context.matchdict['discussion_id'] permissions = get_permissions(user_id, discussion_id) include_emails = P_ADMIN_DISC in permissions or P_SYSADMIN in permissions if include_emails or self.id == user_id: return self.get_preferred_email()
def notif_collection_add_json(request): 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.CREATE) typename = ctx.collection_class.external_typename() typename = request.json_body.get( '@type', ctx.collection_class.external_typename()) json = request.json_body try: instances = ctx.create_object(typename, json, user_id) except Exception as e: raise HTTPBadRequest(e) if instances: first = instances[0] db = first.db for instance in instances: db.add(instance) db.flush() templates = ctx.find_collection('Discussion.user_templates') if templates: templates.parent_instance.reset_participant_default_subscriptions(False) view = request.GET.get('view', None) or 'default' return Response( dumps(first.generic_json(view, user_id, permissions)), location=first.uri_generic(first.id), status_code=201)
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 _get_ideas_real(discussion, view_def=None, ids=None, user_id=None): user_id = user_id or Everyone # optimization: Recursive widget links. from assembl.models import ( Widget, IdeaWidgetLink, IdeaDescendantsShowingWidgetLink) universal_widget_links = [] by_idea_widget_links = defaultdict(list) widget_links = discussion.db.query(IdeaWidgetLink ).join(Widget).join(Discussion).filter( Widget.test_active(), Discussion.id == discussion.id, IdeaDescendantsShowingWidgetLink.polymorphic_filter() ).options(joinedload_all(IdeaWidgetLink.idea)).all() for wlink in widget_links: if isinstance(wlink.idea, RootIdea): universal_widget_links.append({ '@type': wlink.external_typename(), 'widget': Widget.uri_generic(wlink.widget_id)}) else: for id in wlink.idea.get_all_descendants(True): by_idea_widget_links[Idea.uri_generic(id)].append({ '@type': wlink.external_typename(), 'widget': Widget.uri_generic(wlink.widget_id)}) next_synthesis = discussion.get_next_synthesis() ideas = discussion.db.query(Idea).filter_by( discussion_id=discussion.id ) ideas = ideas.outerjoin(SubGraphIdeaAssociation, and_(SubGraphIdeaAssociation.sub_graph_id==next_synthesis.id, SubGraphIdeaAssociation.idea_id==Idea.id) ) ideas = ideas.outerjoin(IdeaLink, and_(IdeaLink.target_id==Idea.id) ) ideas = ideas.order_by(IdeaLink.order, Idea.creation_date) if ids: ids = [get_database_id("Idea", id) for id in ids] ideas = ideas.filter(Idea.id.in_(ids)) # remove tombstones ideas = ideas.filter(and_(*Idea.base_conditions())) ideas = ideas.options( joinedload_all(Idea.source_links), joinedload_all(Idea.has_showing_widget_links), undefer(Idea.num_children)) permissions = get_permissions(user_id, discussion.id) Idea.prepare_counters(discussion.id, True) retval = [idea.generic_json(view_def, user_id, permissions) for idea in ideas] retval = [x for x in retval if x is not None] for r in retval: if r.get('widget_links', None) is not None: links = r['widget_links'][:] links.extend(universal_widget_links) links.extend(by_idea_widget_links[r['@id']]) r['active_widget_links'] = links return retval
def create_object(self, typename=None, json=None, user_id=None, **kwargs): cls = self.get_collection_class(typename) permissions = get_permissions( user_id, self.get_discussion_id()) permissions.extend(self.ctx_permissions(permissions)) with self.parent_instance.db.no_autoflush: try: if json is None: mapper = sqlainspect(cls) for prop in ('creator_id', 'user_id'): if prop in mapper.c and prop not in kwargs: kwargs[prop] = user_id break inst = cls(**dict(process_args(kwargs, cls))) else: inst = cls.create_from_json( json, user_id, self, permissions=permissions) kwargs.update(json) except Exception as e: print_exc() raise e assocs = [inst] self.decorate_instance(inst, assocs, user_id, self, kwargs) if json is None: inst = inst.handle_duplication( None, None, self, permissions, user_id, None, None, kwargs) assocs[0] = inst return assocs
def delete_post_instance(request): # Users who are allowed to delete (actually tombstone) a Post instance: # - user who is the author of the Post instance and who has the P_DELETE_MY_POST permission in this discussion # - user who has the P_DELETE_POST permission in this discussion ctx = request.context user_id = request.authenticated_userid or Everyone permissions = get_permissions( user_id, ctx.get_discussion_id()) instance = ctx._instance allowed = False if (user_id == instance.creator_id and P_DELETE_MY_POST in permissions) or (P_DELETE_POST in permissions): allowed = True if not allowed: raise HTTPUnauthorized() # Remove extracts associated to this post extracts_to_remove = instance.db.query(Extract).filter(Extract.content_id == instance.id).all() number_of_extracts = len(extracts_to_remove) for extract in extracts_to_remove: extract.delete() if user_id == instance.creator_id and P_DELETE_MY_POST in permissions: cause = PublicationStates.DELETED_BY_USER elif P_DELETE_POST in permissions: cause = PublicationStates.DELETED_BY_ADMIN instance.delete_post(cause) return { "result": "Post has been successfully deleted.", "removed_extracts": number_of_extracts }
def set_local_role(request): # Do not use check_permissions, this is a special case ctx = request.context instance = ctx._instance user_id = authenticated_userid(request) if not user_id: raise HTTPUnauthorized() discussion_id = ctx.get_discussion_id() user_uri = User.uri_generic(user_id) if discussion_id is None: raise HTTPBadRequest() permissions = get_permissions(user_id, discussion_id) json = request.json_body requested_user = json.get('user', None) if not requested_user: json['user'] = requested_user = user_uri elif requested_user != user_uri and P_ADMIN_DISC not in permissions: raise HTTPUnauthorized() if P_ADMIN_DISC not in permissions: if P_SELF_REGISTER in permissions: json['requested'] = False json['role'] = R_PARTICIPANT elif P_SELF_REGISTER_REQUEST in permissions: json['requested'] = True else: raise HTTPUnauthorized() updated = instance.update_from_json(json, user_id, ctx) view = request.GET.get('view', None) or 'default' if view == 'id_only': return [updated.uri()] else: return updated.generic_json(view, user_id, permissions)
def instance_view_jsonld(request): from assembl.semantic.virtuoso_mapping import AssemblQuadStorageManager from rdflib import URIRef, ConjunctiveGraph ctx = request.context user_id = authenticated_userid(request) or Everyone permissions = get_permissions( user_id, ctx.get_discussion_id()) instance = ctx._instance if not instance.user_can(user_id, CrudPermissions.READ, permissions): return HTTPUnauthorized() discussion = ctx.get_instance_of_class(Discussion) if not discussion: raise HTTPNotFound() aqsm = AssemblQuadStorageManager() uri = URIRef(aqsm.local_uri() + instance.uri()[6:]) d_storage_name = aqsm.discussion_storage_name(discussion.id) v = get_virtuoso(instance.db, d_storage_name) cg = ConjunctiveGraph(v, d_storage_name) result = cg.triples((uri, None, None)) #result = v.query('select ?p ?o ?g where {graph ?g {<%s> ?p ?o}}' % uri) # Something is wrong here. triples = '\n'.join([ '%s %s %s.' % (uri.n3(), p.n3(), o.n3()) for (s, p, o) in result if '_with_no_name_entry' not in o]) return aqsm.quads_to_jsonld(triples)
def collection_add(request, args): 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.CREATE) if 'type' in args: args = dict(args) typename = args['type'] del args['type'] else: typename = ctx.collection_class.external_typename() session = User.default_db old_autoflush = session.autoflush session.autoflush = False try: instances = ctx.create_object(typename, None, user_id, **args) except Exception as e: session.autoflush = old_autoflush raise HTTPBadRequest(e) if instances: first = instances[0] db = first.db for instance in instances: db.add(instance) session.autoflush = old_autoflush session.flush() return Response( dumps(first.generic_json('default', user_id, permissions)), location=first.uri_generic(first.id), status_code=201) raise HTTPBadRequest()
def class_add(request): 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.CREATE) args = request.params typename = args.get('type', None) if typename: cls = ctx.get_class(typename) else: cls = request.context._class typename = cls.external_typename() try: instances = ctx.create_object(typename, None, user_id, **args) except ObjectNotUniqueError as e: raise JSONError(409, str(e)) except Exception as e: raise HTTPBadRequest(e) if instances: first = instances[0] db = first.db for instance in instances: db.add(instance) db.flush() return Response( dumps(first.generic_json('default', user_id, permissions)), location=first.uri_generic(first.id), status_code=201) raise HTTPBadRequest()
def fetch_posts(request): ctx = request.context csource = ctx._instance force_restart = request.params.get('force_restart', False) reimport = request.params.get('reimport', False) limit = request.params.get('limit', None) if limit: try: limit = int(limit) except ValueError: raise HTTPBadRequest("Non-numeric limit value: "+limit) if force_restart or reimport or limit: # Only discussion admins user_id = authenticated_userid(request) or Everyone permissions = get_permissions( user_id, ctx.get_discussion_id()) if P_ADMIN_DISC not in permissions: requested = [] if reimport: requested.append('reimport') if force_restart: requested.append('force restart') raise HTTPUnauthorized("Only discussion administrator can "+'and'.join(requested)) wake(csource.id, reimport, force_restart, limit=limit) return {"message": "Source notified", "name": csource.name}
def collection_add_json(request, json=None): ctx = request.context json = request.json_body if json is None else json user_id = authenticated_userid(request) or Everyone permissions = get_permissions( user_id, ctx.get_discussion_id()) typename = ctx.collection_class.external_typename() typename = json.get( '@type', ctx.collection_class.external_typename()) cls = ctx.get_collection_class(typename) check_permissions(ctx, user_id, permissions, CrudPermissions.CREATE, cls) try: instances = ctx.create_object(typename, json, user_id) except Exception as e: raise HTTPBadRequest(e) if instances: first = instances[0] db = first.db for instance in instances: db.add(instance) db.flush() view = request.GET.get('view', None) or 'default' return Response( dumps(first.generic_json(view, user_id, permissions)), location=first.uri_generic(first.id), status_code=201)
def raise_if_cannot_moderate(request): ctx = request.context user_id = request.authenticated_userid if not user_id: raise HTTPUnauthorized() permissions = get_permissions( user_id, ctx.get_discussion_id()) if P_MODERATE not in permissions: raise HTTPUnauthorized()
def get_idea_sibling_criteria(request): ctx = request.context view = (request.matchdict or {}).get('view', None)\ or ctx.get_default_view() or 'default' user_id = authenticated_userid(request) or Everyone permissions = get_permissions( user_id, ctx.get_discussion_id()) return [cr.generic_json(view, user_id, permissions) for cr in ctx._instance.get_siblings_of_type(Idea)]
def instance_del(request): ctx = request.context user_id = authenticated_userid(request) permissions = get_permissions( user_id, ctx.get_discussion_id()) instance = ctx._instance if not instance.user_can(user_id, CrudPermissions.DELETE, permissions): return HTTPUnauthorized() instance.db.delete(instance) return {}
def instance_view(request): ctx = request.context user_id = authenticated_userid(request) or Everyone permissions = get_permissions( user_id, ctx.get_discussion_id()) instance = ctx._instance if not instance.user_can(user_id, CrudPermissions.READ, permissions): return HTTPUnauthorized() view = ctx.get_default_view() or 'default' view = request.GET.get('view', view) return instance.generic_json(view, user_id, permissions)
def vote_results(request): ctx = request.context user_id = authenticated_userid(request) or Everyone widget = ctx.get_instance_of_class(MultiCriterionVotingWidget) if not widget: raise HTTPNotFound() if widget.activity_state != "ended": permissions = get_permissions(user_id, ctx.get_discussion_id()) if P_ADMIN_DISC not in permissions: raise HTTPUnauthorized() return ctx._instance.all_voting_results()
def get_post(request): post_id = request.matchdict['id'] post = Post.get_instance(post_id) view_def = request.GET.get('view') or 'default' if not post: raise HTTPNotFound("Post with id '%s' not found." % post_id) discussion_id = int(request.matchdict['discussion_id']) user_id = authenticated_userid(request) or Everyone permissions = get_permissions(user_id, discussion_id) return post.generic_json(view_def, user_id, permissions)
def upload_file(request): """ POSTing a file upload is very different than any other endpoint in assembl API because all of the content will be passed in using a MULTIPART_HEADER, with all of data as well as the file (along with its metadata) """ # Testing purposes on front-end # raise Exception("Upload file exception occured!") db = Document.default_db ctx = request.context user_id = request.authenticated_userid or Everyone discusison_id = ctx.get_discussion_id() discussion = Discussion.get(discusison_id) permissions = get_permissions(user_id, discusison_id) mime = request.POST['mime_type'] file_name = request.POST['name'] # Check if the file has previously existed, if so, change the name by appending "(n)" # to it's name try: blob = File(discussion=discussion, mime_type=mime, title=file_name) db.add(blob) with request.POST['file'].file as f: blob.add_file_data(f) db.flush() except Exception as e: raise HTTPServerError(e) view = 'default' return blob.generic_json(view, user_id, permissions)
def mutate(root, args, context, info): EMBED_ATTACHMENT = models.AttachmentPurpose.EMBED_ATTACHMENT.value MEDIA_ATTACHMENT = models.AttachmentPurpose.MEDIA_ATTACHMENT.value cls = models.Idea phase_identifier = args.get('identifier') if phase_identifier == Phases.survey.value: cls = models.Thematic discussion_id = context.matchdict['discussion_id'] discussion = models.Discussion.get(discussion_id) user_id = context.authenticated_userid or Everyone permissions = get_permissions(user_id, discussion_id) allowed = cls.user_can_cls(user_id, CrudPermissions.CREATE, permissions) if not allowed or (allowed == IF_OWNED and user_id == Everyone): raise HTTPUnauthorized() with cls.default_db.no_autoflush as db: title_entries = args.get('title_entries') if len(title_entries) == 0: raise Exception( 'Thematic titleEntries needs at least one entry') # Better to have this message than # 'NoneType' object has no attribute 'owner_object' # when creating the saobj below if title=None title_langstring = langstring_from_input_entries(title_entries) description_langstring = langstring_from_input_entries( args.get('description_entries')) kwargs = {} if description_langstring is not None: kwargs['description'] = description_langstring kwargs['message_view_override'] = args.get('message_view_override') video = args.get('video') video_media = None if video is not None: video_title = langstring_from_input_entries( video.get('title_entries', None)) if video_title is not None: kwargs['video_title'] = video_title video_description_top = langstring_from_input_entries( video.get('description_entries_top', None)) if video_description_top is not None: kwargs['video_description_top'] = video_description_top video_description_bottom = langstring_from_input_entries( video.get('description_entries_bottom', None)) if video_description_bottom is not None: kwargs[ 'video_description_bottom'] = video_description_bottom video_description_side = langstring_from_input_entries( video.get('description_entries_side', None)) if video_description_side is not None: kwargs['video_description_side'] = video_description_side video_html_code = video.get('html_code', None) if video_html_code is not None: kwargs['video_html_code'] = video_html_code video_media = video.get('media_file', None) parent_idea_id = args.get('parent_id') if parent_idea_id: parent_idea_id = int(Node.from_global_id(parent_idea_id)[1]) parent_idea = models.Idea.get(parent_idea_id) if not parent_idea: raise Exception('Parent Idea not found') if parent_idea.discussion != discussion: # No cross-debate references are allowed, # for security reasons raise Exception( 'Parent Idea does not belong to this discussion' ) # noqa: E501 else: if phase_identifier in (Phases.thread.value, Phases.multiColumns.value): parent_idea = discussion.root_idea else: # Our thematic, because it inherits from Idea, needs to be # associated to the root idea of the discussion. # We create a hidden root thematic, corresponding to the # `identifier` phase, child of the root idea, # and add our thematic as a child of this root thematic. parent_idea = get_root_thematic_for_phase( discussion, phase_identifier) if parent_idea is None: parent_idea = create_root_thematic( discussion, phase_identifier) saobj = cls(discussion_id=discussion_id, title=title_langstring, **kwargs) if cls == models.Thematic: saobj.identifier = phase_identifier # I don't think this is really used db.add(saobj) order = len(parent_idea.get_children()) + 1.0 db.add( models.IdeaLink(source=parent_idea, target=saobj, order=args.get('order', order))) # Create the idea announcement object which corresponds to the instructions announcement = args.get('announcement') if announcement is not None: announcement_title_entries = announcement.get('title_entries') if len(announcement_title_entries) == 0: raise Exception( 'Announcement titleEntries needs at least one entry') announcement_title_langstring = langstring_from_input_entries( announcement_title_entries) announcement_body_langstring = langstring_from_input_entries( announcement.get('body_entries', None)) saobj2 = create_idea_announcement( user_id, discussion, saobj, announcement_title_langstring, announcement_body_langstring) db.add(saobj2) # add uploaded image as an attachment to the idea image = args.get('image') if image is not None: new_attachment = create_attachment(discussion, models.IdeaAttachment, image, EMBED_ATTACHMENT, context) new_attachment.idea = saobj db.add(new_attachment) # add uploaded image as an attachment to the idea if video_media is not None: new_attachment = create_attachment(discussion, models.IdeaAttachment, video_media, MEDIA_ATTACHMENT, context) new_attachment.idea = saobj db.add(new_attachment) questions_input = args.get('questions') if questions_input is not None: for idx, question_input in enumerate(questions_input): title_ls = langstring_from_input_entries( question_input['title_entries']) question = models.Question(title=title_ls, discussion_id=discussion_id) db.add( models.IdeaLink(source=saobj, target=question, order=idx + 1.0)) db.flush() return CreateThematic(thematic=saobj)
def _get_ideas_real(discussion, view_def=None, ids=None, user_id=None): user_id = user_id or Everyone # optimization: Recursive widget links. from assembl.models import (Widget, IdeaWidgetLink, IdeaDescendantsShowingWidgetLink) universal_widget_links = [] by_idea_widget_links = defaultdict(list) widget_links = discussion.db.query(IdeaWidgetLink).join(Widget).join( Discussion).filter( Widget.test_active(), Discussion.id == discussion.id, IdeaDescendantsShowingWidgetLink.polymorphic_filter()).options( joinedload_all(IdeaWidgetLink.idea)).all() for wlink in widget_links: if isinstance(wlink.idea, RootIdea): universal_widget_links.append({ '@type': wlink.external_typename(), 'widget': Widget.uri_generic(wlink.widget_id) }) else: for id in wlink.idea.get_all_descendants(True): by_idea_widget_links[Idea.uri_generic(id)].append({ '@type': wlink.external_typename(), 'widget': Widget.uri_generic(wlink.widget_id) }) next_synthesis = discussion.get_next_synthesis() ideas = discussion.db.query(Idea).filter_by(discussion_id=discussion.id) ideas = ideas.outerjoin( SubGraphIdeaAssociation, and_(SubGraphIdeaAssociation.sub_graph_id == next_synthesis.id, SubGraphIdeaAssociation.idea_id == Idea.id)) ideas = ideas.outerjoin(IdeaLink, and_(IdeaLink.target_id == Idea.id)) ideas = ideas.order_by(IdeaLink.order, Idea.creation_date) if ids: ids = [get_database_id("Idea", id) for id in ids] ideas = ideas.filter(Idea.id.in_(ids)) # remove tombstones ideas = ideas.filter(and_(*Idea.base_conditions())) ideas = ideas.options(joinedload_all(Idea.source_links), joinedload_all(Idea.attachments), joinedload_all(Idea.message_columns), joinedload_all(Idea.has_showing_widget_links), undefer(Idea.num_children)) permissions = get_permissions(user_id, discussion.id) Idea.prepare_counters(discussion.id, True) retval = [ idea.generic_json(view_def, user_id, permissions) for idea in ideas ] retval = [x for x in retval if x is not None] for r in retval: if r.get('widget_links', None) is not None: links = r['widget_links'][:] links.extend(universal_widget_links) links.extend(by_idea_widget_links[r['@id']]) r['active_widget_links'] = links return retval
def mutate(root, args, context, info): cls = models.Discussion discussion_id = context.matchdict['discussion_id'] discussion = models.Discussion.get(discussion_id) user_id = context.authenticated_userid or Everyone permissions = get_permissions(user_id, discussion_id) allowed = discussion.user_can( user_id, CrudPermissions.UPDATE, permissions) if not allowed: raise HTTPUnauthorized() with cls.default_db.no_autoflush as db: legal_notice_entries = args.get('legal_notice_entries') if legal_notice_entries is not None and len(legal_notice_entries) == 0: raise Exception( 'Legal notice entries needs at least one entry') # Better to have this message than # 'NoneType' object has no attribute 'owner_object' # when creating the saobj below if title=None update_langstring_from_input_entries( discussion, 'legal_notice', legal_notice_entries) terms_and_conditions_entries = args.get( 'terms_and_conditions_entries') if terms_and_conditions_entries is not None and len(terms_and_conditions_entries) == 0: raise Exception( 'Terms and conditions entries needs at least one entry') # Better to have this message than # 'NoneType' object has no attribute 'owner_object' # when creating the saobj below if title=None update_langstring_from_input_entries( discussion, 'terms_and_conditions', terms_and_conditions_entries) cookies_policy_entries = args.get('cookies_policy_entries') if cookies_policy_entries is not None and len(cookies_policy_entries) == 0: raise Exception( 'Cookies policy entries needs at least one entry') update_langstring_from_input_entries( discussion, 'cookies_policy', cookies_policy_entries) privacy_policy_entries = args.get('privacy_policy_entries') if privacy_policy_entries is not None and len(privacy_policy_entries) == 0: raise Exception( 'Privacy policy entries need to be at least one entry' ) update_langstring_from_input_entries( discussion, 'privacy_policy', privacy_policy_entries) user_guidelines_entries = args.get('user_guidelines_entries') if user_guidelines_entries is not None and len(user_guidelines_entries) == 0: raise Exception( 'User guidelines entries need to be at least one entry' ) update_langstring_from_input_entries( discussion, 'user_guidelines', user_guidelines_entries ) db.flush() legal_contents = LegalContents() return UpdateLegalContents(legal_contents=legal_contents)
def get_posts(request): """ Query interface on posts Filters have two forms: only_*, is for filters that cannot be reversed (ex: only_synthesis, only_orphan) is_*, is for filters that can be reversed (ex:is_unread=true returns only unread message, is_unread=false returns only read messages) order: can be chronological, reverse_chronological, popularity root_post_id: all posts below the one specified. family_post_id: all posts below the one specified, and all its ancestors. post_reply_to: replies to a given post root_idea_id: all posts associated with the given idea ids: explicit message ids. posted_after_date, posted_before_date: date selection (ISO format) post_author: filter by author classifier: filter on message_classifier, or absence thereof (classifier=null). Can be negated with "!" """ localizer = request.localizer discussion_id = int(request.matchdict['discussion_id']) discussion = Discussion.get(int(discussion_id)) if not discussion: raise HTTPNotFound( localizer.translate(_("No discussion found with id=%s")) % discussion_id) discussion.import_from_sources() user_id = authenticated_userid(request) or Everyone permissions = get_permissions(user_id, discussion_id) DEFAULT_PAGE_SIZE = 25 page_size = DEFAULT_PAGE_SIZE filter_names = [ filter_name for filter_name in request.GET.getone('filters').split(',') if filter_name ] if request.GET.get('filters') else [] try: page = int(request.GET.getone('page')) except (ValueError, KeyError): page = 1 text_search = request.GET.get('text_search', None) order = request.GET.get('order') if order is None: order = 'chronological' assert order in ('chronological', 'reverse_chronological', 'score', 'popularity') if order == 'score': assert text_search is not None if page < 1: page = 1 root_post_id = request.GET.getall('root_post_id') if root_post_id: root_post_id = get_database_id("Post", root_post_id[0]) family_post_id = request.GET.getall('family_post_id') if family_post_id: family_post_id = get_database_id("Post", family_post_id[0]) root_idea_id = request.GET.getall('root_idea_id') if root_idea_id: root_idea_id = get_database_id("Idea", root_idea_id[0]) ids = request.GET.getall('ids[]') if ids: ids = [get_database_id("Post", id) for id in ids] view_def = request.GET.get('view') or 'default' only_synthesis = request.GET.get('only_synthesis') post_author_id = request.GET.get('post_author') if post_author_id: post_author_id = get_database_id("AgentProfile", post_author_id) assert AgentProfile.get( post_author_id ), "Unable to find agent profile with id " + post_author_id post_replies_to = request.GET.get('post_replies_to') if post_replies_to: post_replies_to = get_database_id("AgentProfile", post_replies_to) assert AgentProfile.get( post_replies_to ), "Unable to find agent profile with id " + post_replies_to posted_after_date = request.GET.get('posted_after_date') posted_before_date = request.GET.get('posted_before_date') message_classifiers = request.GET.getall('classifier') PostClass = SynthesisPost if only_synthesis == "true" else Post if order == 'score': posts = discussion.db.query(PostClass, Content.body_text_index.score_name) else: posts = discussion.db.query(PostClass) posts = posts.filter(PostClass.discussion_id == discussion_id, ) ##no_of_posts_to_discussion = posts.count() post_data = [] # True means deleted only, False (default) means non-deleted only. None means both. # v0 # deleted = request.GET.get('deleted', None) # end v0 # v1: we would like something like that # deleted = request.GET.get('deleted', None) # if deleted is None: # if view_def == 'id_only': # deleted = None # else: # deleted = False # end v1 # v2 # deleted = request.GET.get('deleted', None) # if deleted is None: # if not ids: # deleted = False # else: # deleted = None # # if deleted == 'false': # deleted = False # posts = posts.filter(PostClass.tombstone_condition()) # elif deleted == 'true': # deleted = True # posts = posts.filter(PostClass.not_tombstone_condition()) # elif deleted == 'any': # deleted = None # # result will contain deleted and non-deleted posts # pass # end v2 # v3 # deleted = request.GET.get('deleted', None) # if deleted is None: # if not ids: # deleted = False # else: # deleted = None # if deleted == 'true': # deleted = True # posts = posts.filter(PostClass.not_tombstone_condition()) # end v3 # v4 deleted = request.GET.get('deleted', None) if deleted is None: if not ids: deleted = False else: deleted = None elif deleted.lower() == "any": deleted = None else: deleted = asbool(deleted) # if deleted is not in (False, True, None): # deleted = False # end v4 only_orphan = asbool(request.GET.get('only_orphan', False)) if only_orphan: if root_idea_id: raise HTTPBadRequest( localizer.translate( _("Getting orphan posts of a specific idea isn't supported." ))) orphans = Idea._get_orphan_posts_statement( discussion_id, True, include_deleted=deleted).subquery("orphans") posts = posts.join(orphans, PostClass.id == orphans.c.post_id) if root_idea_id: related = Idea.get_related_posts_query_c(discussion_id, root_idea_id, True, include_deleted=deleted) posts = posts.join(related, PostClass.id == related.c.post_id) elif not only_orphan: if deleted is not None: if deleted: posts = posts.filter( PostClass.publication_state.in_( deleted_publication_states)) else: posts = posts.filter(PostClass.tombstone_date == None) if root_post_id: root_post = Post.get(root_post_id) posts = posts.filter((Post.ancestry.like(root_post.ancestry + cast(root_post.id, String) + ',%')) | (PostClass.id == root_post.id)) elif family_post_id: root_post = Post.get(family_post_id) ancestor_ids = root_post.ancestor_ids() posts = posts.filter((Post.ancestry.like(root_post.ancestry + cast(root_post.id, String) + ',%')) | (PostClass.id == root_post.id) | (PostClass.id.in_(ancestor_ids))) else: root_post = None if ids: posts = posts.filter(Post.id.in_(ids)) if posted_after_date: posted_after_date = parse_datetime(posted_after_date) if posted_after_date: posts = posts.filter(PostClass.creation_date >= posted_after_date) #Maybe we should do something if the date is invalid. benoitg if posted_before_date: posted_before_date = parse_datetime(posted_before_date) if posted_before_date: posts = posts.filter(PostClass.creation_date <= posted_before_date) #Maybe we should do something if the date is invalid. benoitg if post_author_id: posts = posts.filter(PostClass.creator_id == post_author_id) if message_classifiers: if any([len(classifier) == 0 for classifier in message_classifiers]): return {'total': 0, 'posts': []} polarities = [ classifier[0] != "!" for classifier in message_classifiers ] polarity = all(polarities) if not polarity: message_classifiers = [c.strip("!") for c in message_classifiers] if polarity != any(polarities): raise HTTPBadRequest( _("Do not combine negative and positive classifiers")) # Treat null as no classifier includes_null = 'null' in message_classifiers if includes_null: message_classifiers_nonull = filter(lambda c: c != "null", message_classifiers) if polarity: if len(message_classifiers) == 1: term = PostClass.message_classifier == ( None if includes_null else message_classifiers[0]) else: term = PostClass.message_classifier.in_( message_classifiers_nonull) if includes_null: term = term | (PostClass.message_classifier == None) else: if len(message_classifiers) == 1: term = PostClass.message_classifier != ( None if includes_null else message_classifiers[0]) else: term = PostClass.message_classifier.notin_( message_classifiers_nonull) if not includes_null: term = term | (PostClass.message_classifier == None) posts = posts.filter(term) if post_replies_to: parent_alias = aliased(PostClass) posts = posts.join(parent_alias, PostClass.parent) posts = posts.filter(parent_alias.creator_id == post_replies_to) # Post read/unread management is_unread = request.GET.get('is_unread') translations = None if user_id != Everyone: # This is horrible, but the join creates complex subqueries that # virtuoso cannot decode properly. read_posts = { v.post_id for v in discussion.db.query(ViewPost).filter( ViewPost.tombstone_condition(), ViewPost.actor_id == user_id, *ViewPost.get_discussion_conditions(discussion_id)) } my_sentiments = { l.post_id: l for l in discussion.db.query(SentimentOfPost).filter( SentimentOfPost.tombstone_condition(), SentimentOfPost.actor_id == user_id, *SentimentOfPost.get_discussion_conditions(discussion_id)) } if is_unread != None: posts = posts.outerjoin( ViewPost, and_(ViewPost.actor_id == user_id, ViewPost.post_id == PostClass.id, ViewPost.tombstone_date == None)) if is_unread == "true": posts = posts.filter(ViewPost.id == None) elif is_unread == "false": posts = posts.filter(ViewPost.id != None) user = AgentProfile.get(user_id) service = discussion.translation_service() if service: translations = PrefCollectionTranslationTable( service, LanguagePreferenceCollection.getCurrent(request)) else: #If there is no user_id, all posts are always unread my_sentiments = {} if is_unread == "false": raise HTTPBadRequest( localizer.translate( _("You must be logged in to view which posts are read"))) if text_search is not None: # another Virtuoso bug: offband kills score. but it helps speed. offband = () if (order == 'score') else None posts = posts.filter( Post.body_text_index.contains(text_search.encode('utf-8'), offband=offband)) # posts = posts.options(contains_eager(Post.source)) # Horrible hack... But useful for structure load if view_def == 'id_only': pass # posts = posts.options(defer(Post.body)) else: ideaContentLinkQuery = posts.with_entities( PostClass.id, PostClass.idea_content_links_above_post) ideaContentLinkCache = dict(ideaContentLinkQuery.all()) # Note: we could count the like the same way and kill the subquery. # But it interferes with the popularity order, # and the benefit is not that high. sentiment_counts = discussion.db.query( PostClass.id, SentimentOfPost.type, count(SentimentOfPost.id)).join(SentimentOfPost).filter( PostClass.id.in_(posts.with_entities(PostClass.id).subquery()), SentimentOfPost.tombstone_condition()).group_by( PostClass.id, SentimentOfPost.type) sentiment_counts_by_post_id = defaultdict(dict) for (post_id, sentiment_type, sentiment_count) in sentiment_counts: sentiment_counts_by_post_id[post_id][sentiment_type[ SentimentOfPost.TYPE_PREFIX_LEN:]] = sentiment_count posts = posts.options( # undefer(Post.idea_content_links_above_post), joinedload_all(Post.creator), joinedload_all(Post.extracts), joinedload_all(Post.widget_idea_links), joinedload_all(SynthesisPost.publishes_synthesis), subqueryload_all(Post.attachments)) if len(discussion.discussion_locales) > 1: posts = posts.options(*Content.subqueryload_options()) else: posts = posts.options(*Content.joinedload_options()) if order == 'chronological': posts = posts.order_by(Content.creation_date) elif order == 'reverse_chronological': posts = posts.order_by(Content.creation_date.desc()) elif order == 'score': posts = posts.order_by(Content.body_text_index.score_name.desc()) elif order == 'popularity': # assume reverse chronological otherwise posts = posts.order_by(Content.disagree_count - Content.like_count, Content.creation_date.desc()) else: posts = posts.order_by(Content.id) # print str(posts) no_of_posts = 0 no_of_posts_viewed_by_user = 0 if deleted is True: # We just got deleted posts, now we want their ancestors for context post_ids = set() ancestor_ids = set() def add_ancestors(post): post_ids.add(post.id) ancestor_ids.update( [int(x) for x in post.ancestry.strip(",").split(",") if x]) posts = list(posts) for post in posts: add_ancestors(post) ancestor_ids -= post_ids if ancestor_ids: ancestors = discussion.db.query(PostClass).filter( PostClass.id.in_(ancestor_ids)) if view_def == 'id_only': pass # ancestors = ancestors.options(defer(Post.body)) else: ancestors = ancestors.options( # undefer(Post.idea_content_links_above_post), joinedload_all(Post.creator), joinedload_all(Post.extracts), joinedload_all(Post.widget_idea_links), joinedload_all(SynthesisPost.publishes_synthesis), subqueryload_all(Post.attachments)) if len(discussion.discussion_locales) > 1: ancestors = ancestors.options( *Content.subqueryload_options()) else: ancestors = ancestors.options( *Content.joinedload_options()) posts.extend(ancestors.all()) for query_result in posts: score, viewpost = None, None if not isinstance(query_result, (list, tuple)): query_result = [query_result] post = query_result[0] if deleted is True: add_ancestors(post) if user_id != Everyone: viewpost = post.id in read_posts if view_def != "id_only": translate_content(post, translation_table=translations, service=service) no_of_posts += 1 serializable_post = post.generic_json(view_def, user_id, permissions) or {} if order == 'score': score = query_result[1] serializable_post['score'] = score if viewpost: serializable_post['read'] = True no_of_posts_viewed_by_user += 1 elif user_id != Everyone and root_post is not None and root_post.id == post.id: # Mark post read, we requested it explicitely viewed_post = ViewPost(actor_id=user_id, post=root_post) discussion.db.add(viewed_post) serializable_post['read'] = True else: serializable_post['read'] = False my_sentiment = my_sentiments.get(post.id, None) if my_sentiment is not None: my_sentiment = my_sentiment.generic_json('default', user_id, permissions) serializable_post['my_sentiment'] = my_sentiment if view_def != "id_only": serializable_post['indirect_idea_content_links'] = ( post.indirect_idea_content_links_with_cache( ideaContentLinkCache.get(post.id, None))) serializable_post[ 'sentiment_counts'] = sentiment_counts_by_post_id[post.id] post_data.append(serializable_post) # Benoitg: For now, this completely garbles threading without intelligent #handling of pagination. Disabling #posts = posts.limit(page_size).offset(data['startIndex']-1) # This code isn't up to date. If limiting the query by page, we need to # calculate the counts with a separate query to have the right number of # results #no_of_messages_viewed_by_user = discussion.db.query(ViewPost).join( # Post #).filter( # Post.discussion_id == discussion_id, # ViewPost.actor_id == user_id, #).count() if user_id else 0 data = {} data["page"] = page data["unread"] = no_of_posts - no_of_posts_viewed_by_user data["total"] = no_of_posts data["maxPage"] = max(1, ceil(float(data["total"]) / page_size)) #TODO: Check if we want 1 based index in the api data["startIndex"] = (page_size * page) - (page_size - 1) if data["page"] == data["maxPage"]: data["endIndex"] = data["total"] else: data["endIndex"] = data["startIndex"] + (page_size - 1) data["posts"] = post_data return data
def global_vote_results_csv(request): ctx = request.context user_id = request.authenticated_userid if not user_id: raise HTTPUnauthorized widget = ctx._instance if widget.activity_state != "ended": permissions = get_permissions(user_id, ctx.get_discussion_id()) if P_ADMIN_DISC not in permissions: raise HTTPUnauthorized() user_prefs = LanguagePreferenceCollection.getCurrent() # first fetch the ideas voted on ideas = widget.db.query(Idea).join(AbstractVoteSpecification).filter( AbstractVoteSpecification.widget_id == widget.id).distinct().all() rowtitles = [(idea.safe_title(user_prefs, request.localizer), idea.id) for idea in ideas] rowtitles.sort() specs = widget.vote_specifications # specs and their templates specids_by_template_specid = defaultdict(list) spec_by_idea_id_and_template_specid = {} for spec in specs: specids_by_template_specid[spec.vote_spec_template_id or spec.id].append(spec.id) spec_by_idea_id_and_template_specid[(spec.criterion_idea_id, (spec.vote_spec_template_id or spec.id))] = spec # then get the vote specs templates only template_specs = [(spec.title.best_lang(user_prefs).value if spec.title else str(spec.id), spec) for spec in widget.specification_templates] template_specs.sort() coltitles = ["Proposition", "Nombre de participants sur la proposition"] # number of participants for a proposal (distinct voter_id from all specs related to the proposal) num_participants_by_idea_id = {} for idea in ideas: vote_specifications = idea.criterion_for query = vote_specifications[0].get_voter_ids_query() for vote_spec in vote_specifications[1:]: query = query.union(vote_spec.get_voter_ids_query()) num_participants_by_idea_id[idea.id] = query.count() # construct a query with each votespec creating columns for: # either each token count (for token votes) OR # sum of vote values, and count of votes otherwise. # Ideas are rows (and Idea.id is column 0) for title, template_spec in template_specs: if isinstance(template_spec, TokenVoteSpecification): for tokencat in template_spec.token_categories: coltitles.append( tokencat.name.best_lang(user_prefs).value.encode('utf-8')) else: coltitles.append( u'{title} - moyenne'.format(title=title).encode('utf-8')) if isinstance(template_spec, NumberGaugeVoteSpecification): for choice_value in range_float(template_spec.minimum, template_spec.maximum, template_spec.nb_ticks): coltitles.append(u'{value} {unit}'.format( value=choice_value, unit=template_spec.unit).encode('utf-8')) else: for choice in template_spec.get_choices(): coltitles.append( choice.label.best_lang(user_prefs).value.encode( 'utf-8')) coltitles.append('Total votes') output = StringIO() # include BOM for Excel to open the file in UTF-8 properly output.write(u'\ufeff'.encode('utf-8')) csvw = csv.writer(output) csvw.writerow(coltitles) from assembl.graphql.vote_session import get_avg_choice for title, idea_id in rowtitles: row = [title.encode('utf-8'), num_participants_by_idea_id[idea_id]] for t, template_spec in template_specs: spec = spec_by_idea_id_and_template_specid.get( (idea_id, template_spec.id), None) if isinstance(template_spec, TokenVoteSpecification): for token_category in template_spec.token_categories: if spec is None: row.append('-') else: query = spec.db.query( func.sum( getattr( spec.get_vote_class(), "vote_value"))).filter_by( vote_spec_id=spec.id, tombstone_date=None, token_category_id=token_category.id) # when there is no votes, query.first() equals (None,) # in this case set num_token to 0 num_token = query.first()[0] row.append(num_token or "-") else: # this is a number or text gauge if spec is None: row.append('-') if isinstance(template_spec, NumberGaugeVoteSpecification): for choice_value in range_float( template_spec.minimum, template_spec.maximum, template_spec.nb_ticks): row.append('-') else: for choice in template_spec.get_choices(): row.append('-') elif isinstance(template_spec, NumberGaugeVoteSpecification): vote_cls = spec.get_vote_class() voting_avg = spec.db.query( func.avg(getattr(vote_cls, 'vote_value'))).filter_by( vote_spec_id=spec.id, tombstone_date=None, idea_id=spec.criterion_idea_id).first() # when there is no votes, query.first() equals (None,) avg = voting_avg[0] or '-' row.append(avg) q_histogram = spec.db.query( getattr(vote_cls, 'vote_value'), func.count(getattr(vote_cls, 'voter_id'))).filter_by( vote_spec_id=spec.id, tombstone_date=None, idea_id=spec.criterion_idea_id).group_by( getattr(vote_cls, 'vote_value')) histogram = dict(q_histogram.all()) for choice_value in range_float(template_spec.minimum, template_spec.maximum, template_spec.nb_ticks): row.append(histogram.get(choice_value, 0)) else: vote_cls = spec.get_vote_class() avg_choice = get_avg_choice(spec) if not avg_choice: label_avg = '-' else: label_avg = avg_choice.label.best_lang( user_prefs).value.encode('utf-8') row.append(label_avg) q_histogram = spec.db.query( getattr(vote_cls, 'vote_value'), func.count(getattr(vote_cls, 'voter_id'))).filter_by( vote_spec_id=spec.id, tombstone_date=None, idea_id=spec.criterion_idea_id).group_by( getattr(vote_cls, 'vote_value')) histogram = dict(q_histogram.all()) for choice in template_spec.get_choices(): row.append(histogram.get(choice.value, 0)) if spec is None: row.append('-') else: num_votes = spec.db.query( getattr(spec.get_vote_class(), "voter_id")).filter_by( vote_spec_id=spec.id, tombstone_date=None).count() row.append(num_votes) csvw.writerow(row) output.seek(0) return Response( body_file=output, content_type='text/csv', content_disposition='attachment; filename="vote_results.csv"')
def assembl_register_user(request): forget(request) localizer = request.localizer session = AgentProfile.default_db json = request.json logger = logging.getLogger() discussion = discussion_from_request(request) permissions = get_permissions(Everyone, discussion.id if discussion else None) name = json.get('real_name', '').strip() errors = JSONError() if not name or len(name) < 3: errors.add_error( localizer.translate( _("Please use a name of at least 3 characters")), ErrorTypes.SHORT_NAME) password = json.get('password', '').strip() # TODO: Check password strength. maybe pwdmeter? email = None for account in json.get('accounts', ()): email = account.get('email', None) if not is_email(email): errors.add_error( localizer.translate(_("This is not a valid email")), ErrorTypes.INVALID_EMAIL) continue email = EmailString.normalize_email_case(email) # Find agent account to avoid duplicates! if session.query(AbstractAgentAccount).filter_by( email_ci=email).count(): if not discussion.preferences['generic_errors']: errors.add_error( localizer.translate( _("We already have a user with this email.")), ErrorTypes.EXISTING_EMAIL, HTTPConflict.code) else: errors.add_error(localizer.translate(generic_error_message), ErrorTypes.GENERIC, HTTPConflict.code) logger.error( "[User creation]: We already have a user with this email %s" % email) if not email: errors.add_error(localizer.translate(_("No email.")), ErrorTypes.INVALID_EMAIL) username = json.get('username', None) if username: if session.query(Username).filter( func.lower(Username.username) == username.lower()).count(): if not discussion.preferences['generic_errors']: errors.add_error( localizer.translate( _("We already have a user with this username.")), ErrorTypes.EXISTING_USERNAME, HTTPConflict.code) else: errors.add_error(localizer.translate(generic_error_message), ErrorTypes.GENERIC, HTTPConflict.code) logger.error("We already have a user with username %s" % username) if len(username) > 20: errors.add_error( localizer.translate( _("The username must be less than 20 characters.")), ErrorTypes.USERNAME_TOO_LONG, HTTPBadRequest.code) if discussion: check_subscription = discussion.preferences['whitelist_on_register'] whitelist = discussion.preferences['require_email_domain'] if check_subscription and whitelist: status = discussion.check_email(email) if not status: admin_emails = discussion.get_admin_emails() num = len(admin_emails) errors.add_error( localizer.pluralize( _("Your email domain has not been approved for registration. Please contact ${emails} for support." ), _("Your email domain has not been approved for registration. Please contact one of ${emails} for support." ), num, mapping={'emails': ", ".join(admin_emails)})) if errors: raise errors # This logic needs to be above the JSONError checks to ensure that whitelisting is applied # even if the discussion does not have a P_SELF_REGISTER on system.Everyone if discussion and not (P_SELF_REGISTER in permissions or P_SELF_REGISTER_REQUEST in permissions): # Consider it without context discussion = None validate_registration = asbool( config.get('assembl.validate_registration_emails')) old_autoflush = session.autoflush session.autoflush = False try: now = datetime.utcnow() user = User(name=name, password=password, verified=not validate_registration, creation_date=now) session.add(user) session.flush() user.update_from_json(json, user_id=user.id) account = user.accounts[0] email = account.email account.verified = not validate_registration if discussion: agent_status = AgentStatusInDiscussion( agent_profile=user, discussion=discussion, first_visit=now, last_visit=now, user_created_on_this_discussion=True) session.add(agent_status) session.flush() # create the profile fields for custom fields for global_id, value in json.get('profileFields', {}).iteritems(): configurable_field_id = from_global_id(global_id)[1] configurable_field = AbstractConfigurableField.get( configurable_field_id) profile_field = ProfileField( agent_profile=user, configurable_field=configurable_field, discussion=configurable_field.discussion, value_data={u'value': value}) session.add(profile_field) session.flush() if validate_registration: send_confirmation_email(request, account) else: user.verified = True for account in user.accounts: account.verified = True user.successful_login() if asbool(config.get('pyramid.debug_authorization')): # for debugging purposes from assembl.auth.password import email_token print "email token:", request.route_url( 'user_confirm_email', token=email_token(account)) if discussion: check_subscription = discussion.preferences[ 'whitelist_on_register'] maybe_auto_subscribe(user, discussion, check_authorization=check_subscription) session.flush() return CreationResponse(user, Everyone, permissions) finally: session.autoflush = old_autoflush
def mutate(root, args, context, info): ATTACHMENT_PURPOSE_IMAGE = models.AttachmentPurpose.IMAGE.value ATTACHMENT_PURPOSE_DOCUMENT = models.AttachmentPurpose.DOCUMENT.value cls = models.Resource discussion_id = context.matchdict['discussion_id'] discussion = models.Discussion.get(discussion_id) user_id = context.authenticated_userid or Everyone resource_id = args.get('id') resource_id = int(Node.from_global_id(resource_id)[1]) resource = cls.get(resource_id) permissions = get_permissions(user_id, discussion_id) allowed = resource.user_can( user_id, CrudPermissions.UPDATE, permissions) if not allowed: raise HTTPUnauthorized() with cls.default_db.no_autoflush: title_entries = args.get('title_entries') if title_entries is not None and len(title_entries) == 0: raise Exception( 'Resource titleEntries needs at least one entry') # Better to have this message than # 'NoneType' object has no attribute 'owner_object' # when creating the saobj below if title=None update_langstring_from_input_entries( resource, 'title', title_entries) update_langstring_from_input_entries( resource, 'text', args.get('text_entries')) kwargs = {} kwargs['embed_code'] = args.get('embed_code', None) for attr, value in kwargs.items(): setattr(resource, attr, value) db = resource.db # add uploaded image as an attachment to the resource image = args.get('image') if image is not None: filename = os.path.basename(context.POST[image].filename) mime_type = context.POST[image].type document = models.File( discussion=discussion, mime_type=mime_type, title=filename) document.add_file_data(context.POST[image].file) # if there is already an IMAGE, remove it with the # associated document images = [ att for att in resource.attachments if att.attachmentPurpose == ATTACHMENT_PURPOSE_IMAGE] if images: image = images[0] image.document.delete_file() db.delete(image.document) resource.attachments.remove(image) db.add(models.ResourceAttachment( document=document, discussion=discussion, resource=resource, creator_id=context.authenticated_userid, title=filename, attachmentPurpose=ATTACHMENT_PURPOSE_IMAGE )) # add uploaded doc as an attachment to the resource doc = args.get('doc') if doc is not None: filename = os.path.basename(context.POST[doc].filename) mime_type = context.POST[doc].type document = models.File( discussion=discussion, mime_type=mime_type, title=filename) document.add_file_data(context.POST[doc].file) # if there is already a DOCUMENT, remove it with the # associated document docs = [ att for att in resource.attachments if att.attachmentPurpose == ATTACHMENT_PURPOSE_DOCUMENT] if docs: doc = docs[0] doc.document.delete_file() db.delete(doc.document) resource.attachments.remove(doc) resource.db.add(models.ResourceAttachment( document=document, discussion=discussion, resource=resource, creator_id=context.authenticated_userid, title=filename, attachmentPurpose=ATTACHMENT_PURPOSE_DOCUMENT )) db.flush() return UpdateResource(resource=resource)
def mutate(root, args, context, info): ATTACHMENT_PURPOSE_IMAGE = models.AttachmentPurpose.IMAGE.value ATTACHMENT_PURPOSE_DOCUMENT = models.AttachmentPurpose.DOCUMENT.value cls = models.Resource discussion_id = context.matchdict['discussion_id'] discussion = models.Discussion.get(discussion_id) user_id = context.authenticated_userid or Everyone permissions = get_permissions(user_id, discussion_id) allowed = cls.user_can_cls( user_id, CrudPermissions.CREATE, permissions) if not allowed or (allowed == IF_OWNED and user_id == Everyone): raise HTTPUnauthorized() with cls.default_db.no_autoflush: title_entries = args.get('title_entries') if len(title_entries) == 0: raise Exception( 'Resource titleEntries needs at least one entry') # Better to have this message than # 'NoneType' object has no attribute 'owner_object' # when creating the saobj below if title=None title_langstring = langstring_from_input_entries(title_entries) text_langstring = langstring_from_input_entries( args.get('text_entries')) kwargs = {} if text_langstring is not None: kwargs['text'] = text_langstring kwargs['embed_code'] = args.get('embed_code') saobj = cls( discussion_id=discussion_id, title=title_langstring, **kwargs) db = saobj.db db.add(saobj) image = args.get('image') if image is not None: filename = os.path.basename(context.POST[image].filename) mime_type = context.POST[image].type document = models.File( discussion=discussion, mime_type=mime_type, title=filename) document.add_file_data(context.POST[image].file) db.add(models.ResourceAttachment( document=document, resource=saobj, discussion=discussion, creator_id=context.authenticated_userid, title=filename, attachmentPurpose=ATTACHMENT_PURPOSE_IMAGE )) doc = args.get('doc') if doc is not None: filename = os.path.basename(context.POST[doc].filename) mime_type = context.POST[doc].type document = models.File( discussion=discussion, mime_type=mime_type, title=filename) document.add_file_data(context.POST[doc].file) db.add(models.ResourceAttachment( document=document, resource=saobj, discussion=discussion, creator_id=context.authenticated_userid, title=filename, attachmentPurpose=ATTACHMENT_PURPOSE_DOCUMENT )) db.flush() return CreateResource(resource=saobj)
def get_posts(request): """ Query interface on posts Filters have two forms: only_*, is for filters that cannot be reversed (ex: only_synthesis) is_*, is for filters that can be reversed (ex:is_unread=true returns only unread order can be chronological, reverse_chronological message, is_unread=false returns only read messages) """ localizer = request.localizer discussion_id = int(request.matchdict['discussion_id']) discussion = Discussion.get(int(discussion_id)) if not discussion: raise HTTPNotFound( localizer.translate(_("No discussion found with id=%s")) % discussion_id) discussion.import_from_sources() user_id = authenticated_userid(request) or Everyone permissions = get_permissions(user_id, discussion_id) DEFAULT_PAGE_SIZE = 25 page_size = DEFAULT_PAGE_SIZE filter_names = [ filter_name for filter_name \ in request.GET.getone('filters').split(',') \ if filter_name ] if request.GET.get('filters') else [] try: page = int(request.GET.getone('page')) except (ValueError, KeyError): page = 1 text_search = request.GET.get('text_search', None) order = request.GET.get('order') if order == None: order = 'chronological' assert order in ('chronological', 'reverse_chronological', 'score') if order == 'score': assert text_search is not None if page < 1: page = 1 root_post_id = request.GET.getall('root_post_id') if root_post_id: root_post_id = get_database_id("Post", root_post_id[0]) family_post_id = request.GET.getall('family_post_id') if family_post_id: family_post_id = get_database_id("Post", family_post_id[0]) root_idea_id = request.GET.getall('root_idea_id') if root_idea_id: root_idea_id = get_database_id("Idea", root_idea_id[0]) ids = request.GET.getall('ids[]') if ids: ids = [get_database_id("Post", id) for id in ids] view_def = request.GET.get('view') or 'default' only_synthesis = request.GET.get('only_synthesis') post_author_id = request.GET.get('post_author') if post_author_id: post_author_id = get_database_id("AgentProfile", post_author_id) assert AgentProfile.get( post_author_id ), "Unable to find agent profile with id " + post_author_id post_replies_to = request.GET.get('post_replies_to') if post_replies_to: post_replies_to = get_database_id("AgentProfile", post_replies_to) assert AgentProfile.get( post_replies_to ), "Unable to find agent profile with id " + post_replies_to posted_after_date = request.GET.get('posted_after_date') posted_before_date = request.GET.get('posted_before_date') PostClass = SynthesisPost if only_synthesis == "true" else Post if order == 'score': posts = discussion.db.query(PostClass, Content.body_text_index.score_name) else: posts = discussion.db.query(PostClass) posts = posts.filter(PostClass.discussion_id == discussion_id, ) ##no_of_posts_to_discussion = posts.count() post_data = [] only_orphan = request.GET.get('only_orphan') if only_orphan == "true": if root_idea_id: raise HTTPBadRequest( localizer.translate( _("Getting orphan posts of a specific idea isn't supported." ))) orphans = text(Idea._get_orphan_posts_statement(), bindparams=[ bindparam('discussion_id', discussion_id) ]).columns(column('post_id')).alias('orphans') posts = posts.join(orphans, PostClass.id == orphans.c.post_id) elif only_orphan == "false": raise HTTPBadRequest( localizer.translate( _("Getting non-orphan posts isn't supported."))) # "true" means hidden only, "false" (default) means visible only. "any" means both. hidden = request.GET.get('hidden_messages', "false") if hidden != 'any': posts = posts.filter(PostClass.hidden == asbool(hidden)) if root_idea_id: related = text(Idea._get_related_posts_statement(), bindparams=[ bindparam('root_idea_id', root_idea_id), bindparam('discussion_id', discussion_id) ]).columns(column('post_id')).alias('related') #Virtuoso bug: This should work... #posts = posts.join(related, PostClass.id==related.c.post_id) posts = posts.join(related, PostClass.id == related.c.post_id) if root_post_id: root_post = Post.get(root_post_id) posts = posts.filter((Post.ancestry.like(root_post.ancestry + cast(root_post.id, String) + ',%')) | (PostClass.id == root_post.id)) elif family_post_id: root_post = Post.get(family_post_id) ancestor_ids = root_post.ancestor_ids() posts = posts.filter((Post.ancestry.like(root_post.ancestry + cast(root_post.id, String) + ',%')) | (PostClass.id == root_post.id) | (PostClass.id.in_(ancestor_ids))) else: root_post = None if ids: posts = posts.filter(Post.id.in_(ids)) if posted_after_date: posted_after_date = parse_datetime(posted_after_date) if posted_after_date: posts = posts.filter(PostClass.creation_date >= posted_after_date) #Maybe we should do something if the date is invalid. benoitg if posted_before_date: posted_before_date = parse_datetime(posted_before_date) if posted_before_date: posts = posts.filter(PostClass.creation_date <= posted_before_date) #Maybe we should do something if the date is invalid. benoitg if post_author_id: posts = posts.filter(PostClass.creator_id == post_author_id) if post_replies_to: parent_alias = aliased(PostClass) posts = posts.join(parent_alias, PostClass.parent) posts = posts.filter(parent_alias.creator_id == post_replies_to) # Post read/unread management is_unread = request.GET.get('is_unread') if user_id != Everyone: # This is horrible, but the join creates complex subqueries that # virtuoso cannot decode properly. read_posts = { v.post_id for v in discussion.db.query(ViewPost).filter( ViewPost.tombstone_condition(), ViewPost.actor_id == user_id, *ViewPost.get_discussion_conditions(discussion_id)) } liked_posts = { l.post_id: l.id for l in discussion.db.query(LikedPost).filter( LikedPost.tombstone_condition(), LikedPost.actor_id == user_id, *LikedPost.get_discussion_conditions(discussion_id)) } if is_unread != None: posts = posts.outerjoin( ViewPost, and_(ViewPost.actor_id == user_id, ViewPost.post_id == PostClass.id, ViewPost.tombstone_date == None)) if is_unread == "true": posts = posts.filter(ViewPost.id == None) elif is_unread == "false": posts = posts.filter(ViewPost.id != None) else: #If there is no user_id, all posts are always unread if is_unread == "false": raise HTTPBadRequest( localizer.translate( _("You must be logged in to view which posts are read"))) if text_search is not None: # another Virtuoso bug: offband kills score. but it helps speed. offband = () if (order == 'score') else None posts = posts.filter( Post.body_text_index.contains(text_search.encode('utf-8'), offband=offband)) #posts = posts.options(contains_eager(Post.source)) # Horrible hack... But useful for structure load if view_def == 'id_only': pass # posts = posts.options(defer(Post.body)) else: posts = posts.options(joinedload_all(Post.creator)) posts = posts.options(joinedload_all(Post.extracts)) posts = posts.options(joinedload_all(Post.widget_idea_links)) posts = posts.options(joinedload_all( SynthesisPost.publishes_synthesis)) if order == 'chronological': posts = posts.order_by(Content.creation_date) elif order == 'reverse_chronological': posts = posts.order_by(Content.creation_date.desc()) elif order == 'score': posts = posts.order_by(Content.body_text_index.score_name.desc()) print str(posts) no_of_posts = 0 no_of_posts_viewed_by_user = 0 for query_result in posts: score, viewpost, likedpost = None, None, None if not isinstance(query_result, (list, tuple)): query_result = [query_result] post = query_result[0] if user_id != Everyone: viewpost = post.id in read_posts likedpost = liked_posts.get(post.id, None) no_of_posts += 1 serializable_post = post.generic_json(view_def, user_id, permissions) or {} if order == 'score': score = query_result[1] serializable_post['score'] = score if viewpost: serializable_post['read'] = True no_of_posts_viewed_by_user += 1 elif user_id != Everyone and root_post is not None and root_post.id == post.id: # Mark post read, we requested it explicitely viewed_post = ViewPost(actor_id=user_id, post=root_post) discussion.db.add(viewed_post) serializable_post['read'] = True else: serializable_post['read'] = False # serializable_post['liked'] = likedpost.uri() if likedpost else False serializable_post['liked'] = LikedPost.uri_generic( likedpost) if likedpost else False post_data.append(serializable_post) # Benoitg: For now, this completely garbles threading without intelligent #handling of pagination. Disabling #posts = posts.limit(page_size).offset(data['startIndex']-1) # This code isn't up to date. If limiting the query by page, we need to # calculate the counts with a separate query to have the right number of # results #no_of_messages_viewed_by_user = discussion.db.query(ViewPost).join( # Post #).filter( # Post.discussion_id == discussion_id, # ViewPost.actor_id == user_id, #).count() if user_id else 0 data = {} data["page"] = page data["unread"] = no_of_posts - no_of_posts_viewed_by_user data["total"] = no_of_posts data["maxPage"] = max(1, ceil(float(data["total"]) / page_size)) #TODO: Check if we want 1 based index in the api data["startIndex"] = (page_size * page) - (page_size - 1) if data["page"] == data["maxPage"]: data["endIndex"] = data["total"] else: data["endIndex"] = data["startIndex"] + (page_size - 1) data["posts"] = post_data return data
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, 'message_id': uuid.uuid1().hex + "@" + config.get('public_hostname'), '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) for source in discussion.sources: source.send_post(new_post) permissions = get_permissions(user_id, discussion_id) return new_post.generic_json('default', user_id, permissions)
def __call__(self, context, request): return isinstance(context, InstanceContext) and get_permissions( request.authenticated_userid, request.discussion_id, context._instance)
def post_extract(request): """ Create a new extract. """ extract_data = json.loads(request.body) discussion = request.context db = discussion.db 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 permissions = get_permissions(user_id, discussion_id) else: permissions = request.permissions if P_ADD_EXTRACT not in permissions: #TODO: maparent: restore this code once it works: #raise HTTPForbidden(result=ACLDenied(permission=P_ADD_EXTRACT)) raise 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 = extract_data.get('text') target = extract_data.get('target') if not uri: # Extract from an internal post if not target: 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 = db.query(AnnotatorSource).filter_by( discussion=discussion).filter( cast(AnnotatorSource.name, Unicode) == 'Annotator').first() if not source: source = AnnotatorSource(name='Annotator', discussion=discussion) db.add(source) content = Webpage(url=uri, discussion=discussion) db.add(content) extract_body = extract_data.get('quote', None) 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()) if not idea.has_permission_req(P_ASSOCIATE_EXTRACT): raise HTTPForbidden("Cannot associate extact with this idea") else: idea = None new_extract = Extract(creator_id=user_id, owner_id=user_id, discussion=discussion, idea=idea, important=important, annotation_text=annotation_text, content=content) db.add(new_extract) for range_data in extract_data.get('ranges', []): range = TextFragmentIdentifier(extract=new_extract, body=extract_body, xpath_start=range_data['start'], offset_start=range_data['startOffset'], xpath_end=range_data['end'], offset_end=range_data['endOffset']) db.add(range) db.flush() return {'ok': True, '@id': new_extract.uri()}
def mutate(root, args, context, info): cls = models.Thematic discussion_id = context.matchdict['discussion_id'] discussion = models.Discussion.get(discussion_id) user_id = context.authenticated_userid or Everyone permissions = get_permissions(user_id, discussion_id) allowed = cls.user_can_cls(user_id, CrudPermissions.CREATE, permissions) if not allowed or (allowed == IF_OWNED and user_id == Everyone): raise HTTPUnauthorized() identifier = args.get('identifier') with cls.default_db.no_autoflush: title_entries = args.get('title_entries') if len(title_entries) == 0: raise Exception( 'Thematic titleEntries needs at least one entry') # Better to have this message than # 'NoneType' object has no attribute 'owner_object' # when creating the saobj below if title=None title_langstring = langstring_from_input_entries(title_entries) description_langstring = langstring_from_input_entries( args.get('description_entries')) kwargs = {} if description_langstring is not None: kwargs['description'] = description_langstring video = args.get('video') if video is not None: video_title = langstring_from_input_entries( video['title_entries']) if video_title is not None: kwargs['video_title'] = video_title video_description = langstring_from_input_entries( video['description_entries']) if video_description is not None: kwargs['video_description'] = video_description kwargs['video_html_code'] = video['html_code'] # Our thematic, because it inherits from Idea, needs to be # associated to the root idea of the discussion. # We create a hidden root thematic, corresponding to the # `identifier` phase, child of the root idea, # and add our thematic as a child of this root thematic. root_thematic = [ idea for idea in discussion.root_idea.get_children() if getattr(idea, 'identifier', '') == identifier ] if not root_thematic: short_title = u'Phase {}'.format(identifier) root_thematic = cls(discussion_id=discussion_id, short_title=short_title, title=langstring_from_input_entries([{ 'locale_code': 'en', 'value': short_title }]), identifier=identifier, hidden=True) discussion.root_idea.children.append(root_thematic) else: root_thematic = root_thematic[0] # take the first entry and set it for short_title short_title = title_entries[0]['value'] saobj = cls(discussion_id=discussion_id, title=title_langstring, short_title=short_title, identifier=identifier, **kwargs) root_thematic.children.append(saobj) db = saobj.db db.add(saobj) db.flush() questions_input = args.get('questions') if questions_input is not None: for question_input in questions_input: title_ls = langstring_from_input_entries( question_input['title_entries']) saobj.children.append( models.Question(title=title_ls, discussion_id=discussion_id)) db.flush() return CreateThematic(thematic=saobj)
def extract_voters(request): extract_votes = [] ctx = request.context user_id = request.authenticated_userid if not user_id: raise HTTPUnauthorized widget = ctx._instance user_id = request.authenticated_userid if widget.activity_state != "ended": permissions = get_permissions(user_id, ctx.get_discussion_id()) if P_ADMIN_DISC not in permissions: raise HTTPUnauthorized() user_prefs = LanguagePreferenceCollection.getCurrent() fieldnames = [ "Nom du contributeur", "Nom d'utilisateur du contributeur", "Adresse mail du contributeur", "Date/heure du vote", "Proposition" ] votes = widget.db.query(AbstractIdeaVote).filter( AbstractVoteSpecification.widget_id == widget.id).filter( AbstractIdeaVote.tombstone_date == None).order_by( AbstractIdeaVote.vote_spec_id.desc()).all() for count, vote in enumerate(votes): voter = vote.voter contributor = voter.real_name() or u"" contributor_username = voter.username_p or u"" contributor_mail = voter.get_preferred_email() or u"" vote_date = vote.vote_date or u"" proposition = Idea.get( vote.idea_id).title.best_lang(user_prefs).value or u"" vote_value = vote.vote_value if votes[count].vote_spec_id != votes[ count - 1].vote_spec_id and fieldnames[-1] != " ": fieldnames.append(" ") extract_info = { "Nom du contributeur": contributor.encode('utf-8'), "Nom d'utilisateur du contributeur": contributor_username.encode('utf-8'), "Adresse mail du contributeur": contributor_mail.encode('utf-8'), "Date/heure du vote": str(vote_date), "Proposition": proposition.encode('utf-8'), } if vote.type == u'token_idea_vote': token_category = vote.token_category.name.best_lang( user_prefs).value or u"" token_category_encoded = token_category.encode('utf-8') if token_category_encoded not in fieldnames: fieldnames.append(token_category_encoded) extract_info.update({token_category: str(vote_value)}) extract_votes.append(extract_info) if vote.type == u'gauge_idea_vote': spec = vote.vote_spec if isinstance(spec, NumberGaugeVoteSpecification): for choice_value in range_float(spec.minimum, spec.maximum, spec.nb_ticks): option = u"{} {}".format(choice_value, spec.unit).encode('utf-8') if option not in fieldnames: fieldnames.append(option) extract_info.update( {option: "1" if vote_value == choice_value else "0"}) else: for choice in spec.get_choices(): option = choice.label.best_lang(user_prefs).value.encode( 'utf-8') if option not in fieldnames: fieldnames.append(option) extract_info.update( {option: "1" if vote_value == choice.value else "0"}) extract_votes.append(extract_info) extract_votes.sort(key=operator.itemgetter('Nom du contributeur')) return csv_response( extract_votes, CSV_MIMETYPE, fieldnames, content_disposition='attachment; filename="detailed_vote_results.csv"')
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 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 mutate(root, args, context, info): cls = models.User db = cls.default_db discussion_id = context.matchdict['discussion_id'] user_id = context.authenticated_userid or Everyone instance = cls.get(user_id) if user_id != Everyone else None require_instance_permission(CrudPermissions.UPDATE, instance, context) permissions = get_permissions(user_id, discussion_id) from assembl.models.action import ( RejectCGUOnDiscussion, RejectSessionOnDiscussion, RejectTrackingOnDiscussion, RejectPrivacyPolicyOnDiscussion, AcceptCGUOnDiscussion, AcceptSessionOnDiscussion, AcceptTrackingOnDiscussion, AcceptPrivacyPolicyOnDiscussion, AcceptUserGuidelineOnDiscussion, RejectUserGuidelineOnDiscussion ) with cls.default_db.no_autoflush as db: user = models.User.get(user_id) actions = args.get('actions', []) agent_status_in_discussion = user.get_status_in_discussion(discussion_id) for action_type in actions: action_type_enum = PyCookieTypes(action_type) if action_type_enum == PyCookieTypes.ACCEPT_CGU: action = AcceptCGUOnDiscussion(discussion_id=discussion_id, actor_id=user_id) user.user_last_accepted_cgu_date = datetime.utcnow() agent_status_in_discussion.delete_cookie(PyCookieTypes.REJECT_CGU) agent_status_in_discussion.update_cookie(action_type_enum) elif action_type_enum == PyCookieTypes.ACCEPT_SESSION_ON_DISCUSSION: action = AcceptSessionOnDiscussion(discussion_id=discussion_id, actor_id=user_id) agent_status_in_discussion.delete_cookie(PyCookieTypes.REJECT_SESSION_ON_DISCUSSION) agent_status_in_discussion.update_cookie(action_type_enum) elif action_type_enum == PyCookieTypes.ACCEPT_TRACKING_ON_DISCUSSION: action = AcceptTrackingOnDiscussion(discussion_id=discussion_id, actor_id=user_id) agent_status_in_discussion.delete_cookie(PyCookieTypes.REJECT_TRACKING_ON_DISCUSSION) agent_status_in_discussion.update_cookie(action_type_enum) elif action_type_enum == PyCookieTypes.ACCEPT_PRIVACY_POLICY_ON_DISCUSSION: action = AcceptPrivacyPolicyOnDiscussion(discussion_id=discussion_id, actor_id=user_id) user.user_last_accepted_privacy_policy_date = datetime.utcnow() agent_status_in_discussion.delete_cookie(PyCookieTypes.REJECT_PRIVACY_POLICY_ON_DISCUSSION) agent_status_in_discussion.update_cookie(action_type_enum) elif action_type_enum == PyCookieTypes.ACCEPT_LOCALE: action = AcceptPrivacyPolicyOnDiscussion(discussion_id=discussion_id, actor_id=user_id) user.user_last_accepted_privacy_policy_date = datetime.utcnow() agent_status_in_discussion.delete_cookie(PyCookieTypes.REJECT_LOCALE) agent_status_in_discussion.update_cookie(action_type_enum) elif action_type_enum == PyCookieTypes.ACCEPT_USER_GUIDELINE_ON_DISCUSSION: action = AcceptUserGuidelineOnDiscussion(discussion_id=discussion_id, actor_id=user_id) user.last_accepted_user_guideline_date = datetime.utcnow() agent_status_in_discussion.delete_cookie(PyCookieTypes.REJECT_USER_GUIDELINE_ON_DISCUSSION) agent_status_in_discussion.update_cookie(action_type_enum) elif action_type_enum == PyCookieTypes.REJECT_CGU: action = RejectCGUOnDiscussion(discussion_id=discussion_id, actor_id=user_id) user.user_last_rejected_cgu_date = datetime.utcnow() agent_status_in_discussion.delete_cookie(PyCookieTypes.ACCEPT_CGU) agent_status_in_discussion.update_cookie(action_type_enum) elif action_type_enum == PyCookieTypes.REJECT_SESSION_ON_DISCUSSION: action = RejectSessionOnDiscussion(discussion_id=discussion_id, actor_id=user_id) agent_status_in_discussion.delete_cookie(PyCookieTypes.ACCEPT_SESSION_ON_DISCUSSION) agent_status_in_discussion.update_cookie(action_type_enum) elif action_type_enum == PyCookieTypes.REJECT_TRACKING_ON_DISCUSSION: action = RejectTrackingOnDiscussion(discussion_id=discussion_id, actor_id=user_id) agent_status_in_discussion.delete_cookie(PyCookieTypes.ACCEPT_TRACKING_ON_DISCUSSION) agent_status_in_discussion.update_cookie(action_type_enum) elif action_type_enum == PyCookieTypes.REJECT_PRIVACY_POLICY_ON_DISCUSSION: action = RejectPrivacyPolicyOnDiscussion(discussion_id=discussion_id, actor_id=user_id) user.user_last_rejected_privacy_policy_date = datetime.utcnow() agent_status_in_discussion.delete_cookie(PyCookieTypes.ACCEPT_PRIVACY_POLICY_ON_DISCUSSION) agent_status_in_discussion.update_cookie(action_type_enum) elif action_type_enum == PyCookieTypes.REJECT_LOCALE: action = AcceptPrivacyPolicyOnDiscussion(discussion_id=discussion_id, actor_id=user_id) user.user_last_accepted_privacy_policy_date = datetime.utcnow() agent_status_in_discussion.delete_cookie(PyCookieTypes.ACCEPT_LOCALE) agent_status_in_discussion.update_cookie(action_type_enum) elif action_type_enum == PyCookieTypes.REJECT_USER_GUIDELINE_ON_DISCUSSION: action = RejectUserGuidelineOnDiscussion(discussion_id=discussion_id, actor_id=user_id) user.last_rejected_user_guideline_date = datetime.utcnow() agent_status_in_discussion.delete_cookie(PyCookieTypes.ACCEPT_USER_GUIDELINE_ON_DISCUSSION) agent_status_in_discussion.update_cookie(action_type_enum) action = action.handle_duplication(permissions=permissions, user_id=user.id) db.add(action) db.flush() return UpdateAcceptedCookies(user=user)
def assembl_register_view(request): slug = request.matchdict.get('discussion_slug', "") next_view = handle_next_view(request) if not request.params.get('email'): if request.scheme == "http"\ and asbool(config.get("accept_secure_connection")): return HTTPFound(get_global_base_url(True) + request.path_qs) response = get_login_context(request) return response forget(request) session = AgentProfile.default_db localizer = request.localizer name = request.params.get('name', '').strip() if not name or len(name) < 3: return dict(get_default_context(request), error=localizer.translate(_( "Please use a name of at least 3 characters"))) password = request.params.get('password', '').strip() password2 = request.params.get('password2', '').strip() email = request.params.get('email', '').strip() if not is_email(email): return dict(get_default_context(request), error=localizer.translate(_( "This is not a valid email"))) email = EmailString.normalize_email_case(email) # Find agent account to avoid duplicates! if session.query(AbstractAgentAccount).filter_by( email_ci=email, verified=True).count(): return dict(get_default_context(request), error=localizer.translate(_( "We already have a user with this email."))) if password != password2: return dict(get_default_context(request), error=localizer.translate(_( "The passwords should be identical"))) # TODO: Validate password quality # otherwise create. validate_registration = asbool(config.get( 'assembl.validate_registration_emails')) user = User( name=name, password=password, verified=not validate_registration, creation_date=datetime.utcnow() ) email_account = EmailAccount( email=email, verified=not validate_registration, profile=user ) session.add(user) session.add(email_account) discussion = discussion_from_request(request) if discussion: permissions = get_permissions(Everyone, discussion.id) if not (P_SELF_REGISTER in permissions or P_SELF_REGISTER_REQUEST in permissions): discussion = None if discussion: _now = datetime.utcnow() agent_status = AgentStatusInDiscussion( agent_profile=user, discussion=discussion, first_visit=_now, last_visit=_now, user_created_on_this_discussion=True) session.add(agent_status) session.flush() if not validate_registration: if asbool(config.get('pyramid.debug_authorization')): # for debugging purposes from assembl.auth.password import email_token print "email token:", request.route_url( 'user_confirm_email', token=email_token(email_account)) headers = remember(request, user.id) user.last_login = datetime.utcnow() request.response.headerlist.extend(headers) if discussion: maybe_auto_subscribe(user, discussion) # TODO: Tell them to expect an email. return HTTPFound(location=next_view) return HTTPFound(location=maybe_contextual_route( request, 'confirm_emailid_sent', email_account_id=email_account.id))
def instance_put(request): ctx = request.context user_id = authenticated_userid(request) or Everyone permissions = get_permissions(user_id, ctx.get_discussion_id()) instance = ctx._instance if not instance.user_can(user_id, CrudPermissions.UPDATE, permissions): return HTTPUnauthorized() mapper = inspect(instance.__class__) cols = {c.key: c for c in mapper.columns if not c.foreign_keys} setables = dict( pyinspect.getmembers( instance.__class__, lambda p: pyinspect.isdatadescriptor(p) and getattr( p, 'fset', None))) relns = { r.key: r for r in mapper.relationships if not r.uselist and len(r._calculated_foreign_keys) == 1 and iter(r._calculated_foreign_keys).next().table == mapper.local_table } unknown = set(request.params.keys()) - (set(cols.keys()).union( set(setables.keys())).union(set(relns.keys()))) if unknown: raise HTTPBadRequest("Unknown keys: " + ",".join(unknown)) params = dict(request.params) # type checking columns = {c.key: c for c in mapper.columns} for key, value in params.items(): if key in relns and isinstance(value, (str, unicode)): val_inst = relns[key].class_.get_instance(value) if not val_inst: raise HTTPBadRequest("Unknown instance: " + value) params[key] = val_inst elif key in columns and isinstance(columns[key].type, DeclEnumType) \ and isinstance(value, (str, unicode)): val_det = columns[key].type.enum.from_string(value) if not val_det: raise HTTPBadRequest("Cannot interpret " + value) params[key] = val_det elif key in columns and columns[key].type.python_type == datetime.datetime \ and isinstance(value, (str, unicode)): val_dt = datetime.datetime.strpstr(value) if not val_dt: raise HTTPBadRequest("Cannot interpret " + value) params[key] = val_dt elif key in columns and columns[key].type.python_type == int \ and isinstance(value, (str, unicode)): try: params[key] = int(value) except ValueError as err: raise HTTPBadRequest("Not a number: " + value) elif key in columns and not isinstance(value, columns[key].type.python_type): raise HTTPBadRequest("Value %s for key %s should be a %s" % (value, key, columns[key].type.python_type)) try: for key, value in params.items(): setattr(instance, key, value) except: raise HTTPBadRequest() view = request.GET.get('view', None) or 'default' if view == 'id_only': return [instance.uri()] else: user_id = authenticated_userid(request) or Everyone return instance.generic_json(view, user_id, permissions)
def mutate(root, args, context, info): cls = models.Discussion discussion_id = context.matchdict['discussion_id'] discussion = cls.get(discussion_id) user_id = context.authenticated_userid or Everyone permissions = get_permissions(user_id, discussion_id) allowed = discussion.user_can( user_id, CrudPermissions.UPDATE, permissions) if not allowed: raise HTTPUnauthorized() with cls.default_db.no_autoflush as db: title_entries = args.get('title_entries') if title_entries is not None and len(title_entries) == 0: raise Exception( 'Title entries needs at least one entry') # Better to have this message than # 'NoneType' object has no attribute 'owner_object' # when creating the saobj below if title=None update_langstring_from_input_entries( discussion, 'title', title_entries) subtitle_entries = args.get('subtitle_entries') update_langstring_from_input_entries( discussion, 'subtitle', subtitle_entries) button_label_entries = args.get('button_label_entries') update_langstring_from_input_entries( discussion, 'button_label', button_label_entries) # add uploaded header image as an attachment to the discussion LANDING_PAGE_HEADER_IMAGE = models.AttachmentPurpose.LANDING_PAGE_HEADER_IMAGE.value image = args.get('header_image') if image is not None: header_images = [ att for att in discussion.attachments if att.attachmentPurpose == LANDING_PAGE_HEADER_IMAGE ] if image == 'TO_DELETE' and header_images: header_image = header_images[0] header_image.document.delete_file() db.delete(header_image.document) discussion.attachments.remove(header_image) else: filename = os.path.basename(context.POST[image].filename) mime_type = context.POST[image].type document = models.File( discussion=discussion, mime_type=mime_type, title=filename) document.add_file_data(context.POST[image].file) # if there is already an IMAGE, remove it with the # associated document if header_images: header_image = header_images[0] header_image.document.delete_file() db.delete(header_image.document) discussion.attachments.remove(header_image) db.add(models.DiscussionAttachment( document=document, discussion=discussion, creator_id=context.authenticated_userid, title=filename, attachmentPurpose=LANDING_PAGE_HEADER_IMAGE )) # add uploaded logo image as an attachment to the discussion LANDING_PAGE_LOGO_IMAGE = models.AttachmentPurpose.LANDING_PAGE_LOGO_IMAGE.value image = args.get('logo_image') if image is not None: logo_images = [ att for att in discussion.attachments if att.attachmentPurpose == LANDING_PAGE_LOGO_IMAGE ] if image == 'TO_DELETE' and logo_images: logo_image = logo_images[0] logo_image.document.delete_file() db.delete(logo_image.document) discussion.attachments.remove(logo_image) else: filename = os.path.basename(context.POST[image].filename) mime_type = context.POST[image].type document = models.File( discussion=discussion, mime_type=mime_type, title=filename) document.add_file_data(context.POST[image].file) # if there is already an IMAGE, remove it with the # associated document if logo_images: logo_image = logo_images[0] logo_image.document.delete_file() db.delete(logo_image.document) discussion.attachments.remove(logo_image) db.add(models.DiscussionAttachment( document=document, discussion=discussion, creator_id=context.authenticated_userid, title=filename, attachmentPurpose=LANDING_PAGE_LOGO_IMAGE )) db.flush() discussion = cls.get(discussion_id) return UpdateDiscussion(discussion=discussion)
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 mutate(root, args, context, info): EMBED_ATTACHMENT = models.AttachmentPurpose.EMBED_ATTACHMENT.value cls = models.Idea discussion_id = context.matchdict['discussion_id'] discussion = models.Discussion.get(discussion_id) user_id = context.authenticated_userid or Everyone permissions = get_permissions(user_id, discussion_id) allowed = cls.user_can_cls(user_id, CrudPermissions.CREATE, permissions) if not allowed or (allowed == IF_OWNED and user_id == Everyone): raise HTTPUnauthorized() with cls.default_db.no_autoflush: title_entries = args.get('title_entries') if len(title_entries) == 0: raise Exception('Idea titleEntries needs at least one entry') # Better to have this message than # 'NoneType' object has no attribute 'owner_object' # when creating the saobj below if title=None title_langstring = langstring_from_input_entries(title_entries) description_langstring = langstring_from_input_entries( args.get('description_entries')) kwargs = {} if description_langstring is not None: kwargs['description'] = description_langstring parent_idea_id = args.get('parent_id') if parent_idea_id: parent_idea_id = int(Node.from_global_id(parent_idea_id)[1]) if parent_idea_id: parent_idea = models.Idea.get(parent_idea_id) if not parent_idea: raise Exception('Parent Idea not found') if parent_idea.discussion != discussion: # No cross-debate references are allowed, # for security reasons raise Exception( 'Parent Idea does not belong to this discussion' ) # noqa: E501 else: raise Exception('Parent Idea not found') if not parent_idea_id: parent_idea = discussion.root_idea saobj = cls(discussion_id=discussion_id, title=title_langstring, **kwargs) db = saobj.db db.add(saobj) order = len(parent_idea.get_children()) + 1.0 db.add( models.IdeaLink(source=parent_idea, target=saobj, order=args.get('order', order))) # add uploaded image as an attachment to the idea image = args.get('image') if image is not None: filename = os.path.basename(context.POST[image].filename) mime_type = context.POST[image].type document = models.File(discussion=discussion, mime_type=mime_type, title=filename) document.add_file_data(context.POST[image].file) db.add( models.IdeaAttachment( document=document, idea=saobj, discussion=discussion, creator_id=context.authenticated_userid, title=filename, attachmentPurpose=EMBED_ATTACHMENT)) db.flush() return CreateIdea(idea=saobj)
def mutate(root, args, context, info): EMBED_ATTACHMENT = models.AttachmentPurpose.EMBED_ATTACHMENT.value cls = models.Thematic discussion_id = context.matchdict['discussion_id'] discussion = models.Discussion.get(discussion_id) user_id = context.authenticated_userid or Everyone permissions = get_permissions(user_id, discussion_id) allowed = cls.user_can_cls(user_id, CrudPermissions.CREATE, permissions) if not allowed or (allowed == IF_OWNED and user_id == Everyone): raise HTTPUnauthorized() identifier = args.get('identifier') with cls.default_db.no_autoflush: title_entries = args.get('title_entries') if len(title_entries) == 0: raise Exception( 'Thematic titleEntries needs at least one entry') # Better to have this message than # 'NoneType' object has no attribute 'owner_object' # when creating the saobj below if title=None title_langstring = langstring_from_input_entries(title_entries) description_langstring = langstring_from_input_entries( args.get('description_entries')) kwargs = {} if description_langstring is not None: kwargs['description'] = description_langstring video = args.get('video') if video is not None: video_title = langstring_from_input_entries( video.get('title_entries', None)) if video_title is not None: kwargs['video_title'] = video_title video_description_top = langstring_from_input_entries( video.get('description_entries_top', None)) if video_description_top is not None: kwargs['video_description_top'] = video_description_top video_description_bottom = langstring_from_input_entries( video.get('description_entries_bottom', None)) if video_description_bottom is not None: kwargs[ 'video_description_bottom'] = video_description_bottom video_description_side = langstring_from_input_entries( video.get('description_entries_side', None)) if video_description_side is not None: kwargs['video_description_side'] = video_description_side video_html_code = video.get('html_code', None) if video_html_code is not None: kwargs['video_html_code'] = video_html_code # Our thematic, because it inherits from Idea, needs to be # associated to the root idea of the discussion. # We create a hidden root thematic, corresponding to the # `identifier` phase, child of the root idea, # and add our thematic as a child of this root thematic. root_thematic = get_root_thematic_for_phase(discussion, identifier) if root_thematic is None: root_thematic = create_root_thematic(discussion, identifier) saobj = cls(discussion_id=discussion_id, title=title_langstring, identifier=identifier, **kwargs) db = saobj.db db.add(saobj) order = len(root_thematic.get_children()) + 1.0 db.add( models.IdeaLink(source=root_thematic, target=saobj, order=args.get('order', order))) # add uploaded image as an attachment to the idea image = args.get('image') if image is not None: filename = os.path.basename(context.POST[image].filename) mime_type = context.POST[image].type document = models.File(discussion=discussion, mime_type=mime_type, title=filename) document.add_file_data(context.POST[image].file) db.add( models.IdeaAttachment( document=document, idea=saobj, discussion=discussion, creator_id=context.authenticated_userid, title=filename, attachmentPurpose=EMBED_ATTACHMENT)) db.flush() questions_input = args.get('questions') if questions_input is not None: for idx, question_input in enumerate(questions_input): title_ls = langstring_from_input_entries( question_input['title_entries']) question = models.Question(title=title_ls, discussion_id=discussion_id) db.add( models.IdeaLink(source=saobj, target=question, order=idx + 1.0)) db.flush() return CreateThematic(thematic=saobj)
def assembl_register_user(request): forget(request) localizer = request.localizer session = AgentProfile.default_db json = request.json discussion = discussion_from_request(request) permissions = get_permissions(Everyone, discussion.id if discussion else None) if discussion and not (P_SELF_REGISTER in permissions or P_SELF_REGISTER_REQUEST in permissions): # Consider it without context discussion = None name = json.get('real_name', '').strip() errors = JSONError() if not name or len(name) < 3: errors.add_error( localizer.translate( _("Please use a name of at least 3 characters")), ErrorTypes.SHORT_NAME) password = json.get('password', '').strip() # TODO: Check password strength. maybe pwdmeter? email = None for account in json.get('accounts', ()): email = account.get('email', None) if not is_email(email): errors.add_error( localizer.translate(_("This is not a valid email")), ErrorTypes.INVALID_EMAIL) continue email = EmailString.normalize_email_case(email) # Find agent account to avoid duplicates! if session.query(AbstractAgentAccount).filter_by( email_ci=email, verified=True).count(): errors.add_error( localizer.translate( _("We already have a user with this email.")), ErrorTypes.EXISTING_EMAIL, HTTPConflict.code) if not email: errors.add_error(localizer.translate(_("No email.")), ErrorTypes.INVALID_EMAIL) username = json.get('username', None) if username: if session.query(Username).filter_by(username=username).count(): errors.add_error( localizer.translate( _("We already have a user with this username.")), ErrorTypes.EXISTING_USERNAME, HTTPConflict.code) if errors: raise errors validate_registration = asbool( config.get('assembl.validate_registration_emails')) old_autoflush = session.autoflush session.autoflush = False try: now = datetime.utcnow() user = User(name=name, password=password, verified=not validate_registration, creation_date=now) session.add(user) session.flush() user.update_from_json(json, user_id=user.id) account = user.accounts[0] email = account.email account.verified = not validate_registration if discussion: agent_status = AgentStatusInDiscussion( agent_profile=user, discussion=discussion, first_visit=now, last_visit=now, user_created_on_this_discussion=True) session.add(agent_status) session.flush() if validate_registration: send_confirmation_email(request, account) else: user.verified = True for account in user.accounts: account.verified = True if asbool(config.get('pyramid.debug_authorization')): # for debugging purposes from assembl.auth.password import email_token print "email token:", request.route_url( 'user_confirm_email', token=email_token(account)) if discussion: maybe_auto_subscribe(user, discussion) session.flush() return CreationResponse(user, Everyone, permissions) finally: session.autoflush = old_autoflush
def mutate(root, args, context, info): EMBED_ATTACHMENT = models.AttachmentPurpose.EMBED_ATTACHMENT.value cls = models.Thematic discussion_id = context.matchdict['discussion_id'] discussion = models.Discussion.get(discussion_id) user_id = context.authenticated_userid or Everyone thematic_id = args.get('id') id_ = int(Node.from_global_id(thematic_id)[1]) thematic = cls.get(id_) permissions = get_permissions(user_id, discussion_id) allowed = thematic.user_can(user_id, CrudPermissions.UPDATE, permissions) if not allowed: raise HTTPUnauthorized() with cls.default_db.no_autoflush: # introducing history at every step, including thematics + questions # noqa: E501 thematic.copy(tombstone=True) title_entries = args.get('title_entries') if title_entries is not None and len(title_entries) == 0: raise Exception( 'Thematic titleEntries needs at least one entry') # Better to have this message than # 'NoneType' object has no attribute 'owner_object' # when creating the saobj below if title=None update_langstring_from_input_entries(thematic, 'title', title_entries) update_langstring_from_input_entries( thematic, 'description', args.get('description_entries')) kwargs = {} video = args.get('video', None) if video is not None: update_langstring_from_input_entries( thematic, 'video_title', video.get('title_entries', [])) update_langstring_from_input_entries( thematic, 'video_description_top', video.get('description_entries_top', [])) update_langstring_from_input_entries( thematic, 'video_description_bottom', video.get('description_entries_bottom', [])) update_langstring_from_input_entries( thematic, 'video_description_side', video.get('description_entries_side', [])) kwargs['video_html_code'] = video.get('html_code', None) if args.get('identifier') is not None: kwargs['identifier'] = args.get('identifier') for attr, value in kwargs.items(): setattr(thematic, attr, value) db = thematic.db # change order if needed order = args.get('order') if order: thematic.source_links[0].order = order # add uploaded image as an attachment to the idea image = args.get('image') if image is not None: if image == 'TO_DELETE' and thematic.attachments: # delete the image attachment = thematic.attachments[0] attachment.document.delete_file() db.delete(attachment.document) db.delete(attachment) thematic.attachments.remove(attachment) else: filename = os.path.basename(context.POST[image].filename) mime_type = context.POST[image].type document = models.File(discussion=discussion, mime_type=mime_type, title=filename) document.add_file_data(context.POST[image].file) # if there is already an attachment, remove it with the # associated document (image) if thematic.attachments: thematic.attachments[0].document.delete_file() db.delete(thematic.attachments[0].document) thematic.attachments.remove(thematic.attachments[0]) attachment = models.IdeaAttachment( document=document, discussion=discussion, creator_id=context.authenticated_userid, title=filename, attachmentPurpose=EMBED_ATTACHMENT) thematic.attachments.append(attachment) db.flush() questions_input = args.get('questions') existing_questions = { question.id: question for question in thematic.get_children() } updated_questions = set() if questions_input is not None: for idx, question_input in enumerate(questions_input): if question_input.get('id', None) is not None: id_ = int(Node.from_global_id(question_input['id'])[1]) updated_questions.add(id_) question = models.Question.get(id_) # archive the question question.copy(tombstone=True) update_langstring_from_input_entries( question, 'title', question_input['title_entries']) # modify question order question.source_links[0].order = idx + 1.0 else: title_ls = langstring_from_input_entries( question_input['title_entries']) question = models.Question(title=title_ls, discussion_id=discussion_id) db.add( models.IdeaLink(source=thematic, target=question, order=idx + 1.0)) # remove question (tombstone) that are not in questions_input for question_id in set(existing_questions.keys()).difference( updated_questions): existing_questions[question_id].is_tombstone = True db.flush() return UpdateThematic(thematic=thematic)
def search_endpoint(context, request): if not indexing_active(): return HTTPServiceUnavailable("Indexing inactive") query = request.json_body # u'query': {u'bool': {u'filter': [{u'term': {u'discussion_id': u'23'}}]}} filters = [fil for fil in query['query']['bool']['filter']] discussion_id = [ f.values()[0].values()[0] for f in filters if 'discussion_id' in f.values()[0].keys() ][0] discussion = models.Discussion.get_instance(discussion_id) if discussion is None: raise HTTPUnauthorized() user_id = authenticated_userid(request) or Everyone permissions = get_permissions(user_id, discussion_id) if not discussion.user_can(user_id, CrudPermissions.READ, permissions): raise HTTPUnauthorized() es = connect() index_name = get_index_settings()['index_name'] # print get_curl_query(query) result = es.search(index=index_name, body=query) # add creator_name in each hit creator_ids = set([ hit['_source']['creator_id'] for hit in result['hits']['hits'] if hit['_source'].get('creator_id', None) is not None ]) session = get_session_maker() creators = session.query( models.AgentProfile.id, models.AgentProfile.name).filter( models.AgentProfile.id.in_(creator_ids)).all() creators_by_id = dict(creators) for hit in result['hits']['hits']: source = hit['_source'] creator_id = source.get('creator_id', None) # Remove inner_hits key to not leak posts from private discussion. # You can easily craft a query to get the participants of a public # discussion and do a has_child filter with inner_hits on a private discussion. if 'inner_hits' in hit: del hit['inner_hits'] if creator_id is not None: source['creator_name'] = creators_by_id.get(creator_id) if hit['_type'] == 'idea': idea = models.Idea.get_instance(source['id']) # The check is not really necessary because it's the same # 'read' permission as the discussion, but it doesn't cost anything # to check it and the READ permission may change in the future. if not idea.user_can(user_id, CrudPermissions.READ, permissions): raise HTTPUnauthorized source['num_posts'] = idea.num_posts source['num_contributors'] = idea.num_contributors elif hit['_type'] == 'user': agent_profile = models.AgentProfile.get_instance(source['id']) if not agent_profile.user_can(user_id, CrudPermissions.READ, permissions): raise HTTPUnauthorized source['num_posts'] = agent_profile.count_posts_in_discussion( discussion_id) # Don't do an extra request to verify the CrudPermissions.READ permission # for post or synthesis. # It's currently the same 'read' permission as the discussion. # elif hit['_type'] in ('synthesis', 'post'): # post = models.Post.get_instance(source['id']) # if not post.user_can(user_id, CrudPermissions.READ, permissions): # raise HTTPUnauthorized return result
def mutate(root, args, context, info): cls = models.Thematic discussion_id = context.matchdict['discussion_id'] user_id = context.authenticated_userid or Everyone thematic_id = args.get('id') id_ = int(Node.from_global_id(thematic_id)[1]) thematic = cls.get(id_) permissions = get_permissions(user_id, discussion_id) allowed = thematic.user_can(user_id, CrudPermissions.UPDATE, permissions) if not allowed or (allowed == IF_OWNED and user_id == Everyone): raise HTTPUnauthorized() with cls.default_db.no_autoflush: title_entries = args.get('title_entries') if len(title_entries) == 0: raise Exception( 'Thematic titleEntries needs at least one entry') # Better to have this message than # 'NoneType' object has no attribute 'owner_object' # when creating the saobj below if title=None update_langstring_from_input_entries(thematic, 'title', title_entries) update_langstring_from_input_entries( thematic, 'description', args.get('description_entries')) kwargs = {} video = args.get('video') if video is not None: update_langstring_from_input_entries(thematic, 'video_title', video['title_entries']) update_langstring_from_input_entries( thematic, 'video_description', video['description_entries']) kwargs['video_html_code'] = video['html_code'] # take the first entry and set it for short_title kwargs['short_title'] = title_entries[0]['value'] kwargs['identifier'] = args.get('identifier') for attr, value in kwargs.items(): setattr(thematic, attr, value) db = thematic.db db.flush() questions_input = args.get('questions') existing_questions = { question.id: question for question in thematic.get_children() } updated_questions = set() if questions_input is not None: for question_input in questions_input: if question_input.get('id', None) is not None: id_ = int(Node.from_global_id(question_input['id'])[1]) updated_questions.add(id_) question = models.Question.get(id_) update_langstring_from_input_entries( question, 'title', question_input['title_entries']) else: title_ls = langstring_from_input_entries( question_input['title_entries']) models.Question(title=title_ls, discussion_id=discussion_id) thematic.children.append( models.Question(title=title_ls, discussion_id=discussion_id)) # remove question (tombstone it) that are not in questions_input for question_id in set( existing_questions.keys()).difference(updated_questions): existing_questions[ question_id].tombstone_date = datetime.utcnow() db.flush() return UpdateThematic(thematic=thematic)
def mutate(root, args, context, info): PROFILE_PICTURE = models.AttachmentPurpose.PROFILE_PICTURE.value cls = models.User discussion_id = context.matchdict['discussion_id'] discussion = models.Discussion.get(discussion_id) user_id = context.authenticated_userid or Everyone global_id = args.get('id') id_ = int(Node.from_global_id(global_id)[1]) user = cls.get(id_) permissions = get_permissions(user_id, discussion_id) allowed = user.user_can( user_id, CrudPermissions.UPDATE, permissions) if not allowed: raise HTTPUnauthorized("You don't have the authorization to update this user. If you think it's an error, please reconnect to assembl.") with cls.default_db.no_autoflush as db: username = args.get('username') # only modify the username if it was given in parameter if username is not None: if username != user.username_p: if db.query(models.Username).filter_by( username=username ).count(): raise Exception(u"001: We already have a user with this username.") user.username_p = username name = args.get('name') # only modify the name if it was given in parameter if name is not None: user.real_name_p = name old_password = args.get('old_password') new_password = args.get('new_password') new_password2 = args.get('new_password2') # only modify the password if it was given in parameter if old_password is not None and new_password is not None and new_password2 is not None: if not user.check_password(old_password): raise Exception(u"002: The entered password doesn't match your current password.") if new_password != new_password2: raise Exception(u"003: You entered two different passwords.") if old_password == new_password: raise Exception(u"004: The new password has to be different than the current password.") try: user.password_p = new_password except LocalizableError as e: raise JSONError(e.localized_message(context.localizer)) except Exception as e: raise JSONError(str(e), code=409) # add uploaded image as an attachment to the user image = args.get('image') if image is not None: filename = os.path.basename(context.POST[image].filename) mime_type = context.POST[image].type document = models.File( discussion=discussion, mime_type=mime_type, title=filename) document.add_file_data(context.POST[image].file) # if there is already an PROFILE_PICTURE, remove it with the # associated document images = [ att for att in user.profile_attachments if att.attachmentPurpose == PROFILE_PICTURE] if images: image = images[0] allowed = image.user_can( user_id, CrudPermissions.DELETE, permissions) if not allowed: raise HTTPUnauthorized("The authenticated user can't delete the existing AgentProfileAttachment") image.document.delete_file() db.delete(image.document) user.profile_attachments.remove(image) allowed = models.AgentProfileAttachment.user_can_cls( user_id, CrudPermissions.CREATE, permissions) if not allowed: raise HTTPUnauthorized("The authenticated user can't create an AgentProfileAttachment") discussion.db.add(models.AgentProfileAttachment( document=document, discussion=discussion, user=user, creator_id=context.authenticated_userid, title=filename, attachmentPurpose=PROFILE_PICTURE )) db.flush() return UpdateUser(user=user)
def mutate(root, args, context, info): EMBED_ATTACHMENT = models.AttachmentPurpose.EMBED_ATTACHMENT.value MEDIA_ATTACHMENT = models.AttachmentPurpose.MEDIA_ATTACHMENT.value cls = models.Idea discussion_id = context.matchdict['discussion_id'] discussion = models.Discussion.get(discussion_id) user_id = context.authenticated_userid or Everyone thematic_id = args.get('id') id_ = int(Node.from_global_id(thematic_id)[1]) thematic = cls.get(id_) permissions = get_permissions(user_id, discussion_id) allowed = thematic.user_can(user_id, CrudPermissions.UPDATE, permissions) if not allowed: raise HTTPUnauthorized() with cls.default_db.no_autoflush as db: # introducing history at every step, including thematics + questions # noqa: E501 thematic.copy(tombstone=True) title_entries = args.get('title_entries') if title_entries is not None and len(title_entries) == 0: raise Exception( 'Thematic titleEntries needs at least one entry') # Better to have this message than # 'NoneType' object has no attribute 'owner_object' # when creating the saobj below if title=None update_langstring_from_input_entries(thematic, 'title', title_entries) update_langstring_from_input_entries( thematic, 'description', args.get('description_entries')) kwargs = {} video = args.get('video', None) if video is not None: update_langstring_from_input_entries( thematic, 'video_title', video.get('title_entries', [])) update_langstring_from_input_entries( thematic, 'video_description_top', video.get('description_entries_top', [])) update_langstring_from_input_entries( thematic, 'video_description_bottom', video.get('description_entries_bottom', [])) update_langstring_from_input_entries( thematic, 'video_description_side', video.get('description_entries_side', [])) kwargs['video_html_code'] = video.get('html_code', None) video_media = video.get('media_file', None) if video_media: update_attachment(discussion, models.IdeaAttachment, video_media, thematic.attachments, MEDIA_ATTACHMENT, db, context) kwargs['message_view_override'] = args.get('message_view_override') for attr, value in kwargs.items(): setattr(thematic, attr, value) # change order if needed order = args.get('order') if order: thematic.source_links[0].order = order # add uploaded image as an attachment to the idea image = args.get('image') if image is not None: update_attachment(discussion, models.IdeaAttachment, image, thematic.attachments, EMBED_ATTACHMENT, db, context) # Create the idea announcement object which corresponds to the instructions announcement = args.get('announcement') if announcement is not None: announcement_title_entries = announcement.get('title_entries') if len(announcement_title_entries) == 0: raise Exception( 'Announcement titleEntries needs at least one entry') announcement_title_langstring = langstring_from_input_entries( announcement_title_entries) announcement_body_langstring = langstring_from_input_entries( announcement.get('body_entries', None)) saobj2 = create_idea_announcement( user_id, discussion, thematic, announcement_title_langstring, announcement_body_langstring) db.add(saobj2) questions_input = args.get('questions') existing_questions = { question.id: question for question in thematic.get_children() } updated_questions = set() if questions_input is not None: for idx, question_input in enumerate(questions_input): if question_input.get('id', None) is not None: id_ = int(Node.from_global_id(question_input['id'])[1]) updated_questions.add(id_) question = models.Question.get(id_) # archive the question question.copy(tombstone=True) update_langstring_from_input_entries( question, 'title', question_input['title_entries']) # modify question order question.source_links[0].order = idx + 1.0 else: title_ls = langstring_from_input_entries( question_input['title_entries']) question = models.Question(title=title_ls, discussion_id=discussion_id) db.add( models.IdeaLink(source=thematic, target=question, order=idx + 1.0)) # remove question (tombstone) that are not in questions_input for question_id in set(existing_questions.keys()).difference( updated_questions): existing_questions[question_id].is_tombstone = True db.flush() return UpdateThematic(thematic=thematic)