From 75c72ce8a56adf55e788ec1c2382d02daba282ca Mon Sep 17 00:00:00 2001 From: Marc Aymerich Date: Tue, 7 Jul 2015 10:41:34 +0000 Subject: [PATCH] Preliminar implementation of amended invoices --- orchestra/contrib/bills/actions.py | 33 +- orchestra/contrib/bills/admin.py | 26 +- orchestra/contrib/bills/filters.py | 24 +- .../bills/locale/ca/LC_MESSAGES/django.mo | Bin 6962 -> 7618 bytes .../bills/locale/ca/LC_MESSAGES/django.po | 281 ++++++++++----- .../bills/locale/es/LC_MESSAGES/django.mo | Bin 3100 -> 4327 bytes .../bills/locale/es/LC_MESSAGES/django.po | 340 +++++++++++------- orchestra/contrib/bills/models.py | 41 ++- .../bills/templates/admin/bills/report.html | 60 ++++ orchestra/contrib/websites/forms.py | 14 +- orchestra/contrib/websites/validators.py | 8 + orchestra/templatetags/utils.py | 4 + 12 files changed, 570 insertions(+), 261 deletions(-) create mode 100644 orchestra/contrib/bills/templates/admin/bills/report.html diff --git a/orchestra/contrib/bills/actions.py b/orchestra/contrib/bills/actions.py index dc5d8618..b8dc3e7d 100644 --- a/orchestra/contrib/bills/actions.py +++ b/orchestra/contrib/bills/actions.py @@ -17,6 +17,7 @@ from orchestra.admin.forms import adminmodelformset_factory from orchestra.admin.utils import get_object_from_url, change_url from orchestra.utils.html import html_to_pdf +from . import settings from .forms import SelectSourceForm from .helpers import validate_contact from .models import Bill, BillLine @@ -217,7 +218,7 @@ def amend_bills(modeladmin, request, queryset): if queryset.filter(is_open=True).exists(): messages.warning(request, _("Selected bills should be in closed state")) return - ids = [] + amend_ids = [] for bill in queryset: with translation.override(bill.account.language): amend_type = bill.get_amend_type() @@ -239,17 +240,33 @@ def amend_bills(modeladmin, request, queryset): line = BillLine.objects.create( bill=amend, start_on=bill.created_on, - description=_("Amend of %(related_type)s %(number)s, tax %(tax)s%%") % context, + description=_("%(related_type)s %(number)s subtotal for tax %(tax)s%%") % context, subtotal=subtotals[0], tax=tax ) - ids.append(bill.pk) - amend_url = reverse('admin:bills_bill_changelist') - amend_url += '?id=%s' % ','.join(map(str, ids)) + amend_ids.append(amend.pk) + num = len(amend_ids) + if num == 1: + amend_url = reverse('admin:bills_bill_change', args=amend_ids) + else: + amend_url = reverse('admin:bills_bill_changelist') + amend_url += '?id=%s' % ','.join(map(str, amend_ids)) + context = { + 'url': amend_url, + 'num': num, + } messages.success(request, mark_safe(ungettext( - _('One amendment bill have been generated.') % amend_url, - _('%i amendment bills have been generated.') % (amend_url, len(ids)), - len(ids) + _('One amendment bill have been generated.') % context, + _('%(num)i amendment bills have been generated.') % context, + num ))) amend_bills.verbose_name = _("Amend") amend_bills.url_name = 'amend' + + +def report(modeladmin, request, queryset): + context = { + 'bills': queryset, + 'currency': settings.BILLS_CURRENCY, + } + return render(request, 'admin/bills/report.html', context) diff --git a/orchestra/contrib/bills/admin.py b/orchestra/contrib/bills/admin.py index 427527e3..4c5c55fb 100644 --- a/orchestra/contrib/bills/admin.py +++ b/orchestra/contrib/bills/admin.py @@ -186,10 +186,10 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin): 'num_lines', 'display_total', 'display_payment_state', 'is_open', 'is_sent' ) list_filter = (BillTypeListFilter, 'is_open', 'is_sent', TotalListFilter, PaymentStateListFilter) - add_fields = ('account', 'type', 'is_open', 'due_on', 'comments') + add_fields = ('account', 'type', 'amend_of', 'is_open', 'due_on', 'comments') fieldsets = ( (None, { - 'fields': ('number', 'type', 'account_link', 'display_total', + 'fields': ('number', 'type', 'amend_of_link', 'account_link', 'display_total', 'display_payment_state', 'is_sent', 'due_on', 'comments'), }), (_("Raw"), { @@ -205,13 +205,14 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin): ] actions = [ actions.manage_lines, actions.download_bills, actions.close_bills, actions.send_bills, - actions.amend_bills, + actions.amend_bills, actions.report ] - change_readonly_fields = ('account_link', 'type', 'is_open') + change_readonly_fields = ('account_link', 'type', 'is_open', 'amend_of_link') readonly_fields = ('number', 'display_total', 'is_sent', 'display_payment_state') inlines = [BillLineInline, ClosedBillLineInline] created_on_display = admin_date('created_on', short_description=_("Created")) + amend_of_link = admin_link('amend_of') def num_lines(self, bill): return bill.lines__count @@ -267,8 +268,12 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin): def get_fieldsets(self, request, obj=None): fieldsets = super(BillAdmin, self).get_fieldsets(request, obj) - if obj and obj.is_open: - fieldsets = (fieldsets[0],) + if obj: +# if obj.amend_of_id: +# fieldsets = list(fieldsets) +# fieldsets[0][1]['fields'] = fieldsets[0][1]['fields'] + ('amend_of_link',) + if obj.is_open: + fieldsets = (fieldsets[0],) return fieldsets def get_change_view_actions(self, obj=None): @@ -289,10 +294,13 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin): """ Make value input widget bigger """ if db_field.name == 'comments': kwargs['widget'] = forms.Textarea(attrs={'cols': 70, 'rows': 4}) - if db_field.name == 'html': + elif db_field.name == 'html': kwargs['widget'] = forms.Textarea(attrs={'cols': 150, 'rows': 20}) - return super(BillAdmin, self).formfield_for_dbfield(db_field, **kwargs) - + formfield = super(BillAdmin, self).formfield_for_dbfield(db_field, **kwargs) + if db_field.name == 'amend_of': + formfield.queryset = formfield.queryset.filter(is_open=False) + return formfield + def get_queryset(self, request): qs = super(BillAdmin, self).get_queryset(request) qs = qs.annotate( diff --git a/orchestra/contrib/bills/filters.py b/orchestra/contrib/bills/filters.py index aad3d800..8df9ffee 100644 --- a/orchestra/contrib/bills/filters.py +++ b/orchestra/contrib/bills/filters.py @@ -4,6 +4,8 @@ from django.db.models import Q from django.utils.safestring import mark_safe from django.utils.translation import ugettext_lazy as _ +from . models import Bill + class BillTypeListFilter(SimpleListFilter): """ Filter tickets by created_by according to request.user """ @@ -89,6 +91,7 @@ class PaymentStateListFilter(SimpleListFilter): ('PAID', _("Paid")), ('PENDING', _("Pending")), ('BAD_DEBT', _("Bad debt")), + ('AMENDED', _("Amended")), ) def queryset(self, request, queryset): @@ -96,10 +99,12 @@ class PaymentStateListFilter(SimpleListFilter): if self.value() == 'OPEN': return queryset.filter(Q(is_open=True)|Q(type=queryset.model.PROFORMA)) elif self.value() == 'PAID': - zeros = queryset.filter(computed_total=0, computed_total__isnull=True).values_list('id', flat=True) + zeros = queryset.filter(computed_total=0, computed_total__isnull=True) + zeros = zeros.values_list('id', flat=True) ammounts = Transaction.objects.exclude(bill_id__in=zeros).secured().group_by('bill_id') paid = [] - for bill_id, total in queryset.exclude(computed_total=0, computed_total__isnull=True, is_open=True).values_list('id', 'computed_total'): + relevant = queryset.exclude(computed_total=0, computed_total__isnull=True, is_open=True) + for bill_id, total in relevant.values_list('id', 'computed_total'): try: ammount = sum([t.ammount for t in ammounts[bill_id]]) except KeyError: @@ -107,7 +112,11 @@ class PaymentStateListFilter(SimpleListFilter): else: if abs(total) <= abs(ammount): paid.append(bill_id) - return queryset.filter(Q(computed_total=0)|Q(computed_total__isnull=True)|Q(id__in=paid)).exclude(is_open=True) + return queryset.filter( + Q(computed_total=0) | + Q(computed_total__isnull=True) | + Q(id__in=paid) + ).exclude(is_open=True) elif self.value() == 'PENDING': has_transaction = queryset.exclude(transactions__isnull=True) non_rejected = has_transaction.exclude(transactions__state=Transaction.REJECTED) @@ -115,4 +124,11 @@ class PaymentStateListFilter(SimpleListFilter): return queryset.filter(pk__in=non_rejected) elif self.value() == 'BAD_DEBT': closed = queryset.filter(is_open=False).exclude(computed_total=0) - return closed.filter(Q(transactions__state=Transaction.REJECTED)|Q(transactions__isnull=True)) + return closed.filter( + Q(transactions__state=Transaction.REJECTED) | + Q(transactions__isnull=True) + ) + elif self.value() == 'AMENDED': + amendeds = queryset.filter(type__in=Bill.AMEND_MAP.values(), is_open=False) + amendeds_ids = amendeds.values_list('amend_of', flat=True) + return queryset.filter(id__in=amendeds) diff --git a/orchestra/contrib/bills/locale/ca/LC_MESSAGES/django.mo b/orchestra/contrib/bills/locale/ca/LC_MESSAGES/django.mo index 0e97045d1b0dc71795f321951cfb87dfbce934a4..943758b54fd324ee247b379a5bb2965dbf43c73b 100644 GIT binary patch delta 2847 zcmaLY32anF9LMo#sajf&QpyeX2`z2WQp#0?Qk51I5n6mX5JjH z?~MO=l7A{cYqKGRiNVDBF2?A>U)S?PYUparPdFPtz{xqr)Zk^z$Ld^TDzF~MqmNT? z5BA4DY&pN1F?Ez1aWZa2+VRbCGBbD*bm>g z_hnlEW#a_iAPa$bPOH*5jCy3J&oy&`S=J9NAHqn_7_%WPDCd8nBm z=Vb-APZ9om=dDQbSqXw|e zmi?V%)WL34#l5J39JJRzvwnqY;3%qr4pjX!xDLCGl#HBLbd*vA`jt@Tx;N%Ilv zpdLdFybq^Y+uy+)`Zu*?Gy}6xQ#lvafs0z(X4G0mQ4Op{EzwhW6K+LK`99=j%@?Sd z`WA=a8Prl{@^*SY2ieD_4|b)0Q%pv?x&lXIJ?hx4vDcr(5tO%~8u}V_?2h9`Jcmq< zSv@fQBAZc5^P(-kZp&|>o=>7)Y%luCd}1#gMonc0YNRJo4V}euyolqla8TMt)SEty zYG(tg{#MkCzGi*Xx&zhzZtHu4n15B=Z!5H;-u!da5*$K}_y}tAoUr8!$bK`IQ1!C; z;%fjsQ3D@= z^>3g$*l+z5HK4<&0d$}`K5fhAQ2qSklhK2JTC>hdof`1dV#6wyq{WE<#m*FjFj}|or#Y`>b!QAOUUR0 zqSTzmEW?F1FEq8c6SD}9c!*HasnHQ1OFT%F>-=j+cc%NuOdyInFQC>}Ny{>g(2m!d zD$OPCAZiJn{TYNdW|#;O`cBLvw6`V^w-H)KC4HQ#>EBclb%b`V(k;YHVh-UDe7LTp z;Wk%{6N$;T%s0j?wRt`Vrp)HG2^)xeYZ}W{fU<^OAi9&l()DWe~%h_Wx{nDt{ z;>Nw;;`qwtURlg3E)B0}4R}$qu@!-MB<{92OCnJx?mo&re#>IT#fDtEdSnS#uik_k z4mwRy&y9y7VJApG)D~qiQyg*vp_atB-0~8))e8rSxURIssw>US za;nNlRXLTTCRR>Jy1mY2`qz4^D--iS6B5)bUSmR91DXfdp`IqDDb$|)cTVP%G`nHP w3%7+5^9vRxmK8*k+Y4r7B@+D$lc)Pn&*;_QF4bhkHT3qkBS9}wGw|1c058#AzW@LL delta 2418 zcmX}ue`wTo9LMp`-KNgDJe~WYo7v57+O%tW^XBF}mp0v2&Zf0Af0f?d*0tcaa|SAL zgk)AL%&Yxn2o?r4`(qb~{b9BO38E;32w6lFB&irkK?(JI-Mz05zW4ZiKKJ>4KA-RB z#}_BtCYsYfmKVS6NC{#g5i4>|730(SqcqdG-@${Q;tgDf=SrO`$LrXL|KfaXoaI~x zcA?t6jx+EpRJ$w45U#k)xx1W8yOMmyRpDHI2%s7^V+r0{Q30_@e>d zMYaDB8N+>SR25N15sI{AKE<%kMK=s>z?4t|%SQ!1#LWKs1Sc4AKz+Gl9 zvV3j_s(lW|KWe^(8t@cqjo(L&m-VqRn$agz%)yJO2fsnR!S|>E zr%(_4Y~^dH3Ef0(#y|G^Y&v04TqSBE0o3zB)C59Sj-dMAkS3#sU8sp{wIBM-U8oNB zp*rYCwI9agco+vgJjq?QWNV^I$>cEe@ zj9ZR%xE49WZZ9$y_Z(^^Uc|fc7;3BDLp?u^?1DRw8sIYOuzrgn`~!7=0$d8!FU4iL zn4M(QQ3`c|4&$5n2J(#a^S#lVtVS(;#LAnjyczX;A8Oz|=72ejTFE1*iKbEgyoy1_ zcNsEa{0#Mmf1x_MgK9XP4budCW~Et+xh*p5t-ir*M7?nswe&5hiMFH8L?@>bo~I&1Y4sP-4I7QaCC|C9L}YQndye0u@wua56f zp}q6gmngwl3GSBIwV0b&QSf!IrE8y_Q{A(jwIONlt4H)luxPY;vx zYX7$p1H|J53z17Yc)KjGCDjR5(v?%{A@<~Q`8R&h^7{BF>4s>w2X|db0MYg zB^R_()a=KE<;6y#jp!zxCJISQ*i7jA@g&he=p$1|ePmV<{z3)n;3@4U*6aT3#A{EL z9wqegSx4wwu!+z&Kq*G(ENmsXnYpxvzuG<}9pDzCiD)NQ6H50HUBnhb|MSolFQmn} zb*EHUs4?}y*>8D1T&~{oI%JO!omSq7PguSe8;Q+?PJbb_k_lvcW;S^yp;rHj)-_G7*(>GOy_s-T pG&5Bd%(hk66lY(n4S6bu;;GNiC1Y_nl!_np=leLA`Fi2){{RMZ$n5|C diff --git a/orchestra/contrib/bills/locale/ca/LC_MESSAGES/django.po b/orchestra/contrib/bills/locale/ca/LC_MESSAGES/django.po index 67cc7bf4..eb755540 100644 --- a/orchestra/contrib/bills/locale/ca/LC_MESSAGES/django.po +++ b/orchestra/contrib/bills/locale/ca/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-05-29 09:39+0000\n" +"POT-Creation-Date: 2015-07-07 10:18+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -18,37 +18,37 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: actions.py:37 +#: actions.py:40 msgid "Download" msgstr "Descarrega" -#: actions.py:47 +#: actions.py:50 msgid "View" msgstr "Vista" -#: actions.py:55 +#: actions.py:58 msgid "Selected bills should be in open state" msgstr "Les factures seleccionades han d'estar en estat obert" -#: actions.py:73 +#: actions.py:76 msgid "Selected bills have been closed" msgstr "Les factures seleccionades han estat tancades" -#: actions.py:86 +#: actions.py:89 #, python-format msgid "One related transaction has been created" msgstr "S'ha creat una transacció" -#: actions.py:87 +#: actions.py:90 #, python-format msgid "%(num)i related transactions have been created" msgstr "S'han creat les %(num)i següents transaccions" -#: actions.py:93 +#: actions.py:96 msgid "Are you sure about closing the following bills?" msgstr "Estàs a punt de tancar les següents factures, estàs segur?" -#: actions.py:94 +#: actions.py:97 msgid "" "Once a bill is closed it can not be further modified.

Please select a " "payment source for the selected bills" @@ -56,138 +56,183 @@ msgstr "" "Una vegada la factura estigui tancada no podrà ser modificada.

Si us " "plau selecciona un mètode de pagament per les factures seleccionades" -#: actions.py:107 +#: actions.py:110 msgid "Close" msgstr "Tanca" -#: actions.py:125 +#: actions.py:124 msgid "One bill has been sent." msgstr "S'ha creat una factura" -#: actions.py:126 +#: actions.py:125 #, python-format msgid "%i bills have been sent." msgstr "S'han enviat %i factures." -#: actions.py:128 +#: actions.py:127 msgid "Resend" msgstr "Reenviat" -#: actions.py:189 +#: actions.py:188 #, python-format msgid "%(norders)s orders and %(nlines)s lines undoed." msgstr "%(norders)s ordres i %(nlines)s línies desfetes." -#: actions.py:208 +#: actions.py:207 msgid "Lines moved" msgstr "Línies mogudes" -#: admin.py:49 admin.py:93 admin.py:128 forms.py:11 +#: actions.py:219 +msgid "Selected bills should be in closed state" +msgstr "Les factures seleccionades han d'estar en estat obert" + +#: actions.py:236 +#, python-format +msgid "%(type)s of %(related_type)s %(number)s and creation date %(date)s" +msgstr "%(type)s de %(related_type)s %(number)s amb data de creació %(date)s" + +#: actions.py:243 +#, python-format +msgid "%(related_type)s %(number)s subtotal for tax %(tax)s%%" +msgstr "%(related_type)s %(number)s subtotal %(tax)s%%" + +#: actions.py:259 +#, python-format +msgid "One amendment bill have been generated." +msgstr "S'ha creat una transacció" + +#: actions.py:260 +#, python-format +msgid "%(num)i amendment bills have been generated." +msgstr "S'han creat les %(num)i següents transaccions" + +#: actions.py:263 +msgid "Amend" +msgstr "" + +#: admin.py:54 admin.py:98 admin.py:133 forms.py:11 +#: templates/admin/bills/report.html:43 msgid "Total" msgstr "Total" -#: admin.py:80 +#: admin.py:85 msgid "Description" msgstr "Descripció" -#: admin.py:88 +#: admin.py:93 msgid "Subtotal" msgstr "Subtotal" -#: admin.py:118 +#: admin.py:123 msgid "Is open" msgstr "És oberta" -#: admin.py:123 +#: admin.py:128 msgid "Subline" msgstr "Sublínia" -#: admin.py:157 +#: admin.py:162 msgid "No bills selected." msgstr "No hi ha factures seleccionades" -#: admin.py:164 +#: admin.py:169 #, python-format msgid "Manage %s bill lines." msgstr "Gestiona %s línies de factura." -#: admin.py:166 +#: admin.py:171 msgid "Bill not in open state." msgstr "La factura no està en estat obert" -#: admin.py:169 +#: admin.py:174 msgid "Not all bills are in open state." msgstr "No totes les factures estan en estat obert" -#: admin.py:170 +#: admin.py:175 msgid "Manage bill lines of multiple bills." msgstr "Gestiona línies de factura de multiples factures." -#: admin.py:190 +#: admin.py:195 msgid "Raw" msgstr "Raw" -#: admin.py:208 +#: admin.py:214 models.py:72 msgid "Created" msgstr "Creada" -#: admin.py:213 +#: admin.py:220 msgid "lines" msgstr "línies" -#: admin.py:218 templates/bills/microspective.html:118 +#: admin.py:225 filters.py:44 templates/bills/microspective.html:118 msgid "total" msgstr "total" -#: admin.py:226 models.py:88 models.py:352 +#: admin.py:233 models.py:103 models.py:446 msgid "type" msgstr "tipus" -#: admin.py:243 +#: admin.py:250 msgid "Payment" msgstr "Pagament" -#: filters.py:17 +#: filters.py:19 msgid "All" msgstr "Tot" -#: filters.py:18 models.py:78 +#: filters.py:20 models.py:87 msgid "Invoice" msgstr "Factura" -#: filters.py:19 models.py:79 +#: filters.py:21 models.py:88 msgid "Amendment invoice" msgstr "Factura rectificativa" -#: filters.py:20 models.py:80 +#: filters.py:22 models.py:89 msgid "Fee" msgstr "Quota de soci" -#: filters.py:21 +#: filters.py:23 msgid "Amendment fee" msgstr "Rectificació de quota de soci" -#: filters.py:22 +#: filters.py:24 msgid "Pro-forma" msgstr "Pro-forma" -#: filters.py:41 -msgid "positive price" -msgstr "preu positiu" - -#: filters.py:46 filters.py:64 -msgid "Yes" -msgstr "Si" - -#: filters.py:47 filters.py:65 -msgid "No" -msgstr "No" - -#: filters.py:59 +#: filters.py:66 msgid "has bill contact" msgstr "té contacte de facturació" -#: forms.py:9 +#: filters.py:71 +msgid "Yes" +msgstr "Si" + +#: filters.py:72 +msgid "No" +msgstr "No" + +#: filters.py:83 +msgid "payment state" +msgstr "Pagament" + +#: filters.py:88 models.py:71 +msgid "Open" +msgstr "" + +#: filters.py:89 models.py:75 +msgid "Paid" +msgstr "Pagat" + +#: filters.py:90 +msgid "Pending" +msgstr "Pendent" + +#: filters.py:91 models.py:78 +msgid "Bad debt" +msgstr "Incobrable" + +#: forms.py:9 templates/admin/bills/report.html:37 msgid "Number" msgstr "Número" @@ -219,7 +264,7 @@ msgstr "Relacionat" msgid "Main" msgstr "Principal" -#: models.py:23 models.py:86 +#: models.py:23 models.py:99 msgid "account" msgstr "compte" @@ -251,141 +296,186 @@ msgstr "Introdueix un codi postal vàlid." msgid "country" msgstr "país" -#: models.py:35 +#: models.py:35 templates/admin/bills/report.html:38 msgid "VAT number" msgstr "NIF" -#: models.py:67 -msgid "Paid" -msgstr "Pagat" +#: models.py:73 +msgid "Processed" +msgstr "" -#: models.py:68 -msgid "Pending" -msgstr "Pendent" +#: models.py:74 +#, fuzzy +#| msgid "amended line" +msgid "Amended" +msgstr "línia rectificada" -#: models.py:69 -msgid "Bad debt" -msgstr "Incobrable" +#: models.py:76 +msgid "Incomplete" +msgstr "" -#: models.py:81 +#: models.py:77 +msgid "Executed" +msgstr "" + +#: models.py:90 msgid "Amendment Fee" msgstr "Rectificació de quota de soci" -#: models.py:82 +#: models.py:91 msgid "Pro forma" msgstr "Pro forma" -#: models.py:85 +#: models.py:98 msgid "number" msgstr "número" -#: models.py:89 +#: models.py:101 +#, fuzzy +#| msgid "amended line" +msgid "amend of" +msgstr "línia rectificada" + +#: models.py:104 msgid "created on" msgstr "creat el" -#: models.py:90 +#: models.py:105 msgid "closed on" msgstr "tancat el" -#: models.py:91 +#: models.py:106 msgid "open" msgstr "obert" -#: models.py:92 +#: models.py:107 msgid "sent" msgstr "enviat" -#: models.py:93 +#: models.py:108 msgid "due on" msgstr "es deu" -#: models.py:94 +#: models.py:109 msgid "updated on" msgstr "actualitzada el" -#: models.py:97 +#: models.py:112 msgid "comments" msgstr "comentaris" -#: models.py:98 +#: models.py:113 msgid "HTML" msgstr "HTML" -#: models.py:285 +#: models.py:192 +#, python-format +msgid "Type %s is not an amendment." +msgstr "" + +#: models.py:194 +msgid "Amend of related account doesn't match bill account." +msgstr "" + +#: models.py:199 +#, python-format +msgid "Type %s requires an amend of link." +msgstr "" + +#: models.py:378 msgid "bill" msgstr "factura" -#: models.py:286 models.py:350 templates/bills/microspective.html:73 +#: models.py:379 models.py:444 templates/bills/microspective.html:73 msgid "description" msgstr "descripció" -#: models.py:287 +#: models.py:380 msgid "rate" msgstr "tarifa" -#: models.py:288 +#: models.py:381 msgid "quantity" msgstr "quantitat" -#: models.py:289 +#: models.py:383 #, fuzzy #| msgid "quantity" msgid "Verbose quantity" msgstr "quantitat" -#: models.py:290 templates/bills/microspective.html:77 +#: models.py:384 templates/bills/microspective.html:77 #: templates/bills/microspective.html:111 msgid "subtotal" msgstr "subtotal" -#: models.py:291 +#: models.py:385 msgid "tax" msgstr "impostos" -#: models.py:292 +#: models.py:386 msgid "start" msgstr "iniciar" -#: models.py:293 +#: models.py:387 msgid "end" msgstr "finalitzar" -#: models.py:295 +#: models.py:389 msgid "Informative link back to the order" msgstr "Enllaç informatiu de l'ordre" -#: models.py:296 +#: models.py:390 msgid "order billed" msgstr "ordre facturada" -#: models.py:297 +#: models.py:391 msgid "order billed until" msgstr "ordre facturada fins a" -#: models.py:298 +#: models.py:392 msgid "created" msgstr "creada" -#: models.py:300 +#: models.py:394 msgid "amended line" msgstr "línia rectificada" -#: models.py:343 +#: models.py:437 msgid "Volume" msgstr "Volum" -#: models.py:344 +#: models.py:438 msgid "Compensation" msgstr "Compensació" -#: models.py:345 +#: models.py:439 msgid "Other" msgstr "Altre" -#: models.py:349 +#: models.py:443 msgid "bill line" msgstr "línia de factura" +#: templates/admin/bills/report.html:39 +msgid "Contact" +msgstr "" + +#: templates/admin/bills/report.html:40 +#, fuzzy +#| msgid "Due date" +msgid "Close date" +msgstr "Data de pagament" + +#: templates/admin/bills/report.html:41 +msgid "Base" +msgstr "" + +#: templates/admin/bills/report.html:42 templates/bills/microspective.html:111 +#: templates/bills/microspective.html:114 +msgid "VAT" +msgstr "IVA" + #: templates/bills/microspective-fee.html:107 msgid "Due date" msgstr "Data de pagament" @@ -433,11 +523,6 @@ msgstr "hrs/qnt" msgid "rate/price" msgstr "tarifa/preu" -#: templates/bills/microspective.html:111 -#: templates/bills/microspective.html:114 -msgid "VAT" -msgstr "IVA" - #: templates/bills/microspective.html:114 msgid "taxes" msgstr "impostos" @@ -451,6 +536,7 @@ msgid "PAYMENT" msgstr "PAGAMENT" #: templates/bills/microspective.html:140 +#, python-format msgid "" "\n" " You can pay our %(type)s by bank transfer.
\n" @@ -483,3 +569,6 @@ msgstr "" " contacta amb nosaltres a %(email)s. Et respondrem el més " "ràpidament possible.\n" " " + +#~ msgid "positive price" +#~ msgstr "preu positiu" diff --git a/orchestra/contrib/bills/locale/es/LC_MESSAGES/django.mo b/orchestra/contrib/bills/locale/es/LC_MESSAGES/django.mo index 258c2c4c2ca0b50aee0744613f6d614dba7082f8..a9cc33e6e0c7562990a2cf30181a618a681059d6 100644 GIT binary patch delta 2107 zcmZ{kU5Hd=7{?!NcXiit(=5$QM{RYt(p|IE6qI(iEO1*hlOm+fob!%*Xy%;RGiP=) zf*}aPZX~9lFsZBR#tOs>Met%1ybuYmq@WLwq9h11=;Nl}f9Bo2>t&wvdq1A@KF{-i z4)fuPpVv0NTiWrSF?M0Au$%MD#^L2#crX@rn%#ii@B;i3E`?{h%!c75I0_dnFxw6H z!`t9-xCEYrgYXO30l$X};dRJW4f~DBt!(@O`L#~vu#kti#B(K7PV3=vxDo2YJy7qB z!aLwTI0;{d^1BKNWWPfB{qFe}Ttj?HGur?PP>y?{0*%4j;WJQyijZRM2;^N$pc0?< z=WjxOcFeO0<#!S)@F%bbehwGGv#?9?FEUZU%TSJA>i}MZEy?`)I@EfRN8e--3s>v+6En>WG6aW+06o%3*jawOqTa4-A^J_?6v=vMeT)cKe2 zPWTlZf!{zSx_WWDvh`2}3_^}<8&pE0{(Q`@Us#O(awxLF&#FA`feok&Pe3X1F;w8s z;2ZEfyc_QBZTmg%dDycEALRT9RO0VKDRT)*ktUabFv@9&W?*{noVW740+(CJh2XXcIc z_2);cZlG33ONGAv%$0-OMKAfizW$_MDY}f~Ac+cL=7Ky&HvXv zMDmggCxQZwDkzL+PABaKG#lMNY@QV!-5)#f)HvF^EDN$Z@mv10>Zi@gp7FN)7kbWi zjOSU}oL)557?J8x5FtASdEFDsT$snDIP^gyXHq^W%*0vaIDB^Smi{hf-*!r~;>a;{ZzrCN72g2a{O?ODxKNl=bo^I87~&9r-S delta 883 zcmX}q&u?w0Lhwd!X`B$d@RVmSzQ9VFI@;-naB#H4h#Gt;O88yuPt z5uVfuaiA9mr6D4Oqd%gNh=Yho2uBC+?{s*w&%Wk)X7}?v-)H8_+Iz*;$9!^1XshWf z>4i3tP1up(i#CJb@EN|t`wK+!IGhxzViR}b3(VqgT!M*|NC^v=!7B2RT09<_wG*!Jeg6KI*fMGnyKp0(M1CrhsJSva1K}CaZcYQJ z^Z#H=pINd)gw0DHDg1w1%#~K8$GVEH&t02_vX|&T8rVzMrtgowHX8n~EsHO-FfCGB zv?j9LwrE0+r<<a*7us7No+wPLM?%}OUdYL@ww>YD9MJ!vxsv)yJx`gN{SuhsS* zI6Nekb6((>bf#>onQ8MQb85ZoT#&PVP#Oy_O4IRO@3i=K=#-ki+i)bbC!E0aSx2m` zn+M8k`UiFl_Rrq1yoA}4oy!wT>h44P>Lfe9Z~JC{u5KRYUYX&}@zPN@2<<4mVULD! Yx<{t(p0QcLX}F%a=bPhB5PE_91xl%JX8-^I diff --git a/orchestra/contrib/bills/locale/es/LC_MESSAGES/django.po b/orchestra/contrib/bills/locale/es/LC_MESSAGES/django.po index 406aa281..35811b42 100644 --- a/orchestra/contrib/bills/locale/es/LC_MESSAGES/django.po +++ b/orchestra/contrib/bills/locale/es/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-05-28 12:31+0000\n" +"POT-Creation-Date: 2015-07-07 10:10+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -18,37 +18,37 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: actions.py:37 +#: actions.py:40 msgid "Download" msgstr "Descarga" -#: actions.py:47 +#: actions.py:50 msgid "View" msgstr "Vista" -#: actions.py:55 +#: actions.py:58 msgid "Selected bills should be in open state" msgstr "Las facturas seleccionadas están en estado abierto" -#: actions.py:73 +#: actions.py:76 msgid "Selected bills have been closed" msgstr "Las facturas seleccionadas han sido cerradas" -#: actions.py:86 +#: actions.py:89 #, python-format msgid "One related transaction has been created" msgstr "Se ha creado una transacción" -#: actions.py:87 +#: actions.py:90 #, python-format msgid "%(num)i related transactions have been created" msgstr "Se han creado %(num)i transacciones" -#: actions.py:93 +#: actions.py:96 msgid "Are you sure about closing the following bills?" msgstr "Estás a punto de cerrar las sigüientes facturas. ¿Estás seguro?" -#: actions.py:94 +#: actions.py:97 msgid "" "Once a bill is closed it can not be further modified.

Please select a " "payment source for the selected bills" @@ -56,138 +56,180 @@ msgstr "" "Una vez cerrada la factura ya no se podrá modificar.

Por favor " "seleciona un metodo de pago para las facturas seleccionadas" -#: actions.py:107 +#: actions.py:110 msgid "Close" msgstr "Cerrar" -#: actions.py:125 +#: actions.py:124 msgid "One bill has been sent." msgstr "Se ha enviado una factura" -#: actions.py:126 +#: actions.py:125 #, python-format msgid "%i bills have been sent." msgstr "" -#: actions.py:128 +#: actions.py:127 msgid "Resend" msgstr "" -#: actions.py:189 +#: actions.py:188 #, python-format msgid "%(norders)s orders and %(nlines)s lines undoed." msgstr "" -#: actions.py:208 +#: actions.py:207 msgid "Lines moved" msgstr "" -#: admin.py:49 admin.py:93 admin.py:128 forms.py:11 +#: actions.py:219 +msgid "Selected bills should be in closed state" +msgstr "Las facturas seleccionadas están en estado abierto" + +#: actions.py:236 +#, python-format +msgid "%(type)s of %(related_type)s %(number)s and creation date %(date)s" +msgstr "%(type)s de %(related_type)s %(number)s con fecha de creación %(date)s" + +#: actions.py:243 +msgid "%(related_type)s %(number)s subtotal for tax %(tax)s%%" +msgstr "%(related_type)s %(number)s subtotal %(tax)s%%" + +#: actions.py:255 +msgid "One amendment bill have been generated." +msgstr "Se ha creado una transacción" + +#: actions.py:256 +msgid "%(num)i amendment bills have been generated." +msgstr "Se han creado %(num)i transacciones" + +#: actions.py:259 +msgid "Amend" +msgstr "" + +#: admin.py:54 admin.py:98 admin.py:133 forms.py:11 +#: templates/admin/bills/report.html:43 msgid "Total" msgstr "" -#: admin.py:80 +#: admin.py:85 msgid "Description" msgstr "" -#: admin.py:88 +#: admin.py:93 msgid "Subtotal" msgstr "" -#: admin.py:118 +#: admin.py:123 msgid "Is open" msgstr "" -#: admin.py:123 +#: admin.py:128 msgid "Subline" msgstr "" -#: admin.py:157 +#: admin.py:162 msgid "No bills selected." msgstr "" -#: admin.py:164 +#: admin.py:169 #, python-format msgid "Manage %s bill lines." msgstr "" -#: admin.py:166 +#: admin.py:171 msgid "Bill not in open state." msgstr "" -#: admin.py:169 +#: admin.py:174 msgid "Not all bills are in open state." msgstr "" -#: admin.py:170 +#: admin.py:175 msgid "Manage bill lines of multiple bills." msgstr "" -#: admin.py:190 +#: admin.py:195 msgid "Raw" msgstr "" -#: admin.py:208 +#: admin.py:214 models.py:72 msgid "Created" msgstr "" -#: admin.py:213 +#: admin.py:220 msgid "lines" msgstr "" -#: admin.py:218 templates/bills/microspective.html:118 +#: admin.py:225 filters.py:44 templates/bills/microspective.html:118 msgid "total" msgstr "" -#: admin.py:226 models.py:88 models.py:352 +#: admin.py:233 models.py:103 models.py:446 msgid "type" msgstr "" -#: admin.py:243 +#: admin.py:250 msgid "Payment" msgstr "Pago" -#: filters.py:17 +#: filters.py:19 msgid "All" msgstr "" -#: filters.py:18 models.py:78 +#: filters.py:20 models.py:87 msgid "Invoice" msgstr "Factura" -#: filters.py:19 models.py:79 +#: filters.py:21 models.py:88 msgid "Amendment invoice" msgstr "Factura rectificative" -#: filters.py:20 models.py:80 +#: filters.py:22 models.py:89 msgid "Fee" msgstr "Quota de socio" -#: filters.py:21 +#: filters.py:23 msgid "Amendment fee" msgstr "Quota rectificativa" -#: filters.py:22 +#: filters.py:24 msgid "Pro-forma" msgstr "" -#: filters.py:41 -msgid "positive price" -msgstr "" - -#: filters.py:46 filters.py:64 -msgid "Yes" -msgstr "" - -#: filters.py:47 filters.py:65 -msgid "No" -msgstr "" - -#: filters.py:59 +#: filters.py:66 msgid "has bill contact" msgstr "" -#: forms.py:9 +#: filters.py:71 +msgid "Yes" +msgstr "" + +#: filters.py:72 +msgid "No" +msgstr "" + +#: filters.py:83 +msgid "payment state" +msgstr "Pago" + +#: filters.py:88 models.py:71 +msgid "Open" +msgstr "" + +#: filters.py:89 models.py:75 +msgid "Paid" +msgstr "" + +#: filters.py:90 +msgid "Pending" +msgstr "" + +#: filters.py:91 models.py:78 +msgid "Bad debt" +msgstr "" + +#: forms.py:9 templates/admin/bills/report.html:37 msgid "Number" msgstr "" @@ -217,7 +259,7 @@ msgstr "" msgid "Main" msgstr "" -#: models.py:23 models.py:86 +#: models.py:23 models.py:99 msgid "account" msgstr "" @@ -249,138 +291,179 @@ msgstr "" msgid "country" msgstr "" -#: models.py:35 +#: models.py:35 templates/admin/bills/report.html:38 msgid "VAT number" msgstr "" -#: models.py:67 -msgid "Paid" +#: models.py:73 +msgid "Processed" msgstr "" -#: models.py:68 -msgid "Pending" +#: models.py:74 +msgid "Amended" +msgstr "Quota rectificativa" + +#: models.py:76 +msgid "Incomplete" msgstr "" -#: models.py:69 -msgid "Bad debt" -msgstr "" - -#: models.py:81 -msgid "Amendment Fee" -msgstr "" - -#: models.py:82 -msgid "Pro forma" -msgstr "" - -#: models.py:85 -msgid "number" -msgstr "" - -#: models.py:89 -msgid "created on" +#: models.py:77 +msgid "Executed" msgstr "" #: models.py:90 -msgid "closed on" +msgid "Amendment Fee" msgstr "" #: models.py:91 -msgid "open" -msgstr "" - -#: models.py:92 -msgid "sent" -msgstr "" - -#: models.py:93 -msgid "due on" -msgstr "" - -#: models.py:94 -msgid "updated on" -msgstr "" - -#: models.py:97 -msgid "comments" +msgid "Pro forma" msgstr "" #: models.py:98 +msgid "number" +msgstr "número" + +#: models.py:101 +msgid "amend of" +msgstr "rectificación de" + +#: models.py:104 +msgid "created on" +msgstr "creado en" + +#: models.py:105 +msgid "closed on" +msgstr "cerrada en" + +#: models.py:106 +msgid "open" +msgstr "abierta" + +#: models.py:107 +msgid "sent" +msgstr "enviada" + +#: models.py:108 +msgid "due on" +msgstr "vencimiento" + +#: models.py:109 +msgid "updated on" +msgstr "actualizada en" + +#: models.py:112 +msgid "comments" +msgstr "comentarios" + +#: models.py:113 msgid "HTML" +msgstr "HTML" + +#: models.py:192 +#, python-format +msgid "Type %s is not an amendment." msgstr "" -#: models.py:285 +#: models.py:194 +msgid "Amend of related account doesn't match bill account." +msgstr "" + +#: models.py:199 +#, python-format +msgid "Type %s requires an amend of link." +msgstr "" + +#: models.py:378 msgid "bill" -msgstr "" +msgstr "factura" -#: models.py:286 models.py:350 templates/bills/microspective.html:73 +#: models.py:379 models.py:444 templates/bills/microspective.html:73 msgid "description" -msgstr "" +msgstr "descripción" -#: models.py:287 +#: models.py:380 msgid "rate" -msgstr "" +msgstr "tarifa" -#: models.py:288 +#: models.py:381 msgid "quantity" -msgstr "" +msgstr "cantidad" -#: models.py:289 +#: models.py:383 msgid "Verbose quantity" -msgstr "" +msgstr "Cantidad" -#: models.py:290 templates/bills/microspective.html:77 +#: models.py:384 templates/bills/microspective.html:77 #: templates/bills/microspective.html:111 msgid "subtotal" -msgstr "" +msgstr "subtotal" -#: models.py:291 +#: models.py:385 msgid "tax" -msgstr "" +msgstr "impuesto" -#: models.py:292 +#: models.py:386 msgid "start" -msgstr "" +msgstr "inicio" -#: models.py:293 +#: models.py:387 msgid "end" -msgstr "" +msgstr "fín" -#: models.py:295 +#: models.py:389 msgid "Informative link back to the order" msgstr "" -#: models.py:296 +#: models.py:390 msgid "order billed" msgstr "" -#: models.py:297 +#: models.py:391 msgid "order billed until" msgstr "" -#: models.py:298 +#: models.py:392 msgid "created" -msgstr "" +msgstr "creado" -#: models.py:300 +#: models.py:394 msgid "amended line" -msgstr "" +msgstr "linea rectificativa" -#: models.py:343 +#: models.py:437 msgid "Volume" -msgstr "" +msgstr "Volumen" -#: models.py:344 +#: models.py:438 msgid "Compensation" -msgstr "" +msgstr "Compensación" -#: models.py:345 +#: models.py:439 msgid "Other" -msgstr "" +msgstr "Otro" -#: models.py:349 +#: models.py:443 msgid "bill line" -msgstr "" +msgstr "linea de factura" + +#: templates/admin/bills/report.html:39 +msgid "Contact" +msgstr "Contacto" + +#: templates/admin/bills/report.html:40 +#, fuzzy +#| msgid "Due date" +msgid "Close date" +msgstr "Fecha de pago" + +#: templates/admin/bills/report.html:41 +msgid "Base" +msgstr "Base" + +#: templates/admin/bills/report.html:42 templates/bills/microspective.html:111 +#: templates/bills/microspective.html:114 +msgid "VAT" +msgstr "IVA" #: templates/bills/microspective-fee.html:107 msgid "Due date" @@ -427,11 +510,6 @@ msgstr "hrs/cant" msgid "rate/price" msgstr "tarifa/precio" -#: templates/bills/microspective.html:111 -#: templates/bills/microspective.html:114 -msgid "VAT" -msgstr "IVA" - #: templates/bills/microspective.html:114 msgid "taxes" msgstr "impuestos" diff --git a/orchestra/contrib/bills/models.py b/orchestra/contrib/bills/models.py index 2a3e31c5..d7d933ea 100644 --- a/orchestra/contrib/bills/models.py +++ b/orchestra/contrib/bills/models.py @@ -90,6 +90,10 @@ class Bill(models.Model): (AMENDMENTFEE, _("Amendment Fee")), (PROFORMA, _("Pro forma")), ) + AMEND_MAP = { + INVOICE: AMENDMENTINVOICE, + FEE: AMENDMENTFEE, + } number = models.CharField(_("number"), max_length=16, unique=True, blank=True) account = models.ForeignKey('accounts.Account', verbose_name=_("account"), @@ -181,6 +185,24 @@ class Bill(models.Model): return self.EXECUTED return self.BAD_DEBT + def clean(self): + if self.amend_of_id: + errors = {} + if self.type not in self.AMEND_MAP.values(): + errors['amend_of'] = _("Type %s is not an amendment.") % self.get_type_display() + if self.amend_of.account_id != self.account_id: + errors['account'] = _("Amend of related account doesn't match bill account.") + if self.amend_of.is_open: + errors['amend_of'] = _("Related invoice is in open state.") + if self.amend_of.type in self.AMEND_MAP.values(): + errors['amend_of'] = _("Related invoice is an amendment.") + if errors: + raise ValidationError(errors) + elif self.type in self.AMEND_MAP.values(): + raise ValidationError({ + 'amend_of': _("Type %s requires an amend of link.") % self.get_type_display() + }) + def get_total(self): if not self.is_open: return self.total @@ -201,11 +223,7 @@ class Bill(models.Model): return self.type or self.get_class_type() def get_amend_type(self): - amend_map = { - self.INVOICE: self.AMENDMENTINVOICE, - self.FEE: self.AMENDMENTFEE, - } - amend_type = amend_map.get(self.type) + amend_type = self.AMEND_MAP.get(self.type) if amend_type is None: raise TypeError("%s has no associated amend type." % self.type) return amend_type @@ -321,10 +339,17 @@ class Bill(models.Model): subtotals[tax] = (subtotal, round(tax/100*subtotal, 2)) return subtotals + def compute_base(self): + bases = self.lines.annotate( + bases=F('subtotal') + Coalesce(F('sublines__total'), 0) + ) + return round(bases.aggregate(Sum('bases'))['bases__sum'] or 0, 2) + def compute_total(self): totals = self.lines.annotate( - totals=(F('subtotal') + Coalesce(F('sublines__total'), 0)) * (1+F('tax')/100)) - return round(totals.aggregate(Sum('totals'))['totals__sum'], 2) + totals=(F('subtotal') + Coalesce(F('sublines__total'), 0)) * (1+F('tax')/100) + ) + return round(totals.aggregate(Sum('totals'))['totals__sum'] or 0, 2) class Invoice(Bill): @@ -363,7 +388,7 @@ class BillLine(models.Model): subtotal = models.DecimalField(_("subtotal"), max_digits=12, decimal_places=2) tax = models.DecimalField(_("tax"), max_digits=4, decimal_places=2) start_on = models.DateField(_("start")) - end_on = models.DateField(_("end"), null=True) + end_on = models.DateField(_("end"), null=True, blank=True) order = models.ForeignKey(settings.BILLS_ORDER_MODEL, null=True, blank=True, help_text=_("Informative link back to the order"), on_delete=models.SET_NULL) order_billed_on = models.DateField(_("order billed"), null=True, blank=True) diff --git a/orchestra/contrib/bills/templates/admin/bills/report.html b/orchestra/contrib/bills/templates/admin/bills/report.html new file mode 100644 index 00000000..7cb63a83 --- /dev/null +++ b/orchestra/contrib/bills/templates/admin/bills/report.html @@ -0,0 +1,60 @@ +{% load i18n utils %} + + + + Bill Report + + + + + + + + + + + + + + +{% for bill in bills %} + + + + + + {% with base=bill.compute_base total=bill.compute_total %} + + + + {% endwith %} + +{% endfor %} +
{% trans "Number" %}{% trans "VAT number" %}{% trans "Contact" %}{% trans "Close date" %}{% trans "Base" %}{% trans "VAT" %}{% trans "Total" %}
{{ bill.number }}{{ bill.buyer.vat }}{{ bill.buyer.get_name }}{{ bill.closed_on|date }}{{ base }} &{{ currency }};{{ total|sub:base }} &{{ currency }};{{ total }} &{{ currency }};
+ + diff --git a/orchestra/contrib/websites/forms.py b/orchestra/contrib/websites/forms.py index 9a3e797a..982649b2 100644 --- a/orchestra/contrib/websites/forms.py +++ b/orchestra/contrib/websites/forms.py @@ -4,7 +4,7 @@ from django import forms from django.core.exceptions import ValidationError from .utils import normurlpath -from .validators import validate_domain_protocol +from .validators import validate_domain_protocol, validate_server_name class WebsiteAdminForm(forms.ModelForm): @@ -15,12 +15,16 @@ class WebsiteAdminForm(forms.ModelForm): if not domains: return self.cleaned_data protocol = self.cleaned_data.get('protocol') - for domain in domains.all(): + domains = domains.all() + for domain in domains: try: validate_domain_protocol(self.instance, domain, protocol) - except ValidationError as e: - # TODO not sure about this one - self.add_error(None, e) + except ValidationError as err: + self.add_error(None, err) + try: + validate_server_name(domains) + except ValidationError as err: + self.add_error('domains', err) return self.cleaned_data diff --git a/orchestra/contrib/websites/validators.py b/orchestra/contrib/websites/validators.py index bfb8af5a..144cc52a 100644 --- a/orchestra/contrib/websites/validators.py +++ b/orchestra/contrib/websites/validators.py @@ -28,3 +28,11 @@ def validate_domain_protocol(website, domain, protocol): raise ValidationError({ 'domains': 'A website is already defined for "%s" on protocol %s' % (domain, protocol), }) + + +def validate_server_name(domains): + if domains: + for domain in domains: + if not domain.name.startswith('*'): + return + raise ValidationError(_("At least one non-wildcard domain should be provided.")) diff --git a/orchestra/templatetags/utils.py b/orchestra/templatetags/utils.py index 63f261cc..f94bc3f5 100644 --- a/orchestra/templatetags/utils.py +++ b/orchestra/templatetags/utils.py @@ -96,3 +96,7 @@ def admin_url(obj): def isactive(obj): return getattr(obj, 'is_active', True) + +@register.filter +def sub(value, arg): + return value - arg