"""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 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 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"
)