Search code examples
djangofilterdjango-rest-frameworkormm2m

[Django][DRF] How to get UNORDERED queryset from `.filter`?


first of all, I'm not a native at ENG so plz be patient my ENG skills or give me some advice.

(FYI, Django ver 3.2.9 & DRF ver 3.12.4 with postgres ver 12.7)

I'm writing API creating a post by using drf serializer. And I've been facing a problem with "the order of tags adding to the post".

  • post and tag models are connected through M2M field

which means, let's say I add tags a, b, and c on a post. The logic I use as follows.

# class Serializer
# def create(instance):
# instance : specific post instance

tags = ['t', 'a', 'g', 's']
Tag.objects.bulk_create([Tag(name = tag) for tag in tags])
instance.tags.add(*[tag for tag in Tag.objects.filter(name__in = tags)])

run this at first. tags are added in this order

TagQuerySet[Tag<'t'>, Tag<'a'>, Tag<'g'>, Tag<'s'>]

And my API takes the other create request that include the tags of the existing like,

tags = ['g', 'a', 's']

django add in the sorted order, not in the order I want like,

# tags new created instance
TagQuerySet[Tag<'a'>, Tag<'g'>, Tag<'s'>]

I know the method that adding tags following my list order using for loop

for tag in tags:
    instance.tags.add(Tag.objects.get(name=tag))

but it's not efficient.

So my question is

  • how to get queryset using .filter following my list order.
  • If there is no way for this. What is the efficient way for adding tags to post

Solution

  • You can add another column to Tag class namely priority and whenever you insert rows add the priority of the value.

    You can then order by priority something like this:

    tags = [('t', 4), ('a', 2), ('g', 1), ('s', 3)]
    Tag.objects.bulk_create([Tag(name = tag[0], priority=tag[1]) for tag in tags])
    
    Tag.objects.all().order_by('priority')
    

    also the the optimized version of this:

    for tag in tags:
        instance.tags.add(Tag.objects.get(name=tag))
    

    is this:

    tags_db = Tag.objects.filter(name__in=[tag for tag in tags])
    instance.tags.set(list(tags_db))
    

    Edit:

    Sorting using python:

    my_order_list = ['g', 'a', 's', 't']
    sorted(Tag.objects.all(), key=lambda x: my_order_list.index(x.name))
    

    Sorting using sql case expressions:

    Tag.objects.raw("
       SELECT * FROM Tag ORDER BY 
           CASE name
             WHEN 'g' THEN 1
             WHEN 'a' THEN 2
             WHEN 's' THEN 3
             ELSE 4 
           END
    ")