Search code examples
djangodjango-views

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:

  1. All compound verbs: Lemma that have values in self.components and where self.pos.term="verb"
  2. 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?


Solution

  • 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 …View suffix, to avoid a clash with the model names. Therefore you might consider renaming the view class to CompVerbView, instead of CompVerb.