# -*- coding: utf-8 -*-
# built-in
from collections import namedtuple
# project
from .exceptions import StatusCodeError, SubValidationError
from .parsers import Default as _DefaultParser
from .utils import is_django_installed
# Django
if is_django_installed:
from django.utils.decorators import classonlymethod
from django.views.generic import View
else:
from .mocks import DjangoView as View
classonlymethod = classmethod
__all__ = ['rule', 'ViewBase']
_fields = ('decorators', 'parser', 'prevalidator', 'prerenderer', 'controller',
'postvalidator', 'postrenderer', 'renderer')
_aliases = ('d', 'p', 'prev', 'prer', 'c', 'postv', 'postr', 'r')
_Rule = namedtuple('Rule', _fields)
def _get_value(v, kwargs):
if type(v) is str:
return _get_value(kwargs[v], kwargs)
else:
return v
[docs]def rule(**kwargs):
"""Factory for _Rule objects
* Any kwarg can contain str which point where function can get value for kwarg.
* Some kwargs can contain None.
Example::
>>> rule(
... decorators=[login_required, csrf_exempt],
... prevalidator=SomeDjangoForm,
... prerenderer='postrenderer',
... # ^ here `prerenderer` point to `postrenderer`
... controller=some_controller,
... postvalidator=None,
... # ^ here post-validator is missed
... postrenderer='renderer',
... renderer=djburger.renderers.JSON(),
... )
:param list decorators: list of decorators.
:param callable parser: parse request body. `djburger.parsers.Default` by default.
:param djburger.validators.bases.IValidator prevalidator: validate and clean user params.
:param callable prerenderer: renderer for pre-validation errors.
:param callable controller:
:param djburger.validators.bases.IValidator postvalidator: validate and clean response.
:param callable postrenderer: renderer for post-validation errors.
:param callable renderer: renderer for successfull response.
:return: rule.
:rtype: djburger._Rule
:raises TypeError: if missed `c` or `r`.
"""
# aliases support
for field, alias in zip(_fields, _aliases):
if alias in kwargs:
kwargs[field] = kwargs[alias]
# check required kwargs
if 'controller' not in kwargs:
TypeError('Controller is required')
if 'renderer' not in kwargs:
TypeError('Renderer is required')
# set default parser
if 'parser' not in kwargs:
kwargs['parser'] = _DefaultParser()
# set 'r' as default for error-renderers
for field in ('prerenderer', 'postrenderer'):
if field not in kwargs:
kwargs[field] = 'renderer'
# set None as default for others
for field in ('decorators', 'prevalidator', 'postvalidator'):
if field not in kwargs:
kwargs[field] = None
# crosslinks support
for k, v in kwargs.items():
kwargs[k] = _get_value(v, kwargs)
# drop aliases and junk
kwargs = {field: kwargs[field] for field in _fields}
# make namedtuple
return _Rule(**kwargs)
[docs]class ViewBase(View):
"""Base views for DjBurger usage.
:param django.http.request.HttpRequest request: user request object.
:param \**kwargs: kwargs from urls.py.
:return: django response.
:rtype: django.http.HttpResponse
"""
rules = None
rule = None
default_rule = None
@classonlymethod
def as_view(cls, **initkwargs): # noQA
if not cls.rules and not cls.default_rule:
raise NotImplementedError('Please, set default_rule or rules attr')
view = super(ViewBase, cls).as_view(**initkwargs)
if getattr(cls, 'csrf_exempt', False):
view.csrf_exempt = cls.csrf_exempt
return view
def get_rule(self, method, **kwargs):
if self.rules and method in self.rules:
return self.rules[method]
if self.default_rule:
return self.default_rule
[docs] def dispatch(self, request, **kwargs):
"""Entrypoint for view
1. Select rule from rules.
2. Decorate view
3. Call `validate` method.
:param django.http.request.HttpRequest request: user request object.
:param \**kwargs: kwargs from urls.py.
:return: django response.
:rtype: django.http.HttpResponse
"""
self.method = request.method.lower()
self.rule = self.get_rule(self.method, **kwargs)
if self.rule is None:
# not allowed method
return self.http_method_not_allowed(request)
# decorators
base = self.validate_request
if self.rule.decorators:
for decorator in self.rule.decorators:
base = decorator(base)
return base(request, **kwargs)
[docs] def get_data(self, request):
"""Extract data from request by parser.
:param django.http.request.HttpRequest request: user request object.
:return: parsed data.
"""
return self.rule.parser(request)
# pre-validator
[docs] def validate_request(self, request, **kwargs):
"""
1. Call `request_valid` method if validation is successfull or missed.
2. Call `request_invalid` method otherwise.
:param django.http.request.HttpRequest request: user request object.
:param \**kwargs: kwargs from urls.py.
:return: django response.
:rtype: django.http.HttpResponse
"""
# data
data = self.get_data(request)
# no validator
if not self.rule.prevalidator:
return self.request_valid(data, **kwargs)
# validate
validator = self.rule.prevalidator(**self.get_validator_kwargs(data))
try:
is_valid = validator.is_valid()
except StatusCodeError as e:
is_valid = False
status_code = e.status_code
else:
status_code = None
if is_valid:
return self.request_valid(validator.cleaned_data, **kwargs)
else:
return self.request_invalid(validator, status_code=status_code)
# pre-validation error renderer
[docs] def request_invalid(self, validator, status_code):
"""Return result of prer (renderer for pre-validator errors)
:param djburger.validators.bases.IValidator validator: validator object with `errors` attr.
:param int status_code: status code for HTTP-response.
:return: django response.
:rtype: django.http.HttpResponse
"""
return self.rule.prerenderer(
request=self.request,
validator=validator,
status_code=status_code,
)
# controller
[docs] def request_valid(self, data, **kwargs):
"""Call controller.
Get response from controller and return result of validate_response
method.
:param data: cleaned and validated data from user.
:param \**kwargs: kwargs from urls.py.
:return: django response.
:rtype: django.http.HttpResponse
"""
# get response from controller
try:
response = self.rule.controller(self.request, data, **kwargs)
except SubValidationError as e:
validator = e.args[0]
return self.subvalidation_invalid(validator)
return self.validate_response(response)
# post-validator
[docs] def validate_response(self, response):
"""Validate response by postv (post-validator)
1. Return make_response method result if post-validator is missed.
2. Validate data by post-validator otherwise and call...
* response_valid if validation is passed
* or response_invalid otherwise.
:param response: unvalidated data from controller.
:return: django response.
:rtype: django.http.HttpResponse
"""
# no post-validator
if not self.rule.postvalidator:
return self.make_response(data=response)
# post-validation
params = self.get_validator_kwargs(response)
validator = self.rule.postvalidator(**params)
try:
is_valid = validator.is_valid()
except StatusCodeError as e:
is_valid = False
status_code = e.status_code
else:
status_code = None
if is_valid:
return self.response_valid(validator)
else:
return self.response_invalid(validator, status_code=status_code)
# renderer for errors in subcontroller's validator
[docs] def subvalidation_invalid(self, validator, status_code=200):
"""Return result of postr (renderer for post-validation errors).
:param djburger.validators.bases.IValidator validator: validator object with `errors` attr.
:param int status_code: status code for HTTP-response.
:return: django response.
:rtype: django.http.HttpResponse
"""
return self.response_invalid(validator, status_code)
# post-validation error renderer
[docs] def response_invalid(self, validator, status_code):
"""Return result of postr (renderer for post-validation errors).
:param djburger.validators.bases.IValidator validator: validator object with `errors` attr.
:param int status_code: status code for HTTP-response.
:return: django response.
:rtype: django.http.HttpResponse
"""
return self.rule.postrenderer(
request=self.request,
validator=validator,
status_code=status_code,
)
# successfull response renderer
[docs] def response_valid(self, validator):
"""Return result of make_response.
This method calls only if postv is not None.
:param djburger.validators.bases.IValidator validator: validator object with `cleaned_data` attr.
:return: django response.
:rtype: django.http.HttpResponse
"""
return self.make_response(validator.cleaned_data)
[docs] def make_response(self, data):
"""Make response by renderer
:param data: cleaned and validated data from controller.
:return: django response.
:rtype: django.http.HttpResponse
"""
return self.rule.renderer(request=self.request, data=data)
# send request and data into validator
[docs] def get_validator_kwargs(self, data):
"""Get kwargs for validators
:param data: data which will be validated.
:return: kwargs for (post)validator.
:rtype: dict
"""
try:
kwargs = super(ViewBase, self).get_form_kwargs()
except AttributeError:
kwargs = {}
kwargs['request'] = self.request
kwargs['data'] = data
return kwargs