Projects (server only, REST API) (#754)

* Initial version of projects
* Added tests for Projects REST API.
* Added information about projects into CHANGELOG
main
Nikita Manovich 6 years ago committed by GitHub
parent 73bab7bea6
commit ff1f6cfc5c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,3 +1,4 @@
exclude_paths:
- '**/3rdparty/**'
- '**/engine/js/cvat-core.min.js'
- CHANGELOG.md

@ -4,6 +4,25 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.0.0.alpha] - 2020-02-XX
### Added
- Server only support for projects. Extend REST API v1 (/api/v1/projects*).
### Changed
-
### Deprecated
-
### Removed
-
### Fixed
-
### Security
-
## [0.5.0] - 2019-10-12
### Added
- A converter to YOLO format

@ -63,20 +63,33 @@ has_user_role = rules.is_group_member(str(AUTH_ROLE.USER))
has_annotator_role = rules.is_group_member(str(AUTH_ROLE.ANNOTATOR))
has_observer_role = rules.is_group_member(str(AUTH_ROLE.OBSERVER))
@rules.predicate
def is_project_owner(db_user, db_project):
# If owner is None (null) the task can be accessed/changed/deleted
# only by admin. At the moment each task has an owner.
return db_project is not None and db_project.owner == db_user
@rules.predicate
def is_project_assignee(db_user, db_project):
return db_project is not None and db_project.assignee == db_user
@rules.predicate
def is_project_annotator(db_user, db_project):
db_tasks = list(db_project.tasks.prefetch_related('segment_set').all())
return any([is_task_annotator(db_user, db_task) for db_task in db_tasks])
@rules.predicate
def is_task_owner(db_user, db_task):
# If owner is None (null) the task can be accessed/changed/deleted
# only by admin. At the moment each task has an owner.
return db_task.owner == db_user
return db_task.owner == db_user or is_project_owner(db_user, db_task.project)
@rules.predicate
def is_task_assignee(db_user, db_task):
return db_task.assignee == db_user
return db_task.assignee == db_user or is_project_assignee(db_user, db_task.project)
@rules.predicate
def is_task_annotator(db_user, db_task):
from functools import reduce
db_segments = list(db_task.segment_set.prefetch_related('job_set__assignee').all())
return any([is_job_annotator(db_user, db_job)
for db_segment in db_segments for db_job in db_segment.job_set.all()])
@ -101,6 +114,13 @@ rules.add_perm('engine.role.admin', has_admin_role)
rules.add_perm('engine.role.annotator', has_annotator_role)
rules.add_perm('engine.role.observer', has_observer_role)
rules.add_perm('engine.project.create', has_admin_role | has_user_role)
rules.add_perm('engine.project.access', has_admin_role | has_observer_role |
is_project_owner | is_project_annotator)
rules.add_perm('engine.project.change', has_admin_role | is_project_owner |
is_project_assignee)
rules.add_perm('engine.project.delete', has_admin_role | is_project_owner)
rules.add_perm('engine.task.create', has_admin_role | has_user_role)
rules.add_perm('engine.task.access', has_admin_role | has_observer_role |
is_task_owner | is_task_annotator)
@ -133,6 +153,26 @@ class ObserverRolePermission(BasePermission):
def has_permission(self, request, view):
return request.user.has_perm("engine.role.observer")
class ProjectCreatePermission(BasePermission):
# pylint: disable=no-self-use
def has_permission(self, request, view):
return request.user.has_perm("engine.project.create")
class ProjectAccessPermission(BasePermission):
# pylint: disable=no-self-use
def has_object_permission(self, request, view, obj):
return request.user.has_perm("engine.project.access", obj)
class ProjectChangePermission(BasePermission):
# pylint: disable=no-self-use
def has_object_permission(self, request, view, obj):
return request.user.has_perm("engine.project.change", obj)
class ProjectDeletePermission(BasePermission):
# pylint: disable=no-self-use
def has_object_permission(self, request, view, obj):
return request.user.has_perm("engine.project.delete", obj)
class TaskCreatePermission(BasePermission):
# pylint: disable=no-self-use
def has_permission(self, request, view):
@ -143,7 +183,8 @@ class TaskAccessPermission(BasePermission):
def has_object_permission(self, request, view, obj):
return request.user.has_perm("engine.task.access", obj)
class TaskGetQuerySetMixin(object):
class ProjectGetQuerySetMixin(object):
def get_queryset(self):
queryset = super().get_queryset()
user = self.request.user
@ -152,7 +193,26 @@ class TaskGetQuerySetMixin(object):
return queryset
else:
return queryset.filter(Q(owner=user) | Q(assignee=user) |
Q(segment__job__assignee=user) | Q(assignee=None)).distinct()
Q(task__owner=user) | Q(task__assignee=user) |
Q(task__segment__job__assignee=user)).distinct()
def filter_task_queryset(queryset, user):
# Don't filter queryset for admin, observer
if has_admin_role(user) or has_observer_role(user):
return queryset
else:
return queryset.filter(Q(owner=user) | Q(assignee=user) |
Q(segment__job__assignee=user) | Q(assignee=None)).distinct()
class TaskGetQuerySetMixin(object):
def get_queryset(self):
queryset = super().get_queryset()
user = self.request.user
# Don't filter queryset for detail methods
if self.detail:
return queryset
else:
return filter_task_queryset(queryset, user)
class TaskChangePermission(BasePermission):
# pylint: disable=no-self-use

