Home > Software design >  Django CBV use Values from Modelform to Calculate other Model Fields
Django CBV use Values from Modelform to Calculate other Model Fields

Time:03-19

I am trying to make a warehouse management system with Django 3.2 based on this models:

class itemtype(models.Model):
    item_id = models.IntegerField(primary_key=True)
    item_name = models.CharField(max_length=100)
    group_name = models.CharField(max_length=100)
    category_name = models.CharField(max_length=100)
    mass = models.FloatField()
    volume = models.FloatField()
    used_in_storage = models.BooleanField(default=False, null=True)

    class Meta:
        indexes = [
            models.Index(fields=['item_id'])
        ]

    def __str__(self):
        return '{}, {}'.format(self.item_id, self.item_name)

class material_storage(models.Model):
    storage_id = models.AutoField(primary_key=True)
    material = models.ForeignKey(itemtype, on_delete=models.PROTECT)
    amount_total = models.IntegerField(null=True)
    price_avg = models.FloatField(null=True)
    order_amount = models.IntegerField(null=True)
    order_price = models.IntegerField(null=True)
    timestamp = models.DateTimeField(default=timezone.now)

    def __str__(self):
        return '{}, {} avg.: {} ISK'.format(self.material, self.amount, self.price)

"itemtype" defines basically the possible objects which could be stored and "material_storage" shows what is in stock. I tried to combine the total amount of every item as well as the average price paid for it and the amount and price for a single order in the same database row. The idea is to get the last record for the chosen item/material when a new order happens, add the amount of that order and recalculate the avg price. Theoretically this could be split up on two tables, but I don't see a reason to do so at the moment. However, I am not able to figure out the actual function code to do the calculations. I am new to Django and therefor a bit overwhelmed by the complexity. I tried to use class based views and model forms, for the easy stuff that worked fine but now I am kind of lost.

Making a form just for adding new rows to that storage table was ok.

class NewAssetForm(forms.ModelForm):

    material = MaterialChoiceField(models.itemtype.objects.filter(used_in_storage= True))

    def __init__(self, *args, **kwargs):
        super(NewAssetForm, self).__init__(*args, **kwargs)
        self.fields['amount'].widget.attrs['min'] = 1
        self.fields['price'].widget.attrs['min'] = 1

    class Meta:
        model = models.material_storage
        fields = ( 
            'material',
            'amount',
            'price'
        )

        widgets = {
            'material': forms.Select(),
        }

Same for the View to process it.

class NewItemView(FormView):

    template_name = 'assetmanager/newasset.html'
    form_class = forms.NewAssetForm
    success_url = '/storage/current'

    def form_valid(self, form):
        return super().form_valid(form)

But now I am stuck. I thought this should be a fairly standard task, but I couldn't find a solution for it by now. The Idea was to put it in the form_valid function, take the material from the form to find the latest relevant record, add the new amount as well as calculate the new average price and save all together to the model. So far i only found a few examples comparable with my problem at all and I wasn't able to translate them to my setup, so maybe someone can give me a hint for a more successful search or provide me an example how to approach this topic.

thx in advance.

CodePudding user response:

To modify the values of the form fields, you can override "clean" method and provide values to the form fields. Data can be accessed using "self.cleaned_data", it is a dictionary.

class NewAssetForm(ModelForm):
    def clean(self):
        super().clean()
        # place code that retrieves existing data and calculate new values.
        self.cleaned_data['price'] = 'New Value'

cleaned_data will be passed to "form_valid", there you can call the save function. "form.save()" will create a new row, make sure you are passing valid values to the views. Since you are accepting few fields in the form, make sure you have default values for the fields that are not included in the form object.

CodePudding user response:

Thank you for your answer I found a solution by using the form_valid() method within the FormView. The majority of the code is used to create entries based on the existing entries or to check whether there are already entries for the same material.

class NewItemView(FormView):

    template_name = 'assetmanager/newasset.html'
    form_class = forms.NewAssetForm
    success_url = '/storage/current'

    def form_valid(self, form):
        try:
            # check if there is already a record for this material.
            material_storage.objects.filter(material_id = form.cleaned_data['material'])[:1]

            # getting total amount and average price values from latest entry with said material.
            total_amount = material_storage.objects.values('amount_total').filter(material_id=form.cleaned_data['material']).order_by('-timestamp')[:1][0]['amount_total']
            avg_price = material_storage.objects.values('price_avg').filter(material_id=form.cleaned_data['material']).order_by('-timestamp')[:1][0]['price_avg']

            amount = form.cleaned_data['amount']
            price = form.cleaned_data['price']

            # calculating new total amount and average price based on old values and new entry. 
            form.instance.amount_total = total_amount   amount
            form.instance.price_avg = ((avg_price * total_amount)   (price * amount)) / (total_amount   amount)
            form.save()
        except material_storage.DoesNotExist:

            # if there is no entry for the chosen material yet, amount = total amount, price = average price.
            form.instance.amount_total = form.cleaned_data['amount']
            form.instance.price_avg = form.cleaned_data['price']
            form.save()       
             
        return super().form_valid(form)

For now this solves my problem, however I don't know if the chosen location (form_valid()) makes sense - your answer suggests it would make more sense elsewhere. Also, checking if an entry already exists for the material and selecting values from such an entry are pretty sure not very elegant and efficient. But as already mentioned, I am a beginner - I would be happy about any suggestions for improvement.

I am also not sure yet if this handles every probable special case which could appear...

  • Related