コード例 #1
0
    def list_users(self, request, *args, **kwargs):
        self.check_permissions(request, "list_users", None)

        token = request.DATA.get('token', None)
        project_id = request.DATA.get('project', None)

        if not project_id:
            raise exc.WrongArguments(_("The project param is needed"))

        importer = AsanaImporter(request.user, token)

        try:
            users = importer.list_users(project_id)
        except exceptions.InvalidRequest:
            raise exc.BadRequest(_('Invalid Asana API request'))
        except exceptions.FailedRequest:
            raise exc.BadRequest(_('Failed to make the request to Asana API'))

        for user in users:
            if user['detected_user']:
                user['user'] = {
                    'id': user['detected_user'].id,
                    'full_name': user['detected_user'].get_full_name(),
                    'gravatar_id': get_user_gravatar_id(user['detected_user']),
                    'photo': get_user_photo_url(user['detected_user']),
                }
            del (user['detected_user'])
        return response.Ok(users)
コード例 #2
0
ファイル: api.py プロジェクト: tuesmoncom/tuesmon-back
    def post_save(self, obj, created=False):
        if not created:
            # Let's reorder the related stuff after edit the element
            orders_updated = {}
            updated = self._reorder_if_needed(obj, self._old_backlog_order_key,
                                              self._backlog_order_key(obj),
                                              "backlog_order", obj.project)
            orders_updated.update(updated)
            updated = self._reorder_if_needed(obj,
                                              self._old_kanban_order_key,
                                              self._kanban_order_key(obj),
                                              "kanban_order",
                                              obj.project,
                                              status=obj.status)
            orders_updated.update(updated)
            updated = self._reorder_if_needed(obj,
                                              self._old_sprint_order_key,
                                              self._sprint_order_key(obj),
                                              "sprint_order",
                                              obj.project,
                                              milestone=obj.milestone)
            orders_updated.update(updated)
            self.headers["Tuesmon-Info-Order-Updated"] = json.dumps(
                orders_updated)

        # Code related to the hack of pre_save method.
        # Rather, this is the continuation of it.
        if self._role_points:
            Points = apps.get_model("projects", "Points")
            RolePoints = apps.get_model("userstories", "RolePoints")

            for role_id, points_id in self._role_points.items():
                try:
                    role_points = RolePoints.objects.get(role__id=role_id,
                                                         user_story_id=obj.pk,
                                                         role__computable=True)
                except (ValueError, RolePoints.DoesNotExist):
                    raise exc.BadRequest({
                        "points":
                        _("Invalid role id '{role_id}'").format(
                            role_id=role_id)
                    })

                try:
                    role_points.points = Points.objects.get(
                        id=points_id, project_id=obj.project_id)
                except (ValueError, Points.DoesNotExist):
                    raise exc.BadRequest({
                        "points":
                        _("Invalid points id '{points_id}'").format(
                            points_id=points_id)
                    })

                role_points.save()

        super().post_save(obj, created)
コード例 #3
0
 def list_projects(self, request, *args, **kwargs):
     self.check_permissions(request, "list_projects", None)
     token = request.DATA.get('token', None)
     importer = AsanaImporter(request.user, token)
     try:
         projects = importer.list_projects()
     except exceptions.InvalidRequest:
         raise exc.BadRequest(_('Invalid Asana API request'))
     except exceptions.FailedRequest:
         raise exc.BadRequest(_('Failed to make the request to Asana API'))
     return response.Ok(projects)
コード例 #4
0
    def _public_register(self, request):
        if not settings.PUBLIC_REGISTER_ENABLED:
            raise exc.BadRequest(_("Public register is disabled."))

        try:
            data = parse_public_register_data(request.DATA)
            user = public_register(**data)
        except exc.IntegrityError as e:
            raise exc.BadRequest(e.detail)

        data = make_auth_response_data(user)
        return response.Created(data)
コード例 #5
0
    def register(self, request, **kwargs):
        accepted_terms = request.DATA.get("accepted_terms", None)
        if accepted_terms in (None, False):
            raise exc.BadRequest(
                _("You must accept our terms of service and privacy policy"))

        self.check_permissions(request, 'register', None)

        type = request.DATA.get("type", None)
        if type == "public":
            return self._public_register(request)
        elif type == "private":
            return self._private_register(request)
        raise exc.BadRequest(_("invalid register type"))
