Django ORM, how to "rebase" a QuerySet to a related model

Question:

With a database model described in the simplified toy example below, we are trying to get a list of EAV attributes used for a specific Product.

The sample source code in the Actions section below serves the purpose; however, we feel the statement is overly verbose: We only need columns of the template_attribute table, but the values arguments need to maintain a fully qualified path starting from the original Product model. See the code below:

# Getting the `id` columns from multiple-orders of related models:
attribute_set = template. values(
  "id",                              # the base model, Product
  "template__id",                    # the related model, Template
  "template__templateattribute__id"  # the second related model, TemplateAttribute
)

So, we wonder if there is a way to refer to the columns directly from the containing model, e.g. templateattribute__id, or even better id, instead of template__templateattribute__id.

We are new to the Django ORM and appreciate any hints or suggestions.

Actions:

template = Product.active_objects.filter(id='xxx').select_related('template')
attribute_set = template. values("id", "template__id", "template__templateattribute__id")

for i, attr in enumerate(attribute_set):
    print("{:03}: {}".format(i, attr))
# Output:
# 000: {'id': xxx, 'template__id': xxxx, 'template__templateattribute__id': xxxxx}
# 001: {'id': xxx, 'template__id': xxxx, 'template__templateattribute__id': xxxxx}
# 002: {'id': xxx, 'template__id': xxxx, 'template__templateattribute__id': xxxxx}
# ...

The models:

# Simplified toy example

class Product(models.Model):
    product_template = models.ForeignKey(Template)
    sku = models.CharField(max_length=100)
    ...

class Template(models.Model):
    base_sku = models.CharField(max_length=100)
    ...

class TemplateAttribute(models.Model):
    product_template = models.ForeignKey(Template)
    attribute = models.ForeignKey(eav_models.Attribute)
    ...

# From the open-source Django EAV library
# imported as `eav_models`
# 
class Attribute(models.Model):
    name = models.CharField(_(u"name"), max_length=100,
                            help_text=_(u"User-friendly attribute name"))
    ...
    slug = EavSlugField(_(u"slug"), max_length=50, db_index=True,
                          help_text=_(u"Short unique attribute label"))
    ...
Asked By: James

||

Answers:

Perhaps using a Manager with annotations or aliases could help?

You could try to add more magic by trying to dynamically add an annotation for each key during the manager’s construction, but really at that point you are writing code that should have been in the EAV itself.

I would warn you, having attribute names and their values in a table instead of just model fields (and columns in the DB) will be an uphill battle, and already you are finding areas where your library isn’t handling things for you.

from django.db import models
from django.db.models import F


class ProductManager(models.Manager):
    def get_queryset(self):
        qs = super().get_queryset() 
            .annotate(
            my_attribute_key=F('template__templateattribute__id')
        )
        return qs
Answered By: Bradleo

In addition, we also figured out a workaround by defining a base path:

base = "template__templateattribute"

So, instead of

attribute_set = template. Values("template__templateattribute__id")

, we can do the following:

attribute_set = template. Values(base+"__id")

This is just an informal workaround, though. The better way is still to use a Manager class.

Answered By: James
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.