CRUDView Reference

class neapolitan.views.CRUDView(**kwargs)

CRUDView is Neapolitan’s core. It provides the standard list, detail, create, edit, and delete views for a model, as well as the hooks you need to be able to customise any part of that.

Request Handlers

The core of a class-based view are the request handlers — methods that convert an HTTP request into an HTTP response. The request handlers are the essence of the Django view.

Neapolitan’s CRUDView provides handlers the standard list, detail, create, edit, and delete views for a model.

List and Detail Views

CRUDView.list(request, *args, **kwargs)

GET handler for the list view.

    def list(self, request, *args, **kwargs):
        """GET handler for the list view."""

        queryset = self.get_queryset()
        filterset = self.get_filterset(queryset)
        if filterset is not None:
            queryset = filterset.qs

        if not self.allow_empty and not queryset.exists():
            raise Http404

        paginate_by = self.get_paginate_by()
        if paginate_by is None:
            # Unpaginated response
            self.object_list = queryset
            context = self.get_context_data(
                page_obj=None,
                is_paginated=False,
                paginator=None,
                filterset=filterset,
            )
        else:
            # Paginated response
            page = self.paginate_queryset(queryset, paginate_by)
            self.object_list = page.object_list
            context = self.get_context_data(
                page_obj=page,
                is_paginated=page.has_other_pages(),
                paginator=page.paginator,
                filterset=filterset,
            )

        return self.render_to_response(context)
CRUDView.detail(request, *args, **kwargs)

GET handler for the detail view.

    def detail(self, request, *args, **kwargs):
        """GET handler for the detail view."""

        self.object = self.get_object()
        context = self.get_context_data()
        return self.render_to_response(context)

Create and Update Views

CRUDView.show_form(request, *args, **kwargs)

GET handler for the create and update form views.

    def show_form(self, request, *args, **kwargs):
        """GET handler for the create and update form views."""

        if self.role is Role.UPDATE:
            self.object = self.get_object()
        form = self.get_form(instance=self.object)
        context = self.get_context_data(form=form)
        return self.render_to_response(context)
CRUDView.process_form(request, *args, **kwargs)

POST handler for the create and update form views.

    def process_form(self, request, *args, **kwargs):
        """POST handler for the create and update form views."""

        if self.role is Role.UPDATE:
            self.object = self.get_object()
        form = self.get_form(
            data=request.POST,
            files=request.FILES,
            instance=self.object,
        )
        if form.is_valid():
            return self.form_valid(form)
        return self.form_invalid(form)

Delete View

CRUDView.confirm_delete(request, *args, **kwargs)

GET handler for the delete confirmation view.

    def confirm_delete(self, request, *args, **kwargs):
        """GET handler for the delete confirmation view."""

        self.object = self.get_object()
        context = self.get_context_data()
        return self.render_to_response(context)
CRUDView.process_deletion(request, *args, **kwargs)

POST handler for the delete confirmation view.

    def process_deletion(self, request, *args, **kwargs):
        """POST handler for the delete confirmation view."""

        self.object = self.get_object()
        self.object.delete()
        return HttpResponseRedirect(self.get_success_url())

QuerySet and object lookup

CRUDView.get_queryset()

Returns the base queryset for the view.

Either used as a list of objects to display, or as the queryset from which to perform the individual object lookup.

    def get_queryset(self):
        """
        Returns the base queryset for the view.

        Either used as a list of objects to display, or as the queryset
        from which to perform the individual object lookup.
        """
        if self.queryset is not None:
            return self.queryset._clone()

        if self.model is not None:
            return self.model._default_manager.all()

        msg = (
            "'%s' must either define 'queryset' or 'model', or override "
            + "'get_queryset()'"
        )
        raise ImproperlyConfigured(msg % self.__class__.__name__)
CRUDView.get_object()

Returns the object the view is displaying.

    def get_object(self):
        """
        Returns the object the view is displaying.
        """
        queryset = self.get_queryset()
        lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field

        try:
            lookup = {self.lookup_field: self.kwargs[lookup_url_kwarg]}
        except KeyError:
            msg = "Lookup field '%s' was not provided in view kwargs to '%s'"
            raise ImproperlyConfigured(
                msg % (lookup_url_kwarg, self.__class__.__name__)
            )

        return get_object_or_404(queryset, **lookup)

Form handling

CRUDView.get_form_class()

Returns the form class to use in this view.

    def get_form_class(self):
        """
        Returns the form class to use in this view.
        """
        if self.form_class is not None:
            return self.form_class

        if self.model is not None and self.fields is not None:
            return model_forms.modelform_factory(self.model, fields=self.fields)

        msg = (
            "'%s' must either define 'form_class' or both 'model' and "
            "'fields', or override 'get_form_class()'"
        )
        raise ImproperlyConfigured(msg % self.__class__.__name__)
CRUDView.get_form(data=None, files=None, **kwargs)

Returns a form instance.

    def get_form(self, data=None, files=None, **kwargs):
        """
        Returns a form instance.
        """
        cls = self.get_form_class()
        return cls(data=data, files=files, **kwargs)
CRUDView.form_valid(form)
    def form_valid(self, form):
        self.object = form.save()
        return HttpResponseRedirect(self.get_success_url())
CRUDView.form_invalid(form)
    def form_invalid(self, form):
        context = self.get_context_data(form=form)
        return self.render_to_response(context)
CRUDView.get_success_url()
    def get_success_url(self):
        assert self.model is not None, (
            "'%s' must define 'model' or override 'get_success_url()'"
            % self.__class__.__name__
        )
        if self.role is Role.DELETE:
            success_url = reverse(f"{self.url_base}-list")
        else:
            success_url = reverse(
                f"{self.url_base}-detail", kwargs={"pk": self.object.pk}
            )
        return success_url

