How to manipulate value of one Model Field from another Model? - python

I have two models
class Employee(models.Model):
name = models.CharField(max_length=20)
ID = models.IntegerField()
basic_salary = models.IntegerField()
total_leave = models.IntegerField(default = 14)
paid_leave = models.IntegerField(default = 0)
unpaid_leave = models.IntegerField(default = 0)
def __str__(self):
return self.name
class Leave_management(models.Model):
name = models.OnetoOneField(Employee,on_delete= models.CASCADE)
reason = models.CharField(max_length=50)
from = models.DateTimeField()
to = models.DateTimeField()
total_days = models.IntegerField()
def __str__(self):
return self.name
So,i want to minus 'total_days' of 'model-Leave_management' from 'total_leave' field of 'model-Employee'. And as per leaves taken i want to update 'paid_leave' and 'unpaid_leave' sections.
I can perform so if these two models would be one model(example below), But i dont know how to perform so in different models.
def save(self,*args,**kwargs):
if self.total_days<=self.total_leave:
self.total_leave -= self.total_days
self.unpaid_leave = 14 - self.total_leave
else:
self.total_days -= 14
self.paid_leaves = self.total_days
super(Model_name,self).save(*args,**kwargs)
`
Please be guiding me.

In fact your OneToOneField(..) to an Employee is not a name. At the database level it will store values that correspond to primary keys of an Employee, and in Django, name will be a lazy fetch to the corresponding Employee. Therefore I suggest to rename your function to (for example) employee.
Another problem is that you define it as a OneToOneField. That means that an Employee has one Leave_management. But based on the fields (reason, from, to, etc.), it looks like an Employee can have zero, one, or more Leave_managements. So that means it is a ForeignKey.
So our model looks like:
class Leave_management(models.Model):
employee = models.ForeignKey(Employee,on_delete= models.CASCADE)
reason = models.CharField(max_length=50)
from = models.DateTimeField()
to = models.DateTimeField()
total_days = models.IntegerField()
def __str__(self):
return self.employee.name
Like the __str__ function already suggests, we can obtain the name of the employee by querying self.employee, and we can then fetch its .name attribute.
But now the challenge is what to do when save a Leave_management object. In that case the number of total_leave and paid_leave should be updated.
We first have to figure out the total number of total_days that are stored in Leave_management objects related to an Employee, this is equal to:
(Leave_management.objects.filter(employee=some_employee)
.aggregate(totals=Sum('total_days'))['totals'] or 0
So we can then subtract this from 14, and store the (possibly) remaining days in paid_leave, like:
class Leave_management(models.Model):
# ...
def save(self, *args, **kwargs):
super(Leave_management, self).save(*args, **kwargs)
totals = (Leave_management.objects
.filter(employee=some_employee)
.aggregate(totals=Sum('total_days'))['totals'] or 0
employee = self.employee
unpaid = min(14, totals)
employee.total_leave = 14 - unpaid
employee.unpaid_leave = unpaid
employee.paid_leave = totals - unpaid
employee.save()
Note: typically we do not handle this by overriding the .save(..) function, but by using Django signals: triggers that can be implemented when certain objects are saved, etc. This especially should be used since the objects can be changed circumventing the .save(..) function, and sometimes such objects might get deleted as well. So the above is not a good design decision.
Even when we use signals, it is a good idea to frequently (for example once a day) recalculate the total leave, and update the corresponding Employee models.

Related

Django CBV use Values from Modelform to Calculate other Model Fields

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.
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.
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...

how to make summation of two or more similar objects and prevent creating the new one if its exists ? django

i try to make a system for a storage , sometimes it happen two similar objects are entry in two different time , i have to summation the quantity of both and looks like one object instead of two different object
for example i've entry this data one week ago name=mouse , qnt=20 , price=20 and today i add a new collection of mouse (the same mouse as before) name=mouse , qnt=10 , price=15 i have to whenever i face similar problem it automatically summation quantities =(30) and price = ((20+15)/2) instead of creating a new object to the second mouse object
is it possible ? or is there anything else for similar issues ?!
this is my model
this is the Products model
class Products(models.Model):
name = models.CharField(max_length=20,unique=True)
qnt = models.IntegerField()
price = models.DecimalField(max_digits=20,decimal_places=3,null=True,blank=True)
def __str__(self):
return self.name
this is the Collections model
class Collections(models.Model):
name = models.ForeignKey(Products, on_delete=models.CASCADE)
qnt = models.IntegerField()
price = models.DecimalField(max_digits=20,decimal_places=3,null=True,blank=True)
i dont need to create new object to the same product as exist in collection , i have to just summation the quantities ?!
is it possible ?thanks
You can overwrite the save method of the model. For example:
class Products(models.Model):
name = models.CharField(max_length=20,unique=True)
qnt = models.IntegerField()
price = models.DecimalField(max_digits=20,decimal_places=3,null=True,blank=True)
def __str__(self):
return self.name
def save(self, *args, **kwargs):
product = Products.objects.filter(name=self.name)
if product.count()>0:
product.quantity = product.quantity + self.quantity
product.price = (product.price + self.price)/2
product.save()
return
else:
super(Products, self).save(*args, **kwargs

create value to a model from another model's method

I'm trying to create a new table (Payslip model) that will contain the computed salary on an employee in a cutoff.
I want Payslip.salary to get value from Employee.compute_pay()
Given the example in the url link above, what should my views.py look like?
Is this the best approach to this kind of process? or Is there any library that can help with what I want to do here?
https://imgur.com/a/wVG5qrd
model.py
class Employee(models.Model):
name = models.CharField(max_length=20)
rate = models.IntegerField()
absent = models.IntegerField()
def __str__(self):
return self.name
def compute_pay(self):
daily = rate / 20
return rate - (daily*absent)
class Payslip(models.Model):
name = models.CharField(max_length=20)
salary = models.IntegerField()
def __str__(self):
return self.name
views.py
def compute(request):
if request.method == "POST":
return render(request,'payroll/compute/compute.html')
I do not think there is a need for another model Payslip, also you have no ForeignKey connections between the two models for it to work.
Considering your requirement, property decorator should work. Read up on how #property works. Basically, it acts as a pseudo model field, keep in mind the value of this field is not stored anywhere in the database, but it is tailor-made for situations like this where the field changes based on other fields and is needed for read-only.
Try this
class Employee(models.Model):
name = models.CharField(max_length=20)
rate = models.IntegerField()
absent = models.IntegerField()
def __str__(self):
return self.name
#property
def compute_pay(self):
daily = self.rate / 20
return (self.rate - (daily*self.absent))
you can get the employee's salary by Employee.compute_pay just like any other model field

Linking one models with a set of another in Django

I am currently working on developing a database and API system where users can create a portfolio which contains a list of coins. I am using Django and I searched everywhere but I kept seeing foreign keys but I'm not sure that's what I need in this situation.
I want two models, one for portfolios which a user will be able to query on, and another coin model which the user will be able to also query on. However in the portfolio there should be a list of coins. I know how to do this in Java using objects but not sure the method in Django.
Here is my model class:
from django.db import models
class Portfolio(models.Model):
name = models.CharField(max_length=250)
def __str__(self):
return self.name
class Coin(models.Model):
name = models.CharField(max_length=100)
symbol = models.CharField(max_length=5)
price = models.DecimalField(max_digits=20, decimal_places=9)
info = models.TextField()
website = models.TextField()
rank = models.IntegerField()
def __str__(self):
return self.name + " - " + self.symbol
Now I would ideally have something like coins = list of Coins model if I was using java to make the objects, but since this is for a database and in Django I'm not sure how I should link the two.
I've seen related objects but did not understand the explanations for my issue. How should I go about setting up these models? Thanks.
It sounds like you want to have a number of Portfolio objects each of which can have varying investments in Coin objects. In this case, you'd want to use a ManyToManyField:
class Portfolio(models.Model):
name = models.CharField(max_length=250)
coins = models.ManyToManyField(Coin)
The database would then store the two dimensional table of which Portfolio holds which coin.
However an alternate approach you could try is to create an object that separately represents the investment:
class Investment(models.Model):
portfolio = models.ForeignKey(Portfolio)
coin = models.ForeignKey(Coin)
bought = models.DateTimeField() # date the investment was made
sold = models.DateTimeField() # date the investment was sold
amount = models.DecimalField() # number of coins held
You could then add a property to Portfolio:
class Portfolio(models.Model):
name = models.CharField(max_length=250)
#property
def coins(self):
return Investment.objects.filter(portfolio=self)
In this way you can not only keep track of which portfolio holds which coins, buy also the entire historical positions too.

Defining a Django query set with a many-to-one relationship with calculation

I have three models: Assets, AssetTypes and Services. Assets need to get serviced every n months, and have a many-to-one relation with services.
class AssetType(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(verbose_name="Asset Type", max_length=100)
service_period = models.PositiveSmallIntegerField(verbose_name="Service Period (in months)", null=True, blank=True, default=12)
class Asset(models.Model):
id = models.AutoField(primary_key=True)
type = models.ForeignKey(AssetType, on_delete=models.PROTECT)
def service_period(self):
return AssetType.objects.get(pk=self.type.id).service_period
def service_history(self):
return self.service_set.all().order_by('-date')
def service_due_date(self):
if self.service_period()==None:
return None
elif self.service_history().count()==0:
return datetime.strptime('2017-01-01', '%Y-%m-%d').date()
else:
last_service_date = self.service_history().latest('date').date
return last_service_date + timedelta(self.service_period()*30)
def service_overdue(self):
return ((self.service_due_date()!=None) and self.service_due_date() < date.today())
class Service(models.Model):
id = models.AutoField(primary_key=True)
date = models.DateField()
asset = models.ForeignKey(Asset, on_delete=models.CASCADE)
I'm trying to work out how to make a query set that would return a list of assets that are overdue for their service. I feel like using a model method is a red herring, and that I would be better off defining a query set filter?
I need the list of overdue assets to be a query set so I can use further query set filters on it.
Any assistance would be greatly appreciated!
So this is a bit tricky.
To put the query in words, you are looking for all Assets whose latest Service date is earlier than today minus the Type's service period multiplied by 30.
To be honest, I would be tempted to denormalize this; you could add a next_service_due field on Asset which is updated when you add a new Service. Then the query is simply all assets with that field less than today.

Categories