コード例 #6
0
 def _transform_value(value):
     try:
         return int(value)
     except:
         if value in self._special_values_dict:
             return self._special_values_dict[value]
     raise exc.BadRequest()
コード例 #7
0
    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)
コード例 #8
0
    def filter_queryset(self, request, queryset, view):
        query_params = {}

        if not hasattr(view, "filter_fields"):
            return queryset

        for field in view.filter_fields:
            if isinstance(field, (tuple, list)):
                param_name, field_name = field
            else:
                param_name, field_name = field, field

            if param_name in request.QUERY_PARAMS:
                field_data = request.QUERY_PARAMS[param_name]
                if field_data in self._special_values_dict:
                    query_params[field_name] = self._special_values_dict[
                        field_data]
                else:
                    query_params[field_name] = field_data

        if query_params:
            try:
                queryset = queryset.filter(**query_params)
            except ValueError:
                raise exc.BadRequest(_("Error in filter params types."))

        return queryset
コード例 #9
0
ファイル: api.py プロジェクト: tuesmoncom/tuesmon-back
    def auth_url(self, request, *args, **kwargs):
        self.check_permissions(request, "auth_url", None)
        jira_url = request.QUERY_PARAMS.get('url', None)

        if not jira_url:
            raise exc.WrongArguments(_("The url param is needed"))

        try:
            (oauth_token, oauth_secret, url) = JiraNormalImporter.get_auth_url(
                jira_url,
                settings.IMPORTERS.get('jira', {}).get('consumer_key', None),
                settings.IMPORTERS.get('jira', {}).get('cert', None), True)
        except exceptions.InvalidServiceConfiguration:
            raise exc.BadRequest(_("Invalid Jira server configuration."))

        (auth_data,
         created) = AuthData.objects.get_or_create(user=request.user,
                                                   key="jira-oauth",
                                                   defaults={
                                                       "value":
                                                       uuid.uuid4().hex,
                                                       "extra": {},
                                                   })
        auth_data.extra = {
            "oauth_token": oauth_token,
            "oauth_secret": oauth_secret,
            "url": jira_url,
        }
        auth_data.save()

        return response.Ok({"url": url})
コード例 #10
0
ファイル: api.py プロジェクト: tuesmoncom/tuesmon-back
    def authorize(self, request, *args, **kwargs):
        self.check_permissions(request, "authorize", None)

        code = request.DATA.get('code', None)
        if code is None:
            raise exc.BadRequest(_("Code param needed"))

        try:
            token = GithubImporter.get_access_token(
                settings.IMPORTERS.get('github', {}).get('client_id', None),
                settings.IMPORTERS.get('github',
                                       {}).get('client_secret', None), code)
            return response.Ok({"token": token})
        except exceptions.InvalidAuthResult:
            raise exc.BadRequest(_("Invalid auth data"))
        except exceptions.FailedRequest:
            raise exc.BadRequest(_("Third party service failing"))
コード例 #11
0
 def create(self, *args, **kwargs):
     key = self.request.DATA.get("key", None)
     if (key and self.request.user.is_authenticated() and
             self.request.user.storage_entries.filter(key=key).exists()):
         raise exc.BadRequest(
             _("Duplicate key value violates unique constraint. "
               "Key '{}' already exists.").format(key))
     return super().create(*args, **kwargs)
