diff --git a/ROADMAP.md b/ROADMAP.md index 4f44b598..2d5656e2 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -12,12 +12,13 @@ 2. [x] [Orchestra-orm](https://github.com/glic3rinu/orchestra-orm) a Python library for easily interacting with the REST API 3. [x] Service orchestration framework 4. [ ] Data model, input validation, admin and REST interfaces, permissions, unit and functional tests, service management, migration scripts and some documentation of: - 1. [ ] Web applications and FTP accounts + 1. [x] Web applications + 2. [ ] FTP accounts 2. [ ] Databases 1. [ ] Mail accounts, aliases, forwards - 1. [ ] DNS + 1. [x] DNS 1. [ ] Mailing lists -1. [ ] Contact management and service contraction +1. [x] Contact management and service contraction 1. [ ] Object level permissions system 1. [ ] Unittests of all the logic 2. [ ] Functional tests of all Admin and REST interations @@ -26,12 +27,12 @@ ### 1.0b1 Milestone (first beta release on Jul '14) -1. [ ] Resource monitoring +1. [x] Resource monitoring 1. [ ] Orders 2. [ ] Pricing 3. [ ] Billing -1. [ ] Payment gateways -2. [ ] Scheduling of service cancellations +1. [ ] Payment methods +2. [ ] Scheduling of service cancellations and deactivations 1. [ ] Full documentation @@ -41,3 +42,4 @@ 1. [ ] Integration with third-party service providers, e.g. Gandi 1. [ ] Support for additional services like VPS 2. [ ] Issue tracking system +3. [ ] Translation to Spanish and Catalan diff --git a/orchestra/apps/accounts/admin.py b/orchestra/apps/accounts/admin.py index 856ccbd7..2f0c7eef 100644 --- a/orchestra/apps/accounts/admin.py +++ b/orchestra/apps/accounts/admin.py @@ -42,6 +42,7 @@ class AccountAdmin(ExtendedModelAdmin): search_fields = ('users__username',) add_form = AccountCreationForm form = AccountChangeForm + change_form_template = 'admin/accounts/account/change_form.html' user_link = admin_link('user', order='user__username') diff --git a/orchestra/apps/accounts/templates/admin/accounts/account/change_list.html b/orchestra/apps/accounts/templates/admin/accounts/account/change_list.html index 9901fd38..ea9d101b 100644 --- a/orchestra/apps/accounts/templates/admin/accounts/account/change_list.html +++ b/orchestra/apps/accounts/templates/admin/accounts/account/change_list.html @@ -19,6 +19,9 @@ {% block object-tools-items %} {% if from_account %} +
  • + {{ account|truncatewords:"18" }} +
  • {% trans 'Show all' %}
  • diff --git a/orchestra/apps/bills/admin.py b/orchestra/apps/bills/admin.py index 5002131b..dcba6b74 100644 --- a/orchestra/apps/bills/admin.py +++ b/orchestra/apps/bills/admin.py @@ -1,3 +1,4 @@ +from django import forms from django.contrib import admin from django.core.urlresolvers import reverse from django.utils.translation import ugettext_lazy as _ @@ -17,6 +18,21 @@ class BillLineInline(admin.TabularInline): fields = ( 'description', 'initial_date', 'final_date', 'price', 'amount', 'tax' ) + + def get_readonly_fields(self, request, obj=None): + if obj and obj.status != Bill.OPEN: + return self.fields + return super(BillLineInline, self).get_readonly_fields(request, obj=obj) + + def has_add_permission(self, request): + if request.__bill__ and request.__bill__.status != Bill.OPEN: + return False + return super(BillLineInline, self).has_add_permission(request) + + def has_delete_permission(self, request, obj=None): + if obj and obj.status != Bill.OPEN: + return False + return super(BillLineInline, self).has_delete_permission(request, obj=obj) class BudgetLineInline(admin.TabularInline): @@ -28,15 +44,25 @@ class BudgetLineInline(admin.TabularInline): class BillAdmin(AccountAdminMixin, ExtendedModelAdmin): list_display = ( - 'ident', 'status', 'type_link', 'account_link', 'created_on_display' + 'number', 'status', 'type_link', 'account_link', 'created_on_display' ) list_filter = (BillTypeListFilter, 'status',) + add_fields = ('account', 'type', 'status', 'due_on', 'comments') + fieldsets = ( + (None, { + 'fields': ('number', 'account_link', 'type', 'status', 'due_on', + 'comments'), + }), + (_("Raw"), { + 'classes': ('collapse',), + 'fields': ('html',), + }), + ) change_view_actions = [generate_bill] - change_readonly_fields = ('account', 'type', 'status') - readonly_fields = ('ident',) + change_readonly_fields = ('account_link', 'type', 'status') + readonly_fields = ('number',) inlines = [BillLineInline] - account_link = admin_link('account') created_on_display = admin_date('created_on') def type_link(self, bill): @@ -47,11 +73,27 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin): type_link.short_description = _("type") type_link.admin_order_field = 'type' + def get_readonly_fields(self, request, obj=None): + fields = super(BillAdmin, self).get_readonly_fields(request, obj=obj) + if obj and obj.status != Bill.OPEN: + fields += self.add_fields + return fields + def get_inline_instances(self, request, obj=None): if self.model is Budget: self.inlines = [BudgetLineInline] + # Make parent object available for inline.has_add_permission() + request.__bill__ = obj return super(BillAdmin, self).get_inline_instances(request, obj=obj) - + + 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) + admin.site.register(Bill, BillAdmin) admin.site.register(Invoice, BillAdmin) diff --git a/orchestra/apps/bills/models.py b/orchestra/apps/bills/models.py index ad62defc..bce7b2d4 100644 --- a/orchestra/apps/bills/models.py +++ b/orchestra/apps/bills/models.py @@ -1,3 +1,5 @@ +import inspect + from django.db import models from django.template import loader, Context from django.utils import timezone @@ -43,7 +45,7 @@ class Bill(models.Model): ('BUDGET', _("Budget")), ) - ident = models.CharField(_("identifier"), max_length=16, unique=True, + number = models.CharField(_("number"), max_length=16, unique=True, blank=True) account = models.ForeignKey('accounts.Account', verbose_name=_("account"), related_name='%(class)s') @@ -62,7 +64,7 @@ class Bill(models.Model): objects = BillManager() def __unicode__(self): - return self.ident + return self.number @cached_property def seller(self): @@ -77,31 +79,34 @@ class Bill(models.Model): return self.billlines @classmethod - def get_type(cls): + def get_class_type(cls): return cls.__name__.upper() - def set_ident(self): + def get_type(self): + return self.type or self.get_class_type() + + def set_number(self): cls = type(self) - bill_type = self.type or cls.get_type() + bill_type = self.get_type() if bill_type == 'BILL': - raise TypeError("get_new_ident() can not be used on a Bill class") + raise TypeError("get_new_number() can not be used on a Bill class") # Bill number resets every natural year year = timezone.now().strftime("%Y") bills = cls.objects.filter(created_on__year=year) - number_length = settings.BILLS_IDENT_NUMBER_LENGTH - prefix = getattr(settings, 'BILLS_%s_IDENT_PREFIX' % bill_type) + number_length = settings.BILLS_NUMBER_LENGTH + prefix = getattr(settings, 'BILLS_%s_NUMBER_PREFIX' % bill_type) if self.status == self.OPEN: prefix = 'O{}'.format(prefix) bills = bills.filter(status=self.OPEN) - num_bills = bills.order_by('-ident').first() or 0 + num_bills = bills.order_by('-number').first() or 0 if num_bills is not 0: - num_bills = int(num_bills.ident[-number_length:]) + num_bills = int(num_bills.number[-number_length:]) else: bills = bills.exclude(status=self.OPEN) num_bills = bills.count() zeros = (number_length - len(str(num_bills))) * '0' number = zeros + str(num_bills + 1) - self.ident = '{prefix}{year}{number}'.format( + self.number = '{prefix}{year}{number}'.format( prefix=prefix, year=year, number=number) def close(self): @@ -130,9 +135,9 @@ class Bill(models.Model): def save(self, *args, **kwargs): 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() + self.type = self.get_type() + if not self.number or (self.number.startswith('O') and self.status != self.OPEN): + self.set_number() super(Bill, self).save(*args, **kwargs) @@ -177,6 +182,14 @@ class BaseBillLine(models.Model): class Meta: abstract = True + + def __unicode__(self): + return "#%i" % self.number + + @property + def number(self): + lines = type(self).objects.filter(bill=self.bill_id) + return lines.filter(id__lte=self.id).order_by('id').count() class BudgetLine(BaseBillLine): diff --git a/orchestra/apps/bills/serializers.py b/orchestra/apps/bills/serializers.py index d8180ebf..9d747722 100644 --- a/orchestra/apps/bills/serializers.py +++ b/orchestra/apps/bills/serializers.py @@ -17,6 +17,6 @@ class BillSerializer(AccountSerializerMixin, serializers.HyperlinkedModelSeriali class Meta: model = Bill fields = ( - 'url', 'ident', 'bill_type', 'status', 'created_on', 'due_on', + 'url', 'number', 'bill_type', 'status', 'created_on', 'due_on', 'comments', 'html', 'lines' ) diff --git a/orchestra/apps/bills/settings.py b/orchestra/apps/bills/settings.py index 8817347a..b00c5935 100644 --- a/orchestra/apps/bills/settings.py +++ b/orchestra/apps/bills/settings.py @@ -1,17 +1,17 @@ from django.conf import settings -BILLS_IDENT_NUMBER_LENGTH = getattr(settings, 'BILLS_IDENT_NUMBER_LENGTH', 4) +BILLS_NUMBER_LENGTH = getattr(settings, 'BILLS_NUMBER_LENGTH', 4) -BILLS_INVOICE_IDENT_PREFIX = getattr(settings, 'BILLS_INVOICE_IDENT_PREFIX', 'I') +BILLS_INVOICE_NUMBER_PREFIX = getattr(settings, 'BILLS_INVOICE_NUMBER_PREFIX', 'I') -BILLS_AMENDMENT_INVOICE_IDENT_PREFIX = getattr(settings, 'BILLS_AMENDMENT_INVOICE_IDENT_PREFIX', 'A') +BILLS_AMENDMENT_INVOICE_NUMBER_PREFIX = getattr(settings, 'BILLS_AMENDMENT_INVOICE_NUMBER_PREFIX', 'A') -BILLS_FEE_IDENT_PREFIX = getattr(settings, 'BILLS_FEE_IDENT_PREFIX', 'F') +BILLS_FEE_NUMBER_PREFIX = getattr(settings, 'BILLS_FEE_NUMBER_PREFIX', 'F') -BILLS_AMENDMENT_FEE_IDENT_PREFIX = getattr(settings, 'BILLS_AMENDMENT_FEE_IDENT_PREFIX', 'B') +BILLS_AMENDMENT_FEE_NUMBER_PREFIX = getattr(settings, 'BILLS_AMENDMENT_FEE_NUMBER_PREFIX', 'B') -BILLS_BUDGET_IDENT_PREFIX = getattr(settings, 'BILLS_BUDGET_IDENT_PREFIX', 'Q') +BILLS_BUDGET_NUMBER_PREFIX = getattr(settings, 'BILLS_BUDGET_NUMBER_PREFIX', 'Q') BILLS_INVOICE_TEMPLATE = getattr(settings, 'BILLS_INVOICE_TEMPLATE', 'bills/microspective.html') diff --git a/orchestra/apps/bills/templates/bills/base.html b/orchestra/apps/bills/templates/bills/base.html index 5990ab52..ce8a782e 100644 --- a/orchestra/apps/bills/templates/bills/base.html +++ b/orchestra/apps/bills/templates/bills/base.html @@ -1,6 +1,6 @@ - {% block title %}Invoice - I20110223{% endblock %} + {% block title %}{{ bill.get_type_display }} - {{ bill.number }}{% endblock %} {% block head %}{% endblock %} diff --git a/orchestra/apps/bills/templates/bills/microspective.html b/orchestra/apps/bills/templates/bills/microspective.html index 304f1020..ecbd0b20 100644 --- a/orchestra/apps/bills/templates/bills/microspective.html +++ b/orchestra/apps/bills/templates/bills/microspective.html @@ -21,48 +21,47 @@
    - Associacio Pangea -
    - Coordinadora Comunicacio per a la Cooperacio
    + {{ seller.name }}
    -

    Pl. Eusebi Guell 6-7, planta 0
    - 08034 - Barcelona
    - Spain
    +

    {{ seller.address }}
    + {{ seller.zipcode }} - {{ seller.city }}
    + {{ seller.country }}

    -

    93-803-21-32
    - sales@pangea.org
    - www.pangea.org

    +

    {{ seller_info.phone }}
    + {{ seller_info.email }}
    + {{ seller_info.website }}

    {% endblock %} {% block summary %}
    - Invoice
    - F20110232
    + {{ bill.get_type_display }}
    + {{ bill.number }}
    Page 1 of 1

    DUE DATE
    - Nov 21, 2011 + {{ bill.due_on|date }}
    TOTAL
    - 122,03 € + {{ bill.total }} &{{ currency.lower }};
    - INVOICE DATE
    - Oct 20, 2012 + {{ bill.get_type_display.upper }} DATE
    + {{ bill.created_on|date }}
    - Aadults
    - ES01939933
    - Carrer nnoseque, 0
    - 08034 - Barcelona
    - Spain
    + {{ buyer.name }}
    + {{ buyer.vat }}
    + {{ buyer.address }}
    + {{ buyer.zipcode }} - {{ buyer.city }}
    + {{ buyer.country }}
    {% endblock %} @@ -74,50 +73,24 @@ rate/price subtotal
    - - 1
    -  
    - - Hola que passa
    - nosquevols
    - - 1
    -  
    - - 1,00 €
    -  
    - - 111,00 €
    - -10,00 €
    -
    - 1 - Merda pura - 1 - 1,00 € - 111,00 € -
    - 1 - I tu que et passa - 1 - 1,00 € - 111,00 € -
    - 1 - Joder hostia puta - 1 - 1,00 € - 111,00 € + {% for line in bill.lines.all %} + {{ line.id }} + {{ line.description }} + {{ line.amount }} + {{ line.rate }} + {{ line.price }} &{{ currency.lower }};
    + {% endfor %}
    subtotal - 33,03 € + {{ bill.subtotal }} &{{ currency.lower }};
    tax - 33,03 € + {{ bill.taxes }} &{{ currency.lower }};
    total - 33,03 € + {{ bill.total }} &{{ currency.lower }};
    {% endblock %} @@ -126,13 +99,14 @@