django admin list_display с подсчетом комментариев - PullRequest
0 голосов
/ 12 декабря 2018

Я хотел бы добавить поле «количество аннотаций» в list_display в Django Admin.

models.py

class Log(models.Model):
    ...
    ip_address = models.GenericIPAddressField(
        verbose_name=_('IP address'),
        db_index=True,
    )
    ...

admin.py

class LogAdmin(admin.ModelAdmin):
    list_display = (..., 'ip_address', 'ip_address_count', ...)

    def ip_address_count(self, instance):
        return models.Log.objects \
            .filter(ip_address=instance.ip_address) \
            .count()

    ip_address_count.short_description = _('IP Address Count')

Он отлично работает с SQL-запросами "LOTS OF".

Я хотел бы улучшить свой admin.py следующим образом:

class Log(models.Model):
    ...

    def get_queryset(self, request):
        qs = super(LogAdmin, self).get_queryset(request)
        qs = qs.values('ip_address') \
               .annotate(ip_address_count=Count('ip_address'))
        return qs

    def ip_address_count(self, instance):
        return instance.ip_address_count

Однако я столкнулся с сообщениями об ошибках:

'dict' object has no attribute '_meta'

Кажется, я нашел причину возникновения ошибки:

Администратор Django, не может группировать по: Исключение Значение: объект dict имеетбез атрибута '_meta'

Однако, это не помогает мне решить проблему.

Спасибо за ваши ответы.

Редактировать: Работает неправильно, еслиЯ не добавляю .values('ip_address).

Это не group by ip_address.Это group by все поля group by field1, field2, ip_address, ... Следовательно, это приводит к "1" во всех случаях.

1 Ответ

0 голосов
/ 13 декабря 2018

Это потому, что когда вы применяете values к queryset, он возвращает список словаря, тогда как администратор ожидает набор запросов objects.

Я был в этой ситуации раньше, поэтому я сделал что-то вроде этого:

def changelist_view(self, request, extra_context=None):
    response = super().changelist_view(request, extra_context)
    try:
        # this is the final queryset to be rendered on the page after pagination.
        _cl = response.context_data['cl']
        qs = _cl.result_list._clone()
        # since in my case mysql 5.5 does'nt support subquery with LIMIT
        # fetch all the ips on that page
        ips = list(set([obj.ip_address for obj in qs]))
        result_qs = models.Log.objects.values('ip_address') \
                .filter(ip_address__in=ips) \
                .annotate(ip_address_count=Count('ip_address'))
        result = {_r['ip_address']: _r['ip_address_count'] for _r in result_qs}
        setattr(self, '_ip_addr_count', result)
    except:
        pass
    return response

def ip_address_count(self, instance):
    # fetch the count from the dict we set above
    return self._ip_addr_count.get(instance.ip_address)

Следовательно, вы видите, что я сделал, извлеките ips из окончательного набора запросов и затем используйте его длязапросить количество.Делая это, вы будете запрашивать базу данных только один раз на страницу.

Я надеюсь, что вы получите основную идею. Пожалуйста, подходите в соответствии с вашими потребностями.

...