Class-based-views mixins for handling related objects
pip install django-related==0.0.2
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.
Contents
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 ...
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.
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()
)None
(does no use forms).existing_form_field
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()
)existing_form_class
form (if form
is used).existing_pk_field
'pk'
.existing_slug_field
'slug'
existing_request_pk_key
'pk'
.existing_request_slug_key
existing_redirect_url
(get_existing_redirect_url()
)existing_form_name
(get_existing_form_name
)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_404_redirect_url
(get_related_404_url()
)django.http.Http404
or returning django.http.HttpResponseGone
.
Default is None
.related_404_message
(get_rlated_404_message()
)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
related_model
that contains the primary key. Defaults
to 'pk'
.related_pk_url_kwarg
'pk'
.related_slug_field
related_model
that contains the sulug field. Defaults
to 'slug'
.related_slug_url_kwarg
'slug'
.related_object_name
(get_related_object_name()
)cache_backend
(get_cache_backend
)django.core.caching.cache
by default. Any interface that you specify
must provide the same methods as the default one.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:
get_object
is forced)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_field
related_model
's class name (e.g., 'foo'
for a
model called Foo
).related_404_redirect_url
(get_related_404_url()
)django.http.Http404
or returning django.http.HttpResponseGone
.
Default is None
.related_404_message
(get_rlated_404_message()
)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
related_model
that contains the primary key. Defaults
to 'pk'
.related_pk_url_kwarg
'pk'
.related_slug_field
related_model
that contains the sulug field. Defaults
to 'slug'
.related_slug_url_kwarg
'slug'
.related_object_name
(get_related_object_name()
)integritiy_error_message
(get_integrity_error_message()
)django.contrib.messages
. This attribute
customizes the message. Default is 'Such record already exists'
.cache_backend
(get_cache_backend
)django.core.caching.cache
by default. Any interface that you specify
must provide the same methods as the default one.Please report bugs and feature requests to the Bitbucket issue tracker.