Home > OS >  GROUP By in Django ORM for page in Django Admin
GROUP By in Django ORM for page in Django Admin

Time:11-28

I'm not very long in Django, sorry for the probably stupid question. But after many hours of trying to solve and a huge number of searches on the Internet, I did not find a solution.

My Models:

class Offer(models.Model):
    seller = models.ForeignKey()<..>
    # other fields
class OfferViewCount(models.Model):
    offer = models.ForeignKey(Offer, verbose_name=_('Offer'), on_delete=models.CASCADE)
    user_agent = models.CharField(verbose_name=_('User Agent'), max_length=200)
    ip_address = models.CharField(verbose_name=_('IP Address'), max_length=32)
    created_date = models.DateTimeField(auto_now_add=True)

The database of the OfferViewCount model has the following data:

id;user_agent;ip_address;created_date;offer_id
24;insomnia/2022.6.0f;127.0.0.1;2022-11-18 14:14:52.501008 00;192
25;insomnia/2022.6.0z;127.0.0.1;2022-11-18 15:03:31.471366 00;192
23;insomnia/2022.6.0;127.0.0.1;2022-11-18 14:14:49.840141 00;193
28;insomnia/2022.6.0;127.0.0.1;2022-11-18 15:04:18.867051 00;195
29;insomnia/2022.6.0;127.0.0.1;2022-11-21 11:33:15.719524 00;195
30;test;127.0.1.1;2022-11-22 19:34:39 00;195

If I use the default output in Django Admin like this:

class OfferViewCountAdmin(admin.ModelAdmin):
    list_display = ('offer',)

I get this:

Offer
offer #192
offer #192
offer #193
offer #195
offer #195
offer #195

I want to get a result like this:

Offer;Views
offer #192;2
offer #193;1
offer #195;3

Simply put, I want to display one instance of each duplicate post in the admin, and display the total number of them in a custom field. In SQL it would look something like this:

SELECT offer_id, COUNT(*) AS count FROM offer_offerviewcount GROUP BY offer_id ORDER BY COUNT DESC;

I've tried many options, including overwriting get_queryset. In general, I managed to achieve the desired result like this:

class OfferViewCountAdmin(admin.ModelAdmin):
    list_display = ('offer', 'get_views')
    list_filter = ['created_date', 'offer']
    list_per_page = 20

    def get_views(self, obj):
        return OfferViewCount.objects.filter(offer=obj.offer).count()

    def get_queryset(self, request):
        qs = OfferViewCount.objects.filter(
            ~Exists(OfferViewCount.objects.filter(
                Q(offer__lt=OuterRef('offer')) | Q(offer=OuterRef('offer'), pk__lt=OuterRef('pk')),
                offer=OuterRef('offer')
            ))
        )
        
        return qs

    get_views.short_description = _('Views')

But in this case, sorting by Views does not work. If I add it explicitly via admin_order_field for get_views, I get an error because there is no such field in the database. To avoid such an error, it is necessary to fasten the overwritten annotate queriset, something like this:

qs = OfferViewCount.objects.filter(
    ~Exists(OfferViewCount.objects.filter(
        Q(offer__lt=OuterRef('offer')) | Q(offer=OuterRef('offer'), pk__lt=OuterRef('pk')),
        offer=OuterRef('offer')
    ))
).annotate(_views_count=Count('offer'))

And change get_views to:

def get_views(self, obj):
    return obj._views_count

But in this case, Count('offer') always returns 1, probably because not the entire base is analyzed there.

Actually, tell me, please, how to add a working sorting? If there is some much simpler way (without ~Exists and constructions with Q()|Q()).

CodePudding user response:

you should use the Django group by like below

def get_queryset(self, request):
    qs = OfferViewCount.objects.values("offer")
    .annotate(count=Count("offer")).distinct().order_by("count")
    return qs

CodePudding user response:

You can use below queryset for group by query

from django.db.models import Count

def get_queryset(self, request):
    qs = OfferViewCount.objects.values(
            'offer'
        ).annotate(
            offer_count=Count('id')
        ).order_by("-offer_count")
    return qs

In mysql raw query like,

SELECT 
`offer_offerviewcount`.`offer_id`, 
COUNT(`offer_offerviewcount`.`id`) AS `offer_count` 
FROM `offer_offerviewcount` 
GROUP BY `offer_offerviewcount`.`offer_id` 
ORDER BY `offer_count` DESC

But if you use admin default change_list template then this query gives you an error. Because django admin when render values in template expect list of objects in queryset and group_by query return in queryset as list of dict.

If you want to use above query, then you override change_list template and rendered data itself.

  • Related