def update(self, request, *args, **kwargs): partial = kwargs.pop('partial', False) if not getattr(self, 'object', None): self.object = self.get_object_or_none() self.check_permissions(request, 'update', self.object) if self.object is None: raise Http404 if hasattr(self, 'pre_validate'): self.pre_validate() validator = self.get_validator(self.object, data=request.DATA, files=request.FILES, partial=partial) if not validator.is_valid(): return response.BadRequest(validator.errors) # Hooks try: self.pre_save(validator.object) self.pre_conditions_on_save(validator.object) except ValidationError as err: # full_clean on model instance may be called in pre_save, # so we have to handle eventual errors. return response.BadRequest(err.message_dict) self.object = validator.save(force_update=True) self.post_save(self.object, created=False) instance = self.get_queryset().get(id=self.object.id) serializer = self.get_serializer(instance) return response.Ok(serializer.data)
def create_template(self, request, **kwargs): template_name = request.DATA.get('template_name', None) template_description = request.DATA.get('template_description', None) if not template_name: raise response.BadRequest(_("Not valid template name")) if not template_description: raise response.BadRequest(_("Not valid template description")) with advisory_lock("create-project-template"): template_slug = slugify_uniquely(template_name, models.ProjectTemplate) project = self.get_object() self.check_permissions(request, 'create_template', project) template = models.ProjectTemplate( name=template_name, slug=template_slug, description=template_description, ) template.load_data_from_project(project) template.save() return response.Created( serializers.ProjectTemplateSerializer(template).data)
def remove_user_from_all_my_projects(self, request, **kwargs): private_only = request.DATA.get('private_only', False) user_id = request.DATA.get('user', None) if user_id is None: raise exc.WrongArguments(_("Invalid user id")) user_model = apps.get_model("users", "User") try: user = user_model.objects.get(id=user_id) except user_model.DoesNotExist: return response.BadRequest(_("The user doesn't exist")) memberships = models.Membership.objects.filter( project__owner=request.user, user=user) if private_only: memberships = memberships.filter(project__is_private=True) errors = [] for membership in memberships: if not services.can_user_leave_project(user, membership.project): errors.append(membership.project.name) if len(errors) > 0: error = _( "This user can't be removed from the following projects, because would " "leave them without any active admin: {}.".format( ", ".join(errors))) return response.BadRequest(error) memberships.delete() return response.NoContent()
def update(self, request, *args, **kwargs): self.object = self.get_object_or_none() project_id = request.DATA.get('project', None) if project_id and self.object and self.object.project.id != project_id: try: new_project = Project.objects.get(pk=project_id) self.check_permissions(request, "destroy", self.object) self.check_permissions(request, "create", new_project) status_id = request.DATA.get('status', None) if status_id is not None: try: old_status = self.object.project.epic_statuses.get( pk=status_id) new_status = new_project.epic_statuses.get( slug=old_status.slug) request.DATA['status'] = new_status.id except EpicStatus.DoesNotExist: request.DATA[ 'status'] = new_project.default_epic_status.id except Project.DoesNotExist: return response.BadRequest(_("The project doesn't exist")) return super().update(request, *args, **kwargs)
def bulk_create(self, request, **kwargs): validator = validators.CreateRelatedUserStoriesBulkValidator( data=request.DATA) if not validator.is_valid(): return response.BadRequest(validator.errors) data = validator.data epic = get_object_or_404(models.Epic, id=kwargs["epic"]) project = Project.objects.get(pk=data.get('project_id')) self.check_permissions(request, 'bulk_create', project) if project.blocked_code is not None: raise exc.Blocked(_("Blocked element")) related_userstories = services.create_related_userstories_in_bulk( data["bulk_userstories"], epic, project=project, owner=request.user) for related_userstory in related_userstories: self.persist_history_snapshot(obj=related_userstory) self.persist_history_snapshot(obj=related_userstory.user_story) related_uss_serialized = self.get_serializer_class()( epic.relateduserstory_set.all(), many=True) return response.Ok(related_uss_serialized.data)
def bulk_create(self, request, **kwargs): validator = validators.TasksBulkValidator(data=request.DATA) if not validator.is_valid(): return response.BadRequest(validator.errors) data = validator.data project = Project.objects.get(id=data["project_id"]) self.check_permissions(request, 'bulk_create', project) if project.blocked_code is not None: raise exc.Blocked(_("Blocked element")) tasks = services.create_tasks_in_bulk( data["bulk_tasks"], milestone_id=data["milestone_id"], user_story_id=data["us_id"], status_id=data.get("status_id") or project.default_task_status_id, project=project, owner=request.user, callback=self.post_save, precall=self.pre_save) tasks = self.get_queryset().filter(id__in=[i.id for i in tasks]) for task in tasks: self.persist_history_snapshot(obj=task) tasks_serialized = self.get_serializer_class()(tasks, many=True) return response.Ok(tasks_serialized.data)
def _bulk_update_order(self, order_field, request, **kwargs): validator = validators.UpdateTasksOrderBulkValidator(data=request.DATA) if not validator.is_valid(): return response.BadRequest(validator.errors) data = validator.data project = get_object_or_404(Project, pk=data["project_id"]) self.check_permissions(request, "bulk_update_order", project) if project.blocked_code is not None: raise exc.Blocked(_("Blocked element")) user_story = None user_story_id = data.get("user_story_id", None) if user_story_id is not None: user_story = get_object_or_404(UserStory, pk=user_story_id) status = None status_id = data.get("status_id", None) if status_id is not None: status = get_object_or_404(TaskStatus, pk=status_id) milestone = None milestone_id = data.get("milestone_id", None) if milestone_id is not None: milestone = get_object_or_404(Milestone, pk=milestone_id) ret = services.update_tasks_order_in_bulk(data["bulk_tasks"], order_field, project, user_story=user_story, status=status, milestone=milestone) return response.Ok(ret)
def duplicate(self, request, pk=None): project = self.get_object() self.check_permissions(request, "duplicate", project) if project.blocked_code is not None: raise exc.Blocked(_("Blocked element")) validator = validators.DuplicateProjectValidator(data=request.DATA) if not validator.is_valid(): return response.BadRequest(validator.errors) data = validator.data # Validate if the project can be imported is_private = data.get('is_private', False) total_memberships = len(data.get("users", [])) + 1 (enough_slots, error_message) = users_services.has_available_slot_for_new_project( self.request.user, is_private, total_memberships) if not enough_slots: raise exc.NotEnoughSlotsForProject(is_private, total_memberships, error_message) new_project = services.duplicate_project( project=project, owner=request.user, name=data["name"], description=data["description"], is_private=data["is_private"], users=data["users"]) new_project = get_object_or_404(self.get_queryset(), id=new_project.id) serializer = self.get_serializer(new_project) return response.Created(serializer.data)
def create_default(self, request, **kwargs): context = {"request": request} validator = validators.DueDatesCreationValidator(data=request.DATA, context=context) if not validator.is_valid(): return response.BadRequest(validator.errors) project_id = request.DATA.get('project_id') project = models.Project.objects.get(id=project_id) if project.issue_duedates.all(): raise exc.BadRequest(_("Project already have issue due dates")) project_template = models.ProjectTemplate.objects.get( id=project.creation_template.id) for issue_duedate in project_template.issue_duedates: models.IssueDueDate.objects.create( name=issue_duedate["name"], by_default=issue_duedate["by_default"], color=issue_duedate["color"], days_to_due=issue_duedate["days_to_due"], order=issue_duedate["order"], project=project) project.save() serializer = self.get_serializer(project.issue_duedates.all(), many=True) return response.Ok(serializer.data)
def edit_comment(self, request, pk): obj = self.get_object() history_entry_id = request.QUERY_PARAMS.get('id', None) history_entry = services.get_history_queryset_by_model_instance(obj).filter(id=history_entry_id).first() if history_entry is None: return response.NotFound() obj = services.get_instance_from_key(history_entry.key) comment = request.DATA.get("comment", None) self.check_permissions(request, 'edit_comment', history_entry) if history_entry is None: return response.NotFound() if comment is None: return response.BadRequest({"error": _("comment is required")}) if history_entry.delete_comment_date or history_entry.delete_comment_user: return response.BadRequest({"error": _("deleted comments can't be edited")}) # comment_versions can be None if there are no historic versions of the comment comment_versions = history_entry.comment_versions or [] comment_versions.append({ "date": history_entry.created_at, "comment": history_entry.comment, "comment_html": history_entry.comment_html, "user": { "id": request.user.pk, } }) new_mentions = self._get_new_mentions(obj, history_entry.comment, comment) history_entry.edit_comment_date = timezone.now() history_entry.comment = comment history_entry.comment_html = mdrender(obj.project, comment) history_entry.comment_versions = comment_versions history_entry.save() if new_mentions: signal_mentions.send(sender=self.__class__, user=self.request.user, obj=obj, mentions=new_mentions) return response.Ok()
def by_ref(self, request): if "ref" not in request.QUERY_PARAMS: return response.BadRequest(_("ref param is needed")) if "project__slug" not in request.QUERY_PARAMS and "project" not in request.QUERY_PARAMS: return response.BadRequest(_("project or project__slug param is needed")) retrieve_kwargs = { "ref": request.QUERY_PARAMS.get("ref", None) } project_id = request.QUERY_PARAMS.get("project", None) if project_id is not None: retrieve_kwargs["project_id"] = project_id project_slug = request.QUERY_PARAMS.get("project__slug", None) if project_slug is not None: retrieve_kwargs["project__slug"] = project_slug return self.retrieve(request, **retrieve_kwargs)
def update(self, request, *args, **kwargs): self.object = self.get_object_or_none() project_id = request.DATA.get('project', None) if project_id and self.object and self.object.project.id != project_id: try: new_project = Project.objects.get(pk=project_id) self.check_permissions(request, "destroy", self.object) self.check_permissions(request, "create", new_project) sprint_id = request.DATA.get('milestone', None) if sprint_id is not None and new_project.milestones.filter(pk=sprint_id).count() == 0: request.DATA['milestone'] = None status_id = request.DATA.get('status', None) if status_id is not None: try: old_status = self.object.project.issue_statuses.get(pk=status_id) new_status = new_project.issue_statuses.get(slug=old_status.slug) request.DATA['status'] = new_status.id except IssueStatus.DoesNotExist: request.DATA['status'] = new_project.default_issue_status.id priority_id = request.DATA.get('priority', None) if priority_id is not None: try: old_priority = self.object.project.priorities.get(pk=priority_id) new_priority = new_project.priorities.get(name=old_priority.name) request.DATA['priority'] = new_priority.id except Priority.DoesNotExist: request.DATA['priority'] = new_project.default_priority.id severity_id = request.DATA.get('severity', None) if severity_id is not None: try: old_severity = self.object.project.severities.get(pk=severity_id) new_severity = new_project.severities.get(name=old_severity.name) request.DATA['severity'] = new_severity.id except Severity.DoesNotExist: request.DATA['severity'] = new_project.default_severity.id type_id = request.DATA.get('type', None) if type_id is not None: try: old_type = self.object.project.issue_types.get(pk=type_id) new_type = new_project.issue_types.get(name=old_type.name) request.DATA['type'] = new_type.id except IssueType.DoesNotExist: request.DATA['type'] = new_project.default_issue_type.id except Project.DoesNotExist: return response.BadRequest(_("The project doesn't exist")) return super().update(request, *args, **kwargs)
def transfer_start(self, request, pk=None): project = self.get_object() self.check_permissions(request, "transfer_start", project) user_id = request.DATA.get('user', None) if user_id is None: raise exc.WrongArguments(_("Invalid user id")) user_model = apps.get_model("users", "User") try: user = user_model.objects.get(id=user_id) except user_model.DoesNotExist: return response.BadRequest(_("The user doesn't exist")) # Check the user is a membership from the project if not project.memberships.filter(user=user).exists(): return response.BadRequest( _("The user must be already a project member")) reason = request.DATA.get('reason', None) services.start_project_transfer(project, user, reason) return response.Ok()
def bulk_update_order(self, request, **kwargs): if self.request.user.is_anonymous(): return response.Unauthorized() validator = validators.UpdateProjectOrderBulkValidator( data=request.DATA, many=True) if not validator.is_valid(): return response.BadRequest(validator.errors) data = validator.data services.update_projects_order_in_bulk(data, "user_order", request.user) return response.NoContent(data=None)
def delete_tag(self, request, pk=None): project = self.get_object() self.check_permissions(request, "delete_tag", project) self._raise_if_blocked(project) validator = validators.DeleteTagValidator(data=request.DATA, project=project) if not validator.is_valid(): return response.BadRequest(validator.errors) data = validator.data services.delete_tag(project, data.get("tag")) return response.Ok()
def mix_tags(self, request, pk=None): project = self.get_object() self.check_permissions(request, "mix_tags", project) self._raise_if_blocked(project) validator = validators.MixTagsValidator(data=request.DATA, project=project) if not validator.is_valid(): return response.BadRequest(validator.errors) data = validator.data services.mix_tags(project, data.get("from_tags"), data.get("to_tag")) return response.Ok()
def bulk_update_milestone(self, request, **kwargs): validator = validators.UpdateMilestoneBulkValidator(data=request.DATA) if not validator.is_valid(): return response.BadRequest(validator.errors) data = validator.data project = get_object_or_404(Project, pk=data["project_id"]) milestone = get_object_or_404(Milestone, pk=data["milestone_id"]) self.check_permissions(request, "bulk_update_milestone", project) ret = services.update_issues_milestone_in_bulk(data["bulk_issues"], milestone) return response.Ok(ret)
def bulk_create(self, request, **kwargs): context = {"request": request} validator = validators.MembersBulkValidator(data=request.DATA, context=context) if not validator.is_valid(): return response.BadRequest(validator.errors) data = validator.data project = models.Project.objects.get(id=data["project_id"]) invitation_extra_text = data.get("invitation_extra_text", None) self.check_permissions(request, 'bulk_create', project) if project.blocked_code is not None: raise exc.Blocked(_("Blocked element")) if "bulk_memberships" in data and isinstance(data["bulk_memberships"], list): total_new_memberships = len(data["bulk_memberships"]) self._check_if_project_can_have_more_memberships( project, total_new_memberships) try: with advisory_lock("membership-creation-{}".format(project.id)): members = services.create_members_in_bulk( data["bulk_memberships"], project=project, invitation_extra_text=invitation_extra_text, callback=self.post_save, precall=self.pre_save) signal_members_added.send(sender=self.__class__, user=self.request.user, project=project, new_members=members) except exc.ValidationError as err: return response.BadRequest(err.message_dict) members_serialized = self.admin_serializer_class(members, many=True) return response.Ok(members_serialized.data)
def create(self, request, *args, **kwargs): validator = self.get_validator(data=request.DATA, files=request.FILES) if validator.is_valid(): self.check_permissions(request, 'create', validator.object) self.pre_save(validator.object) self.pre_conditions_on_save(validator.object) self.object = validator.save(force_insert=True) self.post_save(self.object, created=True) instance = self.get_queryset().get(id=self.object.id) serializer = self.get_serializer(instance) headers = self.get_success_headers(serializer.data) return response.Created(serializer.data, headers=headers) return response.BadRequest(validator.errors)
def edit_tag(self, request, pk=None): project = self.get_object() self.check_permissions(request, "edit_tag", project) self._raise_if_blocked(project) validator = validators.EditTagTagValidator(data=request.DATA, project=project) if not validator.is_valid(): return response.BadRequest(validator.errors) data = validator.data services.edit_tag(project, data.get("from_tag"), to_tag=data.get("to_tag", None), color=data.get("color", None)) return response.Ok()
def undelete_comment(self, request, pk): obj = self.get_object() history_entry_id = request.QUERY_PARAMS.get('id', None) history_entry = services.get_history_queryset_by_model_instance(obj).filter(id=history_entry_id).first() if history_entry is None: return response.NotFound() self.check_permissions(request, 'undelete_comment', history_entry) if history_entry is None: return response.NotFound() if not history_entry.delete_comment_date and not history_entry.delete_comment_user: return response.BadRequest({"error": _("Comment not deleted")}) history_entry.delete_comment_date = None history_entry.delete_comment_user = None history_entry.save() return response.Ok()
def delete_comment(self, request, pk): obj = self.get_object() history_entry_id = request.QUERY_PARAMS.get('id', None) history_entry = services.get_history_queryset_by_model_instance(obj).filter(id=history_entry_id).first() if history_entry is None: return response.NotFound() self.check_permissions(request, 'delete_comment', history_entry) if history_entry is None: return response.NotFound() if history_entry.delete_comment_date or history_entry.delete_comment_user: return response.BadRequest({"error": _("Comment already deleted")}) history_entry.delete_comment_date = timezone.now() history_entry.delete_comment_user = {"pk": request.user.pk, "name": request.user.get_full_name()} history_entry.save() return response.Ok()
def create(self, request, **kwargs): self.check_permissions(request, "create", None) data = copy.deepcopy(request.DATA) data.update({"full_name": request.user.get_full_name(), "email": request.user.email}) validator = self.validator_class(data=data) if not validator.is_valid(): return response.BadRequest(validator.errors) self.object = validator.save(force_insert=True) extra = { "HTTP_HOST": request.META.get("HTTP_HOST", None), "HTTP_REFERER": request.META.get("HTTP_REFERER", None), "HTTP_USER_AGENT": request.META.get("HTTP_USER_AGENT", None), } services.send_feedback(self.object, extra, reply_to=[request.user.email]) return response.Ok(validator.data)
def move_issues_to_sprint(self, request, pk=None, **kwargs): milestone = get_object_or_404(models.Milestone, pk=pk) self.check_permissions(request, "move_related_items", milestone) validator = IssuesUpdateMilestoneValidator(data=request.DATA) if not validator.is_valid(): return response.BadRequest(validator.errors) data = validator.data project = get_object_or_404(Project, pk=data["project_id"]) milestone_result = get_object_or_404(models.Milestone, pk=data["milestone_id"]) if data["bulk_issues"]: self.check_permissions(request, "move_issues_to_sprint", project) services.update_issues_milestone_in_bulk(data["bulk_issues"], milestone_result) services.snapshot_issues_in_bulk(data["bulk_issues"], request.user) return response.NoContent()
def bulk_create(self, request, **kwargs): validator = validators.IssuesBulkValidator(data=request.DATA) if validator.is_valid(): data = validator.data project = Project.objects.get(pk=data["project_id"]) self.check_permissions(request, 'bulk_create', project) if project.blocked_code is not None: raise exc.Blocked(_("Blocked element")) issues = services.create_issues_in_bulk( data["bulk_issues"], milestone_id=data["milestone_id"], project=project, owner=request.user, status=project.default_issue_status, severity=project.default_severity, priority=project.default_priority, type=project.default_issue_type, callback=self.post_save, precall=self.pre_save) issues = self.get_queryset().filter(id__in=[i.id for i in issues]) issues_serialized = self.get_serializer_class()(issues, many=True) return response.Ok(data=issues_serialized.data) return response.BadRequest(validator.errors)
def update(self, request, *args, **kwargs): self.object = self.get_object_or_none() project_id = request.DATA.get('project', None) if project_id and self.object and self.object.project.id != project_id: try: new_project = Project.objects.get(pk=project_id) self.check_permissions(request, "destroy", self.object) self.check_permissions(request, "create", new_project) milestone_id = request.DATA.get('milestone', None) if milestone_id is not None and new_project.milestones.filter( pk=milestone_id).count() == 0: request.DATA['milestone'] = None us_id = request.DATA.get('user_story', None) if us_id is not None and new_project.user_stories.filter( pk=us_id).count() == 0: request.DATA['user_story'] = None status_id = request.DATA.get('status', None) if status_id is not None: try: old_status = self.object.project.task_statuses.get( pk=status_id) new_status = new_project.task_statuses.get( slug=old_status.slug) request.DATA['status'] = new_status.id except TaskStatus.DoesNotExist: request.DATA[ 'status'] = new_project.default_task_status.id except Project.DoesNotExist: return response.BadRequest(_("The project doesn't exist")) return super().update(request, *args, **kwargs)
def load_dump(self, request): throttle = throttling.ImportDumpModeRateThrottle() if not throttle.allow_request(request, self): self.throttled(request, throttle.wait()) self.check_permissions(request, "load_dump", None) dump = request.FILES.get('dump', None) if not dump: raise exc.WrongArguments(_("Needed dump file")) if dump.content_type == "application/gzip": dump = gzip.GzipFile(fileobj=dump) reader = codecs.getreader("utf-8") try: dump = json.load(reader(dump)) except Exception: raise exc.WrongArguments(_("Invalid dump format")) slug = dump.get('slug', None) if slug is not None and Project.objects.filter(slug=slug).exists(): del dump['slug'] user = request.user dump['owner'] = user.email # Validate if the project can be imported is_private = dump.get("is_private", False) total_memberships = len([ m for m in dump.get("memberships", []) if m.get("email", None) != dump["owner"] ]) total_memberships = total_memberships + 1 # 1 is the owner (enough_slots, error_message) = users_services.has_available_slot_for_new_project( user, is_private, total_memberships) if not enough_slots: raise exc.NotEnoughSlotsForProject(is_private, total_memberships, error_message) # Async mode if settings.CELERY_ENABLED: task = tasks.load_project_dump.delay(user, dump) return response.Accepted({"import_id": task.id}) # Sync mode try: project = services.store_project_from_dict(dump, request.user) except err.TinaImportError as e: # On Error ## remove project if e.project: e.project.delete_related_content() e.project.delete() return response.BadRequest({ "error": e.message, "details": e.errors }) else: # On Success project_from_qs = project_utils.attach_extra_info( Project.objects.all()).get(id=project.id) response_data = ProjectSerializer(project_from_qs).data return response.Created(response_data)