Cosmetics

This commit is contained in:
Marc 2014-09-30 14:46:29 +00:00
parent f83571afc9
commit 23d62c2d77
19 changed files with 117 additions and 90 deletions

13
TODO.md
View file

@ -131,15 +131,10 @@ Remember that, as always with QuerySets, any subsequent chained methods which im
* AccountAdminMixin auto adds 'account__name' on searchfields and handle account_link on fieldsets
* account defiition:
* identify a customer or a person
* has one main system user for running website
* pangea staff are different accounts
* An account identify a person
* Maybe merge users into accounts? again. Account contains main_users, users contains FTP shit
* Separate panel from server passwords?
* Store passwords on panel?
* Separate panel from server passwords? Store passwords on panel?
* What fields we really need on contacts? name email phone and what more?
* Redirect junk emails and delete every 30 days?

View file

@ -30,10 +30,7 @@ class AccountAdmin(auth.UserAdmin, ExtendedModelAdmin):
'fields': ('first_name', 'last_name', 'email', ('type', 'language'), 'comments'),
}),
(_("Permissions"), {
'fields': ('is_superuser', 'is_active')
}),
(_("Important dates"), {
'fields': ('last_login', 'date_joined')
'fields': ('is_superuser',)
}),
)
fieldsets = (
@ -47,6 +44,7 @@ class AccountAdmin(auth.UserAdmin, ExtendedModelAdmin):
'fields': ('is_superuser', 'is_active')
}),
(_("Important dates"), {
'classes': ('collapse',),
'fields': ('last_login', 'date_joined')
}),
)

View file

@ -1,32 +0,0 @@
from optparse import make_option
from django.core.management.base import BaseCommand
from django.db import transaction
from orchestra.apps.accounts.models import Account
class Command(BaseCommand):
def __init__(self, *args, **kwargs):
super(Command, self).__init__(*args, **kwargs)
self.option_list = BaseCommand.option_list + (
make_option('--noinput', action='store_false', dest='interactive',
default=True),
make_option('--username', action='store', dest='username'),
make_option('--password', action='store', dest='password'),
make_option('--email', action='store', dest='email'),
)
option_list = BaseCommand.option_list
help = 'Used to create an initial account.'
@transaction.atomic
def handle(self, *args, **options):
interactive = options.get('interactive')
if not interactive:
email = options.get('email')
username = options.get('username')
password = options.get('password')
account = Account.objects.create(name=username)
account.main_user = account.users.create_superuser(username, email, password, account=account, is_main=True)
account.save()

View file

@ -1,15 +0,0 @@
from django.contrib.auth import get_user_model
from django.contrib.auth.management.commands import createsuperuser
from orchestra.apps.accounts.models import Account
class Command(createsuperuser.Command):
def handle(self, *args, **options):
super(Command, self).handle(*args, **options)
raise NotImplementedError
users = get_user_model().objects.filter()
if len(users) == 1 and not Account.objects.all().exists():
user = users[0]
user.account = Account.objects.create(user=user)
user.save()

View file

@ -3,8 +3,11 @@ import zipfile
from django.contrib import messages
from django.contrib.admin import helpers
from django.http import HttpResponse
from django.core.urlresolvers import reverse
from django.http import HttpResponse, HttpResponseServerError
from django.shortcuts import render
from django.utils.encoding import force_text
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _
from orchestra.admin.forms import adminmodelformset_factory
@ -13,6 +16,26 @@ from orchestra.utils.html import html_to_pdf
from .forms import SelectSourceForm
def validate_contact(bill):
""" checks if all the preconditions for bill generation are met """
msg = ''
if not hasattr(bill.account, 'invoicecontact'):
account = force_text(bill.account)
link = reverse('admin:accounts_account_change', args=(bill.account_id,))
link += '#invoicecontact-group'
msg += _('Related account "%s" doesn\'t have a declared invoice contact\n') % account
msg += _('You should <a href="%s">provide</a> one') % link
main = type(bill).account.field.rel.to.get_main()
if not hasattr(main, 'invoicecontact'):
account = force_text(main)
link = reverse('admin:accounts_account_change', args=(main.id,))
link += '#invoicecontact-group'
msg += _('Main account "%s" doesn\'t have a declared invoice contact\n') % account
msg += _('You should <a href="%s">provide</a> one') % link
if msg:
# TODO custom template
return HttpResponseServerError(mark_safe(msg))
def download_bills(modeladmin, request, queryset):
if queryset.count() > 1:
@ -35,6 +58,9 @@ download_bills.url_name = 'download'
def view_bill(modeladmin, request, queryset):
bill = queryset.get()
error = validate_contact(bill)
if error:
return error
html = bill.html or bill.render()
return HttpResponse(html)
view_bill.verbose_name = _("View")
@ -46,6 +72,10 @@ def close_bills(modeladmin, request, queryset):
if not queryset:
messages.warning(request, _("Selected bills should be in open state"))
return
for bill in queryset:
error = validate_contact(bill)
if error:
return error
SelectSourceFormSet = adminmodelformset_factory(modeladmin, SelectSourceForm, extra=0)
formset = SelectSourceFormSet(queryset=queryset)
if request.POST.get('post') == 'generic_confirmation':
@ -79,6 +109,10 @@ close_bills.url_name = 'close'
def send_bills(modeladmin, request, queryset):
for bill in queryset:
error = validate_contact(bill)
if error:
return error
for bill in queryset:
bill.send()
modeladmin.log_change(request, bill, 'Sent')

