In django how to model an invoice which can have multiple items? - django

I am using Django Rest Framework in the backend and Angular for frontend.
If the customer order multiple items, it should be be in a single invoice. For example, if the customer order apple, orange and banana, all these should be in a single invoice.
When the customer order again and it will be a new invoice.
class InvoiceItem(models.Model):
product = models.ForeignKey(
Product, on_delete=models.CASCADE, related_name='invoiceitems')
customer = models.ForeignKey(
Customer, on_delete=models.CASCADE, related_name='invoiceitems')
quantity = models.PositiveIntegerField()
date = models.DateTimeField(auto_now_add=True)
class Invoice(models.Model):
invoice_item = models.OneToOneField(
InvoiceItem, on_delete=models.CASCADE, related_name='invoice')
Now I have to link the InvoiceItem with Invoice. I thought about using post_save signals with InvoiceItem as a sender to create Invoice object and link it with the InvoiceItem.
#receiver(signals.post_save, sender=InvoiceItem)
def create_account(sender, instance, created, **kwargs):
Invoice.objects.update_or_create(invoice_item=instance)
How can I do it for multiple items?
Or there is a better way to implement my requirements?

There are multiple problems with your approach.
First of all, your
invoice_item = models.OneToOneField(
InvoiceItem, on_delete=models.CASCADE, related_name='invoice')
implies that there is only one type of Product per Invoice, i.e. having bought apples Customer can't buy also some oranges in the same Invoice.
If you try to fix that by creating ForeignKey on InvoiceItem, as others have already pointed out, you find yourself struggling with the second problem:
You attach InvoiceItem to a customer. Meaning that there's nothing stopping the system from creating a single Invoice that has 5 Oranges bought by Alice as an InvoiceItem and 7 Apples bought by Bob as another InvoiceItem, which seems wrong.
I would also move the date field from InvoiceItem to Invoice and rename it to timestamp, as I assume an Invoice is a set of Products bought together at one time.
You would end up with something like this:
class InvoiceItem(models.Model):
invoice = models.ForeignKey(
Invoice, on_delete=models.CASCADE, related_name='invoice_items')
product = models.ForeignKey(
Product, on_delete=models.CASCADE, related_name='invoice_items')
quantity = models.PositiveIntegerField()
class Invoice(models.Model):
customer = models.ForeignKey(
Customer, on_delete=models.CASCADE, related_name='invoices')
timestamp = models.DateTimeField(auto_now_add=True)
Now if you examine related_name arguments on the fields you'll see that you can do very useful things with your objects, like
customer = Customer.objects.get(id=<some_id>)
customer.invoices
will give you a QuerySet of all the Invoices belonging to a specific Customer. And having an Invoice object you can
invoice.invoice_items
to get all the items on an invoice
Or, for example, having a Product, say, 'apple', you can find all customers that ever bought an apple:
# Find all invoice_items for apple
inv_items = apple_product.invoice_items.all()
# Filter Customers
Customer.objects.filter(invoices__invoice_items__in=inv_items)
If you squint a little, you'll see that this whole structure is just a django M2M through relation (https://docs.djangoproject.com/en/3.0/topics/db/models/#extra-fields-on-many-to-many-relationships) and can be rewritten like this:
class InvoiceItem(models.Model):
invoice = models.ForeignKey(
Invoice, on_delete=models.CASCADE, related_name='invoice_items')
product = models.ForeignKey(
Product, on_delete=models.CASCADE, related_name='invoice_items')
quantity = models.PositiveIntegerField()
class Invoice(models.Model):
customer = models.ForeignKey(
Customer, on_delete=models.CASCADE, related_name='invoices')
timestamp = models.DateTimeField(auto_now_add=True)
products = models.ManyToManyField(Product, through='InvoiceItem')
So now you have Invoice directly connected to Products, and this connection also contains information about the quantity of this Product on this Invoice. I will leave the exploration of the benefits of this approach to the reader.

