diff --git a/orchestra/admin/options.py b/orchestra/admin/options.py index 5701da6b..6adaf842 100644 --- a/orchestra/admin/options.py +++ b/orchestra/admin/options.py @@ -1,47 +1,11 @@ from django import forms +from django.conf.urls import patterns, url from django.contrib import admin from django.forms.models import BaseInlineFormSet -from .utils import set_url_query +from orchestra.utils.functional import cached - -class ExtendedModelAdmin(admin.ModelAdmin): - add_fields = () - add_fieldsets = () - add_form = None - change_readonly_fields = () - - def get_readonly_fields(self, request, obj=None): - fields = super(ExtendedModelAdmin, self).get_readonly_fields(request, obj=obj) - if obj: - return fields + self.change_readonly_fields - return fields - - def get_fieldsets(self, request, obj=None): - if not obj: - if self.add_fieldsets: - return self.add_fieldsets - elif self.add_fields: - return [(None, {'fields': self.add_fields})] - return super(ExtendedModelAdmin, self).get_fieldsets(request, obj=obj) - - def get_inline_instances(self, request, obj=None): - """ add_inlines and inline.parent_object """ - self.inlines = getattr(self, 'add_inlines', self.inlines) - if obj: - self.inlines = type(self).inlines - inlines = super(ExtendedModelAdmin, self).get_inline_instances(request, obj=obj) - for inline in inlines: - inline.parent_object = obj - return inlines - - def get_form(self, request, obj=None, **kwargs): - """ Use special form during user creation """ - defaults = {} - if obj is None and self.add_form: - defaults['form'] = self.add_form - defaults.update(kwargs) - return super(ExtendedModelAdmin, self).get_form(request, obj, **defaults) +from .utils import set_url_query, action_to_view class ChangeListDefaultFilter(object): @@ -58,8 +22,10 @@ class ChangeListDefaultFilter(object): for key, value in self.default_changelist_filters: set_url_query(request, key, value) defaults.append(key) - # hack response cl context in order to hook default filter awaearness into search_form.html template - response = super(ChangeListDefaultFilter, self).changelist_view(request, extra_context=extra_context) + # hack response cl context in order to hook default filter awaearness + # into search_form.html template + response = super(ChangeListDefaultFilter, self).changelist_view(request, + extra_context=extra_context) if hasattr(response, 'context_data') and 'cl' in response.context_data: response.context_data['cl'].default_changelist_filters = defaults return response @@ -74,3 +40,92 @@ class AtLeastOneRequiredInlineFormSet(BaseInlineFormSet): if not any(cleaned_data and not cleaned_data.get('DELETE', False) for cleaned_data in self.cleaned_data): raise forms.ValidationError('At least one item required.') + + +class ChangeViewActionsMixin(object): + """ Makes actions visible on the admin change view page. """ + + change_view_actions = () + change_form_template = 'orchestra/admin/change_form.html' + + def get_urls(self): + """Returns the additional urls for the change view links""" + urls = super(ChangeViewActionsMixin, self).get_urls() + admin_site = self.admin_site + opts = self.model._meta + new_urls = patterns('') + for action in self.get_change_view_actions(): + new_urls += patterns('', + url('^(\d+)/%s/$' % action.url_name, + admin_site.admin_view(action), + name='%s_%s_%s' % (opts.app_label, + opts.module_name, + action.url_name))) + return new_urls + urls + + @cached + def get_change_view_actions(self): + views = [] + for action in self.change_view_actions: + if isinstance(action, basestring): + action = getattr(self, action) + view = action_to_view(action, self) + view.url_name = getattr(action, 'url_name', action.__name__) + view.verbose_name = getattr(action, 'verbose_name', + view.url_name.capitalize().replace('_', ' ')) + view.css_class = getattr(action, 'css_class', 'historylink') + view.description = getattr(action, 'description', '') + views.append(view) + return views + + def change_view(self, *args, **kwargs): + if not 'extra_context' in kwargs: + kwargs['extra_context'] = {} + kwargs['extra_context']['object_tools_items'] = [ + action.__dict__ for action in self.get_change_view_actions() + ] + return super(ChangeViewActionsMixin, self).change_view(*args, **kwargs) + + +class ChangeAddFieldsMixin(object): + """ Enables to specify different set of fields for change and add views """ + add_fields = () + add_fieldsets = () + add_form = None + change_readonly_fields = () + + def get_readonly_fields(self, request, obj=None): + fields = super(ChangeAddFieldsMixin, self).get_readonly_fields(request, obj=obj) + if obj: + return fields + self.change_readonly_fields + return fields + + def get_fieldsets(self, request, obj=None): + if not obj: + if self.add_fieldsets: + return self.add_fieldsets + elif self.add_fields: + return [(None, {'fields': self.add_fields})] + return super(ChangeAddFieldsMixin, self).get_fieldsets(request, obj=obj) + + def get_inline_instances(self, request, obj=None): + """ add_inlines and inline.parent_object """ + self.inlines = getattr(self, 'add_inlines', self.inlines) + if obj: + self.inlines = type(self).inlines + inlines = super(ChangeAddFieldsMixin, self).get_inline_instances(request, obj=obj) + for inline in inlines: + inline.parent_object = obj + return inlines + + def get_form(self, request, obj=None, **kwargs): + """ Use special form during user creation """ + defaults = {} + if obj is None and self.add_form: + defaults['form'] = self.add_form + defaults.update(kwargs) + return super(ChangeAddFieldsMixin, self).get_form(request, obj, **defaults) + + +class ExtendedModelAdmin(ChangeViewActionsMixin, ChangeAddFieldsMixin, admin.ModelAdmin): + pass diff --git a/orchestra/admin/utils.py b/orchestra/admin/utils.py index bf44e669..a8ec4855 100644 --- a/orchestra/admin/utils.py +++ b/orchestra/admin/utils.py @@ -4,6 +4,7 @@ from django.conf import settings from django.contrib import admin from django.core.urlresolvers import reverse from django.db import models +from django.shortcuts import redirect from django.utils import importlib from django.utils.html import escape from django.utils.safestring import mark_safe @@ -74,6 +75,19 @@ def set_url_query(request, key, value): request.META['QUERY_STRING'] = request.GET.urlencode() +def action_to_view(action, modeladmin): + """ Converts modeladmin action to view function """ + def action_view(request, object_id=1, modeladmin=modeladmin, action=action): + queryset = modeladmin.model.objects.filter(pk=object_id) + response = action(modeladmin, request, queryset) + if not response: + opts = modeladmin.model._meta + url = 'admin:%s_%s_change' % (opts.app_label, opts.module_name) + return redirect(url, object_id) + return response + return action_view + + @admin_field def admin_link(*args, **kwargs): instance = args[-1] diff --git a/orchestra/apps/accounts/models.py b/orchestra/apps/accounts/models.py index 3a9aa0ef..1d555203 100644 --- a/orchestra/apps/accounts/models.py +++ b/orchestra/apps/accounts/models.py @@ -26,6 +26,10 @@ class Account(models.Model): @cached_property def name(self): return self.user.username + + @classmethod + def get_main(cls): + return cls.objects.get(pk=settings.ACCOUNTS_MAIN_PK) services.register(Account, menu=False) diff --git a/orchestra/apps/accounts/settings.py b/orchestra/apps/accounts/settings.py index a6d2d95b..9e52f3c9 100644 --- a/orchestra/apps/accounts/settings.py +++ b/orchestra/apps/accounts/settings.py @@ -19,3 +19,6 @@ ACCOUNTS_LANGUAGES = getattr(settings, 'ACCOUNTS_LANGUAGES', ( ACCOUNTS_DEFAULT_LANGUAGE = getattr(settings, 'ACCOUNTS_DEFAULT_LANGUAGE', 'en') + + +ACCOUNTS_MAIN_PK = getattr(settings, 'ACCOUNTS_MAIN_PK', 1) diff --git a/orchestra/apps/accounts/templates/admin/accounts/account/change_form.html b/orchestra/apps/accounts/templates/admin/accounts/account/change_form.html index 29fb608f..513972c7 100644 --- a/orchestra/apps/accounts/templates/admin/accounts/account/change_form.html +++ b/orchestra/apps/accounts/templates/admin/accounts/account/change_form.html @@ -1,4 +1,4 @@ -{% extends "admin/change_form.html" %} +{% extends "orchestra/admin/change_form.html" %} {% load i18n admin_urls admin_static admin_modify %} @@ -43,9 +43,6 @@
  • {% trans "Disable" %}
  • -
  • - {% url opts|admin_urlname:'history' original.pk|admin_urlquote as history_url %} - {% trans "History" %} -
  • +{{ block.super }} {% endblock %} diff --git a/orchestra/apps/bills/actions.py b/orchestra/apps/bills/actions.py new file mode 100644 index 00000000..4b4d2389 --- /dev/null +++ b/orchestra/apps/bills/actions.py @@ -0,0 +1,3 @@ +def generate_bill(modeladmin, request, queryset): + for bill in queryset: + bill.close() diff --git a/orchestra/apps/bills/admin.py b/orchestra/apps/bills/admin.py index 91c3810d..5002131b 100644 --- a/orchestra/apps/bills/admin.py +++ b/orchestra/apps/bills/admin.py @@ -2,9 +2,11 @@ from django.contrib import admin from django.core.urlresolvers import reverse from django.utils.translation import ugettext_lazy as _ +from orchestra.admin import ExtendedModelAdmin from orchestra.admin.utils import admin_link, admin_date from orchestra.apps.accounts.admin import AccountAdminMixin +from .actions import generate_bill from .filters import BillTypeListFilter from .models import (Bill, Invoice, AmendmentInvoice, Fee, AmendmentFee, Budget, BillLine, BudgetLine) @@ -24,24 +26,26 @@ class BudgetLineInline(admin.TabularInline): ) -class BillAdmin(AccountAdminMixin, admin.ModelAdmin): +class BillAdmin(AccountAdminMixin, ExtendedModelAdmin): list_display = ( - 'ident', 'status', 'bill_type_link', 'account_link', 'created_on_display' + 'ident', 'status', 'type_link', 'account_link', 'created_on_display' ) list_filter = (BillTypeListFilter, 'status',) + change_view_actions = [generate_bill] + change_readonly_fields = ('account', 'type', 'status') readonly_fields = ('ident',) inlines = [BillLineInline] account_link = admin_link('account') created_on_display = admin_date('created_on') - def bill_type_link(self, bill): - bill_type = bill.bill_type.lower() + def type_link(self, bill): + bill_type = bill.type.lower() url = reverse('admin:bills_%s_changelist' % bill_type) - return '%s' % (url, bill.get_bill_type_display()) - bill_type_link.allow_tags = True - bill_type_link.short_description = _("type") - bill_type_link.admin_order_field = 'bill_type' + return '%s' % (url, bill.get_type_display()) + type_link.allow_tags = True + type_link.short_description = _("type") + type_link.admin_order_field = 'type' def get_inline_instances(self, request, obj=None): if self.model is Budget: diff --git a/orchestra/apps/bills/models.py b/orchestra/apps/bills/models.py index 967e1f33..ad62defc 100644 --- a/orchestra/apps/bills/models.py +++ b/orchestra/apps/bills/models.py @@ -1,7 +1,10 @@ from django.db import models +from django.template import loader, Context from django.utils import timezone +from django.utils.functional import cached_property from django.utils.translation import ugettext_lazy as _ +from orchestra.apps.accounts.models import Account from orchestra.core import accounts from . import settings @@ -12,7 +15,7 @@ class BillManager(models.Manager): queryset = super(BillManager, self).get_queryset() if self.model != Bill: bill_type = self.model.get_type() - queryset = queryset.filter(bill_type=bill_type) + queryset = queryset.filter(type=bill_type) return queryset @@ -44,7 +47,7 @@ class Bill(models.Model): blank=True) account = models.ForeignKey('accounts.Account', verbose_name=_("account"), related_name='%(class)s') - bill_type = models.CharField(_("type"), max_length=16, choices=TYPES) + type = models.CharField(_("type"), max_length=16, choices=TYPES) status = models.CharField(_("status"), max_length=16, choices=STATUSES, default=OPEN) created_on = models.DateTimeField(_("created on"), auto_now_add=True) @@ -61,13 +64,25 @@ class Bill(models.Model): def __unicode__(self): return self.ident + @cached_property + def seller(self): + return Account.get_main().invoicecontact + + @cached_property + def buyer(self): + return self.account.invoicecontact + + @property + def lines(self): + return self.billlines + @classmethod def get_type(cls): return cls.__name__.upper() def set_ident(self): cls = type(self) - bill_type = self.bill_type or cls.get_type() + bill_type = self.type or cls.get_type() if bill_type == 'BILL': raise TypeError("get_new_ident() can not be used on a Bill class") # Bill number resets every natural year @@ -91,11 +106,31 @@ class Bill(models.Model): def close(self): self.status = self.CLOSED + self.html = self.render() self.save() + def render(self): + context = Context({ + 'bill': self, + 'lines': self.lines.all(), + 'seller': self.seller, + 'buyer': self.buyer, + 'seller_info': { + 'phone': settings.BILLS_SELLER_PHONE, + 'website': settings.BILLS_SELLER_WEBSITE, + 'email': settings.BILLS_SELLER_EMAIL, + }, + 'currency': settings.BILLS_CURRENCY, + }) + template = getattr(settings, 'BILLS_%s_TEMPLATE' % self.get_type()) + bill_template = loader.get_template(template) + html = bill_template.render(context) + html = html.replace('-pageskip-', '') + return html + def save(self, *args, **kwargs): - if not self.bill_type: - self.bill_type = type(self).get_type() + if not self.type: + self.type = type(self).get_type() if not self.ident or (self.ident.startswith('O') and self.status != self.OPEN): self.set_ident() super(Bill, self).save(*args, **kwargs) @@ -124,6 +159,10 @@ class AmendmentFee(Bill): class Budget(Bill): class Meta: proxy = True + + @property + def lines(self): + return self.budgetlines class BaseBillLine(models.Model): @@ -145,7 +184,7 @@ class BudgetLine(BaseBillLine): class BillLine(BaseBillLine): - order_id = models.PositiveIntegerField(blank=True) + order_id = models.PositiveIntegerField(blank=True, null=True) order_last_bill_date = models.DateTimeField(null=True) order_billed_until = models.DateTimeField(null=True) auto = models.BooleanField(default=False) diff --git a/orchestra/apps/bills/settings.py b/orchestra/apps/bills/settings.py index 45f58ffb..45633671 100644 --- a/orchestra/apps/bills/settings.py +++ b/orchestra/apps/bills/settings.py @@ -1,8 +1,27 @@ from django.conf import settings + BILLS_IDENT_NUMBER_LENGTH = getattr(settings, 'BILLS_IDENT_NUMBER_LENGTH', 4) + BILLS_INVOICE_IDENT_PREFIX = getattr(settings, 'BILLS_INVOICE_IDENT_PREFIX', 'I') + BILLS_AMENDMENT_INVOICE_IDENT_PREFIX = getattr(settings, 'BILLS_AMENDMENT_INVOICE_IDENT_PREFIX', 'A') + BILLS_FEE_IDENT_PREFIX = getattr(settings, 'BILLS_FEE_IDENT_PREFIX', 'F') + BILLS_AMENDMENT_FEE_IDENT_PREFIX = getattr(settings, 'BILLS_AMENDMENT_FEE_IDENT_PREFIX', 'B') + BILLS_BUDGET_IDENT_PREFIX = getattr(settings, 'BILLS_BUDGET_IDENT_PREFIX', 'Q') + + +BILLS_INVOICE_TEMPLATE = getattr(settings, 'BILLS_INVOICE_TEMPLATE', 'bills/invoice.html') + + +BILLS_CURRENCY = getattr(settings, 'BILLS_CURRENCY', 'euro') + + +BILLS_SELLER_PHONE = getattr(settings, 'BILLS_SELLER_PHONE', '111-112-11-222') + +BILLS_SELLER_EMAIL = getattr(settings, 'BILLS_SELLER_EMAIL', 'sales@orchestra.lan') + +BILLS_SELLER_WEBSITE = getattr(settings, 'BILLS_SELLER_WEBSITE', 'www.orchestra.lan') diff --git a/orchestra/apps/bills/templates/bills/altinvoice.html b/orchestra/apps/bills/templates/bills/altinvoice.html new file mode 100644 index 00000000..2888a8a2 --- /dev/null +++ b/orchestra/apps/bills/templates/bills/altinvoice.html @@ -0,0 +1,309 @@ + + +
    + Invoice
    +
    F20110232

    +
    + +
    + + + + +
    + Associacio Pangea - Coordinadora Comunicacio per a la Cooperacio
    + ES2323233
    +
    + Pl eusebi guell 6-7, planta 0
    + 08034 - Barcelona
    + Spain
    +
    +
    + 93-803-21-32
    + sales@pangea.org
    + www.pangea.org
    +
    +
    +
    +
    +
    + DUE DATE
    + Nov 21, 2011 +
    +
    + TOTAL
    + 122,03 € +
    +
    + INVOICE DATE
    + Oct 20, 2012 +
    +
    +
    +
    + Aadults
    + ES01939933
    + Carrer nnoseque, 0
    + 08034 - Barcelona
    + Spain
    +
    +
    + +
    + id + description + hrs/qty + rate/price + subtotal +
    + 1 + Hola que passa + 1 + 1 € + 111 € +
    + 1 + Merda pura + 1 + 1 € + 111 € +
    + 1 + I tu que et passa + 1 + 1 € + 111 € +
    + 1 + Joder hostia puta + 1 + 1 € + 111 € +
    +
    +
    + subtotal + 33,03 € +
    + tax + 33,03 € +
    + total + 33,03 € +
    +
    + + + diff --git a/orchestra/apps/bills/templates/bills/invoice.html b/orchestra/apps/bills/templates/bills/invoice.html new file mode 100644 index 00000000..65ced6b2 --- /dev/null +++ b/orchestra/apps/bills/templates/bills/invoice.html @@ -0,0 +1,217 @@ + + + + +

    {{ bill_type }}

    +
    + + + + + + +
    + {{ buyer.name }}
    + {{ buyer.address }}
    + {{ buyer.zipcode }} {{ buyer.city }}
    + {{ buyer.country }}
    + {{ buyer.vat_number }}
    +
    + Invoice number
    + Date
    + Due date +
    + : {{ bill.ident }}
    + : {{ bill.date|date:"d F, Y" }}
    + : {{ bill.due_on|date:"d F, Y" }}
    +
    +
    +
    + + + + + + + + {% for line in lines %} + + + + + + + {% endfor %} +
    IDDescriptionAmountPrice
    {{ line.order_id }}{{ line.description }} + ({{ line.initial_date|date:"d-m-Y" }}{% if line.initial_date != line.final_date %} - {{ line.final_date|date:"d-m-Y" }}{% endif %}){{ line.amount }}&{{ currency }}; {{ line.price }}
    +
    +
    + + + {% for tax, base in bases.items %} + + + + {% endfor %} + + + {% for tax, value in taxes.items %} + + + + {% endfor %} + + + + + + +
     Subtotal{% if bases.items|length > 1 %} (for {{ tax }}% taxes){% endif %}&{{ currency }}; {{ base }}
     Total {{ tax }}%&{{ currency }}; {{ value }}
     Total&{{ currency }}; {{ total }}
    +
    +
    + + + + + + + + +
    IBAN + Invoice ID + Amount {{ currency.upper }} +
    NL28INGB0004954664{{ bill.ident }}{{ total }}
    +

    The invoice is to be paid before {{ invoice.exp_date|date:"F jS, Y" }} with the mention of the invoice id.

    +
    +
    + + + + + + + + +
    + {{ seller.name }}
    + {{ seller.address }}
    + {{ seller.city }}
    + {{ seller.country }}
    +
    + Tel
    + Web
    + Email
    +
    + {{ seller_info.phone }}
    + {{ seller_info.website }}
    + {{ seller_info.email }} +
    + Bank ING
    + IBAN
    + BTW
    + KvK
    +
    + 4954664
    + NL28INGB0004954664
    + NL 8207.29.449.B01
    + 27343027 +
    +
    + Payment info + + + + diff --git a/orchestra/apps/bills/templates/bills/plans/PL_EN.html b/orchestra/apps/bills/templates/bills/plans/PL_EN.html new file mode 100644 index 00000000..b190a377 --- /dev/null +++ b/orchestra/apps/bills/templates/bills/plans/PL_EN.html @@ -0,0 +1,5 @@ +{% extends 'bills/plans/invoice_base.html' %} +{% block title %}{{ invoice.full_number }}{% endblock %} +{% block body %} +{% include 'bills/plans/PL_EN_layout.html' %} +{% endblock %} diff --git a/orchestra/apps/bills/templates/bills/plans/PL_EN_layout.html b/orchestra/apps/bills/templates/bills/plans/PL_EN_layout.html new file mode 100644 index 00000000..b8d9b09d --- /dev/null +++ b/orchestra/apps/bills/templates/bills/plans/PL_EN_layout.html @@ -0,0 +1,171 @@ + {% if logo_url %} + company logo + {% endif %} + +
    +

    + {{ invoice.full_number }} +

    +

    {% if not copy %}ORYGINAŁ{% else %}KOPIA{% endif %}

    +

    {{ invoice.issued|date:"Y-m-d" }}

    + {% if invoice.type != invoice.INVOICE_TYPES.PROFORMA %}{# Not a PROFORMA #} +

    {{ invoice.selling_date|date:"Y-m-d" }}

    + {% else %} +

     

    + {% endif %} +
    + + + + + + + + + + +
    + +

    + + {{ buyer.name }}
    + {{ buyer.address }}
    + {{ buyer.zipcode }}
    + {{ buyer.country }} + + +
    + +

    + {{ seller.name }}
    + {{ seller.address }}
    + {{ seller.zipcode }}
    + {{ seller.country }}

    + {{ invoice.issuer_tax_number }}
    +

    + +

    + {{ buyer.name }}
    + {{ buyer.address }}
    + {{ buyer.zipcode }}
    + {{ buyer.country }} + + {% if invoice.buyer_tax_number %} +

    + + {{ invoice.buyer_tax_number }} + +

    + {% endif %} +
    +
    + + + + + + + + + + + {% if invoice.rebate %} + + + {% endif %} + + + + + + + + + + + + + + + {% if invoice.rebate %} + + {% endif %} + + + + + + + + + + + + + + + +
    + L.p. + + Nazwa usługi
    + Description + +
    + Cena j. netto
    + Unit price + +
    + + Ilość
    + Qty. +
    + J.m. + + Rabat
    Rebate + +
    + Wartość netto
    + Subtotal +
    + VAT
    TAX +
    + + Kwota VAT
    TAX/VAT Amount +
    + + Wartość brutto
    + Subtotal with TAX/VAT +
    + 1 + {{ invoice.item_description }}{{ invoice.unit_price_net|floatformat:2 }} {{ invoice.currency }}{{ invoice.quantity }}sztuk
    units
    {{ invoice.rebate|floatformat:2 }} %{{ invoice.total_net|floatformat:2 }} {{ invoice.currency }}{% if invoice.tax != None %}{{ invoice.tax|floatformat:2 }} %{% else %}n.p.
    n/a{% endif %}
    {% if invoice.tax_total != None %}{{ invoice.tax_total|floatformat:2 }} {{ invoice.currency }}{% else %}n.p.
    n/a{% endif %}
    {{ invoice.total|floatformat:2 }} {{ invoice.currency }}
    {{ invoice.total_net|floatformat:2 }} {{ invoice.currency }}{% if invoice.tax != None %}{{ invoice.tax|floatformat:2 }} %{% else %}n.p.
    n/a{% endif %}
    {% if invoice.tax_total != None %}{{ invoice.tax_total|floatformat:2 }} {{ invoice.currency }}{% else %}n.p.
    n/a{% endif %}
    {{ invoice.total|floatformat:2 }} {{ invoice.currency }}
    +
    + + {% if invoice.type != invoice.INVOICE_TYPES.PROFORMA %} +

    + {% endif %} + + + + + {% if invoice.type == invoice.INVOICE_TYPES.PROFORMA %} + + + {% else %} + + {% endif %} + + {{ invoice.payment_date|date:"Y-m-d" }} +

    +
    + + {% if invoice.type == invoice.INVOICE_TYPES.PROFORMA %}

    Ten dokument nie jest fakturą VAT (nie jest dokumentem księgowym).This document is not an invoice.

    {% endif %} + + {% if invoice.tax == None and invoice.is_UE_customer %} +

    + Odwrotne obciążenie.-Reverse charge. +

    + {% endif %} + + +
    diff --git a/orchestra/apps/bills/templates/bills/plans/invoice_base.html b/orchestra/apps/bills/templates/bills/plans/invoice_base.html new file mode 100644 index 00000000..725192e2 --- /dev/null +++ b/orchestra/apps/bills/templates/bills/plans/invoice_base.html @@ -0,0 +1,83 @@ + + + + {% block title %}{% endblock %} + + + + {% block head %}{% endblock %} + + + +{% block body %}{% endblock %} + + diff --git a/orchestra/apps/payments/admin.py b/orchestra/apps/payments/admin.py index fbff6333..f69cebe5 100644 --- a/orchestra/apps/payments/admin.py +++ b/orchestra/apps/payments/admin.py @@ -30,6 +30,10 @@ class TransactionAdmin(admin.ModelAdmin): bill_link = admin_link('bill') account_link = admin_link('bill__account') display_state = admin_colored('state', colors=STATE_COLORS) + + def get_queryset(self, request): + qs = super(TransactionAdmin, self).get_queryset(request) + return qs.select_related('source', 'bill__account__user') class PaymentSourceAdmin(AccountAdminMixin, admin.ModelAdmin): @@ -54,7 +58,8 @@ class PaymentProcessAdmin(admin.ModelAdmin): ids = [] lines = [] counter = 0 - tx_ids = process.transactions.order_by('id').values_list('id', flat=True) + # Because of values_list this query doesn't benefit from prefetch_related + tx_ids = process.transactions.values_list('id', flat=True) for tx_id in tx_ids: ids.append(str(tx_id)) counter += 1 diff --git a/orchestra/templates/orchestra/admin/change_form.html b/orchestra/templates/orchestra/admin/change_form.html new file mode 100644 index 00000000..778d5a73 --- /dev/null +++ b/orchestra/templates/orchestra/admin/change_form.html @@ -0,0 +1,10 @@ +{% extends "admin/change_form.html" %} +{% load i18n admin_urls utils %} + + +{% block object-tools-items %} +{% for item in object_tools_items %} +
  • {{ item.verbose_name }}
  • +{% endfor %} +{{ block.super }} +{% endblock %} diff --git a/orchestra/templatetags/utils.py b/orchestra/templatetags/utils.py index f7a9733e..da485c06 100644 --- a/orchestra/templatetags/utils.py +++ b/orchestra/templatetags/utils.py @@ -44,4 +44,3 @@ def size(value, length): @register.filter(name='is_checkbox') def is_checkbox(field): return isinstance(field.field.widget, CheckboxInput) -