Should the calculated values be placed in the database? - django

In the project I use django, django-rest-framework, postgresql database + react/next on frontend. While developing the application, I came across a problem which, for lack of more experience, I am not able to solve on my own despite reading dozens of similar topics. In every topic one statement manifests itself: it depends.
Keep the calculated totals in the database or calculate them with each query?
The application is the simplest WMS (goods receipt, orders, goods release, invoices, etc.) with the ability to display reports with charts. The problem, however, is the volume of data totals I have never encountered.
1000 orders per day *
40 products on order *
25 work days *
12 months
= 12 000 000 rows
and from this data will be made calculations for example charts about profit, total revenue, amount of tax, etc.
Documents will not be changed practically ever, totals values also. However, I will read the values quite often, whether in the customer panel about invoices, orders or charts.... I'm afraid that recalculating these values every time will be a bad solution. On the other hand, these values used for later calculations are quite a lot ~20/30, maybe more.
I will certainly use pagination to display the data in the datagrid so the size of the data should not cause problems even when calculating the values. However, what about preparing the data for graphing. Let's give a check on the profit on a position within a year. Instead of simplistically filtering the pzitems for a given product and adding up the profit field, you will have to filter the pzitems for a given product and subtract the purchase price - selling price * discount from each item. Will this work quickly or is it better to break the normalization of the database and use a simpler calculation.
Below is a sample model (values done on the fly)
class DocumentProductInformation(models.Model):
product = models.ForeignKey("catalogues.Product", on_delete=models.CASCADE)
quantity = models.IntegerField(default = 0)
price = models.DecimalField(max_digits=14, decimal_places=2, default=0.00)
total = models.DecimalField(max_digits=14, decimal_places=2, default=0.00)
final_price = models.DecimalField(max_digits=14, decimal_places=2, default=0.00)
final_total = models.DecimalField(max_digits=14, decimal_places=2, default=0.00)
gross_price = models.DecimalField(max_digits=14, decimal_places=2, default=0.00)
gross_total = models.DecimalField(max_digits=14, decimal_places=2, default=0.00)
gross_final_price = models.DecimalField(max_digits=14, decimal_places=2, default=0.00)
gross_final_total = models.DecimalField(max_digits=14, decimal_places=2, default=0.00)
discount = models.DecimalField(max_digits=4, decimal_places=2, default=0.00, validators=[MinValueValidator(0), MaxValueValidator(1)])
markup = models.DecimalField(max_digits=6, decimal_places=2, default=0.00)
margin = models.DecimalField(max_digits=4, decimal_places=2, default=0.00)
sales_tax_rate = models.ForeignKey("catalogues.TaxRate", related_name='%(class)s_sales_tax_rate', on_delete=models.CASCADE, blank=True, null=True)
purchase_tax_rate = models.ForeignKey("catalogues.TaxRate", related_name='%(class)s_purchase_tax_rate', on_delete=models.CASCADE, blank=True, null=True)
sales_price = models.DecimalField(max_digits=14, decimal_places=2, default=0.00)
sales_total = models.DecimalField(max_digits=14, decimal_places=2, default=0.00)
sales_final_price = models.DecimalField(max_digits=14, decimal_places=2, default=0.00)
sales_final_total = models.DecimalField(max_digits=14, decimal_places=2, default=0.00)
sales_gross_price = models.DecimalField(max_digits=14, decimal_places=2, default=0.00)
sales_gross_total = models.DecimalField(max_digits=14, decimal_places=2, default=0.00)
sales_gross_final_price = models.DecimalField(max_digits=14, decimal_places=2, default=0.00)
sales_gross_final_total = models.DecimalField(max_digits=14, decimal_places=2, default=0.00)
profit = models.DecimalField(max_digits=14, decimal_places=2, default=0.00)
Everything from above could be calculated from these fields below:
product = models.ForeignKey("catalogues.Product", on_delete=models.CASCADE)
quantity = models.IntegerField(default = 0)
price = models.DecimalField(max_digits=14, decimal_places=2, default=0.00)
price = models.DecimalField(max_digits=14, decimal_places=2, default=0.00)
discount = models.DecimalField(max_digits=4, decimal_places=2, default=0.00, validators=[MinValueValidator(0), MaxValueValidator(1)])
markup = models.DecimalField(max_digits=6, decimal_places=2, default=0.00)
margin = models.DecimalField(max_digits=4, decimal_places=2, default=0.00)
sales_price = models.DecimalField(max_digits=14, decimal_places=2, default=0.00)

