1

I've optimized get_queryset in Django ModelAdmin list view with:

  • .only('field1', 'field2',)
  • .select_related('rel1',)

These optimizations are not the same that I need for get_object change view :

  • .only('field1', 'field2', 'field3',)
  • .select_related('rel1__rel2', 'rel3',)
  • .prefetch_related(...)

Thing is, get_queryset and get_object are coupled:

class BaseModelAdmin(...):

  ...

  def get_object(self, request, object_id, from_field=None):
      """
      Returns an instance matching the field and value provided, the primary
      key is used if no field is provided. Returns ``None`` if no match is
      found or the object_id fails validation.
      """
      queryset = self.get_queryset(request)
      model = queryset.model
      field = model._meta.pk if from_field is None else model._meta.get_field(from_field)
      try:
          object_id = field.to_python(object_id)
          return queryset.get(**{field.name: object_id})
      except (model.DoesNotExist, ValidationError, ValueError):
          return None

How can I override get_queryset without having to copy/paste all the boiler plate above?

Community
  • 1
  • 1
Benjamin Toueg
  • 10,511
  • 7
  • 48
  • 79
  • It is unclear what exactly are you trying to achieve here. Instead of listing your possible solutions to problem, it would be much better if you stated the problem itself. What exactly is your optimization in `get_queryset`? What is the optimization you want to apply in `get_object`? – Назар Топольський Dec 28 '16 at 10:25
  • @НазарТопольський I've updated the question – Benjamin Toueg Dec 28 '16 at 14:50

2 Answers2

2

I've come up with this solution, which seems a good compromise:

def get_queryset(self, request):
    queryset = super().get_queryset(request)
    if request.resolver_match.url_name.endswith('_change'):
        # admin change view
        queryset = queryset.only(
            'iso_code',
            *get_translation_fields('name'),
            'official_languages',
            'active',
            'currency_code',
            'currency_symbol',
        )
    else:
        # admin change list
        queryset = queryset.only(
            'iso_code',
            'name',
            'active',
        )
    return queryset
Benjamin Toueg
  • 10,511
  • 7
  • 48
  • 79
1

Your use case isn't very common, so I don't think there is any sensible way to achieve what you need without rewriting get_object. I would do something like this:

# A parameter with default False value shouldn't screw things up
def get_queryset(request, for_object=False):
    qs = super(YourModelAdmin, self).get_queryset(request)
    if for_object:
        qs = qs.select_related('rel1__rel2', 'rel3')
        qs = .prefetch_related('rel4', 'rel5')
        return qs.only('field1', 'field2', 'field3')
    else:
        qs = qs.select_related('rel1')
        return qs.only('field1', 'field2')

def get_object(self, request, object_id, from_field=None):
    # Don't forget to add new param here
    queryset = self.get_queryset_for_object(request, for_object=True)
    model = queryset.model
    field = model._meta.pk if from_field is None else model._meta.get_field(from_field)
    try:
        object_id = field.to_python(object_id)
        return queryset.get(**{field.name: object_id})
    except (model.DoesNotExist, ValidationError, ValueError):
        return None
  • I wouldn't say my use case is not very common. It's optimization 101 to me. – Benjamin Toueg Dec 28 '16 at 17:03
  • 1
    I meant to say that different querysets for `get_object` and list view are not all that common. If you disagree with this statement, you could even raise an improvement ticket at code.djangoproject.com, so that it becomes somewhat easier in the future. – Назар Топольський Dec 28 '16 at 20:14