I have a three-levels Invoice
model which I'd like to display on Django's admin area... in a "kind of special" way.
Allow me to provide a bit of background:
Each Invoice
is conformed by several SubInvoice
(s), and each SubInvoice
is conformed by several InvoiceItem
(s), which contain a break down of the Products
purchased by a customer.
Logically speaking, it'd be something like this (hopefully the ascii art works)
+---------- Invoice id=3 -----------+
| Full total: $100.00 |
| |
| +----- Sub Invoice id=1 -----+ |
| | Subtotal $70 | |
| | | |
| | Item 1 in SubInv.1 | |
| | Item 2 in SubInv.1 | |
| | Item 3 in SubInv.1 | |
| |____________________________| |
| |
| +----- Sub Invoice id=2 -----+ |
| | Subtotal $30 | |
| | | |
| | Item 1 in SubInv.2 | |
| | Item 2 in SubInv.2 | |
| |____________________________| |
| |
|___________________________________|
The models look more or less (they've been simplified for this question) like:
class Invoice(models.Model):
full_total = DecimalField(...)
# has a .sub_invoices RelatedManager through a backref from SubInvoice
class SubInvoice(models.Model):
sub_total = DecimalField(...)
invoice = ForeignKey('server.Invoice', related_name='sub_invoices')
# has an .items RelatedManager through a backref from InvoiceItem
class InvoiceItem(models.Model):
sub_invoice = ForeignKey('server.SubInvoice', related_name='items')
product = ForeignKey('server.Product', related_name='+')
quantity = PositiveIntegerField(...)
price = DecimalField(...)
Now, I am aware that nesting two levels of relationships in Django Admin is very complex, and I'm not trying to nest the InvoiceItem
into the SubInvoice
and nest that one into the Invoice
. That'd be great, but I'm ready to give that up due to the difficulties of nested inlines. No: what I'd like to to do is showing the Invoice
and, as an inline
, its Items
, "jumping" through Invoice.sub_invoices__items
. I don't really care that much about the information shown in the SubInvoice
(s), but I do care about the information in the Invoice
and in the InvoiceItems
.
What I mean is that, basically, I would like (or "I could live with", rather) if the Invoice
admin view looked like the following:
+---------- Invoice id=3 -----------+
| Full total: $100.00 |
| |
| +----------------------------+ |
| | | |
| | Item 1 in SubInv.1 | |
| | Item 2 in SubInv.1 | |
| | Item 3 in SubInv.1 | |
| | Item 1 in SubInv.2 | |
| | Item 2 in SubInv.2 | |
| |____________________________| |
| |
|___________________________________|
(InvoiceItems
as an inline of the Invoice
(s) without showing any information about the SubInvoices
in it)
I've tried the following in the admin.py
:
class InvoiceItemInline(admin.StackedInline):
fk_name = 'sub_invoice__invoice'
model = InvoiceItem
class InvoiceAdmin(admin.ModelAdmin):
inlines = (InvoiceItemInline,)
But that gives me an error:
<class 'server.admin.invoices.InvoiceItemInline'>: (admin.E202) 'server.InvoiceItem' has no field named 'sub_invoice__invoice'.
I've also tried directly this:
class InvoiceItemInline(admin.StackedInline):
model = InvoiceItem
class InvoiceAdmin(admin.ModelAdmin):
inlines = (InvoiceItemInline,)
But then (this one I was expecting) produces this error:
<class 'server.admin.invoices.InvoiceItemInline'>: (admin.E202) 'server.InvoiceItem' has no ForeignKey to 'server.Invoice'.
Is there any way of achieving this? Thank you in advance.
PS:
As of now, I have a "patched" solution which seems to be the canonical way:
Invoice
model.admin.ModelAdmin
inline for the SubInvoice
(this inline will be "inlined" into the Invoice
's ModelAdmin).SubInvoice
in the admin, so we can calculate a link to its admin view.InvoiceItems
to the aforementioned SubInvoice
's view.SubInvoice
(s) in the Invoice
Pretty much what is described in this other S.O. answer.
But the problem with this approach is that it won't let me see the Invoice
and its InvoiceItems
at a glance (I see the invoice, with sub_invoices in it, and then within the sub_invoices inlines, there's a link to the InvoiceItems which I have to click on in order to see the items). It'd be great if I could get rid of the need for that link.
This is what I have now, basically:
+---------- Invoice id=3 -----------+
| Full total: $100.00 |
| |
| +----- Sub Invoice id=1 -----+ | +--- Sub Invoice id=1 ---+
| | Subtotal $70 | | | Item 1 in SubInv.1 |
| | | | | Item 2 in SubInv.1 |
| | <a>Click for items ==============> | Item 3 in SubInv.1 |
| |____________________________| | |________________________|
| |
| +----- Sub Invoice id=2 -----+ |
| | Subtotal $30 | | +--- Sub Invoice id=2 ---+
| | | | | Item 1 in SubInv.2 |
| | <a>Click for items ==============> | Item 2 in SubInv.2 |
| |____________________________| | |________________________|
| |
|___________________________________|
AFAIK, you can't have a second level of inlines in the default Django admin. The Django admin is just a normal Django application, so nothing prevents you from implementing a second level of nested forms, but IMHO it would be a kind of convoluted design to implement.
One common way around this is to link to an admin between first and second (or second and third) level by having both a ModelAdmin and an Inline for the same model: Give Certificate a ModelAdmin with TrainingDate as an inline.
The Django admin is just a normal Django application, so nothing prevents you from implementing a second level of nested forms, but IMHO it would be a kind of convoluted design to implement. Perhaps that is why there is no provision for it.
One common way around this is to link to an admin between first and second (or second and third) level by having both a ModelAdmin and an Inline for the same model: Give Certificate a ModelAdmin with TrainingDate as an inline. Give CertificateInline an additional field "Details" which is a link to its ModelAdmin change form.
I think your problem could be solved using the ManyToManyField
+ through
. (This is an example)
#models.py
class Invoice(models.Model):
full_total = DecimalField(...)
# has a .sub_invoices RelatedManager through a backref from SubInvoice
class SubInvoice(models.Model):
sub_total = DecimalField(...)
invoice = ManyToManyField(
'server.Invoice',
through='server.InvoiceItem',
through_fields=('sub_invoice', 'invoice'))
# has an .items RelatedManager through a backref from InvoiceItem
class InvoiceItem(models.Model):
sub_invoice = ForeignKey('server.SubInvoice')
invoice = ForeignKey('server.Invoice')
product = ForeignKey('server.Product', related_name='+')
quantity = PositiveIntegerField(...)
price = DecimalField(...)
#admin.py
from django.contrib import admin
from .models import InvoiceItem, Invoice, SubInvoice
class InvoiceItemInline(admin.TabularInline):
model = InvoiceItem
extra = 1
class InvoiceAdmin(admin.ModelAdmin):
inlines = (InvoiceItemInline,)
admin.site.register(Invoice, InvoiceAdmin)
admin.site.register(SubInvoice, InvoiceAdmin)
I would recommend working with classes for this one in your views.py
and use inlineformset_factory
in your forms.py
for this.
Using jquery-formset
library in your frontend as well, as this looks pretty neat together.
NOTE:
You can also use on_delete=models.CASCADE
if you want in the InvoiceItem
on the foreignkeys, so if one of those items is deleted, then the InvoiceItem will be deleted too, or models.SET_NULL
, whichever you prefer.
Hope this might help you out.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With