Skip to content

Commit

Permalink
Improved versioning and dummy implementation for PluginViewSet
Browse files Browse the repository at this point in the history
Replaced URLPathVersioning by Namespace versioning. It fixed a couple of
problems with documentation (api/docs).
  • Loading branch information
Nikita Manovich committed Feb 5, 2019
1 parent 90d0b38 commit d2692da
Show file tree
Hide file tree
Showing 8 changed files with 113 additions and 70 deletions.
14 changes: 0 additions & 14 deletions cvat/apps/engine/__init__.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,3 @@
# Copyright (C) 2018 Intel Corporation
#
# SPDX-License-Identifier: MIT

from rest_framework import versioning

class URLPathVersioning(versioning.URLPathVersioning):
def determine_version(self, request, *args, **kwargs):
# When you try to specify a version (e.g /api/v1/docs) it will generate
# an error inside a template of Rest Framework (reverse of an URL
# without argument). Also when you try don't use version at all it will
# give you an error 'invalid version'. To avoid these problems just
# redefine determine_version for /api/docs/*.
if request.path.startswith('/api/docs/'):
return None

return super().determine_version(request, *args, **kwargs)
30 changes: 30 additions & 0 deletions cvat/apps/engine/migrations/0023_plugin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Generated by Django 2.1.5 on 2019-02-05 10:07

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', '0022_auto_20190201_2223'),
]

operations = [
migrations.CreateModel(
name='Plugin',
fields=[
('name', models.SlugField(max_length=32, primary_key=True, serialize=False)),
('description', cvat.apps.engine.models.SafeCharField(max_length=8192)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now_add=True)),
('maintainer', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='maintainers', to=settings.AUTH_USER_MODEL)),
],
options={
'default_permissions': (),
},
),
]
17 changes: 17 additions & 0 deletions cvat/apps/engine/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -297,3 +297,20 @@ class TrackedPoints(TrackedObject, PolyShape):

class TrackedPointsAttributeVal(AttributeVal):
points = models.ForeignKey(TrackedPoints, on_delete=models.CASCADE)

class Plugin(models.Model):
name = models.SlugField(max_length=32, primary_key=True)
description = SafeCharField(max_length=8192)
maintainer = models.ForeignKey(User, null=True, blank=True,
on_delete=models.SET_NULL, related_name="maintainers")
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now_add=True)

# Extend default permission model
class Meta:
default_permissions = ()

class PluginOption(models.Model):
plugin = models.ForeignKey(Plugin, on_delete=models.CASCADE)
name = SafeCharField(max_length=32)
value = SafeCharField(max_length=1024)
8 changes: 7 additions & 1 deletion cvat/apps/engine/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from rest_framework import serializers
from cvat.apps.engine.models import (Task, Job, Label, AttributeSpec,
Segment, ClientFile, ServerFile, RemoteFile)
Segment, ClientFile, ServerFile, RemoteFile, Plugin)

from django.contrib.auth.models import User, Group
import os
Expand Down Expand Up @@ -176,3 +176,9 @@ class AboutSerializer(serializers.Serializer):
class ImageMetaSerializer(serializers.Serializer):
width = serializers.IntegerField()
height = serializers.IntegerField()

class PluginSerializer(serializers.ModelSerializer):
class Meta:
model = Plugin
fields = ('name', 'description', 'maintainer', 'created_at',
'updated_at')
40 changes: 3 additions & 37 deletions cvat/apps/engine/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,52 +8,18 @@
from rest_framework import routers
from rest_framework.documentation import include_docs_urls

REST_API_PREFIX = 'api/<version>/'

router = routers.DefaultRouter(trailing_slash=False)
router.register('tasks', views.TaskViewSet)
router.register('jobs', views.JobViewSet)
router.register('users', views.UserViewSet)
router.register('', views.ServerViewSet, basename='server')
router.register('plugins', views.PluginViewSet)

