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...
Related
I might be missing something simple here. And I simply lack the knowledge or some how-to.
I got two models, one is site, the other one is siteField and the most important one - siteFieldValue.
My idea is to create a django table (for site) that uses the values from siteFieldValue as a number in a row, for a specific site, under certain header. The problem is - each site can have 50s of them. That * number of columns specified by def render_ functions * number of sites equals to a lot of queries and I want to avoid that.
My question is - is it possible to, for example, prefetch all the values for each site (SiteFieldValue.objects.filter(site=record).first() somewhere in the SiteListTable class), put them into an array and then use them in the def render_ functions by simply checking the value assigned to a key (id of the field).
Models:
class Site(models.Model):
name = models.CharField(max_length=100)
class SiteField(models.Model):
name = models.CharField(max_length=100)
description = models.CharField(max_length=500, null=True, blank=True)
def __str__(self):
return self.name
class SiteFieldValue(models.Model):
site = models.ForeignKey(Site, on_delete=models.CASCADE)
field = models.ForeignKey(SiteField, on_delete=models.CASCADE)
value = models.CharField(max_length=500)
Table view
class SiteListTable(tables.Table):
name = tables.Column()
importance = tables.Column(verbose_name='Importance',empty_values=())
vertical = tables.Column(verbose_name='Vertical',empty_values=())
#... and many more to come... all values based on siteFieldValue
def render_importance(self, value, record):
q = SiteFieldValue.objects.filter(site=record, field=1).first()
# ^^ I don't want this!! I would want the SiteFieldValue to be prefetched somewhere else for that model and just check the array for field id in here.
if (q):
return q.value
else:
return None
def render_vertical(self, value, record):
q = SiteFieldValue.objects.filter(site=record, field=2).first()
# ^^ I don't want this!! I would want the SiteFieldValue to be prefetched somewhere else for that model and just check the array for field id in here.
if (q):
return q.value
else:
return None
class Meta:
model = Site
attrs = {
"class": "table table-striped","thead" : {'class': 'thead-light',}}
template_name = "django_tables2/bootstrap.html"
fields = ("name", "importance", "vertical",)
This might get you started. I've broken it up into steps but they can be chained quite easily.
#Get all the objects you'll need. You can filter as appropriate, say by site__name).
qs = SiteFieldValue.objects.select_related('site', 'field')
#lets keep things simple and only get the values we want
qs_values = qs.values('site__name','field__name','value')
#qs_values is a queryset. For ease of manipulation, let's make it a list
qs_list = list(qs_values)
#set up a final dict
final_dict = {}
# create the keys (sites) and values so they are all grouped
for site in qs_list:
#create the sub_dic for the fields if not already created
if site['site__name'] not in final_dict:
final_dict[site['site__name']] = {}
final_dict[site['site__name']][site['name']] = site['site__name']
final_dict[site['site__name']][site['field__name']] = site['value']
#now lets convert our dict of dicts into a list of dicts
# for use as per table2 docs
data = []
for site in final_dict:
data.append(final_dict[site])
Now you have a list of dicts eg,
[{'name':site__name, 'col1name':value...] and can add it as shown in the table2 docs
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.
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.
I am using two related models in my Django application. The objects so created in the models are being displayed using the listview class. In the child model I can create multiple rows based on some key date. When I try to display values from both the models, all the child objects for the respective FK fields are displayed (wherever more than one records are there).
Let me make the situation clearer as below:
models.py
class MatPriceDoc(models.Model):
mat_price_doc_number = models.IntegerField(null=True, blank=True.....)
mat_num = models.ForeignKey(Material, on_delete=.......)
mat_group = models.ForeignKey(MatGrp, on_delete=.......)
doc_type = models.CharField(max_length=2, null=True, default='RF'....)
create_date = models.DateField(default=timezone.now,...)
class MatPriceItems(models.Model):
price_doc_header = models.ForeignKey(MatPriceDoc, on_delete=...)
price_item_num = models.CharField(max_length=3, default=10,...)
price_valid_from_date = models.DateField(null=True, verbose_name='From date')
price_valid_to_date = models.DateField(null=True, verbose_name='To date')
mat_price = models.DecimalField(max_digits=7, decimal_places=2, null=True, blank=True,...)
views.py
class MatPriceListView(ListView):
template_name = "mat_price_list.html"
context_object_name = 'matprice'
model = MatPriceDoc
def get_context_data(self, **kwargs):
context = super(MatPriceListView, self).get_context_data(**kwargs)
context.update({
'mat_price_item_list': MatPriceItems.objects.distinct().order_by('price_doc_header'), #This is where I have tried **distinct()**
})
return context
def get_queryset(self):
return MatPriceDoc.objects.order_by('mat_num')
Now the material price changes periodically and there would always be more than one price item for each material for each key date (price_valid_from_date). My objective is to display the latest price of all those materials for which price documents exist. My predicament is how to pick up only one of the many price items for the same material / document combination. My code line 'mat_price_item_list': MatPriceItems.objects.distinct().order_by('price_doc_header'), is of course not yielding any result (all price items are being displayed in successive columns).
Is there a way to show only one price item in the listview?
Edit
In the following image the prices maintained for various dates for materials are shown. What I was trying to get was only the price for the latest (valid) date is displayed for a particular material. So in the instant case (as displayed), prices for only 4th March 2020 should be displayed for each item MS and HSD.
Edit 2
This is how the child model data for an instance of header doc number (no. 32 here) looks like (image grab from the child table using data browser):
The columns are: Child obj. item no. / Price / Valid From / Valid To / Hdr Doc no. / Child Obj Row no.
My thinking was: Can I not pick up only the first object (a subset) from a collection of doc number? In the instant case (ref the image), doc no. 32 has three child items (bearing number 148, 149 and 156). Is it not possible to only pick up item number 156 and discard the rest?
I tried:
MatPriceItems.objects.order_by('-price_valid_to_date').first()
but raises error "MatPriceItems" object is not iterable.
What would enable me to get the only item for a header item and display it?
I believe you need the following:
class MatPriceListView(ListView):
template_name = "mat_price_list.html"
context_object_name = 'matprice'
model = MatPriceDoc
def get_context_data(self, **kwargs):
context = super(MatPriceListView, self).get_context_data(**kwargs)
context.update({
'mat_price_item_list': MatPriceItems.objects.all().('-price_doc_header').first(),
})
return context
def get_queryset(self):
return MatPriceDoc.objects.order_by('mat_num')
The key line change being:
MatPriceItems.objects.all().order_by('-price_doc_header').first()
Such that you order by the price_doc_header descending (hence the minus infront of that value). Then take the .first() in the returned <QuerySet> object.
Likewise, the following would also be valid:
MatPriceItems.objects.all().order_by('price_doc_header').last()
You could also make use of Django's built in aggregation in the ORM:
from django.db.models import Max
MatPriceItems.objects.all().aggregate(Max('price_doc_header'))
Putting this all together, I would say the best solution for readabiltiy of the code would be using Django's built-in Max function:
from django.db.models import Max
class MatPriceListView(ListView):
template_name = "mat_price_list.html"
context_object_name = 'matprice'
model = MatPriceDoc
def get_context_data(self, **kwargs):
context = super(MatPriceListView, self).get_context_data(**kwargs)
context.update({
'mat_price_item_list': MatPriceItems.objects.all().aggregate(Max('price_doc_header'))
})
return context
def get_queryset(self):
return MatPriceDoc.objects.order_by('mat_num')
Because it tells any colleague exactly what you're doing: taking the maximum value.
Update: On a second glance of your question - I believe you may also need to do some sort of .filter()...then make an .annotate() for the Max...
I'm also adding what I now believe is the correct answer based on a re-reading of your question.
I feel like the query needs to be ammended to:
MatPriceItems.objects.all().select_related(
'mat_price_docs'
).order_by(
'price_doc_header__mat_num'
)
i.e.,
class MatPriceListView(ListView):
template_name = "mat_price_list.html"
context_object_name = 'matprice'
model = MatPriceDoc
def get_context_data(self, **kwargs):
context = super(MatPriceListView, self).get_context_data(**kwargs)
context.update({
'mat_price_item_list': MatPriceItems.objects.all().select_related('mat_price_docs').order_by('price_doc_header__mat_num'),
})
return context
def get_queryset(self):
return MatPriceDoc.objects.order_by('mat_num')
To achieve this, you also add the related_name argument to the price_doc_header ForeignKey field.
class MatPriceItems(models.Model):
price_doc_header = models.ForeignKey(MatPriceDoc, related_name='mat_price_docs', on_delete=...)
price_item_num = models.CharField(max_length=3, default=10,...)
price_valid_from_date = models.DateField(null=True, verbose_name='From date')
price_valid_to_date = models.DateField(null=True, verbose_name='To date')
mat_price = models.DecimalField(max_digits=7, decimal_places=2, null=True, blank=True,...)
As always, any changes to your models - please re-run the makemigarations and migrate management commands.
Edit: You may even be able to define a new queryset using an annotation to link to the ForeignKey:
def get_queryset(self):
return MatPriceItems.objects.all().order_by(
'price_doc_header__mat_num'
).annotate(
mat_price_doc_number = F('price_doc_header__mat_price_doc_number')
...
)
N.B. You can import the F() expression as from django.db.models import F
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.
I have a project that I need to open and close tickets. So, this is my Ticket model:
class Ticket(models.Model):
issue = models.CharField(max_length=100)
user = models.ForeignKey('Users', blank=True, null=True, related_name="tickets")
date_opened = models.DateTimeField('Date opened')
date_closed = models.DateTimeField('Date closed', blank=True, null=True)
def __str__(self):
return self.issue
def time_to_solve(self):
time_to_solve = self.date_opened - self.date_closed
out = [ time_to_solve.hours//60 ]
return '{:d} hours'.format(*out)
and I want to calculate the average of the time difference between date_opened and date_closed.
In my views.py I have created a view :
class Dashboard(ListView):
model = Ticket
template_name = 'assets/dashboard.html'
def get_context_data(self, **kwargs):
context = super(Dashboard, self).get_context_data(**kwargs)
context['time_to_complete'] = Q(status__contains='closed')).aggregate(time_opened = Avg('time_to_solve'))
return context
Unfortunately it does not work because "time_to_solve" is not a part of the database.
How can I achieve that?
You can only aggregate model fields, but it's not hard to do that in python:
tickets = Ticket.objects.filter(status__contains='closed')
average = sum(map(lambda x: x.time_to_solve(), tickets)) / tickets.count()
In this case time_to_solve should return something like the number of seconds and you can format that as you need right after that.
Depending on the number of tickets, this might not be the fastest solution. If performance are an issue you might want to use some kind of denormalization.
I don't think you can do that directly with the ORM. You can do it in Python but that will retrieve all closed Ticket rows from the database. If you want to do it in SQL you'll need to express your query as raw SQL. If you're using PostgreSQL you might find this useful : Working with Dates and Times in PostgreSQL.
Found the answer from a friend in #irc - django on Freenode:
average = Ticket.objects.extra(
select={ 'date_difference': 'AVG(time_to_sec(TIMEDIFF(date_closed,date_opened)))'}).first().date_difference
context['average'] = "{:.2f}".format(average/86400)
return context
This way it returns the average with 2 decimals accuracy and does everything in the database level so it's much lighter to run than fetching all rows.