I am trying to annotate a model that includes a many2many field:
class Link(models.Model):
products = models.ManyToManyField(Product, related_name = "%(class)s_name", related_query_name = "product_link_qs", blank = True)
position = models.ForeignKey(Position, on_delete = models.CASCADE)
class Position(models.Model):
place = models.PositiveIntegerField(unique = True)
store = models.ForeignKey(Store, on_delete = models.CASCADE)
class Store(models.Model):
name = models.CharField("name", max_length = 32)
in my admin I used annotate()
to couple the information:
@admin.register(Link)
class LinkAdmin(admin.ModelAdmin):
list_display = ["product", "get_store"]
list_filter = ["position__store"]
### extend by product_link_qs related name property to make field sortable in the admin
def get_queryset(self, request):
qs = super().get_queryset(request)
return qs.annotate(storename = Product.objects.filter(product_link_qs = OuterRef("id")).values("store__name"))
@admin.display(description = "store name", ordering = "storename")
def get_store(self, obj):
return obj.storename or None
this works perfectly fine in SQLite on testing and MariaDB in production
works on SQLite in testing, but gives: django.db.utils.OperationalError: (1242, 'Subquery returns more than 1 row') on MariaDB
Likely the simplest way is to annotate it with the name of the store:
from django.db.models import F
@admin.register(Link)
class LinkAdmin(admin.ModelAdmin):
list_display = ['product', 'get_store']
list_filter = ['position__store']
### extend by product_link_qs related name property to make field sortable in the admin
def get_queryset(self, *args, **kwargs):
return (
super()
.get_queryset(*args, **kwargs)
.annotate(storename=F('products__store__name'))
.distinct()
)
@admin.display(description='store name', ordering='storename')
def get_store(self, obj):
return obj.storename or None