コード例 #12
0
    def filter_queryset(self, request, queryset, view):
        project_id = None
        project = None
        qs = queryset.filter(is_active=True)
        if "project" in request.QUERY_PARAMS:
            try:
                project_id = int(request.QUERY_PARAMS["project"])
            except:
                logger.error(
                    "Filtering project diferent value than an integer: {}".
                    format(request.QUERY_PARAMS["project"]))
                raise exc.BadRequest(_("'project' must be an integer value."))

        if project_id:
            Project = apps.get_model('projects', 'Project')
            project = get_object_or_404(Project, pk=project_id)

        if request.user.is_authenticated() and request.user.is_superuser:
            qs = qs
        elif request.user.is_authenticated():
            Membership = apps.get_model('projects', 'Membership')
            memberships_qs = Membership.objects.filter(user=request.user)
            if project_id:
                memberships_qs = memberships_qs.filter(project_id=project_id)
            memberships_qs = memberships_qs.filter(
                Q(role__permissions__contains=[self.permission])
                | Q(is_admin=True))

            projects_list = [
                membership.project_id for membership in memberships_qs
            ]

            if project:
                is_member = project.id in projects_list
                has_project_public_view_permission = "view_project" in project.public_permissions
                if not is_member and not has_project_public_view_permission:
                    qs = qs.none()

            q = Q(memberships__project_id__in=projects_list) | Q(
                id=request.user.id)

            # If there is no selected project we want access to users from public projects
            if not project:
                q = q | Q(memberships__project__public_permissions__contains=[
                    self.permission
                ])

            qs = qs.filter(q)

        else:
            if project and "view_project" not in project.anon_permissions:
                qs = qs.none()

            qs = qs.filter(memberships__project__anon_permissions__contains=[
                self.permission
            ])

        return qs.distinct()
コード例 #13
0
ファイル: ordering.py プロジェクト: tuesmoncom/tuesmon-back
    def bulk_update_order(self, request, **kwargs):
        bulk_data = request.DATA.get(self.bulk_update_param, None)

        if bulk_data is None:
            raise exc.BadRequest(_("'{param}' parameter is mandatory".format(param=self.bulk_update_param)))

        project_id = request.DATA.get('project', None)
        if project_id is None:
            raise exc.BadRequest(_("'project' parameter is mandatory"))

        project = get_object_or_404(Project, id=project_id)

        self.check_permissions(request, 'bulk_update_order', project)
        if project.blocked_code is not None:
            raise exc.Blocked(_("Blocked element"))
            
        self.__class__.bulk_update_order_action(project, request.user, bulk_data)
        return response.NoContent(data=None)
コード例 #14
0
    def authorize(self, request, *args, **kwargs):
        self.check_permissions(request, "authorize", None)

        code = request.DATA.get('code', None)
        if code is None:
            raise exc.BadRequest(_("Code param needed"))

        try:
            asana_token = AsanaImporter.get_access_token(
                code,
                settings.IMPORTERS.get('asana', {}).get('app_id', None),
                settings.IMPORTERS.get('asana', {}).get('app_secret', None),
                settings.IMPORTERS.get('asana', {}).get('callback_url', None))
        except exceptions.InvalidRequest:
            raise exc.BadRequest(_('Invalid Asana API request'))
        except exceptions.FailedRequest:
            raise exc.BadRequest(_('Failed to make the request to Asana API'))

        return response.Ok({"token": asana_token})
コード例 #15
0
ファイル: api.py プロジェクト: tuesmoncom/tuesmon-back
    def wiki_link(self, request, *args, **kwargs):
        project = self.get_object_or_none()
        self.check_permissions(request, 'import_item', project)

        wiki_link = services.store.store_wiki_link(project, request.DATA.copy())

        errors = services.store.get_errors()
        if errors:
            raise exc.BadRequest(errors)

        data = serializers.WikiLinkExportSerializer(wiki_link.object).data
        headers = self.get_success_headers(data)
        return response.Created(data, headers=headers)
コード例 #16
0
    def create(self, request, **kwargs):
        self.check_permissions(request, 'create', None)
        auth_plugins = get_auth_plugins()

        login_type = request.DATA.get("type", None)
        invitation_token = request.DATA.get("invitation_token", None)

        if login_type in auth_plugins:
            data = auth_plugins[login_type]['login_func'](request)
            if invitation_token:
                accept_invitation_by_existing_user(invitation_token,
                                                   data['id'])
            return response.Ok(data)

        raise exc.BadRequest(_("invalid login type"))
