django-orchestra/orchestra/apps/bills/admin.py

168 lines
6.8 KiB
Python
Raw Normal View History

2014-08-22 11:28:46 +00:00
from django import forms
2014-09-30 14:46:29 +00:00
from django.contrib import admin, messages
from django.contrib.admin.utils import unquote
2014-07-23 16:24:56 +00:00
from django.core.urlresolvers import reverse
2014-09-03 13:56:02 +00:00
from django.db import models
2014-09-30 14:46:29 +00:00
from django.utils.safestring import mark_safe
2014-07-23 16:24:56 +00:00
from django.utils.translation import ugettext_lazy as _
2014-08-19 18:59:23 +00:00
from orchestra.admin import ExtendedModelAdmin
2014-09-26 15:05:20 +00:00
from orchestra.admin.utils import admin_date
2014-07-23 16:24:56 +00:00
from orchestra.apps.accounts.admin import AccountAdminMixin
2014-09-03 13:56:02 +00:00
from . import settings
2014-09-04 15:55:43 +00:00
from .actions import download_bills, view_bill, close_bills, send_bills
2014-07-23 16:24:56 +00:00
from .filters import BillTypeListFilter
2014-09-30 14:46:29 +00:00
from .models import Bill, Invoice, AmendmentInvoice, Fee, AmendmentFee, ProForma, BillLine
2014-07-23 16:24:56 +00:00
2014-09-18 15:07:39 +00:00
PAYMENT_STATE_COLORS = {
Bill.PAID: 'green',
Bill.PENDING: 'darkorange',
Bill.BAD_DEBT: 'red',
}
2014-07-23 16:24:56 +00:00
class BillLineInline(admin.TabularInline):
model = BillLine
2014-09-18 15:07:39 +00:00
fields = ('description', 'rate', 'quantity', 'tax', 'subtotal', 'get_total')
2014-09-10 16:53:09 +00:00
readonly_fields = ('get_total',)
2014-08-22 11:28:46 +00:00
def get_readonly_fields(self, request, obj=None):
2014-09-18 15:07:39 +00:00
if obj and not obj.is_open:
2014-08-22 11:28:46 +00:00
return self.fields
return super(BillLineInline, self).get_readonly_fields(request, obj=obj)
def has_add_permission(self, request):
2014-09-18 15:07:39 +00:00
if request.__bill__ and not request.__bill__.is_open:
2014-08-22 11:28:46 +00:00
return False
return super(BillLineInline, self).has_add_permission(request)
def has_delete_permission(self, request, obj=None):
2014-09-18 15:07:39 +00:00
if obj and not obj.is_open:
2014-08-22 11:28:46 +00:00
return False
return super(BillLineInline, self).has_delete_permission(request, obj=obj)
2014-09-10 16:53:09 +00:00
def formfield_for_dbfield(self, db_field, **kwargs):
""" Make value input widget bigger """
if db_field.name == 'description':
kwargs['widget'] = forms.TextInput(attrs={'size':'110'})
else:
kwargs['widget'] = forms.TextInput(attrs={'size':'13'})
return super(BillLineInline, self).formfield_for_dbfield(db_field, **kwargs)
2014-07-23 16:24:56 +00:00
2014-08-19 18:59:23 +00:00
class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
2014-07-23 16:24:56 +00:00
list_display = (
2014-09-26 15:05:20 +00:00
'number', 'type_link', 'account_link', 'created_on_display',
'num_lines', 'display_total', 'display_payment_state', 'is_open'
2014-07-23 16:24:56 +00:00
)
2014-09-18 15:07:39 +00:00
list_filter = (BillTypeListFilter, 'is_open',)
add_fields = ('account', 'type', 'is_open', 'due_on', 'comments')
2014-08-22 11:28:46 +00:00
fieldsets = (
(None, {
2014-09-03 13:56:02 +00:00
'fields': ('number', 'display_total', 'account_link', 'type',
2014-09-19 14:47:25 +00:00
'display_payment_state', 'is_sent', 'due_on', 'comments'),
2014-08-22 11:28:46 +00:00
}),
(_("Raw"), {
'classes': ('collapse',),
'fields': ('html',),
}),
)
2014-09-04 15:55:43 +00:00
actions = [download_bills, close_bills, send_bills]
change_view_actions = [view_bill, download_bills, send_bills, close_bills]
2014-09-18 15:07:39 +00:00
change_readonly_fields = ('account_link', 'type', 'is_open')
2014-09-19 14:47:25 +00:00
readonly_fields = ('number', 'display_total', 'is_sent', 'display_payment_state')
2014-07-23 16:24:56 +00:00
inlines = [BillLineInline]
created_on_display = admin_date('created_on')
2014-09-03 13:56:02 +00:00
def num_lines(self, bill):
2014-09-11 14:00:20 +00:00
return bill.lines__count
num_lines.admin_order_field = 'lines__count'
2014-09-03 13:56:02 +00:00
num_lines.short_description = _("lines")
def display_total(self, bill):
2014-09-08 15:10:16 +00:00
return "%s &%s;" % (bill.total, settings.BILLS_CURRENCY.lower())
2014-09-03 13:56:02 +00:00
display_total.allow_tags = True
display_total.short_description = _("total")
2014-09-08 15:10:16 +00:00
display_total.admin_order_field = 'total'
2014-09-03 13:56:02 +00:00
2014-08-19 18:59:23 +00:00
def type_link(self, bill):
bill_type = bill.type.lower()
2014-07-23 16:24:56 +00:00
url = reverse('admin:bills_%s_changelist' % bill_type)
2014-08-19 18:59:23 +00:00
return '<a href="%s">%s</a>' % (url, bill.get_type_display())
type_link.allow_tags = True
type_link.short_description = _("type")
type_link.admin_order_field = 'type'
2014-07-23 16:24:56 +00:00
2014-09-18 15:07:39 +00:00
def display_payment_state(self, bill):
topts = bill.transactions.model._meta
url = reverse('admin:%s_%s_changelist' % (topts.app_label, topts.module_name))
url += '?bill=%i' % bill.pk
state = bill.get_payment_state_display().upper()
color = PAYMENT_STATE_COLORS.get(bill.payment_state, 'grey')
return '<a href="{url}" style="color:{color}">{name}</a>'.format(
url=url, color=color, name=state)
display_payment_state.allow_tags = True
display_payment_state.short_description = _("Payment")
2014-08-22 11:28:46 +00:00
def get_readonly_fields(self, request, obj=None):
fields = super(BillAdmin, self).get_readonly_fields(request, obj=obj)
2014-09-18 15:07:39 +00:00
if obj and not obj.is_open:
2014-08-22 11:28:46 +00:00
fields += self.add_fields
return fields
2014-09-08 15:10:16 +00:00
def get_fieldsets(self, request, obj=None):
fieldsets = super(BillAdmin, self).get_fieldsets(request, obj=obj)
2014-09-18 15:07:39 +00:00
if obj and obj.is_open:
2014-09-08 15:10:16 +00:00
fieldsets = (fieldsets[0],)
return fieldsets
def get_change_view_actions(self, obj=None):
2014-09-10 16:53:09 +00:00
actions = super(BillAdmin, self).get_change_view_actions()
2014-09-18 15:07:39 +00:00
exclude = []
2014-09-04 15:55:43 +00:00
if obj:
2014-09-18 15:07:39 +00:00
if not obj.is_open:
exclude.append('close_bills')
return [action for action in actions if action.__name__ not in exclude]
2014-07-23 16:24:56 +00:00
def get_inline_instances(self, request, obj=None):
2014-08-22 11:28:46 +00:00
# Make parent object available for inline.has_add_permission()
request.__bill__ = obj
2014-07-23 16:24:56 +00:00
return super(BillAdmin, self).get_inline_instances(request, obj=obj)
2014-08-22 11:28:46 +00:00
def formfield_for_dbfield(self, db_field, **kwargs):
""" Make value input widget bigger """
if db_field.name == 'comments':
kwargs['widget'] = forms.Textarea(attrs={'cols': 70, 'rows': 4})
if db_field.name == 'html':
kwargs['widget'] = forms.Textarea(attrs={'cols': 150, 'rows': 20})
return super(BillAdmin, self).formfield_for_dbfield(db_field, **kwargs)
2014-09-03 13:56:02 +00:00
def get_queryset(self, request):
qs = super(BillAdmin, self).get_queryset(request)
2014-09-11 14:00:20 +00:00
qs = qs.annotate(models.Count('lines'))
qs = qs.prefetch_related('lines', 'lines__sublines')
2014-09-03 13:56:02 +00:00
return qs
2014-09-30 14:46:29 +00:00
def change_view(self, request, object_id, **kwargs):
bill = self.get_object(request, unquote(object_id))
# TODO raise404, here and everywhere
if not hasattr(bill.account, 'invoicecontact'):
create_link = reverse('admin:accounts_account_change', args=(bill.account_id,))
create_link += '#invoicecontact-group'
messages.warning(request, mark_safe(_(
'Be aware, related contact doesn\'t have a billing contact defined, '
'bill can not be generated until one is <a href="%s">provided</a>' % create_link
)))
return super(BillAdmin, self).change_view(request, object_id, **kwargs)
2014-07-23 16:24:56 +00:00
admin.site.register(Bill, BillAdmin)
admin.site.register(Invoice, BillAdmin)
admin.site.register(AmendmentInvoice, BillAdmin)
admin.site.register(Fee, BillAdmin)
admin.site.register(AmendmentFee, BillAdmin)
2014-09-11 14:00:20 +00:00
admin.site.register(ProForma, BillAdmin)