Довольно мутные интерфейсы в DRF ... с ходу не разберешься, "Django" он называется видимо только потому, что просто умеет с Django ORM работать, интерфейсы и подходы как-то не очень Django-frendly
Пишу весь код в одну кучу, критика приветствуется.
from django.conf.urls import patterns
from django.core.exceptions import PermissionDenied
from django.db import models
from django.db.models.base import ModelBase
from django.utils.decorators import available_attrs
from rest_framework.permissions import IsAuthenticated
from rest_framework.status import HTTP_500_INTERNAL_SERVER_ERROR
# Наш документ в models.py
document_make_some_permission = 'document_custom_permission'
class Document(models.Model):
class Meta:
permissions = [
(document_make_some_permission, "make_some доступ"),
]
@classproperty
def make_some_permission(cls):
""" Возвращает полный код доступа """
return permission_code(cls, document_make_some_permission)
name = models.CharField(max_length=250)
type = models.CharField(max_length=20)
# В settings.py делаем настройки для DRF
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.SessionAuthentication', # Авторизация через сессии Django
),
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.DjangoObjectPermissions' # Доступ к объектам через Django
],
'DEFAULT_RENDERER_CLASSES': (
'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.BrowsableAPIRenderer', # Если надо через браузер вручную удобно дёргать api
)
}
# Создаем router и затем уже регистрируем его в urls.py
from rest_framework import routers
api_router = routers.DefaultRouter()
from rest_framework import serializers, viewsets
from rest_framework.decorators import list_route
from rest_framework.filters import DjangoFilterBackend
from rest_framework.response import Response
# Делаем serializer
class DocumentSerializer(serializers.ModelSerializer):
class Meta:
model = Document
def validate_name(self, value):
# Валидация отдельных полей
if not value:
raise serializers.ValidationError("Error")
return value
def validate(self, data):
# Валидация на уровне объекта
if not data.get('name', None):
raise serializers.ValidationError("Error")
return data
# Делаем ViewSet (по сути форма)
class DocumentViewSet(viewsets.ModelViewSet):
serializer_class = DocumentSerializer
# Если нужна возможность фильтрации
filter_backends = (DjangoFilterBackend,)
# Разрешаем фильтрацию только по отдельным полям
filter_fields = ('name',)
# Можно переопределить к каким объектам ViewSet будет предоставлять доступ
queryset = Document.objects.all()
# На этом собственно всё, если нужен только простой REST с поддержкой доступов Django
# Если нужны свои методы в API для Document
@list_route(permission_classes=[IsAuthenticated, ]) # permission_classes приведён для примера
@permission_required_action(Document.make_some_permission) # Об этом декораторе ниже
def make_some(self, request):
"""
@list_route означает, что метод будет доступен для всех Document (т.е. в нашем случае метод будет доступен
по адресу /api/document/make_some), если нужен метод который будет доступен для конкретного объекта то
надо использовать декоратор detail_route (метод будет доступен по адресу /api/document/1/make_some)
Не забываем про доступы ... для list_route и detail_route модно задавать permission_classes и authentification_classes, о них в документации
Чтобы проверить свой доступ Django для Document (созданный в Document.Meta) начинается цирк,
т.к. встроенного механизма проверки такого доступа нет и надо либо делать свой MyCustomDocumentPersmissionClass унаследованный от BasePermission
с реализацией has_permission и задавать его в permission_classes, либо делать проверку в коде метода make_some request.user.has_perm,
либо адаптировать встроенный декторатор Django persmission_required (адаптировать надо потому, что декоратор рассчитан на function based view,
а у нас метод класса который первым аргументом принимает self, моя реализация в конце в persmission_required_action).
"""
try:
result = Document.objects.filter(type='some')
except Exception as e:
# Ошибка, можно например вернуть произвольный JSON
return Response({'result': 'error', 'msg': str(e)}, status=HTTP_500_INTERNAL_SERVER_ERROR)
# Если надо вернуть массим объектов
vs = DocumentViewSet(result, many=True) # Может быть можно сам DocumentSerializer тут использовать, но так просто работает и проблем не доставляет
return Response(vs.data)
def get_permissions(self):
# А ещё в ViewSet доступ можно проверить переопределив get_permissions ...
from rest_framework.permissions import IsAuthenticated
if self.action == 'make_some':
return [IsAuthenticated(), ]
return super().get_permissions()
# Регистрируем ViewSet в router
api_router.register('document', DocumentViewSet, base_name='document')
# В urls.py регистрируем router
urlpatterns = patterns('',
url(r'^api/', include(api_router.urls)),
)
# Собственно всё
"""
Вспомогательные вещи
"""
from functools import wraps
class classproperty(property):
def __get__(self, cls, owner):
return classmethod(self.fget).__get__(None, owner)()
def permission_code(class_or_model, permission_name):
if isinstance(class_or_model, ModelBase):
return '{0}.{1}'.format(class_or_model._meta.app_label, permission_name)
raise Exception('Не могу составить код для {0} {1}'.format(class_or_model, permission_name))
from django.contrib.auth import REDIRECT_FIELD_NAME
def user_passes_test_action(test_func, login_url=None, redirect_field_name=REDIRECT_FIELD_NAME):
def decorator(view_func):
@wraps(view_func, assigned=available_attrs(view_func))
# Тут первым параметром идёт self, а запрос уже вторым
def _wrapped_view(object, request, *args, **kwargs):
if test_func(request.user):
return view_func(object, request, *args, **kwargs)
else:
raise PermissionDenied()
return _wrapped_view
return decorator
def permission_required_action(perm, login_url=None, raise_exception=False):
def check_perms(user):
if not isinstance(perm, (list, tuple)):
perms = (perm,)
else:
perms = perm
# First check if the user has the permission (even anon users)
if user.has_perms(perms):
return True
# In case the 403 handler should be called raise the exception
if raise_exception:
raise PermissionDenied
# As the last resort, show the login form
return False
return user_passes_test_action(check_perms, login_url=login_url)