urlpatterns = [
# documentation for API
path('api/docs/', include_docs_urls(title='CVAT REST API')),
path('api/docs/', include_docs_urls(title='CVAT REST API', public=True)),
# entry point for API
path(REST_API_PREFIX, include(router.urls)),

path( # GET, DELETE, PATCH, PUT
REST_API_PREFIX + 'tasks/<int:pk>/annotations/',
views.dummy_view,
name='task-annotations'),
path( # GET, DELETE, PATCH, PUT
REST_API_PREFIX + 'jobs/<int:pk>/annotations/',
views.dummy_view,
name='job-annotations'),
path( # GET
REST_API_PREFIX + 'plugins/',
views.dummy_view,
name='plugin-list'),
path( # GET, PATCH, PUT
REST_API_PREFIX + 'plugins/<slug:name>/config/',
views.dummy_view,
name='plugin-config'),
path( # GET, POST
REST_API_PREFIX + 'plugins/<slug:name>/data/',
views.dummy_view,
name='plugin-data-list'),
path( # GET, PATCH, DELETE, PUT
REST_API_PREFIX + 'plugins/<slug:name>/data/<int:id>',
views.dummy_view,
name='plugin-data-detail'),
path( # GET, POST
REST_API_PREFIX + 'plugins/<slug:name>/requests/',
views.dummy_view,
name='plugin-request-list'),
path( # GET, DELETE
REST_API_PREFIX + 'plugins/<slug:name>/requests/<int:id>',
views.dummy_view,
name='plugin-request-detail'),
path('api/v1/', include((router.urls, 'cvat'), namespace='v1')),

path('delete/task/<int:tid>', views.delete_task), ####
path('update/task/<int:tid>', views.update_task), ####
Expand Down
63 changes: 48 additions & 15 deletions cvat/apps/engine/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@
from requests.exceptions import RequestException
import logging
from .log import slogger, clogger
from cvat.apps.engine.models import StatusChoice, Task, Job
from cvat.apps.engine.models import StatusChoice, Task, Job, Plugin
from cvat.apps.engine.serializers import (TaskSerializer, UserSerializer,
ExceptionSerializer, AboutSerializer, JobSerializer, ImageMetaSerializer,
RqStatusSerializer, TaskDataSerializer)
RqStatusSerializer, TaskDataSerializer, PluginSerializer)
from django.contrib.auth.models import User

# Server REST API
Expand All @@ -49,7 +49,7 @@ def get_serializer(self, *args, **kwargs):
return self.serializer_class(*args, **kwargs)

@action(detail=False, methods=['GET'], serializer_class=AboutSerializer)
def about(self, request, version=None):
def about(self, request):
from cvat import __version__ as cvat_version
about = {
"name": "Computer Vision Annotation Tool",
Expand All @@ -67,7 +67,7 @@ def about(self, request, version=None):
return Response(data=serializer.data)

@action(detail=False, methods=['POST'], serializer_class=ExceptionSerializer)
def exception(self, request, version=None):
def exception(self, request):
serializer = ExceptionSerializer(data=request.data)
if serializer.is_valid(raise_exception=True):
message = JSONRenderer().render(serializer.data)
Expand All @@ -88,25 +88,29 @@ class TaskViewSet(viewsets.ModelViewSet):
serializer_class = TaskSerializer

@action(detail=True, methods=['GET'], serializer_class=JobSerializer)
def jobs(self, request, pk, version):
def jobs(self, request, pk):
queryset = Job.objects.filter(segment__task_id=pk)
serializer = JobSerializer(queryset, many=True,
context={"request": request})

return Response(serializer.data)

@action(detail=True, methods=['PUT'], serializer_class=TaskDataSerializer)
def data(self, request, pk, version):
def data(self, request, pk):
db_task = self.get_object()
serializer = TaskDataSerializer(db_task, data=request.data)
if serializer.is_valid(raise_exception=True):
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)

@action(detail=True, methods=['GET'], serializer_class=None)
def annotations(self, request, pk):
pass