@ -0,0 +1,38 @@
# Generated by Django 2.2.3 on 2019-10-04 08:17
import cvat.apps.engine.models
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('engine', '0021_auto_20190826_1827'),
]
operations = [
migrations.CreateModel(
name='Project',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', cvat.apps.engine.models.SafeCharField(max_length=256)),
('bug_tracker', models.CharField(blank=True, default='', max_length=2000)),
('created_date', models.DateTimeField(auto_now_add=True)),
('updated_date', models.DateTimeField(auto_now_add=True)),
('status', models.CharField(choices=[('annotation', 'ANNOTATION'), ('validation', 'VALIDATION'), ('completed', 'COMPLETED')], default=cvat.apps.engine.models.StatusChoice('annotation'), max_length=32)),
('assignee', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
('owner', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
],
options={
'default_permissions': (),
},
),
migrations.AddField(
model_name='task',
name='project',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='tasks', related_query_name='task', to='engine.Project'),
),
]

@ -33,7 +33,26 @@ class StatusChoice(str, Enum):
def __str__(self):
return self.value
class Project(models.Model):
name = SafeCharField(max_length=256)
owner = models.ForeignKey(User, null=True, blank=True,
on_delete=models.SET_NULL, related_name="+")
assignee = models.ForeignKey(User, null=True, blank=True,
on_delete=models.SET_NULL, related_name="+")
bug_tracker = models.CharField(max_length=2000, blank=True, default="")
created_date = models.DateTimeField(auto_now_add=True)
updated_date = models.DateTimeField(auto_now_add=True)
status = models.CharField(max_length=32, choices=StatusChoice.choices(),
default=StatusChoice.ANNOTATION)
# Extend default permission model
class Meta:
default_permissions = ()
class Task(models.Model):
project = models.ForeignKey(Project, on_delete=models.CASCADE,
null=True, blank=True, related_name="tasks",
related_query_name="task")
name = SafeCharField(max_length=256)
size = models.PositiveIntegerField()
mode = models.CharField(max_length=32)

