Commits

Branko Vukelic committed 8885eb0

Extracted GetRelatedMixin from CreateWithRelatedMixin

The new mixin is extracted so it can be used separately. Documentation is
expanded to include the new mixin. For now, all GetRelatedMixin features are
used in the CreateWithRelated mixin, so existing tests should cover both. No
new tests have been written therefore.

Comments (0)

Files changed (2)

     from related.views import GetExistingMixin, CreateWithRelatedMixin
     ...
 
-
 GetExistingMixin
 ================
 
     The model field that contains the slug. Default is ``'slug'``
 
 ``existing_request_pk_key``
-    The request parameter that represents the primary key. Defalt is ``'pk'``.
+    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
 ``existing_form_name`` (``get_existing_form_name``)
     Customizes the name of the context object containing the form.
 
+GetRelatedMixin
+===============
+
+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 ``GetRelatedMixin``, 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 ``GetRelatedMixin`` 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 GetRelatedMixin
+    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(GetRelatedMixin, 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
+
+``GetRelatedMixin`` 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
 ======================
 
 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 ``GetRelatedMixin_`` 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
     ``django.http.Http404`` or returning ``django.http.HttpResponseGone``. 
     Default is ``None``.
 
-``related_404_message`` (``get_rlated_404_message``)
+``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
 ``related_slug_url_kwarg``
     The URL parameter that contains the slug field. Defaults to ``'slug'``.
 
-``related_object_name`` (``get_related_object_name``)
+``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()``)
         return super(GetExistingMixin, self).post(request, *args, **kwargs)
 
 
-class CreateWithRelatedMixin(object):
+class GetRelatedMixin(object):
     related_model = None
-    related_field = None
     related_404_redirect_url = None
     related_404_message = '%s does not exist'
     related_pk_field = 'pk'
     related_slug_url_kwarg = 'slug'
     related_object_gone_message = '<h2>Database record is missing</h2>'
     related_object_name = None
-    integrity_error_message = 'Such record already exists'
     cache_backend = cache
 
     def get_related_404_url(self):
         messages.error(self.request, self.get_related_404_message())
         return  HttpResponseRedirect(self.get_related_404_url())
 
-    def get_related_field(self):
-        return self.related_field or self.related_model.__name__.lower()
-
     def get_object_kwargs(self):
         object_kwargs = dict()
         pk = self.kwargs.get(self.related_pk_url_kwarg)
         cache_backend.set(cache_key, related_object, 30)
         return related_object
 
-    def get_integrity_error_message(self):
-        return self.integrity_error_message
-
     def related_object_not_found(self):
         related_404_url = self.get_related_404_url()
         if related_404_url:
             return HttpResponseRedirect(related_404_url)
         else:
-            if self.request.method in ('GET', 'HEAD'):
-                raise Http404()
-            else:
-                # We assume that, if user was able to GET this response,
-                # and is now sending a POST via a form, the object must
-                # have gone AWOL in between. So we do a 410 (Gone).
-                return self.related_object_gone()
+            raise Http404()
+
+    def get_cache_backend(self):
+        return self.cache_backend
+
+    def get_related_object_name(self):
+        return self.related_object_name
+
+    def get_context_data(self, *args, **kwargs):
+        context = super(GetRelatedMixin, self).get_context_data(*args, **kwargs)
+
+        related_object = self.get_related_object()
+
+        context['related_object'] = related_object
+
+        related_object_name = self.get_related_object_name()
+        if related_object_name:
+            context[related_object_name] = related_object
+
+        return context
+
+class CreateWithRelatedMixin(GetRelatedMixin, object):
+    related_field = None
+    integrity_error_message = 'Such record already exists'
+    cache_backend = cache
+
+    def get_related_field(self):
+        return self.related_field or self.related_model.__name__.lower()
+
+    def get_integrity_error_message(self):
+        return self.integrity_error_message
+
+    def related_object_not_found(self):
+        if self.request.method in ('GET', 'HEAD'):
+            return super(
+                CreateWithRelatedMixin, self
+            ).related_object_not_found()
+        else:
+            # We assume that, if user was able to GET this response,
+            # and is now sending a POST via a form, the object must
+            # have gone AWOL in between. So we do a 410 (Gone).
+            return self.related_object_gone()
 
     def get_related_object_gone_message(self):
         return self.related_object_gone_message
     def related_object_gone(self):
         return HttpResponseGone(self.get_related_object_gone_message())
 
-    def get_related_object_name(self):
-        return self.related_object_name
-
-    def get_cache_backend(self):
-        return self.cache_backend
-
     def form_valid(self, form):
         try:
             related_object = self.get_related_object()
 
         return HttpResponseRedirect(self.get_success_url())
 
-    def get_context_data(self, *args, **kwargs):
-        context = super(
-            CreateWithRelatedMixin, self
-        ).get_context_data(*args, **kwargs)
-
-        related_object = self.get_related_object()
-
-        context['related_object'] = related_object
-
-        related_object_name = self.get_related_object_name()
-        if related_object_name:
-            context[related_object_name] = related_object
-
-        return context
-
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.