Search code examples
pythondjangounit-testingdjango-testingtestcase

Django Testing using Testcase error finding the batch_name


I am performing the testing for the models.py file Django After completing the test case for few classes I am stuck at a point

First, let me share the models.py file



class Batch(models.Model):
    commodity_id = models.PositiveIntegerField(null=True, blank=True)
    commodity_variety_id = models.PositiveIntegerField(null=True, blank=True)
    farm_id = models.ForeignKey(Farm, on_delete=models.CASCADE, related_name="batches", null=True, blank=True,
                                db_column='farm_id')
    start_date = models.DateTimeField(null=True, blank=True)
    acerage = models.FloatField(verbose_name='Batch Acerage', help_text="In Acres;To change this value go to farms>crop"
                                , validators=[MaxValueValidator(1000000), MinValueValidator(0.01)])
    batch_health = models.IntegerField(validators=[MaxValueValidator(100), MinValueValidator(0)],
                                       help_text="In Percentage", default=100, null=True, blank=True)
    stage = models.CharField(max_length=100, choices=config.STAGE_CHOICES, default='germination', null=True, blank=True)
    expected_delivery_date = models.DateTimeField(null=True, blank=True)
    current_pdd = models.FloatField(null=True, blank=True)
    historic_pdd = models.FloatField(null=True, blank=True)
    current_gdd = models.FloatField(null=True, blank=True)
    historic_gdd = models.FloatField(null=True, blank=True)
    sub_farmer_id = models.PositiveIntegerField(null=True, blank=True)
    batch_status = models.CharField(max_length=100, choices=config.BATCH_STATUS, default='to_start')
    updated_at = models.DateTimeField(auto_now=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_by_id = models.PositiveIntegerField(null=True, blank=True)
    created_by_id = models.PositiveIntegerField(null=True, blank=True)
    historical_yield_per_acre = models.FloatField(verbose_name="Yield / Acre - Historical", null=True, blank=True)
    expected_produce = models.FloatField(default=0, null=True, blank=True)
    actual_produce = models.FloatField(default=0, null=True, blank=True)
    sop_adherence = models.FloatField(default=0, null=True, blank=True)
    actual_yield_per_acre = models.FloatField(default=0, null=True, blank=True)
    end_date = models.DateTimeField(null=True, blank=True)
    commodity_name = models.CharField(max_length=200, null=True, blank=True)
    batch_name = models.CharField(max_length=200, null=True, blank=True)
    batch_median_health = models.PositiveIntegerField(null=True, blank=True)
    pending_tasks = models.PositiveIntegerField(null=True, blank=True, default=0)
    history = HistoricalRecords()

    def __str__(self):
        return self.batch_name

    def save(self, *args, **kwargs):
        SOPMaster = apps.get_model('sop_management', 'SOPMaster')
        BatchSOPManagement = apps.get_model('sop_management', 'BatchSOPManagement')
        batch_sop_list = []
        if self.batch_status is 'completed':
            self.update_batch_end_date()
        self.commodity_name = self.update_commodity_name()
        self.batch_median_health = self.update_batch_median_health()
        self.batch_name = self.update_batch_name()
        super(Batch, self).save(*args, **kwargs)

    def update_commodity_name(self):
        if self.commodity_id:
            commodity_name = get_commodity_name(self.commodity_id)
            if commodity_name:
                return commodity_name[0]
        return None

    def update_batch_median_health(self):
        if self.start_date and self.expected_delivery_date:
            start_date = datetime.combine(self.start_date, datetime.min.time())
            expected_delivery_date = datetime.combine(self.expected_delivery_date, datetime.min.time())
            end_date = min([expected_delivery_date, datetime.today()]) - relativedelta(hours=5, minutes=30)
            hours_diff = int((((end_date - start_date).total_seconds()) / 3600 / 2))
            median_date = start_date + relativedelta(hours=hours_diff)
            try:
                median_crop_health = self.history.as_of(median_date).crop_health
            except:
                median_crop_health = self.batch_health
            return median_crop_health
        else:
            return None

    def update_batch_name(self):
        batch_name = "({}) {}".format(self.id, self.commodity_name)
        if self.start_date:
            batch_name += " | {}".format(self.start_date.strftime('%Y-%m-%d'))
        return batch_name

    def update_expected_delivery_date(self):
        self.expected_delivery_date = max([batch_yield.expected_delivery_date for batch_yield in
                                           self.batch_yields.all() if batch_yield.expected_delivery_date])
        self.save()

    def update_batch_status(self):
        number_of_yields = self.batch_yields.all().count()
        end_date_list = len([batch_yield for batch_yield in self.batch_yields.all() if
                             batch_yield.end_date and batch_yield.end_date.date() < datetime.today().date()])
        if number_of_yields == end_date_list:
            self.batch_status = 3
            self.save()

    def update_expected_produce(self):
        self.expected_produce += sum([batch_yields.expected_production for batch_yields in self.batch_yields.all()
                                      if not batch_yields.end_date])
        self.save()

    def update_actual_produce(self):
        for batch_yields in self.batch_yields.all():
            produce = 0
            if batch_yields.grade_a_produce:
                produce += batch_yields.grade_a_produce
            if batch_yields.grade_b_produce:
                produce += batch_yields.grade_b_produce
            if batch_yields.grade_c_rejection:
                produce += batch_yields.grade_c_rejection
            self.actual_produce += produce
            self.save()

    def update_sop_adherence(self):
        if self.batch_sop_management.all():
            total_sop = self.batch_sop_management.filter(due_datetime__lte=datetime.today())
            complete_sop = total_sop.filter(current_status=3)
            if total_sop:
                self.sop_adherence = complete_sop.count() / total_sop.count() * 100
                self.save()

    def update_actual_yield_per_acre(self):
        batch_actual_produce = 0
        for batch_yields in self.batch_yields.all():
            actual_produce = 0
            if batch_yields.end_date and batch_yields.end_date.date() <= datetime.today().date():
                if batch_yields.grade_a_produce:
                    actual_produce += batch_yields.grade_a_produce
                if batch_yields.grade_b_produce:
                    actual_produce += batch_yields.grade_b_produce
                if batch_yields.grade_c_rejection:
                    actual_produce += batch_yields.grade_c_rejection
            batch_actual_produce += actual_produce
        if self.acerage and batch_actual_produce:
            self.actual_yield_per_acre = batch_actual_produce / self.acerage
            self.save()

    def update_batch_end_date(self):
        batch_yields = self.batch_yields.order_by('-end_date')
        if batch_yields.exists():
            batch_yields_id = batch_yields.filter(end_date__isnull=False)
            if batch_yields_id.exists():
                self.end_date = batch_yields[0].end_date
            else:
                self.end_date = datetime.now()
        else:
            raise ValidationError("Batch yield end date does not exists")

    def update_pending_tasks(self):
        BatchSOPManagement = apps.get_model('sop_management', 'BatchSOPManagement')
        self.pending_tasks = BatchSOPManagement.objects.filter(batch_id=self.id, current_status=2,
                                                               due_datetime__lt=datetime.today()).count()
        self.save()


@receiver([post_save, post_delete], sender=Batch)
def update_batch_count(sender, instance, **kwargs):
    instance.farm_id.update_batch_count()


@receiver(post_save, sender=Batch)
def update_farm_health(sender, instance, **kwargs):
    instance.farm_id.update_farm_health()

I am trying to test the update_batch_name

so, my test_batch.py file looks like

from datetime import datetime
from django import apps
from django.dispatch import receiver
from django.test import TestCase
from django.db.utils import IntegrityError
from django.db.models.signals import post_save, post_delete
from farm_management.models import Farm, Device, BatchYield, Batch
from sop_management.models import BatchSOPManagement


class TestBatch(TestCase):

    def setUp(self):

        self.batch1 = Batch.objects.create(
            commodity_id="2",
            commodity_variety_id="4",
            start_date=datetime(2021, 11, 26, 14, 20, 4),
            commodity_name="Apple",
            acerage="90",
            batch_health="100",
            batch_name="(1) Apple | 2021-11-26"

        )
        self.farm1 = Farm.objects.create(
            farm_id="1"
        )
        self.farm1 = Farm.objects.create(
            farm_id="1"
        )

    def test_batch(self):
        self.assertEqual(self.batch1.commodity_id, "2")
        self.assertEqual(self.batch1.commodity_variety_id, "4")
        self.assertEqual(self.farm1.farm_id, "1")
        self.assertEqual(self.batch1.start_date, "2021-11-26 14:20:14.000000")
        self.assertEqual(self.batch1.commodity_name, "Apple")
        self.assertEqual(self.batch1.acerage, "90")
        self.assertEqual(self.batch1.batch_health, "100")
        self.assertEqual(self.batch1.batch_name, "(1) Apple | 2021-11-26")

Clearly, this is no way of doing the testing

so, the error is

  File "/home/adarsh/igrow-api/venv/lib/python3.8/site-packages/django/db/models/base.py", line 774, in save_base
    post_save.send(
  File "/home/adarsh/igrow-api/venv/lib/python3.8/site-packages/django/dispatch/dispatcher.py", line 180, in send
    return [
  File "/home/adarsh/igrow-api/venv/lib/python3.8/site-packages/django/dispatch/dispatcher.py", line 181, in <listcomp>
    (receiver, receiver(signal=self, sender=sender, **named))
  File "/home/adarsh/igrow-api/app/farm_management/models.py", line 482, in update_batch_count
    instance.farm_id.update_batch_count()
AttributeError: 'NoneType' object has no attribute 'update_batch_count'

----------------------------------------------------------------------
Ran 1 test in 0.279s

FAILED (errors=1)

So, if anyone may help to perform the testing of update_batch_name


Solution

  • Pass start_date as a datetime object not as a string. The methods on the Batch model assume that it's a datetime

    from datetime import datetime
    
    ...
    
    class TestBatch(TestCase):
    
        def setUp(self):
            self.batch1 = Batch.objects.create(
                ...
                start_date=datetime(2021, 11, 26, 14, 20, 4),
                ...
            )