@ -196,7 +196,8 @@ class TaskSerializer(WriteOnceMixin, serializers.ModelSerializer):
fields = ('url', 'id', 'name', 'size', 'mode', 'owner', 'assignee',
'bug_tracker', 'created_date', 'updated_date', 'overlap',
'segment_size', 'z_order', 'status', 'labels', 'segments',
'image_quality', 'start_frame', 'stop_frame', 'frame_filter')
'image_quality', 'start_frame', 'stop_frame', 'frame_filter',
'project')
read_only_fields = ('size', 'mode', 'created_date', 'updated_date',
'status')
write_once_fields = ('overlap', 'segment_size', 'image_quality')
@ -245,6 +246,7 @@ class TaskSerializer(WriteOnceMixin, serializers.ModelSerializer):
instance.start_frame = validated_data.get('start_frame', instance.start_frame)
instance.stop_frame = validated_data.get('stop_frame', instance.stop_frame)
instance.frame_filter = validated_data.get('frame_filter', instance.frame_filter)
instance.project = validated_data.get('project', instance.project)
labels = validated_data.get('label_set', [])
for label in labels:
attributes = label.pop('attributespec_set', [])
@ -276,6 +278,14 @@ class TaskSerializer(WriteOnceMixin, serializers.ModelSerializer):
instance.save()
return instance
class ProjectSerializer(serializers.ModelSerializer):
class Meta:
model = models.Project
fields = ('url', 'id', 'name', 'owner', 'assignee', 'bug_tracker',
'created_date', 'updated_date', 'status')
read_only_fields = ('created_date', 'updated_date', 'status')
ordering = ['-id']
class UserSerializer(serializers.ModelSerializer):
groups = serializers.SlugRelatedField(many=True,
slug_field='name', queryset=Group.objects.all())