Related

How can I join two model in django

I want to join two model. I am using postgresql. But the problem is I am not using any foreign key or many to field. But I am using same unique field for both table. So I want depends on thats field I can query from both table. "invoice" is the field. My models are
class Salesinvoice(models.Model):
id = models.AutoField(primary_key=True)
date = models.DateField(default=date.today)
invoice = models.CharField(max_length=20)
customer = models.CharField(max_length=100)
product_name = models.CharField(max_length=80)
price = models.DecimalField(max_digits=9, decimal_places=2, default=0.00)
quantity = models.IntegerField(default=0)
class Salespayment(models.Model):
id = models.AutoField(primary_key=True)
date = models.DateField(default=date.today)
invoice = models.CharField(max_length=20)
amount = models.DecimalField(max_digits=9, decimal_places=2, default=0.00)
discount = models.DecimalField(max_digits=9, decimal_places=2, default=0.00)
payment = models.DecimalField(max_digits=9, decimal_places=2, default=0.00)
What should be my Views for join above both table depends on invoice field.
Change your models, specifically:
SalesInvoice.invoice to be unique. If you cannot guarantee that this value will be unique - think again about your models relationships and the data.
make SalesPayment.invoice a ForeignKey field to SalesInvoice. Read about what on_delete=PROTECT does and what other options there are: https://docs.djangoproject.com/en/4.0/ref/models/fields/#foreignkey
you don't need to explicitly add id fields. This is done by Django anyway.
class SalesInvoice(models.Model):
date = models.DateField(default=date.today)
invoice = models.CharField(max_length=20, unique=True) # changed
customer = models.CharField(max_length=100)
product_name = models.CharField(max_length=80)
price = models.DecimalField(max_digits=9, decimal_places=2, default=0.00)
quantity = models.IntegerField(default=0)
class SalesPayment(models.Model):
date = models.DateField(default=date.today)
invoice = models.ForeignKey(SalesInvoice, on_delete=PROTECT) # changed
amount = models.DecimalField(max_digits=9, decimal_places=2, default=0.00)
discount = models.DecimalField(max_digits=9, decimal_places=2, default=0.00)
payment = models.DecimalField(max_digits=9, decimal_places=2, default=0.00)
Django will automatically support the following lookups and much more:
# get all payments for an invoice (could be empty)
payments = salesInvoice.salespayment_set.all() # this is a queryset
# get the invoice of a specific payment by date of a customer
payment = SalesPayment.objects.filter(customer=cust_pk, date=some_date).first()
if payment:
invoice = payment.invoice.invoice # this is your identifying string
For example, add a function to Salesinvoice class:
class Salesinvoice(models.Model):
id = models.AutoField(primary_key=True)
....
quantity = models.IntegerField(default=0)
def payment(self):
if Salespayment.objects.filter(invoice = self.invoice).count() == 1:
return Salespayment.objects.get(invoice = self.invoice)
else:
return None
So, when you need the related Salespayment in your views.py , just do:
salesinvoice = Salesinvoice.objects.get(...)
salespayment = salesinvoice.payment()
Same in templates:
{{salesinvoice.payment.anyfield}}

django: join two tables without foreignkey

