Linking one models with a set of another in Django - python

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.

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

Django - How to update a field for a model on another model's creation?

I have two models, a Product model and a Rating model. What I want to accomplish is, every time a "Rating" is created, via an API POST to an endpoint created using DRF, I want to compute and update the average_rating field in the associated Product.
class Product(models.Model):
name = models.CharField(max_length=100)
...
average_rating = models.DecimalField(max_digits=3, decimal_places=2)
def __str__(self):
return self.name
class Rating(models.Model):
rating = models.IntegerField()
product = models.ForeignKey('Product', related_name='ratings')
def __str__(self):
return "{}".format(self.rating)
What is the best way to do this? Do I use a post_save (Post create?) signal?
What is the best way to do this? Do I use a post_save (Post create?) signal?
The problem is not that much here how to do this technically I think, but more how you make this robust. After all it is not only creating new ratings that is important: if people change their rating, or remove a rating, then the average rating needs to be updated as well. It is even possible that if you define a ForeignKey with a cascade, then deleting something related to a Rating can result in removing several ratings, and thus updating several Products. So getting the average in sync can become quite hard. Especially if you would allow other programs to manipulate the database.
It might therefore be better to calculate the average rating. For example with an aggregate:
from django.db.models import Avg
class Product(models.Model):
name = models.CharField(max_length=100)
#property
def average_rating(self):
return self.ratings.aggregate(average_rating=Avg('rating'))['average_rating']
def __str__(self):
return self.name
Or if you want to load multiple Products in a QuerySet, you can do an .annotate(..) to calculate the average rating in bulk:
Product.objects.annotate(
average_rating=Avg('rating__rating')
)
Here the Products will have an attribute average_rating that is the average rating of the related ratings.
In case the number of ratings can be huge, it can take considerable time to calculate the average. In that case I propose to add a field, and use a periodic task to update the rating. For example:
from django.db.models import Avg, OuterRef, Subquery
class Product(models.Model):
name = models.CharField(max_length=100)
avg_rating=models.DecimalField(
max_digits=3,
decimal_places=2,
null=True,
default=None
)
#property
def average_rating(self):
return self.avg_rating or self.ratings.aggregate(average_rating=Avg('rating'))['average_rating']
#classmethod
def update_averages(cls):
subq = cls.objects.filter(
id=OuterRef('id')
).annotate(
avg=Avg('rating__rating')
).values('avg')[:1]
cls.objects.update(
avg_rating=Subquery(subq)
)
def __str__(self):
return self.name
You can then periodically call Product.update_averages() to update the average ratings of all products. In case you create, update, or remove a rating, then you can aim to set the avg_rating field of the related product(s) to None to force recalculation, for example with a post_save, etc. But note that signals can be circumveted (for example with the .update(..) of a queryset, or by bulk_create(..)), and thus that it is still a good idea to periodically synchronize the average ratings.

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

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.

Django chain multiple queries in view

I have three models:
Course
Assignment
Term
A course has a ManyToManyField which accesses Django's default User in a field called student, and a ForeignKey with term
An assignment has a ForeignKey with course
Here's the related models:
class Assignment(models.Model):
title = models.CharField(max_length=128, unique=True)
points = models.IntegerField(default=0, blank=True)
description = models.TextField(blank=True)
date_due = models.DateField(blank=True)
time_due = models.TimeField(blank=True)
course = models.ForeignKey(Course)
class Course(models.Model):
subject = models.CharField(max_length=3)
number = models.CharField(max_length=3)
section = models.CharField(max_length=3)
professor = models.ForeignKey("auth.User", limit_choices_to={'groups__name': "Faculty"}, related_name="faculty_profile")
term = models.ForeignKey(Term)
students = models.ManyToManyField("auth.User", limit_choices_to={'groups__name': "Student"}, related_name="student_profile")
When a user logs in to the page, I would like to show them something like this bootstrap collapse card where I can display each term and the corresponding classes with which the student is enrolled.
I am able to access all of the courses in which the student is enrolled, I'm just having difficulty with figuring out the query to select the terms. I've tried using 'select_related' with no luck although I may be using it incorrectly. So far I've got course_list = Course.objects.filter(students = request.user).select_related('term'). Is there a way to acquire all of the terms and their corresponding courses so that I can display them in the way I'd like? If not, should I be modeling my database in a different way?
https://docs.djangoproject.com/en/1.11/ref/models/querysets/#values
You could use values or values_list here to get the fields of the related model Term.
For example expanding on your current request:
To retrieve all the Terms' name and duration for the Courses in your queryset
Course.objects.filter(students = request.user).values('term__name', 'term__duration')
I am not sure what the fields are of your Term model, but you would replace name or duration with whichever you are trying to get at.
I think it helps you
terms = Terms.objects.filter(....) # terms
cources0 = terms[0].course_set.all() # courses for terms[0]
cources0 = terms[0].course_set.filter(students=request.user) # courses for terms[0] for user

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