コード例 #17
0
    def create(self, request, *args, **kwargs):
        project = self._get_project(request)
        if not project:
            raise exc.BadRequest(_("The project doesn't exist"))

        if not self._validate_signature(project, request):
            raise exc.BadRequest(_("Bad signature"))

        if project.blocked_code is not None:
            raise exc.Blocked(_("Blocked element"))

        event_name = self._get_event_name(request)

        payload = self._get_payload(request)

        event_hook_class = self.event_hook_classes.get(event_name, None)
        if event_hook_class is not None:
            event_hook = event_hook_class(project, payload)
            try:
                event_hook.process_event()
            except ActionSyntaxException as e:
                raise exc.BadRequest(e)

        return response.NoContent()
コード例 #18
0
    def filter_queryset(self, request, queryset, view):
        project_id = None
        if "project" in request.QUERY_PARAMS:
            try:
                project_id = int(request.QUERY_PARAMS["project"])
            except ValueError:
                logger.error(
                    "Filtering project different value tpphan an integer: {}".
                    format(request.QUERY_PARAMS["project"]))
                raise exc.BadRequest(_("'project' must be an integer value."))

        if project_id:
            queryset = queryset.filter(status__project_id=project_id)

        return super().filter_queryset(request, queryset, view)
コード例 #19
0
ファイル: api.py プロジェクト: tuesmoncom/tuesmon-back
    def issue(self, request, *args, **kwargs):
        project = self.get_object_or_none()
        self.check_permissions(request, 'import_item', project)

        signals.pre_save.disconnect(sender=Issue,
                                    dispatch_uid="set_finished_date_when_edit_issue")

        issue = services.store.store_issue(project, request.DATA.copy())

        errors = services.store.get_errors()
        if errors:
            raise exc.BadRequest(errors)

        data = serializers.IssueExportSerializer(issue.object).data
        headers = self.get_success_headers(data)
        return response.Created(data, headers=headers)
コード例 #20
0
    def filter_queryset(self, request, queryset, view):
        query_watchers = self._get_watchers_queryparams(request.QUERY_PARAMS)
        if query_watchers:
            WatchedModel = apps.get_model("notifications", "Watched")
            watched_type = ContentType.objects.get_for_model(queryset.model)

            try:
                watched_ids = (WatchedModel.objects.filter(
                    content_type=watched_type,
                    user__id__in=query_watchers).values_list("object_id",
                                                             flat=True))
                queryset = queryset.filter(id__in=watched_ids)
            except ValueError:
                raise exc.BadRequest(_("Error in filter params types."))

        return super().filter_queryset(request, queryset, view)
コード例 #21
0
ファイル: filters.py プロジェクト: tuesmoncom/tuesmon-back
    def filter_queryset(self, request, queryset, view):
        project_id = None

        # Filter by filter_fields
        if (hasattr(view, "filter_fields") and "project" in view.filter_fields
                and "project" in request.QUERY_PARAMS):
            try:
                project_id = int(request.QUERY_PARAMS["project"])
            except:
                logger.error(
                    "Filtering project diferent value than an integer: {}".
                    format(request.QUERY_PARAMS["project"]))
                raise exc.BadRequest(_("'project' must be an integer value."))

        filter_expression = get_filter_expression_can_view_projects(
            request.user, project_id)

        qs = queryset.filter(filter_expression)

        return super().filter_queryset(request, qs, view)
コード例 #22
0
    def filter_queryset(self, request, queryset, view):
        project_id = None
        if (hasattr(view, "filter_fields") and "project" in view.filter_fields
                and "project" in request.QUERY_PARAMS):
            try:
                project_id = int(request.QUERY_PARAMS["project"])
            except:
                logger.error(
                    "Filtering project diferent value than an integer: {}".
                    format(request.QUERY_PARAMS["project"]))
                raise exc.BadRequest(_("'project' must be an integer value."))

        qs = queryset

        if request.user.is_authenticated() and request.user.is_superuser:
            qs = qs
        elif request.user.is_authenticated():
            membership_model = apps.get_model('projects', 'Membership')
            memberships_qs = membership_model.objects.filter(user=request.user)
            if project_id:
                memberships_qs = memberships_qs.filter(project_id=project_id)
            memberships_qs = memberships_qs.filter(
                Q(role__permissions__contains=[self.permission])
                | Q(is_admin=True))

            projects_list = [
                membership.project_id for membership in memberships_qs
            ]

            qs = qs.filter(
                Q(project_id__in=projects_list)
                | Q(project__public_permissions__contains=[self.permission]))
        else:
            qs = qs.filter(
                project__anon_permissions__contains=[self.permission])

        return super().filter_queryset(request, qs, view)