@ -12,7 +12,7 @@ from rest_framework import status
from django.conf import settings
from django.contrib.auth.models import User, Group
from cvat.apps.engine.models import (Task, Segment, Job, StatusChoice,
AttributeType)
AttributeType, Project)
from cvat.apps.annotation.models import AnnotationFormat
from unittest import mock
import io
@ -68,7 +68,7 @@ def create_db_task(data):
return db_task
def create_dummy_db_tasks(obj):
def create_dummy_db_tasks(obj, project=None):
tasks = []
data = {
@ -79,7 +79,8 @@ def create_dummy_db_tasks(obj):
"segment_size": 100,
"z_order": False,
"image_quality": 75,
"size": 100
"size": 100,
"project": project
}
db_task = create_db_task(data)
tasks.append(db_task)
@ -91,7 +92,8 @@ def create_dummy_db_tasks(obj):
"segment_size": 100,
"z_order": True,
"image_quality": 50,
"size": 200
"size": 200,
"project": project
}
db_task = create_db_task(data)
tasks.append(db_task)
@ -104,7 +106,8 @@ def create_dummy_db_tasks(obj):
"segment_size": 100,
"z_order": False,
"image_quality": 75,
"size": 100
"size": 100,
"project": project
}
db_task = create_db_task(data)
tasks.append(db_task)
@ -116,13 +119,61 @@ def create_dummy_db_tasks(obj):
"segment_size": 50,
"z_order": False,
"image_quality": 95,
"size": 50
"size": 50,
"project": project
}
db_task = create_db_task(data)
tasks.append(db_task)
return tasks
def create_dummy_db_projects(obj):
projects = []
data = {
"name": "my empty project",
"owner": obj.owner,
"assignee": obj.assignee,
}
db_project = Project.objects.create(**data)
projects.append(db_project)
data = {
"name": "my project without assignee",
"owner": obj.user,
}
db_project = Project.objects.create(**data)
create_dummy_db_tasks(obj, db_project)
projects.append(db_project)
data = {
"name": "my big project",
"owner": obj.owner,
"assignee": obj.assignee,
}
db_project = Project.objects.create(**data)
create_dummy_db_tasks(obj, db_project)
projects.append(db_project)
data = {
"name": "public project",
}
db_project = Project.objects.create(**data)
create_dummy_db_tasks(obj, db_project)
projects.append(db_project)
data = {
"name": "super project",
"owner": obj.admin,
"assignee": obj.assignee,
}
db_project = Project.objects.create(**data)
create_dummy_db_tasks(obj, db_project)
projects.append(db_project)
return projects
class ForceLogin:
def __init__(self, user, client):
self.user = user
@ -587,6 +638,323 @@ class UserPartialUpdateAPITestCase(UserUpdateAPITestCase):
response = self._run_api_v1_users_id(None, self.user.id, data)
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
class ProjectListAPITestCase(APITestCase):
def setUp(self):
self.client = APIClient()
@classmethod
def setUpTestData(cls):
create_db_users(cls)
cls.projects = create_dummy_db_projects(cls)
def _run_api_v1_projects(self, user, params=""):
with ForceLogin(user, self.client):
response = self.client.get('/api/v1/projects{}'.format(params))
return response
def test_api_v1_projects_admin(self):
response = self._run_api_v1_projects(self.admin)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertListEqual(
sorted([project.name for project in self.projects]),
sorted([res["name"] for res in response.data["results"]]))
def test_api_v1_projects_user(self):
response = self._run_api_v1_projects(self.user)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertListEqual(
sorted([project.name for project in self.projects
if 'my empty project' != project.name]),
sorted([res["name"] for res in response.data["results"]]))
def test_api_v1_projects_observer(self):
response = self._run_api_v1_projects(self.observer)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertListEqual(
sorted([project.name for project in self.projects]),
sorted([res["name"] for res in response.data["results"]]))
def test_api_v1_projects_no_auth(self):
response = self._run_api_v1_projects(None)
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
class ProjectGetAPITestCase(APITestCase):
def setUp(self):
self.client = APIClient()
@classmethod
def setUpTestData(cls):
create_db_users(cls)
cls.projects = create_dummy_db_projects(cls)
def _run_api_v1_projects_id(self, pid, user):
with ForceLogin(user, self.client):
response = self.client.get('/api/v1/projects/{}'.format(pid))
return response
def _check_response(self, response, db_project):
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data["name"], db_project.name)
owner = db_project.owner.id if db_project.owner else None
self.assertEqual(response.data["owner"], owner)
assignee = db_project.assignee.id if db_project.assignee else None
self.assertEqual(response.data["assignee"], assignee)
self.assertEqual(response.data["status"], db_project.status)
def _check_api_v1_projects_id(self, user):
for db_project in self.projects:
response = self._run_api_v1_projects_id(db_project.id, user)
if user and user.has_perm("engine.project.access", db_project):
self._check_response(response, db_project)
elif user:
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
else:
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
def test_api_v1_projects_id_admin(self):
self._check_api_v1_projects_id(self.admin)
def test_api_v1_projects_id_user(self):
self._check_api_v1_projects_id(self.user)
def test_api_v1_projects_id_observer(self):
self._check_api_v1_projects_id(self.observer)
def test_api_v1_projects_id_no_auth(self):
self._check_api_v1_projects_id(None)
class ProjectDeleteAPITestCase(APITestCase):
def setUp(self):
self.client = APIClient()
@classmethod
def setUpTestData(cls):
create_db_users(cls)
cls.projects = create_dummy_db_projects(cls)
def _run_api_v1_projects_id(self, pid, user):
with ForceLogin(user, self.client):
response = self.client.delete('/api/v1/projects/{}'.format(pid), format="json")
return response
def _check_api_v1_projects_id(self, user):
for db_project in self.projects:
response = self._run_api_v1_projects_id(db_project.id, user)
if user and user.has_perm("engine.project.delete", db_project):
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
elif user:
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
else:
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
def test_api_v1_projects_id_admin(self):
self._check_api_v1_projects_id(self.admin)
def test_api_v1_projects_id_user(self):
self._check_api_v1_projects_id(self.user)
def test_api_v1_projects_id_observer(self):
self._check_api_v1_projects_id(self.observer)
def test_api_v1_projects_id_no_auth(self):
self._check_api_v1_projects_id(None)
class ProjectCreateAPITestCase(APITestCase):
def setUp(self):
self.client = APIClient()
@classmethod
def setUpTestData(cls):
create_db_users(cls)
def _run_api_v1_projects(self, user, data):
with ForceLogin(user, self.client):
response = self.client.post('/api/v1/projects', data=data, format="json")
return response
def _check_response(self, response, user, data):
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(response.data["name"], data["name"])
self.assertEqual(response.data["owner"], data.get("owner", user.id))
self.assertEqual(response.data["assignee"], data.get("assignee"))
self.assertEqual(response.data["bug_tracker"], data.get("bug_tracker", ""))
self.assertEqual(response.data["status"], StatusChoice.ANNOTATION)
def _check_api_v1_projects(self, user, data):
response = self._run_api_v1_projects(user, data)
if user and user.has_perm("engine.project.create"):
self._check_response(response, user, data)
elif user:
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
else:
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
def test_api_v1_projects_admin(self):
data = {
"name": "new name for the project",
"bug_tracker": "http://example.com"
}
self._check_api_v1_projects(self.admin, data)
data = {
"owner": self.owner.id,
"assignee": self.assignee.id,
"name": "new name for the project"
}
self._check_api_v1_projects(self.admin, data)
data = {
"owner": self.admin.id,
"name": "2"
}
self._check_api_v1_projects(self.admin, data)
def test_api_v1_projects_user(self):
data = {
"name": "Dummy name",
"bug_tracker": "it is just text"
}
self._check_api_v1_projects(self.user, data)
data = {
"owner": self.owner.id,
"assignee": self.assignee.id,
"name": "My import project with data"
}
self._check_api_v1_projects(self.user, data)
def test_api_v1_projects_observer(self):
data = {
"name": "My Project #1",
"owner": self.owner.id,
"assignee": self.assignee.id
}
self._check_api_v1_projects(self.observer, data)
def test_api_v1_projects_no_auth(self):
data = {
"name": "My Project #2",
"owner": self.admin.id,
}
self._check_api_v1_projects(None, data)
class ProjectPartialUpdateAPITestCase(APITestCase):
def setUp(self):
self.client = APIClient()
@classmethod
def setUpTestData(cls):
create_db_users(cls)
cls.projects = create_dummy_db_projects(cls)
def _run_api_v1_projects_id(self, pid, user, data):
with ForceLogin(user, self.client):
response = self.client.patch('/api/v1/projects/{}'.format(pid),
data=data, format="json")
return response
def _check_response(self, response, db_project, data):
self.assertEqual(response.status_code, status.HTTP_200_OK)
name = data.get("name", db_project.name)
self.assertEqual(response.data["name"], name)
owner = db_project.owner.id if db_project.owner else None
owner = data.get("owner", owner)
self.assertEqual(response.data["owner"], owner)
assignee = db_project.assignee.id if db_project.assignee else None
assignee = data.get("assignee", assignee)
self.assertEqual(response.data["assignee"], assignee)
self.assertEqual(response.data["status"], db_project.status)
def _check_api_v1_projects_id(self, user, data):
for db_project in self.projects:
response = self._run_api_v1_projects_id(db_project.id, user, data)
if user and user.has_perm("engine.project.change", db_project):
self._check_response(response, db_project, data)
elif user:
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
else:
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
def test_api_v1_projects_id_admin(self):
data = {
"name": "new name for the project",
"owner": self.owner.id,
}
self._check_api_v1_projects_id(self.admin, data)
def test_api_v1_projects_id_user(self):
data = {
"name": "new name for the project",
"owner": self.assignee.id,
}
self._check_api_v1_projects_id(self.user, data)
def test_api_v1_projects_id_observer(self):
data = {
"name": "new name for the project",
}
self._check_api_v1_projects_id(self.observer, data)
def test_api_v1_projects_id_no_auth(self):
data = {
"name": "new name for the project",
}
self._check_api_v1_projects_id(None, data)
class ProjectListOfTasksAPITestCase(APITestCase):
def setUp(self):
self.client = APIClient()
@classmethod
def setUpTestData(cls):
create_db_users(cls)
cls.projects = create_dummy_db_projects(cls)
def _run_api_v1_projects_id_tasks(self, user, pid):
with ForceLogin(user, self.client):
response = self.client.get('/api/v1/projects/{}/tasks'.format(pid))
return response
def test_api_v1_projects_id_tasks_admin(self):
project = self.projects[1]
response = self._run_api_v1_projects_id_tasks(self.admin, project.id)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertListEqual(
sorted([task.name for task in project.tasks.all()]),
sorted([res["name"] for res in response.data["results"]]))
def test_api_v1_projects_id_tasks_user(self):
project = self.projects[1]
response = self._run_api_v1_projects_id_tasks(self.user, project.id)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertListEqual(
sorted([task.name for task in project.tasks.all()
if task.owner in [None, self.user] or
task.assignee in [None, self.user]]),
sorted([res["name"] for res in response.data["results"]]))
def test_api_v1_projects_id_tasks_observer(self):
project = self.projects[1]
response = self._run_api_v1_projects_id_tasks(self.observer, project.id)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertListEqual(
sorted([task.name for task in project.tasks.all()]),
sorted([res["name"] for res in response.data["results"]]))
def test_api_v1_projects_id_tasks_no_auth(self):
project = self.projects[1]
response = self._run_api_v1_projects_id_tasks(None, project.id)
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
class TaskListAPITestCase(APITestCase):
def setUp(self):
self.client = APIClient()