You should use a ForeignKey from the InvoiceItem to Invoice:
class InvoiceItem(models.Model):
...
invoice = models.ForeignKey(
Invoice, on_delete=models.CASCADE
)
...
You should be able to then remove the post_save. You'll create items like this:
>>> invoice = Invoice.objects.create(<insert arguments here>)
>>> invoice_item = InvoiceItem.objects.create(invoice=invoice, <insert other arguments>)
>>> invoice_item.invoice.pk == invoice.pk
[out] True (i.e., the invoice item is attached to the invoice)

Related

How to inner join tables based on ManyToManyField and group by a parameter and get latest one in Django?

I have two models with ManyToManyField relationship:
class Education(models.Model):
title = models.CharField(default=None, max_length=100)
content = models.TextField(default=None)
price = models.ManyToManyField(Price)
class Price(models.Model):
cost = models.CharField(default=None, max_length=20)
created_at = models.DateTimeField(auto_now=True, null=True, blank=True)
I can fetch all rows like this:
result = Education.objects.filter(price__in=Price.objects.all()).select_related('Price')/
.values_list('title', 'content', 'price__cost', 'price__created_at')
But now i want to group by education.id and the cost parameter should be latest parameter that inserted(based on created_at).
So i want to have list of all Education with latest cost that inserted for every education.
Will it work for you, It will return the respective id
Education.objects.filter(price__in=Price.objects.all()).select_related('Price').values('id').annotate(price_id=Max('price__id'))

How to store multiple userids in same model?

I'm fairly new to Django and attempting to store a model which will hold transaction information for a purchase between 2 users; a buyer and seller. I therefore want to store 2 UserIDs:
class Transaction(models.Model):
transactionid = models.AutoField(primary_key=True)
# USERID OF SELLER
# USER ID OF BUYER
orderid = models.ForeignKey('Order', db_column='orderid', on_delete=models.SET_NULL, blank=True, null=True)
status = models.CharField(choices=PAYMENT_STATUS, default='pending', max_length=50)
timestamp = models.DateTimeField(auto_now_add=True)
I want to use the following foreign key:
seller = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
)
however, to my understanding, this will only store the current logged in user which means I wouldn't be able to set both? Is there a better way to do this rather than modifying the model twice (esentially from both accounts)?
Any reccomendations or advice appreciated, thank you!
I hope, in the Transaction model you are going to add the entry if someone made any purchase. Buyer Id you can get from the current user. Seller ID yo can get from the product information.
For adding more than 1 foreign key with the same Model, you can use the related_name parameter.
class Transaction(models.Model):
transactionid = models.AutoField(primary_key=True)
seller = models.ForeignKey(CustomUser, on_delete=models.CASCADE, related_name='seller_name')
buyer = models.ForeignKey(CustomUser, on_delete=models.CASCADE, related_name='buyer_name')

Associate child object with parent object per instance in Django?

I've got two models, an Invoice model and an Expense model. Upon creating invoices, the user should be able to add all expenses for an invoice. Once an invoice has been created - these expenses should somehow be "checked" so a new invoice doesn't add the same expenses. I'd need some form of check on the expense instance saying "has been invoiced" or something. Not entirely sure how to proceed with only a ..set.filter(...) queryset My two models looks like this:
Expense model
class Expense(Model):
cost = DecimalField(blank=True, null=True, max_digits=10, decimal_places=2)
project = ForeignKey(Project, on_delete=CASCADE)
user = ForeignKey(User, on_delete=CASCADE)
billable = BooleanField(default=False)
expense_date = DateField()
date_created = DateTimeField(auto_now_add=True)
invoice = ForeignKey("finances.Invoice", blank=True, null=True, on_delete=SET_NULL, help_text="Optionally link this to an existing invoice")
Invoice model
class Invoice(Model):
issue_date = DateField(default=now)
due_date = DateField()
project = ForeignKey(Project, on_delete=CASCADE)
amount = DecimalField(blank=True, null=True, max_digits=100, decimal_places=2)
include_expenses = BooleanField(default=False, help_text='Check this box to include all expenses that are within the invoice billable date range')
billable_start = DateField(help_text="Logged hours start date")
billable_end = DateField(help_text="Logged hours end date")
I tried adding a queryset on the Expense model as such:
def get_billable_expenses(self):
"""Getting all expenses for selected billable period"""
start_week = self.get_start_isoweek()
end_week = self.get_end_isoweek()
return self.project.expense_set.filter(
expense_date__gte=self.billable_start,
expense_date__lte=self.billable_end,
billable=True,
invoice=self.id
)
And then adding a signal that is triggered when an invoice is saved:
#receiver(post_save, sender=Invoice)
def prepare_calculation_data(sender, instance=None, created=False, **kwargs):
if created:
expenses_to_add = []
for expense in instance.get_billable_expenses():
expense.invoice = instance
expenses_to_add.append(expense)
Expense.objects.bulk_update(expenses_to_add, ['invoice'])
Not sure how to handle this - I thought I'd solve it by checking if the expense already has an invoice tied to it - if not - include it in the newly created invoice (as seen in get_billable_expenses() above) but that means no new expenses will ever be added either as the invoicefield won't be empty
Any suggestions on how to solve it?
I solved it by having two methods on my Invoice model. One to query for un-invoiced expenses:
def get_billable_expenses(self):
"""Getting all expenses for selected billable period"""
start_week = self.get_start_isoweek()
end_week = self.get_end_isoweek()
return self.project.expense_set.filter(
expense_date__gte=self.billable_start,
expense_date__lte=self.billable_end,
status='approved',
billable=True,
invoice__isnull=True
)
and then in my template showing those expenses as such:
def get_linked_expenses(self):
return self.project.expense_set.filter(
invoice=self.id
)

