Login

RelatedMixin for Details and Updates with Related Object Lists

Author:
christhekeele
Posted:
May 22, 2012
Language:
Python
Version:
1.3
Tags:
mixin model-filtering class-based-generic-view
Score:
2 (after 2 ratings)

Code for a RelatedMixin I whipped up, useful in instances where you wish to expose details of a single object, including a related group of owned objects, in the same view. Works well with Django's generic DetailView and UpdateView, or any subclass of SingleObjectMixin.

It's a little cleaner than overriding get_context_data differently for every model you want to expose, uses only('id') on querysets it doesn't need anything but relational data from, and makes pulling ownership out of distantly related objects much easier.

Supports simple nested hierarchies of models, ie:

  • View a company and all people belonging to it Detail(Company < People)

  • Edit a company and all computers belonging to its members Update(Company < People < Computers).

Tested with non-generic One-To-Many and reverse object_sets only.

Just provide an OrderedDict called related_chain in your DetailRelatedView that describes the progression of querysets to follow, in the format:

model=Foo,
relation_chain=OrderedDict([
    ('foreign_key_from_foo_to_bar',Bar.objects.all()),
    ('foreign_key_from_baz_to_bar',Baz.objects.all())
])

It also takes two optional attributes:

context_list_name="baz_list"

which provides an alias for the final related object_list (default=related_list), and

keep_intermediaries=True

which, if providing a list deeper than one relation, also passes any intermediary related lists into the context, named after the connecting foreign key, like bar_list (default=False).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
### View implementation in a views.py
class RelatedMixin(object):
    relation_chain = None
    context_list_name = 'related_list'
    keep_intermediaries = False
    def get_context_data(self, **kwargs):
        context = super(RelatedMixin, self).get_context_data(**kwargs)
        assert hasattr(self, 'object'), "RelatedMixin must be used with a view containing an instance of a model named 'object',\nnormally a subclass of SingleObjectMixin."
        assert hasattr(self, 'relation_chain'), "RelatedMixin must supply an OrderedDict named 'relation_chain' of fk/queryset tuples honing in on the desired object_list\n(refer to http://djangosnippets.org/snippets/2756/)"
        ids = [self.object.id]
        current_qs = not self.keep_intermediaries
        for fk, qs in self.relation_chain.iteritems():
            qs = qs.filter(**{fk+"__in":ids})
            ids = qs.only('id')
            if self.keep_intermediaries:
                if current_qs:
                    context[fk+"_list"] = current_qs
                current_qs = qs
        context['object_list'] = context[self.context_list_name] = qs
        return context


### Example mixin application
from mixins.views import RelatedMixin
from django.views.generic import DetailView, UpdateView
class DetailRelatedView(RelatedMixin,DetailView):
    pass
class UpdateRelatedView(RelatedMixin,UpdateView):
    pass

### Example usage in a urls.py
from django.conf.urls import patterns, url
from extra.views import DetailRelatedView
from collections import OrderedDict

from organization.models import Company, People, Computers

urlpatterns = patterns('organization.views',

    ## One level deep
    ## yields 'object'='company' and 'object_list'='related_list' in context
    url(r'^companies/(?P<pk>\d+)/people/$',
        ShowRelatedView.as_view(
            template_name="company/related/people.html",
            model=Company,
            relation_chain=OrderedDict([('company',People.objects.all())])
        ),
        name='people_for_company'
    ),

    ## Two levels deep, non-standard fk on Computers, extra context variable
    ## yields 'object'='company', and 'object_list'='computer_list' in context
    url(r'^companies/(?P<pk>\d+)/computers/$',
        ShowRelatedView.as_view(
            template_name="company/related/computers.html",
            model=Company,
            context_object_name='company',
            relation_chain=OrderedDict([ ('company',People.objects.all()) , ('owner',Computer.objects.all()) ])
            context_list_name='computer_list',
        ),
        name='computers_for_company'
    ),

    ## Three levels deep, keep intermediary object lists, don't use alias for final 'object_list'
    ## yields 'object'='company', 'owner_list', 'computer_list', and 'object_list' (of Software objects) in context
    url(r'^companies/(?P<pk>\d+)/software_downloads/$',
        ShowRelatedView.as_view(
            template_name="company/related/software.html",
            model=Company,
            context_object_name='company',
            relation_chain=OrderedDict([ ('company',People.objects.all()) , ('owner',Computer.objects.all()) , ('computer',Software.objects.all()) ])
            context_list_name=None,
            keep_intermediaries = True,
        ),
        name='software_downloads_for_company'
    ),
)

More like this

  1. Generic views with row-level permission handling by mwicat 4 years, 2 months ago
  2. Generic object_detail view filterable by multiple url values by jlorich 4 years, 1 month ago
  3. django_bulk_save.py - defer saving of django models to bulk SQL commits by preetkukreti 5 years, 4 months ago
  4. UTC DateTime field by ludo 7 years, 10 months ago
  5. Dump a model instance and related objects as a Python data structure by akaihola 3 years, 5 months ago

Comments

Please login first before commenting.