コード例 #23
0
 def pre_delete(self, obj):
     if obj.by_default:
         raise exc.BadRequest(
             _("You can't delete issue due date by default"))
コード例 #24
0
 def _get_payload(self, request):
     try:
         payload = json.loads(request.body.decode("utf-8"))
     except ValueError:
         raise exc.BadRequest(_("The payload is not a valid json"))
     return payload
コード例 #25
0
ファイル: api.py プロジェクト: tuesmoncom/tuesmon-back
    def create(self, request, *args, **kwargs):
        self.check_permissions(request, 'import_project', None)

        data = request.DATA.copy()
        data['owner'] = data.get('owner', request.user.email)

        # Validate if the project can be imported
        is_private = data.get('is_private', False)
        total_memberships = len([m for m in data.get("memberships", []) if m.get("email", None) != data["owner"]])
        total_memberships = total_memberships + 1  # 1 is the owner
        (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)

        # Create Project
        project_serialized = services.store.store_project(data)

        if not project_serialized:
            raise exc.BadRequest(services.store.get_errors())

        # Create roles
        roles_serialized = None
        if "roles" in data:
            roles_serialized = services.store.store_roles(project_serialized.object, data)

        if not roles_serialized:
            raise exc.BadRequest(_("We needed at least one role"))

        # Create memberships
        if "memberships" in data:
            services.store.store_memberships(project_serialized.object, data)

        try:
            owner_membership = project_serialized.object.memberships.get(user=project_serialized.object.owner)
            owner_membership.is_admin = True
            owner_membership.save()
        except Membership.DoesNotExist:
            Membership.objects.create(
                project=project_serialized.object,
                email=project_serialized.object.owner.email,
                user=project_serialized.object.owner,
                role=project_serialized.object.roles.all().first(),
                is_admin=True
            )

        # Create project values choicess
        if "points" in data:
            services.store.store_project_attributes_values(project_serialized.object, data,
                                                           "points", validators.PointsExportValidator)
        if "issue_types" in data:
            services.store.store_project_attributes_values(project_serialized.object, data,
                                                           "issue_types",
                                                           validators.IssueTypeExportValidator)
        if "issue_statuses" in data:
            services.store.store_project_attributes_values(project_serialized.object, data,
                                                           "issue_statuses",
                                                           validators.IssueStatusExportValidator,)
        if "issue_duedates" in data:
            services.store.store_project_attributes_values(project_serialized.object, data,
                                                           "issue_duedates",
                                                           validators.IssueDueDateExportValidator,)
        if "us_statuses" in data:
            services.store.store_project_attributes_values(project_serialized.object, data,
                                                           "us_statuses",
                                                           validators.UserStoryStatusExportValidator,)
        if "us_duedates" in data:
            services.store.store_project_attributes_values(project_serialized.object, data,
                                                           "us_duedates",
                                                           validators.UserStoryDueDateExportValidator,)
        if "task_statuses" in data:
            services.store.store_project_attributes_values(project_serialized.object, data,
                                                           "task_statuses",
                                                           validators.TaskStatusExportValidator)
        if "task_duedates" in data:
            services.store.store_project_attributes_values(project_serialized.object, data,
                                                           "task_duedates",
                                                           validators.TaskDueDateExportValidator)
        if "priorities" in data:
            services.store.store_project_attributes_values(project_serialized.object, data,
                                                           "priorities",
                                                           validators.PriorityExportValidator)
        if "severities" in data:
            services.store.store_project_attributes_values(project_serialized.object, data,
                                                           "severities",
                                                           validators.SeverityExportValidator)

        if ("points" in data or "issues_types" in data or
                "issues_statuses" in data or "us_statuses" in data or
                "task_statuses" in data or "priorities" in data or
                "severities" in data):
            services.store.store_default_project_attributes_values(project_serialized.object, data)

        # Created custom attributes
        if "userstorycustomattributes" in data:
            services.store.store_custom_attributes(project_serialized.object, data,
                                                   "userstorycustomattributes",
                                                   validators.UserStoryCustomAttributeExportValidator)

        if "taskcustomattributes" in data:
            services.store.store_custom_attributes(project_serialized.object, data,
                                                   "taskcustomattributes",
                                                   validators.TaskCustomAttributeExportValidator)

        if "issuecustomattributes" in data:
            services.store.store_custom_attributes(project_serialized.object, data,
                                                   "issuecustomattributes",
                                                   validators.IssueCustomAttributeExportValidator)

        # Is there any error?
        errors = services.store.get_errors()
        if errors:
            raise exc.BadRequest(errors)

        # Importer process is OK
        response_data = serializers.ProjectExportSerializer(project_serialized.object).data
        response_data['id'] = project_serialized.object.id
        headers = self.get_success_headers(response_data)
        return response.Created(response_data, headers=headers)
