Source code for django_sorcery.viewsets.mixins

"""Django REST Framework like viewset mixins for common model sqlalchemy
actions."""

from django.core.exceptions import ImproperlyConfigured
from django.http import HttpResponseRedirect
from django.views.generic.edit import FormMixin

from ..db import meta
from ..forms import modelform_factory
from ..views.base import BaseMultipleObjectMixin, BaseSingleObjectMixin


[docs]class ListModelMixin(BaseMultipleObjectMixin): """A mixin for displaying a list of objects. When used with router, it will map the following operations to actions on the viewset ====== ======================== =============== ====================== Method Path Action Route Name ====== ======================== =============== ====================== GET / list <resource name>-list ====== ======================== =============== ====================== """
[docs] def list(self, request, *args, **kwargs): """List action for displaying a list of objects.""" self.object_list = self.get_queryset() context = self.get_list_context_data() return self.render_to_response(context)
[docs] def get_list_context_object_name(self, object_list): """Get the name to use for the object.""" model = self.get_model() return "%s_list" % model.__name__.lower()
[docs] def get_list_context_data(self, **kwargs): """Returns context data for list action.""" queryset = self.object_list page_size = self.get_paginate_by(queryset) context_object_name = self.get_list_context_object_name(queryset) if page_size: paginator, page, queryset, is_paginated = self.paginate_queryset(queryset, page_size) context = {"paginator": paginator, "page_obj": page, "is_paginated": is_paginated, "object_list": queryset} else: context = {"paginator": None, "page_obj": None, "is_paginated": False, "object_list": queryset} if context_object_name is not None: context[context_object_name] = queryset context.update(kwargs) return super().get_context_data(**context)
[docs]class RetrieveModelMixin(BaseSingleObjectMixin): """A mixin for displaying a single object. When used with router, it will map the following operations to actions on the viewset ====== ======================== =============== ====================== Method Path Action Route Name ====== ======================== =============== ====================== GET /<pk>/ retrieve <resource name>-detail ====== ======================== =============== ====================== """
[docs] def retrieve(self, request, *args, **kwargs): """List action for displaying a single object.""" self.object = self.get_object() context = self.get_detail_context_data(object=self.object) return self.render_to_response(context)
[docs] def get_url_kwargs(self, obj): info = meta.model_info(type(obj)) return {key: getattr(obj, key) for key in info.primary_keys}
[docs] def get_detail_context_object_name(self, obj): """Get the name to use for the object.""" model = self.get_model() return model.__name__.lower()
[docs] def get_detail_context_data(self, **kwargs): """Returns detail context data for template rendering.""" context = {} if self.object: context["object"] = self.object context_object_name = self.get_detail_context_object_name(self.object) if context_object_name: context[context_object_name] = self.object context.update(kwargs) return super().get_context_data(**context)
[docs]class ModelFormMixin(FormMixin, RetrieveModelMixin): """Common mixin for handling sqlalchemy model forms in viewsets.""" fields = None form_class = None success_url = None
[docs] def get_form_class(self): """Returns the form class to be used.""" if self.fields is not None and self.form_class: raise ImproperlyConfigured("Specifying both 'fields' and 'form_class' is not permitted.") if self.form_class: return self.form_class model = self.get_model() return modelform_factory(model, fields=self.fields, session=self.get_session())
[docs] def get_form_kwargs(self): """Returns the keyword arguments for instantiating the form.""" kwargs = super().get_form_kwargs() if hasattr(self, "object"): kwargs.update({"instance": self.object}) kwargs["session"] = self.get_session() return kwargs
[docs] def get_success_url(self): """Return the URL to redirect to after processing a valid form.""" if self.success_url: return self.success_url.format(**vars(self.object)) raise ImproperlyConfigured( "No URL to redirect to. Either provide a url or override this function to return a url" )
[docs] def process_form(self, form): """Checks if form is valid and processes accordingly.""" if form.is_valid(): return self.form_valid(form) return self.form_invalid(form)
[docs] def form_valid(self, form): """Processes a valid form.""" self.object = form.save() return super().form_valid(form)
[docs] def form_invalid(self, form): """Handles invalid form.""" return self.render_to_response(self.get_form_context_data(form=form))
[docs] def get_form_context_data(self, **kwargs): """Return form context data for template rendering.""" if "form" not in kwargs: kwargs["form"] = self.get_form() return self.get_detail_context_data(**kwargs)
[docs]class CreateModelMixin(ModelFormMixin): """A mixin for supporting creating objects. When used with router, it will map the following operations to actions on the viewset ====== ======================== =============== ====================== Method Path Action Route Name ====== ======================== =============== ====================== POST / create <resource name>-list GET /new/ new <resource name>-new ====== ======================== =============== ====================== """
[docs] def new(self, request, *args, **kwargs): """New action for displaying a form for creating an object.""" return self.render_to_response(self.get_create_context_data())
[docs] def create(self, request, *args, **kwargs): """Create action for creating an object.""" form = self.get_form() return self.process_form(form)
[docs] def get_create_context_data(self, **kwargs): """Returns new context data for template rendering.""" return self.get_form_context_data(**kwargs)
[docs]class UpdateModelMixin(ModelFormMixin): """A mixin for supporting updating objects. When used with router, it will map the following operations to actions on the viewset ====== ======================== =============== ====================== Method Path Action Route Name ====== ======================== =============== ====================== GET /<pk>/edit/ edit <resource name>-edit POST /<pk>/ update <resource name>-detail PUT /<pk>/ update <resource name>-detail PATCH /<pk>/ update <resource name>-detail ====== ======================== =============== ====================== """
[docs] def edit(self, request, *args, **kwargs): """Edit action for displaying a form for editing an object.""" self.object = self.get_object() return self.render_to_response(self.get_update_context_data())
[docs] def update(self, request, *args, **kwargs): """Update action for updating an object.""" self.object = self.get_object() form = self.get_form() return self.process_form(form)
[docs] def get_update_context_data(self, **kwargs): """Returns edit context data for template rendering.""" return self.get_form_context_data(**kwargs)
[docs]class DeleteModelMixin(RetrieveModelMixin): """A mixin for supporting deleting objects. When used with router, it will map the following operations to actions on the viewset ====== ======================== =============== ====================== Method Path Action Route Name ====== ======================== =============== ====================== GET /<pk>/delete/ confirm_destoy <resource name>-delete POST /<pk>/delete/ destroy <resource name>-delete DELETE /<pk>/ destroy <resource name>-detail ====== ======================== =============== ====================== """ destroy_success_url = None
[docs] def confirm_destroy(self, request, *args, **kwargs): """Confirm_destory action for displaying deletion confirmation for an object.""" self.object = self.get_object() return self.render_to_response(self.get_destroy_context_data())
[docs] def destroy(self, request, *args, **kwargs): """Destroy action for deletion of an object.""" self.object = self.get_object() self.perform_destoy(self.object) return HttpResponseRedirect(self.get_destroy_success_url())
[docs] def perform_destoy(self, obj): """Performs the deletion operation.""" session = self.get_session() session.delete(obj) session.flush()
[docs] def get_destroy_context_data(self, **kwargs): """Returns destory context data for rendering deletion confirmation page.""" return self.get_detail_context_data(**kwargs)
[docs] def get_destroy_success_url(self): """Return the url to redirect to after successful deletion of an object.""" if self.destroy_success_url: return self.destroy_success_url.format(**vars(self.object)) raise ImproperlyConfigured( "No URL to redirect to. Either provide a url or override this function to return a url" )