I have two models which I want to join but they don't have any foreignkey
class Invoice(models.Model):
invoice_id = models.IntegerField(blank=True, null=True)
quotation_id = models.IntegerField(blank=True, null=True)
client_id = models.ForeignKey(tbl_customer, on_delete=models.CASCADE)
invoice_number = models.IntegerField(blank=True, null=True)
quotation_number = models.IntegerField(blank=True, null=True)
date = models.DateField(blank=True, null=True)
total_amount = models.DecimalField(max_digits=8, decimal_places=2, blank=True, null=True)
total_tax = models.DecimalField(max_digits=8, decimal_places=2, blank=True, null=True)
document_type = models.CharField(max_length=50, default='', blank=True, null=True)
and
class Invoice_Description(models.Model):
invoice_id = models.IntegerField(blank=True, null=True)
client_id = models.ForeignKey(tbl_customer, on_delete=models.CASCADE)
quotation_id = models.IntegerField(blank=True, null=True)
item_id = models.ForeignKey(tbl_item, on_delete=models.CASCADE)
item_qty = models.DecimalField(max_digits=8, decimal_places=2, blank=True, null=True)
item_unit_price = models.DecimalField(max_digits=8, decimal_places=2, blank=True, null=True)
Invoice contains information about the invoice document, its total price, date, etc while Invoice_Description keeps the records of items added on that particular invoice, item price, total quantity, discount on each item, etc.
I want to display all the records in reports with respect to items like
ITEM NAME CUSTOMER NAME INV. NO. QTY DOCUMENT TYPE UNIT PRICE SALE PRICE
Item1 Client1 01 950.00 1000.00
I have all the columns available from Invoice_Description except for the INV. NO. and DOCUMENT TYPE which are in the Invoice model.
I don't want to use a ForeignKey in this case because these models are already in use in many places, changing the database will require changes everywhere.
my problem is just that I want to join two models in Django but without ForeignKey so that I may get Invoice No. and Document Type of the corresponding row.
Any ideas on how can I do this??
If all you want to do is retrieve an InvoiceDescription object that belongs to a given Invoice, you could do the following:
invoice = Invoice.objects.first()
try:
description = InvoiceDescription.objects.get(invoice_id=invoice.invoice_id)
except InvoiceDescription.DoesNotExist:
description = None
I am assuming that the field invoice_id refers to the invoice ID. Although you didn't declare it using models.ForeignKey, it still acts as a foreign key in this situation. You just have to do the lookup manually.

update django models column based on other column