コード例 #26
0
    def list(self, request, **kwargs):
        validator = ResolverValidator(data=request.QUERY_PARAMS)
        if not validator.is_valid():
            raise exc.BadRequest(validator.errors)

        data = validator.data

        project_model = apps.get_model("projects", "Project")
        project = get_object_or_404(project_model, slug=data["project"])

        self.check_permissions(request, "list", project)

        result = {"project": project.pk}

        if data["epic"] and user_has_perm(request.user, "view_epics", project):
            result["epic"] = get_object_or_404(project.epics.all(),
                                               ref=data["epic"]).pk
        if data["us"] and user_has_perm(request.user, "view_us", project):
            result["us"] = get_object_or_404(project.user_stories.all(),
                                             ref=data["us"]).pk
        if data["task"] and user_has_perm(request.user, "view_tasks", project):
            result["task"] = get_object_or_404(project.tasks.all(),
                                               ref=data["task"]).pk
        if data["issue"] and user_has_perm(request.user, "view_issues",
                                           project):
            result["issue"] = get_object_or_404(project.issues.all(),
                                                ref=data["issue"]).pk
        if data["milestone"] and user_has_perm(request.user, "view_milestones",
                                               project):
            result["milestone"] = get_object_or_404(project.milestones.all(),
                                                    slug=data["milestone"]).pk
        if data["wikipage"] and user_has_perm(request.user, "view_wiki_pages",
                                              project):
            result["wikipage"] = get_object_or_404(project.wiki_pages.all(),
                                                   slug=data["wikipage"]).pk

        if data["ref"]:
            ref_found = False  # No need to continue once one ref is found
            try:
                value = int(data["ref"])

                if user_has_perm(request.user, "view_epics", project):
                    epic = project.epics.filter(ref=value).first()
                    if epic:
                        result["epic"] = epic.pk
                        ref_found = True
                if ref_found is False and user_has_perm(
                        request.user, "view_us", project):
                    us = project.user_stories.filter(ref=value).first()
                    if us:
                        result["us"] = us.pk
                        ref_found = True
                if ref_found is False and user_has_perm(
                        request.user, "view_tasks", project):
                    task = project.tasks.filter(ref=value).first()
                    if task:
                        result["task"] = task.pk
                        ref_found = True
                if ref_found is False and user_has_perm(
                        request.user, "view_issues", project):
                    issue = project.issues.filter(ref=value).first()
                    if issue:
                        result["issue"] = issue.pk
            except:
                value = data["ref"]

                if user_has_perm(request.user, "view_wiki_pages", project):
                    wiki_page = project.wiki_pages.filter(slug=value).first()
                    if wiki_page:
                        result["wikipage"] = wiki_page.pk

        return response.Ok(result)
コード例 #27
0
 def pre_delete(self, obj):
     if obj.user is not None and not services.can_user_leave_project(
             obj.user, obj.project):
         raise exc.BadRequest(
             _("The project must have an owner and at least one of the users "
               "must be an active admin"))