View file

@ -1,7 +1,9 @@
from django import forms
from django.contrib import admin
from django.contrib import admin, messages
from django.contrib.admin.utils import unquote
from django.core.urlresolvers import reverse
from django.db import models
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _
from orchestra.admin import ExtendedModelAdmin
@ -11,8 +13,7 @@ from orchestra.apps.accounts.admin import AccountAdminMixin
from . import settings
from .actions import download_bills, view_bill, close_bills, send_bills
from .filters import BillTypeListFilter
from .models import (Bill, Invoice, AmendmentInvoice, Fee, AmendmentFee, ProForma,
BillLine)
from .models import Bill, Invoice, AmendmentInvoice, Fee, AmendmentFee, ProForma, BillLine
PAYMENT_STATE_COLORS = {
@ -144,14 +145,18 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
qs = qs.annotate(models.Count('lines'))
qs = qs.prefetch_related('lines', 'lines__sublines')
return qs
# def change_view(self, request, object_id, **kwargs):
# opts = self.model._meta
# if opts.module_name == 'bill':
# obj = self.get_object(request, unquote(object_id))
# return redirect(
# reverse('admin:bills_%s_change' % obj.type.lower(), args=[obj.pk]))
# return super(BillAdmin, self).change_view(request, object_id, **kwargs)
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)
admin.site.register(Bill, BillAdmin)

View file

@ -144,6 +144,7 @@ class Bill(models.Model):
self.save()
def send(self):
html = self.html or self.render()
self.account.send_email(
template=settings.BILLS_EMAIL_NOTIFICATION_TEMPLATE,
context={
@ -151,7 +152,7 @@ class Bill(models.Model):
},
contacts=(Contact.BILLING,),
attachments=[
('%s.pdf' % self.number, html_to_pdf(self.html), 'application/pdf')
('%s.pdf' % self.number, html_to_pdf(html), 'application/pdf')
]
)
self.is_sent = True

View file

@ -96,11 +96,7 @@ class ContactInline(InvoiceContactInline):
def has_invoice(account):
try:
account.invoicecontact
except InvoiceContact.DoesNotExist:
return False
return True
return hasattr(account, 'invoicecontact')
has_invoice.boolean = True
has_invoice.admin_order_field = 'invoicecontact'

View file

@ -61,6 +61,9 @@ class InvoiceContact(models.Model):
country = models.CharField(_("country"), max_length=20,
default=settings.CONTACTS_DEFAULT_COUNTRY)
vat = models.CharField(_("VAT number"), max_length=64)
def __unicode__(self):
return self.name
accounts.register(Contact)

View file

