DJWOMS1
@DJWOMS1
Веб-программист

Как обработать форму django rest framework?

Здравствуйте, бек работает на django/django rest framework, фронт на vue.js
На фронте делаю форму с полями как в модели, затем отправляю post запросом на сервер.
Подскажите как с помощью django rest/django проверить форму и сохранить в базу.
Как работать с формами в django умею, не совсем ясно как это сделать в API.
  • Вопрос задан
  • 2083 просмотра
Пригласить эксперта
Ответы на вопрос 1
@Sovetnikov
технический директор pulsprodaj.ru
Довольно мутные интерфейсы в 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)
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы