django-related

Class-based-views mixins for handling related objects


License
BSD-3-Clause
Install
pip install django-related==0.0.4

Documentation

django-related

django-related is a set of class-based-view mixins that help with common scenarios that involves related objects. The generic class-based views like CreateView or UpdateView only deal with single objects. However, for nested resources, or in cases where we might want to use an existing object if avaialble, these views lack functionality. This is what django-related provides.

Installation

Installation can be done using pip:

pip install django-related

There is no need to add related to installed apps. It can be used directly:

from related.views import RedirectOnExistingMixin, CreateWithRelatedMixin
...

GetExistingMixin

This mixin is deprecated. It is currently just an alias for RedirectOnExistingMixin to provide backwards compatibility. It will be removed because of the confusing name.

Only the class name has changed. All property and method names are left intact.

RedirectOnExistingMixin

This mixin is used when an attempt to create an object that already exists in the database should result in a redirect. If the object does not exist in the database, it will not do anything.

Basic usage:

from related.views import RedirectOnExistingMixin
from django.views.gneric import CreateView

from models import Foo

class MyView(RedirectOnExistingMixin, CreateView):
    model = Foo
    existing_redirect_url = '/bar'

With the above view, if we submit a form that contains a pk or slug field, and the Foo object with matching pk or slug field exists, user will be redirected to /bar path, and the model form for Foo will not even be processed. The redirect path can be customized using the options discussed further below.

Note that this mixin _will_ result in extra database queries to determine whether an object exists.

The view can be further customized using the following properties (and matching methods):

existing_form_class (get_existing_form_class())
Uses the specified form to process the request rather than request parameters. Default is None (does no use forms).
existing_form_field
Form field that contains data about existence of the object. Note that this field does not need to evaluate to an actual object. Any non-False value will be treated as signifying object's existence. The most common use case is to use a ModelChoiceField or some field that reads the database in advance, and provides a choice of values that are only available when objects exist.
existing_form_initial (get_existing_form_initial())
Dictionary of initial values for the existing_form_class form (if form is used).
existing_pk_field
The model field that contains the primary key. Default is 'pk'.
existing_slug_field
The model field that contains the slug. Default is 'slug'
existing_request_pk_key
The request parameter that represents the primary key. Default is 'pk'.
existing_request_slug_key
The request parameter that represents the slug. Note that if primary key is specified (it is by default), and it is passed in request, slug will not be looked up.
existing_redirect_url (get_existing_redirect_url())
Required attribute. The URL that client will be redirected to if the object exists.
existing_form_name (get_existing_form_name)
Customizes the name of the context object containing the form.

RelatedObjectMixin

This mixin is a generic related object pre-fetching mixin. Before resorting to using this mixin, let's first discuss an approach that works well without the overhead of RelatedObjectMixin, in cases where you are only fetching the related object, but doing nothing else. Consider this URL pattern:

/posts/(?P<pk>\d+)/comments

(Yes, there is a whole comment system included in Django, so, sorry for the corny example.) In the above case You would normally use a list view for the comments. But you might also want to fetch the post object as well. There is no need to use RelatedObjectMixin in this particular case, because you can combine Django's built-in SingleObjectMixin with ListView to achieve what you want.

Another case where you do not want to use this mixin is if you can fetch the related object using Django's DB API. For example, if you have a book object, you can probably get the related author as book.author.

This mixin is useful only if you need two physically unrelated objects (like using two SingleObjectMixin mixins in a view). If there is no actual relationship between the two objects via a foreign key, then you should use this mixin.

For users of the previous versions of django-related, it has to be noted that this mixin is simply ripped out of what used to be a monolithic CreateWithRelatedMixin. It therefore behaves more or less the same as that mixin.

Here is an example:

from related import RelatedObjectMixin
from django.views.detail import SingleObjectMixin
from django.views import FormView

from cards.models import Card
from cards.forms import MatchCardsForm

# view for `/match/(?P<first_card_pk>\d+)/(?<second_card_pk>\d+)/`

class ViewAttachment(RelatedObjectMixin, SingleObjectMixin, FormView):
    model = Card
    related_model = Card
    pk_url_kwarg = 'first_card_pk'
    related_pk_url_kwarg = 'second_card_pk'
    form_class = MatchCardsForm
    template_name = 'attachment.html'
    success_url = '/foo'

    def get_initial(self):
        return {
            'first_card': self.object.pk,
            'second_card': self.related_object.pk,
        }

    def form_valid(self, form):
        # Do something with the form, etc

