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
)
Related
I have a database representing financial transactions. Columns representing payee and category are non-optional.
However, part of my app's functionality will be to ingest external spreadsheets of transactions which do not already have payee and category information. I would then populate a form where the user will select correct payees and categories through drop-down menus, and then save the completed information to the database.
Is the correct approach to simply create two separate but equivalent classes (see below)? Or is there some way to make one a sub-class to another, despite the fact that one is connected to a database and the other is not.
# An initial class representing a transaction read from an Excel sheet
# Payee and category information are missing at this stage, but will be filled
# in by the user later on
class TransactionFromSpreadsheet:
def __init__(self, date, amount):
self.date = date
self.amount = amount
self.payee = None
self.category = None
# The Django class that will be instantiated once all the user has completed all
# necessary information
class Transaction(models.Model):
date = models.DateField()
amount = models.DecimalField(max_digits=14, decimal_places=2)
category = models.ForeignKey('Category', on_delete=models.CASCADE)
payee = models.ForeignKey('Payee', on_delete=models.CASCADE)
One could use optional foreign keys and a custom manager to provide an easy way to query the "incomplete" or "complete" transactions.
class TransactionQueryset(models.query.QuerySet):
def complete(self):
return self.filter(category__isnull=False,
payee__isnull=False)
def incomplete(self):
return self.filter(category__isnull=True,
payee__isnull=True)
class TransactionManager(models.Manager):
def get_queryset(self):
return TransactionQueryset(self.model, using=self._db)
def complete(self):
return self.get_queryset().complete()
def incomplete(self):
return self.get_queryset().incomplete()
class Transaction(models.Model):
date = models.DateField()
amount = models.DecimalField(max_digits=14, decimal_places=2)
category = models.ForeignKey('Category', on_delete=models.CASCADE,
blank=True, null=True)
payee = models.ForeignKey('Payee', on_delete=models.CASCADE,
blank=True, null=True)
objects = TransactionManager()
And if you now need an incomplete transaction you could easily get these in a view:
def view_incomplete(request):
incompletes = Transaction.objects.incomplete()
return render(request, 'incomplete_template.html',
{'incompletes': incompletes})
It is now very comfortable to gather all heavily used filter conditions in the queryset and manager class.
And if you have non complementary filter conditions you could even chain the manager functions.
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)
In my django shop I have a adding to cart function. But if I add the same product 2 times to the cart with a different quantity, 2 different objects are created. What's wrong with my code?
here is my view
def add_to_cart_view(request):
cart = getting_or_creating_cart(request)
product_slug = request.POST.get('product_slug')
product = Product.objects.get(slug=product_slug)
if request.method == "POST":
form = CartAddProductForm(request.POST or None)
if form.is_valid():
quantity = form.cleaned_data['quantity']
new_item, created = CartItem.objects.get_or_create(
product=product,
item_cost=product.price,
quantity=quantity,
all_items_cost=product.price*quantity,
)
if new_item.product.title == product.title:
cart.items.add(new_item)
cart.save()
if not created:
new_item.quantity += quantity
new_item.save(force_update=True)
cart.save()
new_cart_total = 0.00
for item in cart.items.all():
new_cart_total += float(item.all_items_cost)
cart.cart_total_cost = new_cart_total
cart.save()
return JsonResponse({
'cart_total': cart.items.count()
})
And here is my models
class CartItem(models.Model):
product = models.ForeignKey(Product, on_delete=models.SET_NULL, null=True)
quantity = models.PositiveIntegerField(null=True, default=1)
item_cost = models.DecimalField(max_digits=9, decimal_places=2, default=0.00)
all_items_cost = models.DecimalField(max_digits=9, decimal_places=2, default=0.00)
def __str__(self):
return str(self.product.title)
class Cart(models.Model):
items = models.ManyToManyField(CartItem, blank=True)
cart_total_cost = models.DecimalField(max_digits=9, decimal_places=2, default=0.00)
def __str__(self):
return str(self.id)
Thanks for any help!
#dirkgroten provided a very good answer, you can also use unique_together option to prevent creating a duplicate entry
suppose, you've three fields name, size, brand in the Product model
and you don't want to create any new entry with the same name, size and brand
You can set it like
class Product:
name = CharField(....)
size = CharField(....)
brand = CharField(....)
field4 = CharField(....)
class Meta:
unique_together = ("name","size","brand")
I personally do not entertain the use of unique_together, but it'll surely prevent from creating multiple entries in these kinds of situations from DB definition side, but you've to handle the same in the code too
The get_or_create function will try to fetch a CartItem with all the exact properties you pass it. In your case you're trying to match against product, item_cost, quantity and all_items_cost. If you pass it the same product with a different quantity, it won't match, it'll create a new CartItem.
Look at the documentation of get_or_create. Use only product for the query and defaults for setting the value when creating a new CartItem:
new_item, created = CartItem.objects.get_or_create(
product=product,
defaults = dict(
item_cost=product.price,
quantity=quantity,
all_items_cost=product.price*quantity),
)
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'),]
I'm trying to create a transaction history for each transaction on a Django based marketplace.
I thought the best way of keeping track of this data was to override the save() function and create a Transaction record.
class Transaction(models.Model):
item = models.ManyToManyField(Item, blank=True)
buyer = models.ManyToManyField(User, related_name='buyer')
seller = models.ManyToManyField(User, related_name='seller')
description = models.CharField(max_length=500)
purchase_date = models.DateField(auto_now_add=True)
value = models.DecimalField(max_digits=7, decimal_places=2)
def save(self, *args, **kwargs):
self.buyer.money+=self.value
self.seller.money-=self.value
super(Transaction, self).save(*args, **kwargs)
Am I going about this all wrong? Currenlty I get...
'Transaction' instance needs to have a primary key value before a many-to-many relationship can be used.
You have to save your object before you can go through many-to-many relationships.
Please explain how you can have multiple buyers and sellers on a single transaction. (For the rest of this answer, I'm assuming that there aren't and you meant for these to be ForeignKey fields.)
The related names for buyer and seller are not clear. See below.
I'm not sure what description is for. Is it different from the item list?
item should be called items, since it can be plural, and you might want to create a custom junction table (using the "through" parameter) with a quantity field.
You forgot to save the related objects.
Modified version:
class Transaction(models.Model):
items = models.ManyToManyField(Item, through='TransactionItem', blank=True)
buyer = models.ForeignKey(User, related_name='transactions_as_buyer')
seller = models.ForeignKey(User, related_name='transactions_as_seller')
description = models.CharField(max_length=500)
purchase_date = models.DateField(auto_now_add=True)
value = models.DecimalField(max_digits=7, decimal_places=2)
def save(self, *args, **kwargs):
super(Transaction, self).save(*args, **kwargs)
self.buyer.money += self.value
self.buyer.save()
self.seller.money -= self.value
self.seller.save()
class TransactionItem(models.Model):
transaction = models.ForeignKey(Transaction)
item = models.ForeignKey(Item)
quantity = models.IntegerField()
The buyer and seller fields are many to many fields so self.buyer will never work, I think you were meaning to use ForeignKey instead of ManyToManyField.