I have a link between ads and products and stores, and I want to sort them in the admin by store:
class Ad(models.Model):
products = models.ManyToManyField(Product, blank = True)
device = models.ForeignKey(Display, on_delete = models.CASCADE)
class Product(models.Model):
name = models.CharField("name", max_length = 128)
store = models.ForeignKey(Store, on_delete = models.CASCADE)
class Store(models.Model):
name = models.CharField("name", max_length = 128)
so each Ad can have 0, 1, 2, 3 ... products linked to it. Now I want to make the field "store" sortable in the Ads admin list, therefore I tried to overwrite the get_queryset method of AdAdmin but got stuck on the way:
class AdAdmin(admin.ModelAdmin):
list_display = ["get_store", ... ]
def get_queryset(self, request):
qs = super().get_queryset(request)
qs = qs.annotate(storename = ####)
return qs
@admin.display(ordering = "storename")
def get_store(self, obj):
try:
return obj.products.all().first().store.name
except AttributeError:
try:
return obj.device.store.name
except AttributeError:
return "NEW"
So I want to annoatate my queryset by storename and I want to be able to sort by store.name alphabetically on my Ad list admin page. I already found out how to annotate empty stores:
qs = qs.annotate(storename = Case(When(products_pk__isnull = True, then = Value("NEW"), default = Value("old")))
But this only got me so far ... how would I assign the value store.name to default dynamically using my given logic?
Every Ad has a condition: It can only be linked to (many) products of the same store.
As you mentioned, every Ad can only have products from one store. Then you can take the store name from any Product or distinct result for all Products.
To access the Product from the Ad model, you can add a
related_query_nameIt should work. I am unsure about the performance, maybe a better ORM can be written.
UPDATE
Distinct can be replaced with
[:1]. I forgot it yesterday, sorry. The store is FK for Product, but we need to be sure which Product instance we have from the query. That is why OuterRef is used. It means to get the Product instance for the given Ad instance by Ad.id.There are several ways to access the store names. It is probably faster with the related name. I am not that experienced but will try to explain:
Without the related_name, this ORM can be written like
storename=Ad.objects.filter(id=OuterRef("id")).values("products__store__name")[:1]This ORM creates a query like the following. It started from the Ad table, and 3 joins the access to the store table.
If we use a related name
storename=Product.objects.filter(product_ads_qs=OuterRef("id")).values("store__name")[:1]The query output has 2 joins to access the store table
Bonus: If you add the related name to Product model relation
The ORM can also be written as the following, and the query will be pretty much the same as the previous one.
storename=Store.objects.filter(store_product__product_ads_qs__id=OuterRef("id")).values("name")[:1]You can check for related naming convention.