@ -13,7 +13,7 @@ from .models import Address
class MailSystemUserBackend(ServiceController):
verbose_name = _("Mail system user")
model = 'mails.Mailbox'
# TODO related_models = ('resources__content_type') ??
# TODO related_models = ('resources__content_type') ?? needed for updating disk usage from resource.data
DEFAULT_GROUP = 'postfix'
@ -35,16 +35,33 @@ class MailSystemUserBackend(ServiceController):
"# Sieve Filter\n"
"# Generated by Orchestra %s\n\n" % now
)
if mailbox.use_custom_filtering:
if mailbox.custom_filtering:
context['filtering'] += mailbox.custom_filtering
else:
context['filtering'] += settings.EMAILS_DEFAUL_FILTERING
context['filter_path'] = os.path.join(context['home'], '.orchestra.sieve')
self.append("echo '%(filtering)s' > %(filter_path)s" % context)
def set_quota(self, mailbox, context):
if not hasattr(mailbox, 'resources'):
return
context.update({
'maildir_path': '~%(username)s/Maildir' % context,
'maildirsize_path': '~%(username)s/Maildir/maildirsize' % context,
'quota': mailbox.resources.disk.allocated*1000*1000,
})
self.append("mkdir -p %(maildir_path)s" % context)
self.append(
"sed -i '1s/.*/%(quota)s,S/' %(maildirsize_path)s || {"
" echo '%(quota)s,S' > %(maildirsize_path)s && "
" chown %(username)s %(maildirsize_path)s;"
"}" % context
)
def save(self, mailbox):
context = self.get_context(mailbox)
self.create_user(context)
self.set_quota(mailbox, context)
self.generate_filter(mailbox, context)
def delete(self, mailbox):
@ -56,7 +73,7 @@ class MailSystemUserBackend(ServiceController):
def get_context(self, mailbox):
context = {
'name': mailbox.nam,
'name': mailbox.name,
'username': mailbox.name,
'password': mailbox.password if mailbox.is_active else '*%s' % mailbox.password,
'group': self.DEFAULT_GROUP
@ -155,6 +172,6 @@ class MaildirDisk(ServiceMonitor):
def get_context(self, mailbox):
context = MailSystemUserBackend().get_context(mailbox)
context['home'] = settings.EMAILS_HOME % context
context['maildir_path'] = os.path.join(context['home'], 'Maildir/maildirsize')
context['rr_path'] = os.path.join(context['home'], 'Maildir/maildirsize')
context['object_id'] = mailbox.pk
return context

View file

@ -1,5 +1,6 @@
from django.core.validators import RegexValidator
from django.db import models
from django.utils.functional import cached_property
from django.utils.translation import ugettext_lazy as _
from orchestra.core import services
@ -30,6 +31,10 @@ class Mailbox(models.Model):
def __unicode__(self):
return self.name
@cached_property
def active(self):
return self.is_active and self.account.is_active
class Address(models.Model):

View file

@ -68,7 +68,7 @@ class BackendLog(models.Model):
@property
def execution_time(self):
return (self.last_update-self.created).total_seconds()
return (self.updated_at-self.created_at).total_seconds()
def backend_class(self):
return ServiceBackend.get_backend(self.backend)

View file

@ -29,7 +29,7 @@ class OrderAdmin(AccountAdminMixin, admin.ModelAdmin):
def display_billed_until(self, order):
value = order.billed_until
color = ''
if value and value < timezone.now():
if value and value < timezone.now().date():
color = 'style="color:red;"'
return '<span title="{raw}" {color}>{human}</span>'.format(
raw=escape(str(value)), color=color, human=escape(naturaldate(value)),

View file

@ -15,12 +15,12 @@ class BillSelectedOptionsForm(AdminFormMixin, forms.Form):
label=_("Billing point"), widget=widgets.AdminDateWidget,
help_text=_("Date you want to bill selected orders"))
fixed_point = forms.BooleanField(initial=False, required=False,
label=_("fixed point"),
label=_("Fixed point"),
help_text=_("Deisgnates whether you want the billing point to be an "
"exact date, or adapt it to the billing period."))
is_proforma = forms.BooleanField(initial=False, required=False,
label=_("Pro-forma, billing simulation"),
help_text=_("O."))
label=_("Pro-forma (billing simulation)"),
help_text=_("Creates a Pro Forma instead of billing the orders."))
new_open = forms.BooleanField(initial=False, required=False,
label=_("Create a new open bill"),
help_text=_("Deisgnates whether you want to put this orders on a new "

View file

@ -58,6 +58,26 @@ class OrderQuerySet(models.QuerySet):
return self.exclude(**qs)
return self.filter(**qs)
def get_related(self, **options):
Service = get_model(settings.ORDERS_SERVICE_MODEL)
conflictive = self.filter(service__metric='')
conflictive = conflictive.exclude(service__billing_period=Service.NEVER)
conflictive = conflictive.select_related('service').group_by('account_id', 'service')
qs = Q()
for account_id, services in conflictive.iteritems():
for service, orders in services.iteritems():
end = datetime.date.min
bp = None
for order in orders:
bp = service.handler.get_billing_point(order, **options)
end = max(end, bp)
qs = qs | Q(
Q(service=service, account=account_id, registered_on__lt=end) &
Q(Q(billed_until__isnull=True) | Q(billed_until__lt=end))
)
ids = self.values_list('id', flat=True)
return self.model.objects.filter(qs).exclude(id__in=ids)
def pricing_orders(self, ini, end):
return self.filter(billed_until__isnull=False, billed_until__gt=ini,
registered_on__lt=end)
@ -103,7 +123,7 @@ class Order(models.Model):
@classmethod
def update_orders(cls, instance, service=None):
if service is None:
Service = get_model(*settings.ORDERS_SERVICE_MODEL.split('.'))
Service = get_model(settings.ORDERS_SERVICE_MODEL)
services = Service.get_services(instance)
else:
services = [service]

View file

@ -14,7 +14,7 @@ def monitor(resource_id):
# Execute monitors
for monitor_name in resource.monitors:
backend = ServiceMonitor.get_backend(monitor_name)
model = get_model(*backend.model.split('.'))
model = get_model(backend.model)
operations = []
# Execute monitor
for obj in model.objects.all():

View file

@ -14,7 +14,7 @@ from .models import SystemUser
class SystemUserAdmin(AccountAdminMixin, ExtendedModelAdmin):
list_display = ('username', 'account_link', 'shell', 'home', 'is_active',)
list_filter = ('is_active',)
list_filter = ('is_active', 'shell')
fieldsets = (
(None, {
'fields': ('username', 'password', 'account_link', 'is_active')