Dispatch url to the child model: pass parent model object to the CBV

Hello!

I have two related models: the parent model is Event, and the child model is Material (materials are pages related to the same event).

Event has fields ‘year’ and ‘slug’.

Material has fields ‘event’ (foreign key) and ‘slug’.

My url address looks like /events/<int:year>/<slug:event>/<slug:slug>/. In this URL ‘year’ and ‘event’ relates to the parent model and ‘slug’ relates to the child model.

Possibly, that is why my CBV views (MaterialDetailView, MaterialUpdateView), based on the model Material, do not work properly. They look for model instance only by ‘slug’ and ignore ‘year’ and ‘event’, for these fields do not belong to the Material model. As the result, they may find several materials, if their slugs are identical, even if they belong to different events.

My question is, how I can tell material which event it belongs to. Guess I should somehow pass parent (event) object to the child (material) CBV.

Please give me advice, how I can solve this problem.

You will want to override the get_object method of your CBV to use a query applying those additional parameters as filters.

Thank you very much, Ken! Your advice helped me very much!

Now my CBV looks like following:

class MaterialDetailView(DetailView):
model = Material

def get_object(self, queryset=None):
    my_event = Event.objects.get(year = self.kwargs['year'], event_slug = self.kwargs['event_slug'])
    material = Material.objects.get(event = my_event, slug = self.kwargs['slug'])
    return material

Can I ask you another question? I got the material object, but I want to keep my_event object and pass it to the template (probably with the get_context_data method). I’ve written following method:

def get_context_data(self, **kwargs):
    context = super().get_context_data(**kwargs)
    material = self.get_object()
    my_event = material.event
    context['event'] = my_event
    return context

It works fine, but seems to recalculate both my_event and material (it repeats the same SQL queries as the get_object method). I fill that this breaks DRY principle. How can I avoid repeated calculations in this case?

Don’t call get_object within get_context_data - you already have a reference to material as self.object.
Also, you can avoid the reference query by not issuing two separate queries in get_object:

material = Material.objects.get(
  event__year=self.kwargs['year'], event__slug=self.kwargs['event_slug'],
  slug=self.kwargs['slug']
).select_related('event')

since get_context_data already adds object to the context, you don’t need a custom get_context_data method at all - just refer to object within your template.

Thank you very much, Ken! It looks like just what I needed! I’ll try your code and report some later.

It works! (I only had to switch “select_related” and “get”):

 def get_object(self):
    material = Material.objects.select_related('event').get(
                         event__year=self.kwargs['year'], 
                         event__slug=self.kwargs['event_slug'],
                         slug=self.kwargs['slug'])
    return material

Now there’s only one SELECT.

Thanks a lot, Ken!