user3599803
user3599803

Reputation: 7014

Django queryset subset with custom manager?

I have similar models:

class BaseInfo(models.Model):
  is_active = models.BooleanField(default=True)
  # other fields..
  class Meta:
     abstract = True

class Customer(BaseInfo):
   name = models.CharField(max_length=50)
   ## other fields..

In template I want to display a table of this model, but I want to highlight the inactive ones. So I have this fragment in template:

{% for c in customers %}
    <tr {%if not c.is_active %}class="not-active" {%endif%}>
      <td>..</td>
    </tr>
{% endfor %}

Now next to this I want to display the NUMBER of active ones. I could do this in view:

all = Customer.objects.filter(name="foo")
excludeInactive = all.filter(is_active=False)

and then pass both in the context. But I would prefer something like this in template:

{{customers.exclude_deleted.count}}

or maybe: {{customers.exclude_deleted|length}} ?

I have more models which inherit this abstract class. So I think a manager on the base class could work? I just couldn't understand how to write one..
Also, what about performance? If I do two calls to .filter() will that result in two db queries, even though the second query is a subset of an already evaluated queryset?

Upvotes: 0

Views: 835

Answers (1)

crash843
crash843

Reputation: 670

You can do it in many ways. Some of them:

  • Make all calculation in the view and pass to the context prepared data:

    customers = Customer.objects.filter(name="foo")
    active_customers_count = customers.filter(is_active=True)
    
  • Custom template tag (docs):

    tag

    from django import template
    
    register = template.Library()
    
    
    @register.assignment_tag
    def filter_qs(qs, **kwargs):
        return qs.filter(**kwargs)
    

    template

    {% filter_qs customers is_active=True as active_customers %}
    {{ active_customers|length }}            
    
  • Custom Manager with custom QuerySet (docs):

    models

    class IsActiveQuerySet(models.QuerySet):
        def active(self):
            return self.filter(is_active=True)
    
    
    class IsActiveManager(models.Manager):
        def get_queryset(self):
            return IsActiveQuerySet(self.model, using=self._db)
    
    
    class Customer(BaseInfo):
       objects = IsActiveManager()
       name = models.CharField(max_length=50)
    

    template

    {{ customers.active|length }}
    

I would strongly recommend you to go with the first option, it will be the most "Pythonic" way. Anyway django will make 2 separate sql queries for any of solution listed above.

Upvotes: 1

Related Questions