Search code examples
pythondjangodjango-modelsmanytomanyfield

ManytoManyField save and calculate


I just started self learning Python and Django as a hobby recently, and have been trying to self develop a project which helps me in my construction business. I have developed certain functions in my project which give me the result I want, but is not the most ideal in terms of coding practises. However, I am just learning from scratch and modifying my code as I learn.

However, now I am stuck (maybe because my basic concept is only not correct?). Require help on how to proceed.

So here is my models.py

class FootingQuantity(models.Model):
   member_name = models.CharField(max_length=8, unique=True)
   --more fields hidden--
   x_axis = models.FloatField()
   y_axis = models.FloatField()

class BeamQuantity(models.Model):
    work = models.ForeignKey(Work, default=1, on_delete=models.CASCADE)
    member_name = models.CharField(max_length=8, unique=True)
    location = models.ManyToManyField(FootingQuantity)
    length = models.FloatField(blank=True)
    breadth = models.FloatField()
    height = models.FloatField()
    -- more fields --

    @property
    def length_of_beam(self):
        yy = self.location.all().values_list('y_axis', flat=True)
        xx = self.location.all().values_list('x_axis', flat=True)
        ylist = list(yy)
        xlist = list(xx)
        return abs(ylist[1] - ylist[0] + xlist[1] - xlist[0])

    @property
    def total_concrete_quantity(self):
        return float(self.length) * float(self.breadth) * float(self.width)

    def save(self, *args, **kwargs):
       self.length = self.length_of_beam
       self.total_quantity = self.total_concrete_quantity
       super(BeamQuantity, self).save(*args, **kwargs)

def __float__(self):
    return self.length, self.total_quantity

I want my model to accept ManytoManyRelation of 2 Footings selected and then calculate the length. The length will then multiply with height and width to give me the total quantity (There are other calculations as well but I am guessing they are all just crashing due to not getting length).

Currently, when I fill the Form or try to fill in details through the admin page and click on Save, I get a ValueError

ValueError - "<BeamQuantity: B5>" needs to have a value for field "id" before this many-to-many relationship can be used.

I think the ManytoManyField needs to save and then the calculation needs to run. Currently both are trying to occur simultaneously and hence the error. Please help.


Solution

  • Exactly, you need to create your BeamQuantity instance first, before calculating the length of beans. Leave those fields empty for a second.

    To do that, I recommend you to let all the fields that require the many to many relationship to have a value, with blank=True and null=True.

    So, I would rewrite the save method like this:

    def save(self, *args, **kwargs):
        if self.id: # it means the object was already created, so has an id.
            self.length = self.length_of_beam
            self.total_quantity = self.total_concrete_quantity
            super(BeamQuantity, self).save(*args, **kwargs)
    

    Then when you want to create a BeanQuantity, do:

    bean_quantity = BeanQuantity.objects.create(**fields_you_want_here)
    bean_quantity.save()
    

    The second line will run the code in the save method, because now the object has an id.