I have a model like
class tbl_payment(models.Model):
document_id = models.ForeignKey(tbl_invoice, on_delete=models.CASCADE)
client_id = models.ForeignKey(tbl_customer, on_delete=models.CASCADE)
total_amount = models.DecimalField(max_digits=8, decimal_places=2, blank=True, null=True)
paid_amount = models.DecimalField(max_digits=8, decimal_places=2, blank=True, null=True)
balance = models.DecimalField(max_digits=8, decimal_places=2, blank=True, null=True)
date = models.DateField(blank=True, null=True)
status = models.CharField(max_length=50)
Now what I want to do is whenever a new record is added, or an existing record changes, balance should be updated as the difference between total_amount and paid_amount (simple maths), and based on my balance column, I want to save status as Paid, Partial or Unpaid.
I want to refrain from calculating the balance in my views and then saving in the database, instead, I want to handover this part to my models so that my models take care of the balance and I may avoid errors which I am subconsciously afraid of.
I came to know that this is done something like this
class tbl_payment(models.Model):
document_id = models.ForeignKey(tbl_invoice, on_delete=models.CASCADE)
client_id = models.ForeignKey(tbl_customer, on_delete=models.CASCADE)
total_amount = models.DecimalField(max_digits=8, decimal_places=2, blank=True, null=True)
paid_amount = models.DecimalField(max_digits=8, decimal_places=2, blank=True, null=True)
balance = models.DecimalField(max_digits=8, decimal_places=2, blank=True, null=True)
date = models.DateField(blank=True, null=True)
status = models.CharField(max_length=50)
#property
def balance(self, value):
return self.total_amount - paid_amount
but what should I pass in place of value??
also when I try to get due_amount value like this
tbl_bulk_payment.objects.get(pk=1).due_amount
it gives due_amount() missing 1 required positional argument: 'value'
what is the correct way of doing this??
you have to override save() function
class tbl_payment(models.Model):
class Status(models.TextChoices):
paid = 'Paid', 'Paid'
partial = 'Partial', 'Partial'
unpaid = 'Unpaid', 'Unpaid'
document_id = models.ForeignKey(tbl_invoice, on_delete=models.CASCADE)
client_id = models.ForeignKey(tbl_customer, on_delete=models.CASCADE)
total_amount = models.DecimalField(max_digits=8, decimal_places=2, blank=True, null=True)
paid_amount = models.DecimalField(max_digits=8, decimal_places=2, blank=True, null=True)
balance = models.DecimalField(max_digits=8, decimal_places=2, blank=True, null=True)
date = models.DateField(blank=True, null=True)
status = models.CharField(max_length=50, choices=Status.choices)
def save(self, *args, **kwargs):
self.balance = self.total_amount - self.paid_amount
if self.balance >= 0:
self.status = Status.paid
elif self.paid_amount == 0:
self.status = Status.unpaid
else:
self.status = Status.partial
super(tbl_payment, self).save(*args, **kwargs)
P.S. your Model class name is not following the python or Django naming rule.
class Payment(models.Model) would be better.
and document_id => document. because
payment = Payment.objects.get(pk=1)
payment.document_id
in this case payment.document_id would return document instance. (not document instance's id). so document is more Django like style than document_id

Using a variable to complete a Django query returns 'object has no attribute'

I'm trying to create some queries into a product database for shirts. A single table contains all stock and price information (e.g., xs_stock, xs_original_price, xs_sale_price). When a customer adds a product to their cart, it saves what size that is and I'd like to use a variable to make the appropriate queries. For example:
size = 'Small'
size_query = 's_original_price'
product.shirtstyle_set.all()[0].size_query
#substitute in 's_original_price' with query, which would work if typed by hand
Instead, I get AttributeError: 'ShirtStyle' object has no attribute 'size_query'.
I now know that this isn't the optimal way to set up an inventory database, but I'd like to figure this out before I fix it up. Thanks in advance.
Edit to add my models.py:
class ShirtStyle(models.Model):
shirt = models.ForeignKey(Shirt)
product_id = models.ForeignKey(Product, null=True, blank=True)
pattern = models.CharField(max_length=50, null=False, blank=False)
primary = models.BooleanField(default=False)
xs_stock = models.IntegerField(default=0)
xs_original_price = models.DecimalField(max_digits=8, decimal_places=2, default=1999, blank=False, null=False)
xs_sale_price = models.DecimalField(max_digits=8, decimal_places=2, null=True, blank=True)
s_stock = models.IntegerField(default=0)
s_original_price = models.DecimalField(max_digits=8, decimal_places=2, default=1999, blank=False, null=False)
s_sale_price = models.DecimalField(max_digits=8, decimal_places=2, null=True, blank=True)
m_stock = models.IntegerField(default=0)
m_original_price = models.DecimalField(max_digits=8, decimal_places=2, default=1999, blank=False, null=False)
m_sale_price = models.DecimalField(max_digits=8, decimal_places=2, null=True, blank=True)
l_stock = models.IntegerField(default=0)
l_original_price = models.DecimalField(max_digits=8, decimal_places=2, default=1999, blank=False, null=False)
l_sale_price = models.DecimalField(max_digits=8, decimal_places=2, null=True, blank=True)
xl_stock = models.IntegerField(default=0)
xl_original_price = models.DecimalField(max_digits=8, decimal_places=2, default=1999, blank=False, null=False)
xl_sale_price = models.DecimalField(max_digits=8, decimal_places=2, null=True, blank=True)
As you say, there might be deeper issues here on how you structured your data, but try the following:
Try product.shirtstyle_set.all()[0][size_query]
Got this answered on reddit /r/learnprogramming. Entire answer:
But to answer your question, "s_original_price' is an attibute of the
model instance, not a value in a dict(ionary). So you need to use
getattr to get the value of the model field:
getattr(product.shirtstyle_set.all()[0], size_query)
instead of:
product.shirtstyle_set.all()[0][size_query]

django admin foreign key default value inline

I'm converting an ancient Client/Server app (Delphi) into a Django app for a small brick and morter bookstore (wife's).
Since most functions are admin, I'm using the Django admin interface with grappelli for some easier lookups.
I have 3 models: Book, Sale and Item.
class Book(TimeStampedModel):
"""
Books are described individually and are related to collections
as many to many. Every book in this system is unique - i.e. there are
not quantity fields. This is optimized for used book stores where book
condition is essential.
"""
STATUS_CHOICES = (
('IN STOCK', 'IN STOCK'),
('SOLD', 'SOLD'),
('ON LOAN', 'ON LOAN'),
('HOLD', 'HOLD'),
)
isbn = models.CharField(max_length=50, blank=True)
title = models.CharField(max_length=200, blank=False, db_index=True)
author = models.CharField(max_length=200, blank=True)
sell_price = models.DecimalField(max_digits=10,decimal_places=2, default=0)
description = models.TextField()
collections = models.ManyToManyField(Collection)
class Meta:
index_together = [
["author", "title"],
["status", "title", "author"],
]
def __unicode__(self):
return "%s [%d] - %s - $%.2f" % (self.title, self.id, self.book_type, self.sell_price)
#staticmethod
def autocomplete_queryset():
instock = Book.objects.filter(status="IN STOCK")
return instock
#staticmethod
def autocomplete_search_fields():
return("id__iexact", "title__istartswith",)
class Sale(TimeStampedModel):
"""
Sales group all sold items which may or may not be books and are sold to contacts.
We use a "generic" contact of "cash" for non named contacts
"""
PAYMENT_TYPE_CHOICES = ( ('Cash', 'Cash'), ('Charge', 'Charge'), ('Check', 'Check'))
contact = models.ForeignKey(Contact, null=True)
sale_date = models.DateField(blank=True,default=datetime.date.today, db_index=True)
payment_type = models.CharField(max_length=50, choices=PAYMENT_TYPE_CHOICES)
taxed = models.BooleanField(default=True)
tax_exempt_no = models.CharField(blank=True, max_length=50)
sales_tax = models.DecimalField(max_digits=10, decimal_places=2, blank=True, null=True)
amt_tender = models.DecimalField(max_digits=10, decimal_places=2, blank=True, null=True)
pct_discount = models.SmallIntegerField(blank=True, null=True)
amt_credit = models.DecimalField(max_digits=10, decimal_places=2, blank=True, null=True)
amt_shipping = models.DecimalField(max_digits=10, decimal_places=2, blank=True, null=True)
amt_due = models.DecimalField(max_digits=10, decimal_places=2, blank=True, null=True)
tot_sale = models.DecimalField(max_digits=10, decimal_places=2, blank=True, null=True)
tot_items = models.DecimalField(max_digits=10, decimal_places=2, blank=True, null=True)
ordering = ['-sale_date']
def __unicode__(self):
return str(self.sale_date)
class Item(TimeStampedModel):
"""
Items are usually books sold on a sale. Items can also be entered manually
at time of sale if they are not books from inventory
"""
sale = models.ForeignKey(Sale)
book = models.ForeignKey(Book, null=True, blank=True)
item_desc = models.CharField(blank=True, max_length=200)
cost = models.DecimalField(max_digits=10, decimal_places=2, blank=True, null=True)
sell_price = models.DecimalField(max_digits=10, decimal_places=2, blank=True, null=True)
def __unicode__(self):
return self.item_desc
For the Sale form, I use an admin form with Tabular inline Items. Items are usually books (via a foreign key lookup), but can also be entered manually for non-inventory items so I have a sell_price both in the book model and in the item model.
class ItemInline(admin.TabularInline):
model = Item
raw_id_fields = ("book",)
autocomplete_lookup_fields = {
'fk': ['book'],
}
extra = 2
What I'd like to do in the foreign key lookup is to return the key of the book AND fill in the Item's sellprice with the sellprice from the book I looked up.
I have the basic lookup working just fine but can't find out how to set the item sellprice to the book's sellprice immediately after the lookup.
Any advice is appreciated! I have tried figuring out the objects to put some JS logic in but the inlines get object ids created dynamically, I think. I'm no JS expert.