How do I create a delivery model with products and amounts?

I am trying to do some sort of shop. The user gets a delivery (delivery model) with some products (product model). The user can choose the quantity of the products.
The models have a structure like so:
class Delivery(models.Model):
some stuff...
class Product(models.Model):
products = models.ManyToManyField(Product,related_name="deliveries", blank=True, null=True)
some stuff...
But now I am really confused how to solve this seemingly simple problem. Where do I specify the amount of a product in each delivery. Obviously, the amounts vary per delivery, so I can hardly specify a field with amount on the product ... I am really confused about this ... maybe someone can direct me to a helpful resource?
I think these three models should be enough to get you stared:
class Product(models.Model):
name = models.CharField(max_length=100)
unitprice = models.IntegerField()
class Delivery(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE) # each delivery is for a user
class Deliverable(models.Model):
delivery = models.ForeignKey(Delivery, on_delete=models.CASCADE) # each deliverable item belongs to a delivery
product = models.ForeignKey(Product, on_delete=models.CASCADE) # each deliverable item refers to a product
quantity = models.IntegerField() # and how many products are to be delivered

Django: Force uniqueness of many-to-many 'through' field

I am trying to design a database schema for a stock portfolio. What I have so far seems to work with one caveat --- a portfolio can contain two holdings of the same stock. I want a uniqueness of holdings with respect to the stock in the portfolio. I am new to Django, and haven't been able to figure it out.
class Stock(models.Model):
"""
All available stocks
"""
symbol = models.CharField(primary_key=True, max_length=4)
class Portfolio(models.Model):
"""
A user's portfolio containing stocks.
"""
user = models.OneToOneField(User, on_delete=models.CASCADE)
usd = models.FloatField(default=0)
stocks = models.ManyToManyField(Stock, blank=True, through='Holding')
class Holding(models.Model):
"""
A holding of a stock in a portfolio.
"""
stock = models.ForeignKey(Stock)
amount = models.FloatField(default=0.0)
portfolio = models.ForeignKey(Portfolio, on_delete=models.CASCADE)
For example, in the admin interface I can create a Portfolio and then a Holding with amount=20 and stock='ABC'. I can duplicate that same holding, but I shouldn't be able to. The correct way would be to lookup the existing holding and add to amount.
Nevermind, got it...
Trick is to add unique_together in the intermediate table. It makes the stock unique in the portfolio.
class Holding(models.Model):
"""
A holding of a stock in a portfolio.
"""
stock = models.ForeignKey(Stock)
amount = models.FloatField(default=0.0)
portfolio = models.ForeignKey(Portfolio, on_delete=models.CASCADE)
class Meta:
unique_together = [('stock', 'portfolio'),]