Related Models in Django

Hi All,

I have a question regarding models in Django - Currently, I have 2 models, Leads and Leadentry, whereby Leadentry is related to Leads via a foreign key ID, now due to some of the functionality I’m adding to my application, it will be great if I can count the number of leadentry that are related to Leads in my models, in this case, Leadentry simply has the number of monthly payments that are expected on certain Lead, therefore, my question is how would I add a counter to Leads, such that I know if there are any entries in Leadentry corresponding to Leads?, is that possible?

Models

class Leads(models.Model):

    project_id = models.BigAutoField(primary_key=True, serialize=False)
    created_at = models.DateTimeField(auto_now_add=True)
    expected_revenue = MoneyField(decimal_places=2,max_digits=14, default_currency='USD')
    expected_licenses = models.IntegerField()
    country = CountryField(blank_label='(select_country)')
    status = models.CharField(choices=[('Open', 'Open'), ('Closed', 'Closed'), ('Canceled', 'Canceled'),
                                       ('Idle', 'Idle')
                                       ], max_length=10)
    estimated_closing_date = models.DateField()
    services = models.CharField(choices=[('Illumination Studies', 'Illumination Studies'),
                                  ('Training', 'Training'),('Survey Design Consultancy', 'Survey Design Consultancy'),
                                  ('Software License', 'Software License'),
                                  ('Software Development','Software Development')], max_length=40)
    agent = models.ForeignKey(Profile, default='agent',on_delete=models.CASCADE)
    company = models.ForeignKey(Company,on_delete=models.CASCADE)
    point_of_contact = models.ForeignKey(Client, default='agent',on_delete=models.CASCADE)
    updated_at = models.DateTimeField(auto_now=True)
    application = models.CharField(choices=[('O&G','O&G'),('Renewables','Renewables'),('Mining','Mining'),
                                         ('Other','Other'),('CSS','CSS')],
                                default='O&G',max_length=20)
    sub_category = models.CharField(choices=[('Wind','Wind'),('Geo-Thermal','Geo-Thermal'),('Solar','Solar'),
                                             ('Tidal','Tidal')], max_length=20, blank=True)

    @property
    def age_in_days(self):
        today = date.today()
        result = self.estimated_closing_date - today
        return result.days

    def __str__(self):
        return f'{self.project_id}'


class LeadEntry(models.Model):
    revenue = MoneyField(decimal_places=2,max_digits=14, default_currency='USD',blank=True)
    date = models.DateField()
    lead_id = models.ForeignKey(Leads,on_delete=models.CASCADE)
    id = models.BigAutoField(primary_key=True, serialize=False)
    probability = models.DecimalField(max_digits=2,
                                      decimal_places=1,
                                      default=0,
                                      validators=[MaxValueValidator(1.0,'The value should be less than 1.0'),
                                                  MinValueValidator(0.0,'The value should be more than 0.0')]
                                      )
    stage = models.CharField(choices=[('Lead','Lead'),('Deal','Deal')], default='Lead',max_length=10, blank=True)
    sales_order = models.CharField(
        max_length=9,
        validators=[RegexValidator(r'\d{4}.\d{4}', 'The value should be four digits, a character and four digits')],
        default='0000-0000',
        blank=True
    )
    

Can I create an entry in my LeadModel or property that can sum the number of entries for each Lead?, so if there is nothing the count will be zero and otherwise it will return the number of entries

If the related manager for LeadEntry associated with an instance of Lead named some_lead is some_lead.leadentry_set, then some_lead.leadentry_set.count() is the number of related LeadEntry for that Leads.

Or, you can annotate each Leads in a query to add a field with the counts.
e.g. Leads.objects.annotate(le_count=Count('leadentry'))

(In general, what you don’t want to do is try to maintain a field with those counts. It’s way too easy to create inconsistent data.)

Ken, Thanks for answering my question - I guess it is possible but not recommended, and better handled on the query level.

Can I use annotate as part of queryset, like this

leads_plot = Leads.objects.filter(agent_id=c,status='Open').order_by('expected_revenue').annotate(le_count=Count('leadentry'))

Absolutely! (See QuerySet API reference | Django documentation | Django)

You can also change the order of operations such that you sort by the annotated value:
leads_plot = Leads.objects.filter(agent_id=c,status='Open').annotate(le_count=Count('leadentry')).order_by('le_count')
and you can also filter by the annotated value:
leads_plot = Leads.objects.filter(agent_id=c,status='Open').annotate(le_count=Count('leadentry')).order_by('le_count').filter(le_count__gt=5)