2

Newbie here, I have two models which are below

class ReceipeMaster(models.Model):
    receipe_type = models.CharField(max_length=50, choices=TYPE_OPTIONS, default=TYPE_OPTIONS[0])
    item = models.ForeignKey(Item, on_delete=models.CASCADE, related_name='receipe')
    units = models.IntegerField(default=1)
    status = models.BooleanField(default=True)

class ReceipeDetail(models.Model):
    master = models.ForeignKey(ReceipeMaster, on_delete=models.CASCADE, related_name='items')
    item_type = models.ForeignKey(Item_type, null=True, on_delete=models.PROTECT)
    item = models.ForeignKey(Item, on_delete=models.PROTECT)
    quantity = models.IntegerField()

I have a detailed view DetailView

class ReceipeDetailView(PermissionRequiredMixin, DetailView):
    permission_required = 'item_management.view_receipemaster'
    model = ReceipeMaster
    template_name = 'receipes/show.html'
    context_object_name = 'receipe'

I would like to prefetch_related or select_related on the ReceipeMaster model and as well as the ReceipeDetail model. Simply prefetch on both models.

Regards

Muhammad Sharif
  • 406
  • 6
  • 17

2 Answers2

1

To customize how a queryset is fetched in a generic view one needs to override the get_queryset method of the view. Also if one wants to use select_related on a prefetched object one should use Prefetch objects [Django docs] and specify their queryset. Since this is a DetailView and hence deals with one object you will need to override get_object instead:

from django.db.models import Prefetch
from django.http import Http404
from django.utils.translation import gettext as _

class ReceipeDetailView(PermissionRequiredMixin, DetailView):
    permission_required = 'item_management.view_receipemaster'
    model = ReceipeMaster
    template_name = 'receipes/show.html'
    context_object_name = 'receipe'
    
    def get_object(self, queryset=None):
        # Use a custom queryset if provided; this is required for subclasses
        # like DateDetailView
        if queryset is None:
            queryset = self.get_queryset()

        # Next, try looking up by primary key.
        pk = self.kwargs.get(self.pk_url_kwarg)
        slug = self.kwargs.get(self.slug_url_kwarg)
        if pk is not None:
            queryset = queryset.filter(pk=pk)

        # Next, try looking up by slug.
        if slug is not None and (pk is None or self.query_pk_and_slug):
            slug_field = self.get_slug_field()
            queryset = queryset.filter(**{slug_field: slug})

        # If none of those are defined, it's an error.
        if pk is None and slug is None:
            raise AttributeError(
                "Generic detail view %s must be called with either an object "
                "pk or a slug in the URLconf." % self.__class__.__name__
            )
        queryset = queryset.select_related('item').prefetch_related(
            Prefetch(
                'items',
                 queryset=ReceipeDetail.objects.select_related('item_type', 'item')
            )
        )
        try:
            # Get the single item from the filtered queryset
            obj = queryset.get()
        except queryset.model.DoesNotExist:
            raise Http404(_("No %(verbose_name)s found matching the query") %
                          {'verbose_name': queryset.model._meta.verbose_name})
        return obj
Abdul Aziz Barkat
  • 19,475
  • 3
  • 20
  • 33
  • Thank you very much @Abdul Aziz Barkat for your time and answer however even after adding that method still getting the same 22 queries no change – Muhammad Sharif Apr 08 '21 at 12:09
  • getting my recipe items like this in in html file `{% for receipe_detail in receipe.items.all %}` – Muhammad Sharif Apr 08 '21 at 12:12
  • while on the same topic can you also please look at this thread, https://stackoverflow.com/questions/66950747/python-django-prefetch-related-on-form-set-and-inlineformset-factory , thank you – Muhammad Sharif Apr 08 '21 at 12:14
  • @MuhammadSharif Ah I see that this is a `DetailView`, I oversaw that particular detail here. The problem is that `get_object` would call filter on the queryset given by `get_queryset`, hence our prefetching etc. would go to waste. You need to override `get_object` instead here. Check my edit, although there is plenty of extra stuff in the implementation, that is copied from the default implementation of `get_object` (handling whether kwarg to filter is pk or slug etc.). – Abdul Aziz Barkat Apr 08 '21 at 12:36
  • Totally genius, thank you very very much much appreciated for your time and effort accepted as an answer, please if you don't mind can you also look at my other thread as well, please https://stackoverflow.com/questions/66950747/python-django-prefetch-related-on-form-set-and-inlineformset-factory – Muhammad Sharif Apr 08 '21 at 12:44
1

You could override the get_queryset method entirely, without calling super():

def get_queryset(self):
    queryset = ReceipeMaster.objects.select_related('item').prefetch_related(
        Prefetch(
            'items',
            queryset=ReceipeDetail.objects.select_related('item_type', 'item'),
        )
    )
    return queryset

The get_object method will then filter the queryset in order to get the object. This won't be a problem in this case since the order of filter() and select_related() chaining isn’t important (docs).

Zel
  • 131
  • 3
  • 4