Note that naming of properties is basically cloned from the SingleObjectMixin, with a related_ prefix. In most cases, you can guess the properties you need to set if you know what they are called in SingleObjectMixin.

RelatedObjectMixin is currently limited to fetching only one object, just like SingleObjectMixin, and is therefore not suitable for complex nested structures. Again, using Django's DB API would be more reasonable in many of those cases.

The view can be customized using the following attributes (and matching methods):

related_model
Related model under which the current model is nested. This attribute is required.
related_404_redirect_url (get_related_404_url())
If specified, the view will redirect instead of raising django.http.Http404 or returning django.http.HttpResponseGone. Default is None.
related_404_message (get_rlated_404_message())
If related_404_redirect_url is used, the django.contrib.messages is used to display an error message. This attribute is used to customize this message. Default is '%s does not exist' where '%s' will evaluate to the related_model's verbose name.
related_pk_field
The field on the related_model that contains the primary key. Defaults to 'pk'.
related_pk_url_kwarg
The URL parameter that contains the primary key. Defaults to 'pk'.
related_slug_field
The field on the related_model that contains the sulug field. Defaults to 'slug'.
related_slug_url_kwarg
The URL parameter that contains the slug field. Defaults to 'slug'.
related_object_name (get_related_object_name())
Customizes name of the context object that contains the related object.
cache_backend (get_cache_backend)
Specifies the object that implements the caching methods. This object is django.core.caching.cache by default. Any interface that you specify must provide the same methods as the default one.

CreateWithRelatedMixin

This mixin is used when we are dealing with a CreateView for a nested resource. The main assumption is that higher levels of the path contains a slug or pk that points to the related model's object.

As discussed in the RelatedObjectMixin section, this mixin is based on it, so the same customization options are available.

The key difference between normal CreateView (which can be persuaded to give you the related object using the queryset attribute and get_object method) and this mixin, lies in the form processing. This mixin does two things differently:

  1. It ensures that the related object exists (behavior similar to get_object is forced)
  2. It attaches the related object to the appropriate field in the submitted form.

Here is an example:

from related import CreateWithRelatedMixin
from django.views import CreateView

from models import Attachment, Post
from forms import CustomAttachmentModelForm


# View for `/posts/(?P<slug>[\w-]+)/attachments`

class AttachmentCreateView(CreateWithRelatedMixin, CreateView):
    model = Attachment
    form_class = CustomAttachmentModelForm
    related_model = Post

With the above setup, the django.http.Http404 is raised if GET request is made to this view with the slug in the URL that points to a non-existent post. If POST request is made to the same URL, django.http.HttpResponseGone (410) is returned if post does not exist. Otherwise, the CustomAttachmentModelForm is processed, and the Post object that was found based on the slug will be added to post field of the object resulting from the form processing.

The view can be customized using the following attributes (and matching methods):

related_model
Related model under which the current model is nested. This attribute is required.
related_field
Field on the current model that must point to the related object. By default, lower-cased related_model's class name (e.g., 'foo' for a model called Foo).
related_404_redirect_url (get_related_404_url())
If specified, the view will redirect instead of raising django.http.Http404 or returning django.http.HttpResponseGone. Default is None.
related_404_message (get_rlated_404_message())
If related_404_redirect_url is used, the django.contrib.messages is used to display an error message. This attribute is used to customize this message. Default is '%s does not exist' where '%s' will evaluate to the related_model's verbose name.
related_pk_field
The field on the related_model that contains the primary key. Defaults to 'pk'.
related_pk_url_kwarg
The URL parameter that contains the primary key. Defaults to 'pk'.
related_slug_field
The field on the related_model that contains the sulug field. Defaults to 'slug'.
related_slug_url_kwarg
The URL parameter that contains the slug field. Defaults to 'slug'.
related_object_name (get_related_object_name())
Customizes name of the context object that contains the related object.
integritiy_error_message (get_integrity_error_message())
If there is an integrity error saving the object pointing to the related object, the view will rerender the form, but will also add an error message to the response object using django.contrib.messages. This attribute customizes the message. Default is 'Such record already exists'.
cache_backend (get_cache_backend)
Specifies the object that implements the caching methods. This object is django.core.caching.cache by default. Any interface that you specify must provide the same methods as the default one.

Reporting bugs

Please report bugs and feature requests to the Bitbucket issue tracker.