How to create a Django queryset based on values of recursive ManyToManyField?
I have a dictionary app where words (Lemma) can be optionally composed of other words. In my models.py, this looks like:
class Lemma(models.Model):
cf = models.CharField(max_length=200) #citation form
pos = models.ForeignKey(Pos, on_delete=models.CASCADE) #part of speech
components = models.ManyToManyField("self", symmetrical=False, blank=True) #component Lemma
I want to return two query sets:
- All compound verbs: Lemma that have values in self.components and where self.pos.term="verb"
- All unique components of compound verbs that are nouns: Lemmas that are values of some other Lemma's self.component_set() and which themselves have self.pos.term="noun".
I want to use views to pass these two lists to a template.
I'm able to get queryset 1 pretty easily, but my solutions for queryset 2 are all quite convoluted. The relevant class in my views.py looks like this:
class CompVerb(generic.ListView):
model = Lemma
queryset = Lemma.objects.filter(
Q(pos__term="verb") & ~Q(components=None)
).order_by("cf") #queryset 1, compound verbs
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
nouns = self.queryset.values_list("components")
nouns = set([Lemma.objects.get(pk=l[0]) for l in nouns])
nouns= [l for l in nouns if l.pos.term == "noun"]
context["nouns"] = nouns #queryset 2, nouns that are components of compound verbs
return context
This also leaves me with a regular list for my verbs variable rather than a proper queryset where I can use the .order_by() method to sort this alphabetically list by citation form.
Is there a better approach that will return a queryset object?
You can query in reverse:
class CompVerb(generic.ListView):
model = Lemma
queryset = Lemma.objects.filter(
~Q(components=None), pos__term='verb'
).order_by(
'cf'
) # queryset 1, compound verbs
def get_context_data(self, **kwargs):
return super().get_context_data(
**kwargs,
nouns=Lemma.objects.filter(
lemma__in=self.queryset, pos__term='noun'
).order_by('cf')
)
Your queryset will however duplicate the Lemmas that many times as there are related components,you thus might want to use a .distinct() [Django-doc] to prevent this.
Note: In Django, class-based views (CBV) often have a
…Viewsuffix, to avoid a clash with the model names. Therefore you might consider renaming the view class toCompVerbView, instead of.CompVerb