{% endblock main %}
diff --git a/consumption/urls.py b/consumption/urls.py
index 386195d..72b9326 100644
--- a/consumption/urls.py
+++ b/consumption/urls.py
@@ -66,6 +66,11 @@
),
# Record-related URLs
path("record/create/", RecordCreateView.as_view(), name="record-create"),
+ path(
+ "record/create//",
+ RecordCreateView.as_view(),
+ name="record-create",
+ ),
path(
"record//",
RecordDetailView.as_view(),
diff --git a/consumption/views/record.py b/consumption/views/record.py
index f4c704c..85ec132 100644
--- a/consumption/views/record.py
+++ b/consumption/views/record.py
@@ -1,6 +1,6 @@
# SPDX-License-Identifier: MIT
-"""Views related to the :class:`~consumption.models.subject.Subject` model."""
+"""Views related to the :class:`~consumption.models.record.Record` model."""
# Django imports
from django.contrib.auth.mixins import LoginRequiredMixin
@@ -18,6 +18,15 @@ class RecordCreateView(LoginRequiredMixin, generic.CreateView):
(as of now), meaning: every (authenticated) user is able to create
:class:`~consumption.models.record.Record` objects.
+ The view supports two different operation modes:
+
+ - create a new
+ :class:`~consumption.models.record.Record` instance *from scratch*;
+ - create a new instance of
+ :class:`~consumption.models.record.Record` with a pre-defined
+ :class:`~consumption.models.resource.Resource` instance to associate
+ the new instance to.
+
After successfully creating a new instance of
:class:`~consumption.models.record.Record` the user will be redirected
to the URL as provided by
@@ -41,6 +50,37 @@ class RecordCreateView(LoginRequiredMixin, generic.CreateView):
template_name_suffix = "_create"
"""Uses the template ``templates/consumption/record_create.html``."""
+ def get_form_kwargs(self):
+ """Provide *initial values* for the form.
+
+ This method implements the two operation modes.
+
+ If a ``resource_id`` is provided as an URL parameter, the referenced
+ :class:`~consumption.models.resource.Resource` instance is provided as
+ initial value while rendering the form (template).
+ """
+ kwargs = super().get_form_kwargs()
+
+ try:
+ kwargs["initial"]["resource"] = self.kwargs["resource_id"]
+ except KeyError:
+ pass
+
+ return kwargs
+
+ def get_success_url(self): # pragma: nocover
+ """Determine the URL for redirecting after successful deletion.
+
+ This has to be done dynamically with a method instead of statically
+ with the ``success_url`` attribute, because the user should be
+ redirected to the *parent*
+ :class:`~consumption.models.resource.Resource` instance.
+ """
+ resource = self.object.resource
+ return reverse_lazy(
+ "consumption:resource-detail", kwargs={"resource_id": resource.id}
+ )
+
class RecordDetailView(generic.DetailView):
"""Provide the details of :class:`~consumption.models.record.Record` instances.
@@ -120,7 +160,7 @@ class RecordDeleteView(LoginRequiredMixin, generic.DeleteView):
pk_url_kwarg = "record_id"
"""The keyword argument as provided in :mod:`consumption.urls`."""
- def get_success_url(self):
+ def get_success_url(self): # pragma: nocover
"""Determine the URL for redirecting after successful deletion.
This has to be done dynamically with a method instead of statically
diff --git a/consumption/views/resource.py b/consumption/views/resource.py
index 3d3e15b..b20a44a 100644
--- a/consumption/views/resource.py
+++ b/consumption/views/resource.py
@@ -1,6 +1,6 @@
# SPDX-License-Identifier: MIT
-"""Views related to the :class:`~consumption.models.subject.Subject` model."""
+"""Views related to the :class:`~consumption.models.resource.Resource` model."""
# Django imports
from django.contrib.auth.mixins import LoginRequiredMixin
@@ -57,6 +57,53 @@ class ResourceDetailView(generic.DetailView):
pk_url_kwarg = "resource_id"
"""The keyword argument as provided in :mod:`consumption.urls`."""
+ def get_queryset(self):
+ """Optimize database queries.
+
+ Override to the default implementation of ``get_queryset()`` to
+ select the referenced instance of
+ :class:`~consumption.models.subject.Subject` (referenced by
+ :attr:`Resource.subject `)
+ and prefetch all associated instances of
+ :class:`~consumption.models.record.Record` (that is: all instances of
+ :class:`~consumption.models.record.Record` that reference this instance
+ of :class:`~consumption.models.resource.Resource` by their
+ :attr:`~consumption.models.record.Record.resource` attribute).
+
+ Warning
+ -------
+ This method does only modify / extend / prepare the actual database
+ queries, it does not provide the resulting objects in the rendering
+ context for the template (actually the
+ :class:`~consumption.models.subject.Subject` instance will be easily
+ accessible by using ``resource_instance.subject`` in the template).
+
+ See
+ :meth:`~consumption.views.resource.ResourceDetailView.get_context_data`
+ for that.
+ """
+ return (
+ super()
+ .get_queryset()
+ .select_related("subject")
+ .prefetch_related("record_set")
+ )
+
+ def get_context_data(self, **kwargs):
+ """Add a list of related ``Record`` instances to the context.
+
+ :meth:`~consumption.views.resource.ResourceDetailView.get_queryset`
+ will optimize the database access, but the list of
+ :class:`~consumption.models.record.Record` instances must still be
+ added to the rendering context.
+ """
+ context = super().get_context_data(**kwargs)
+
+ if self.object:
+ context["records"] = self.object.record_set.all()
+
+ return context
+
class ResourceUpdateView(LoginRequiredMixin, generic.UpdateView):
"""Generic class-based view to update :class:`~consumption.models.resource.Resource` objects.
@@ -120,7 +167,7 @@ class ResourceDeleteView(LoginRequiredMixin, generic.DeleteView):
pk_url_kwarg = "resource_id"
"""The keyword argument as provided in :mod:`consumption.urls`."""
- def get_success_url(self):
+ def get_success_url(self): # pragma: nocover
"""Determine the URL for redirecting after successful deletion.
This has to be done dynamically with a method instead of statically