@action(detail=True, methods=['GET'], serializer_class=RqStatusSerializer)
def status(self, request, pk, version):
def status(self, request, pk):
response = self._get_rq_response(queue="default",
job_id="/api/{}/tasks/{}".format(version, pk))
job_id="/api/{}/tasks/{}".format(request.version, pk))
serializer = RqStatusSerializer(data=response)

if serializer.is_valid(raise_exception=True):
Expand All @@ -131,7 +135,7 @@ def _get_rq_response(self, queue, job_id):

@action(detail=True, methods=['GET'], serializer_class=ImageMetaSerializer,
url_path='frames/meta')
def data_info(self, request, pk, version=None):
def data_info(self, request, pk):
try:
db_task = models.Task.objects.get(pk=pk)
meta_cache_file = open(db_task.get_image_meta_cache_path())
Expand All @@ -146,7 +150,7 @@ def data_info(self, request, pk, version=None):

@action(detail=True, methods=['GET'], serializer_class=None,
url_path='frames/(?P<frame>\d+)')
def frame(self, request, pk, frame, version=None):
def frame(self, request, pk, frame):
"""Get a frame for the task"""

try:
Expand All @@ -173,19 +177,48 @@ class JobViewSet(viewsets.GenericViewSet,
queryset = Job.objects.all()
serializer_class = JobSerializer

class UserViewSet(viewsets.ModelViewSet):
@action(detail=True, methods=['GET'], serializer_class=None)
def annotations(self, request, pk):
pass


class UserViewSet(viewsets.GenericViewSet,
mixins.RetrieveModelMixin, mixins.UpdateModelMixin):
queryset = User.objects.all()
serializer_class = UserSerializer

@action(detail=False, methods=['GET'], serializer_class=UserSerializer)
def self(self, request, version):
def self(self, request):
serializer = UserSerializer(request.user, context={ "request": request })
return Response(serializer.data)

class PluginViewSet(viewsets.ModelViewSet):
queryset = Plugin.objects.all()
serializer_class = PluginSerializer

# @action(detail=True, methods=['GET', 'PATCH', 'PUT'], serializer_class=None)
# def config(self, request, name):
# pass

# @action(detail=True, methods=['GET', 'POST'], serializer_class=None)
# def data(self, request, name):
# pass

# @action(detail=True, methods=['GET', 'DELETE', 'PATCH', 'PUT'],
# serializer_class=None, url_path='data/(?P<id>\d+)')
# def data_detail(self, request, name, id):
# pass


@action(detail=True, methods=['GET', 'POST'], serializer_class=RqStatusSerializer)
def requests(self, request, name):
pass

@action(detail=True, methods=['GET', 'DELETE'],
serializer_class=RqStatusSerializer, url_path='requests/(?P<id>\d+)')
def request_detail(self, request, name, id):
pass

@api_view(['GET'])
def dummy_view(request, version=None, pk=None, frame=None, id=None, name=None):
return Response()


# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Expand Down
3 changes: 2 additions & 1 deletion cvat/requirements/base.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,5 @@ GitPython==2.1.11
coreapi==2.3.3
django-filter==2.0.0
Markdown==3.0.1
djangorestframework==3.9.0
djangorestframework==3.9.0
Pygments==2.3.1
8 changes: 6 additions & 2 deletions cvat/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,12 @@ def generate_ssh_keys():
'rest_framework.permissions.IsAuthenticated',
],
'DEFAULT_VERSIONING_CLASS':
'cvat.apps.engine.URLPathVersioning',
'ALLOWED_VERSIONS': ('v1', ),
# Don't try to use URLPathVersioning. It will give you /api/{version}
# in path and '/api/docs' will not collapse similar items (flat list
# of all possible methods isn't readable).
'rest_framework.versioning.NamespaceVersioning',
# Need to add 'api-docs' here as a workaround for include_docs_urls.
'ALLOWED_VERSIONS': ('v1', 'api-docs'),
'DEFAULT_PAGINATION_CLASS':
'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 10
Expand Down

0 comments on commit d2692da

Please sign in to comment.