@ -24,6 +24,7 @@ schema_view = get_schema_view(
)
router = routers.DefaultRouter(trailing_slash=False)
router.register('projects', views.ProjectViewSet)
router.register('tasks', views.TaskViewSet)
router.register('jobs', views.JobViewSet)
router.register('users', views.UserViewSet)

@ -36,7 +36,8 @@ from cvat.apps.engine.models import StatusChoice, Task, Job, Plugin
from cvat.apps.engine.serializers import (TaskSerializer, UserSerializer,
ExceptionSerializer, AboutSerializer, JobSerializer, ImageMetaSerializer,
RqStatusSerializer, TaskDataSerializer, LabeledDataSerializer,
PluginSerializer, FileInfoSerializer, LogEventSerializer)
PluginSerializer, FileInfoSerializer, LogEventSerializer,
ProjectSerializer)
from cvat.apps.annotation.serializers import AnnotationFileSerializer
from django.contrib.auth.models import User
from django.core.exceptions import ObjectDoesNotExist
@ -158,7 +159,65 @@ class ServerViewSet(viewsets.ViewSet):
data = get_annotation_formats()
return Response(data)
class ProjectFilter(filters.FilterSet):
name = filters.CharFilter(field_name="name", lookup_expr="icontains")
owner = filters.CharFilter(field_name="owner__username", lookup_expr="icontains")
status = filters.CharFilter(field_name="status", lookup_expr="icontains")
assignee = filters.CharFilter(field_name="assignee__username", lookup_expr="icontains")
class Meta:
model = models.Project
fields = ("id", "name", "owner", "status", "assignee")
class ProjectViewSet(auth.ProjectGetQuerySetMixin, viewsets.ModelViewSet):
queryset = models.Project.objects.all().order_by('-id')
serializer_class = ProjectSerializer
search_fields = ("name", "owner__username", "assignee__username", "status")
filterset_class = ProjectFilter
ordering_fields = ("id", "name", "owner", "status", "assignee")
http_method_names = ['get', 'post', 'head', 'patch', 'delete']
def get_permissions(self):
http_method = self.request.method
permissions = [IsAuthenticated]
if http_method in SAFE_METHODS:
permissions.append(auth.ProjectAccessPermission)
elif http_method in ["POST"]:
permissions.append(auth.ProjectCreatePermission)
elif http_method in ["PATCH"]:
permissions.append(auth.ProjectChangePermission)
elif http_method in ["DELETE"]:
permissions.append(auth.ProjectDeletePermission)
else:
permissions.append(auth.AdminRolePermission)
return [perm() for perm in permissions]
def perform_create(self, serializer):
if self.request.data.get('owner', None):
serializer.save()
else:
serializer.save(owner=self.request.user)
@action(detail=True, methods=['GET'], serializer_class=TaskSerializer)
def tasks(self, request, pk):
self.get_object() # force to call check_object_permissions
queryset = Task.objects.filter(project_id=pk).order_by('-id')
queryset = auth.filter_task_queryset(queryset, request.user)
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True,
context={"request": request})
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True,
context={"request": request})
return Response(serializer.data)
class TaskFilter(filters.FilterSet):
project = filters.CharFilter(field_name="project__name", lookup_expr="icontains")
name = filters.CharFilter(field_name="name", lookup_expr="icontains")
owner = filters.CharFilter(field_name="owner__username", lookup_expr="icontains")
mode = filters.CharFilter(field_name="mode", lookup_expr="icontains")
@ -167,7 +226,8 @@ class TaskFilter(filters.FilterSet):
class Meta:
model = Task
fields = ("id", "name", "owner", "mode", "status", "assignee")
fields = ("id", "project_id", "project", "name", "owner", "mode", "status",
"assignee")
class TaskViewSet(auth.TaskGetQuerySetMixin, viewsets.ModelViewSet):
queryset = Task.objects.all().prefetch_related(

Loading…
Cancel
Save