Source code for djburger.controllers

# -*- coding: utf-8 -*-
# project
from .exceptions import SubValidationError
from .utils import is_django_installed


# Django
if is_django_installed:
    from django.shortcuts import get_object_or_404
    from django.views.generic import ListView
else:
    from .mocks import DjangoListView as ListView, model_to_dict as get_object_or_404


__all__ = [
    'List', 'Info', 'Add', 'Edit', 'Delete',
    'ViewAsController',
    'pre', 'post', 'subcontroller',
]


class _ModelControllerMixin(object):
    def __init__(self, queryset=None, model=None):
        if queryset:
            self.q = queryset
        elif model:
            self.q = model._default_manager.all()
        else:
            raise ValueError("Queryset or model required.")


[docs]class List(ListView): """Controller based on Django ListView 1. Get list of objects 2. Filter list by validated data from user 3. Optional pagination :param bool only_data: return only filtered queryset if True, all context data otherwise. Use `only_data=False` with TemplateSerializerFactory. :param \**kwargs: all arguments of ListView. :return: filtered queryset. :rtype: django.db.models.query.QuerySet """ def __init__(self, only_data=True, **kwargs): """Initialize controller in rule. """ self.only_data = only_data super(List, self).__init__(**kwargs) def __call__(self, request, data, **kwargs): self.data = data return self.get(self, request, **kwargs) def get_queryset(self): q = super(List, self).get_queryset() return q.filter(**self.data) def get_context_data(self, **kwargs): context = super(List, self).get_context_data(**kwargs) # return only filtered queryset if self.only_data: return context['object_list'] return context def render_to_response(self, context, **response_kwargs): return context
[docs]class Info(_ModelControllerMixin): """Return one object from queryset 1. Return object filtered by params from URL kwargs (like `pk` or `slug`) if url-pattern have kwargs. 2. Return object from validated data if data have key `object` or data have only one key. 3. Raise exception otherwise. :param django.db.models.query.QuerySet queryset: QuerySet for retrieving object. :param django.db.models.Model model: Model for retrieving object. :return: one object from queryset or model. :rtype: django.db.models.Model :raises ValueError: if can't select params for queryset filtering. :raises django.http.Http404: if object does not exist or multiple objects returned """ def __call__(self, request, data, **kwargs): if kwargs: return get_object_or_404(self.q, **kwargs) elif 'object' in data: return data['object'] elif len(data) == 1: return data.pop() else: raise ValueError("Can't select object!")
[docs]class Add(object): """Controller for adding object with validated data. :param django.db.models.Model model: Model for adding object. :return: created object. :rtype: django.db.models.Model """ def __init__(self, model): self.model = model def __call__(self, request, data, **kwargs): return self.model._default_manager.create(**data)
[docs]class Edit(_ModelControllerMixin): """Controller for editing objects. 1. Get object of initialized model by URL's kwargs. 2. Set params from validated data. 3. Update tuple into database. :param django.db.models.query.QuerySet queryset: QuerySet for editing object. :param django.db.models.Model model: Model for editing object. :return: edited object. :rtype: django.db.models.Model :raises django.http.Http404: if object does not exist or multiple objects returned """ def __call__(self, request, data, **kwargs): obj = get_object_or_404(self.q, **kwargs) obj.__dict__.update(data) obj.save(force_update=True) return obj
[docs]class Delete(_ModelControllerMixin): """Controller for deleting objects. Delete object filtered by URL's kwargs. :param django.db.models.query.QuerySet queryset: QuerySet for deleting object. :param django.db.models.Model model: Model for deleting object. :return: count of deleted objects. :rtype: int :raises django.http.Http404: if object does not exist or multiple objects returned """ def __call__(self, request, data, **kwargs): obj = get_object_or_404(self.q, **kwargs) result = obj.delete() # hook for old Django versions if result is None: return 1 return result[0]
[docs]class ViewAsController(object): """Allow use any django view as controller. 1. For CBV with render_to_response method return context. 2. Return rendered response otherwise. :param callable view: view. :param str method: optional method which will be forced for request :return: if possible response context or content, response object otherwise. """ def __init__(self, view, method=None): self.view = self.patch_view(view) self.method = method
[docs] @staticmethod def patch_view(view): """Patch view for getting context instead of rendered response. """ if hasattr(view, 'render_to_response'): view.render_to_response = lambda context, **kwargs: context return view
def __call__(self, request, data, **kwargs): # set data to request method = self.method or request.method if method.lower() == 'get': request.GET = data else: request.POST = data # get response response = self.view(request=request, **kwargs) if hasattr(response, 'context'): return response.context elif hasattr(response, 'content'): return response.content else: return response
[docs]class pre(object): # noQA """Decorator for input data validation before subcontroller calling :param djburger.validators.bases.IValidator validator: validator for pre-validation. :param \**kwargs: kwargs for validator. :raises djburger.exceptions.SubValidationError: if pre-validation not passed """ def __init__(self, validator, **kwargs): self.validator = validator self.validator_kwargs = kwargs def __call__(self, controller): self.controller = controller return self._wrapper def _wrapper(self, data, request=None, **kwargs): validator = self.validator( data=data, request=request, **self.validator_kwargs) if not validator.is_valid(): raise SubValidationError(validator) return self.controller( data=validator.cleaned_data, request=request, **kwargs)
[docs]class post(pre): # noQA """Decorator for output data validation before subcontroller calling :param djburger.validators.bases.IValidator validator: validator for post-validation. :param \**kwargs: kwargs for validator. :raises djburger.exceptions.SubValidationError: if post-validation not passed """ def _wrapper(self, data, request=None, **kwargs): result = self.controller( data=data, request=request, **kwargs) validator = self.validator( data=result, request=request, **self.validator_kwargs) if not validator.is_valid(): raise SubValidationError(validator) return validator.cleaned_data
[docs]def subcontroller(controller, prevalidator=None, postvalidator=None): """Constructor for subcontrollers If any validation failed, immediately raise SubValidationError. :param djburger.validators.bases.IValidator prevalidator: :param callable controller: :param djburger.validators.bases.IValidator postvalidator: :raises djburger.exceptions.SubValidationError: if any validation not passed """ if prevalidator: controller = pre(prevalidator)(controller) if postvalidator: controller = post(postvalidator)(controller) return controller