Pagination and filtering

CRUDView.get_paginate_by()

Returns the size of pages to use with pagination.

    def get_paginate_by(self):
        """
        Returns the size of pages to use with pagination.
        """
        return self.paginate_by
CRUDView.get_paginator(queryset, page_size)

Returns a paginator instance.

    def get_paginator(self, queryset, page_size):
        """
        Returns a paginator instance.
        """
        return Paginator(queryset, page_size)
CRUDView.paginate_queryset(queryset, page_size)

Paginates a queryset, and returns a page object.

    def paginate_queryset(self, queryset, page_size):
        """
        Paginates a queryset, and returns a page object.
        """
        paginator = self.get_paginator(queryset, page_size)
        page_kwarg = self.kwargs.get(self.page_kwarg)
        page_query_param = self.request.GET.get(self.page_kwarg)
        page_number = page_kwarg or page_query_param or 1
        try:
            page_number = int(page_number)
        except ValueError:
            if page_number == "last":
                page_number = paginator.num_pages
            else:
                msg = "Page is not 'last', nor can it be converted to an int."
                raise Http404(_(msg))

        try:
            return paginator.page(page_number)
        except InvalidPage as exc:
            msg = "Invalid page (%s): %s"
            raise Http404(_(msg) % (page_number, str(exc)))
CRUDView.get_filterset(queryset=None)
    def get_filterset(self, queryset=None):
        filterset_class = getattr(self, "filterset_class", None)
        filterset_fields = getattr(self, "filterset_fields", None)

        if filterset_class is None and filterset_fields:
            filterset_class = filterset_factory(self.model, fields=filterset_fields)

        if filterset_class is None:
            return None

        return filterset_class(
            self.request.GET,
            queryset=queryset,
            request=self.request,
        )

Response rendering

CRUDView.get_context_object_name(is_list=False)

Returns a descriptive name to use in the context in addition to the default ‘object’/’object_list’.

    def get_context_object_name(self, is_list=False):
        """
        Returns a descriptive name to use in the context in addition to the
        default 'object'/'object_list'.
        """
        if self.context_object_name is not None:
            return self.context_object_name

        elif self.model is not None:
            fmt = "%s_list" if is_list else "%s"
            return fmt % self.model._meta.object_name.lower()

        return None
CRUDView.get_context_data(**kwargs)
    def get_context_data(self, **kwargs):
        kwargs["view"] = self
        kwargs["object_verbose_name"] = self.model._meta.verbose_name
        kwargs["object_verbose_name_plural"] = self.model._meta.verbose_name_plural
        kwargs["create_view_url"] = reverse(f"{self.url_base}-create")

        if getattr(self, "object", None) is not None:
            kwargs["object"] = self.object
            context_object_name = self.get_context_object_name()
            if context_object_name:
                kwargs[context_object_name] = self.object

        if getattr(self, "object_list", None) is not None:
            kwargs["object_list"] = self.object_list
            context_object_name = self.get_context_object_name(is_list=True)
            if context_object_name:
                kwargs[context_object_name] = self.object_list

        return kwargs
CRUDView.get_template_names()

Returns a list of template names to use when rendering the response.

If .template_name is not specified, uses the “{app_label}/{model_name}{template_name_suffix}.html” model template pattern, with the fallback to the “neapolitan/object{template_name_suffix}.html” default templates.

    def get_template_names(self):
        """
        Returns a list of template names to use when rendering the response.

        If `.template_name` is not specified, uses the
        "{app_label}/{model_name}{template_name_suffix}.html" model template
        pattern, with the fallback to the
        "neapolitan/object{template_name_suffix}.html" default templates.
        """
        if self.template_name is not None:
            return [self.template_name]

        if self.model is not None and self.template_name_suffix is not None:
            return [
                f"{self.model._meta.app_label}/"
                f"{self.model._meta.object_name.lower()}"
                f"{self.template_name_suffix}.html",
                f"neapolitan/object{self.template_name_suffix}.html",
            ]
        msg = (
            "'%s' must either define 'template_name' or 'model' and "
            "'template_name_suffix', or override 'get_template_names()'"
        )
        raise ImproperlyConfigured(msg % self.__class__.__name__)
CRUDView.render_to_response(context)

Given a context dictionary, returns an HTTP response.

    def render_to_response(self, context):
        """
        Given a context dictionary, returns an HTTP response.
        """
        return TemplateResponse(
            request=self.request, template=self.get_template_names(), context=context
        )

URLs and view callables

classmethod CRUDView.get_urls(roles=None)

Classmethod to generate URL patterns for the view.

This is the usual entry-point for routing all CRUD URLs in a single pass:

urlpatterns = [
    *BookmarkView.get_urls(),
]

Optionally, you may provide an appropriate set of roles in order to limit the handlers exposed:

urlpatterns = [
    *BookmarkView.get_urls(roles={Role.LIST, Role.DETAIL}),
]

Subclasses may wish to override get_urls() in order to encapsulate such logic.

    @classonlymethod
    def get_urls(cls, roles=None):
        """Classmethod to generate URL patterns for the view."""
        if roles is None:
            roles = iter(Role)
        return [role.get_url(cls) for role in roles]
classmethod CRUDView.as_view(role: Role, **initkwargs)

Main entry point for a request-response process.

This is the lower-level method used to manually route individual URLs.

It’s extends the Django View.as_view() method, and should be passed a an appropriate Role giving the handlers to be exposed:

path(
    "bookmarks/",
    BookmarkCRUDView.as_view(role=Role.LIST),
    name="bookmark-list",
)