Random fixes

This commit is contained in:
Marc Aymerich 2016-05-18 14:08:12 +00:00
parent a0dbf96c8a
commit 2490ff83c8
12 changed files with 145 additions and 104 deletions

View File

@ -430,7 +430,6 @@ mkhomedir_helper or create ssh homes with bash.rc and such
# Automatically re-run backends until success? only timedout executions? # Automatically re-run backends until success? only timedout executions?
# TODO save serialized versions ob backendoperation.instance in order to allow backend reexecution of deleted objects # TODO save serialized versions ob backendoperation.instance in order to allow backend reexecution of deleted objects
# upgrade to django 1.9 and make margins wider
# lets encrypt: DNS vs HTTP challange # lets encrypt: DNS vs HTTP challange
# lets enctypt: autorenew # lets enctypt: autorenew
@ -459,8 +458,7 @@ with open(file) as handler:
os.unlink(file) os.unlink(file)
# change filter By PHP version: by detail
# Mark transaction process as executed should not override higher transaction states # Mark transaction process as executed should not override higher transaction states
# Bill amend and related transaction, what to do? allow edit transaction ammount of amends when their are pending execution
# mailbox.addresses get_Queryset SQL contact @ with mailboxes and forwards # DASHBOARD: Show owned tickets, scheduled actions, maintenance operations (diff domains)

View File

@ -142,6 +142,7 @@ class ChangeViewActionsMixin(object):
view.tool_description = tool_description view.tool_description = tool_description
view.css_class = getattr(action, 'css_class', 'historylink') view.css_class = getattr(action, 'css_class', 'historylink')
view.help_text = getattr(action, 'help_text', '') view.help_text = getattr(action, 'help_text', '')
view.hidden = getattr(action, 'hidden', False)
views.append(view) views.append(view)
return views return views
@ -150,7 +151,7 @@ class ChangeViewActionsMixin(object):
kwargs['extra_context'] = {} kwargs['extra_context'] = {}
obj = self.get_object(request, unquote(object_id)) obj = self.get_object(request, unquote(object_id))
kwargs['extra_context']['object_tools_items'] = [ kwargs['extra_context']['object_tools_items'] = [
action.__dict__ for action in self.get_change_view_actions(obj) action.__dict__ for action in self.get_change_view_actions(obj) if not action.hidden
] ]
return super().change_view(request, object_id, **kwargs) return super().change_view(request, object_id, **kwargs)

View File

@ -32,6 +32,7 @@ def view_bill(modeladmin, request, queryset):
return HttpResponse(html) return HttpResponse(html)
view_bill.tool_description = _("View") view_bill.tool_description = _("View")
view_bill.url_name = 'view' view_bill.url_name = 'view'
view_bill.hidden = True
@transaction.atomic @transaction.atomic

View File

@ -102,6 +102,7 @@ class ClosedBillLineInline(BillLineInline):
'display_subtotal', 'display_total' 'display_subtotal', 'display_total'
) )
readonly_fields = fields readonly_fields = fields
can_delete = False
def display_description(self, line): def display_description(self, line):
descriptions = [line.description] descriptions = [line.description]
@ -128,9 +129,6 @@ class ClosedBillLineInline(BillLineInline):
def has_add_permission(self, request): def has_add_permission(self, request):
return False return False
def has_delete_permission(self, request, obj=None):
return False
class BillLineAdmin(admin.ModelAdmin): class BillLineAdmin(admin.ModelAdmin):
list_display = ( list_display = (
@ -243,7 +241,77 @@ class BillLineManagerAdmin(BillLineAdmin):
return super().changelist_view(request, context) return super().changelist_view(request, context)
class BillAdmin(AccountAdminMixin, ExtendedModelAdmin): class BillAdminMixin(AccountAdminMixin):
def display_total_with_subtotals(self, bill):
if bill.pk:
currency = settings.BILLS_CURRENCY.lower()
subtotals = []
for tax, subtotal in bill.compute_subtotals().items():
subtotals.append(_("Subtotal %s%% VAT %s &%s;") % (tax, subtotal[0], currency))
subtotals.append(_("Taxes %s%% VAT %s &%s;") % (tax, subtotal[1], currency))
subtotals = '\n'.join(subtotals)
return '<span title="%s">%s &%s;</span>' % (subtotals, bill.compute_total(), currency)
display_total_with_subtotals.allow_tags = True
display_total_with_subtotals.short_description = _("total")
display_total_with_subtotals.admin_order_field = 'approx_total'
def display_payment_state(self, bill):
if bill.pk:
t_opts = bill.transactions.model._meta
if bill.get_type() == bill.PROFORMA:
return '<span title="Pro forma">---</span>'
transactions = bill.transactions.all()
if len(transactions) == 1:
args = (transactions[0].pk,)
view = 'admin:%s_%s_change' % (t_opts.app_label, t_opts.model_name)
url = reverse(view, args=args)
else:
url = reverse('admin:%s_%s_changelist' % (t_opts.app_label, t_opts.model_name))
url += '?bill=%i' % bill.pk
state = bill.get_payment_state_display().upper()
title = ''
if bill.closed_amends:
state = '<strike>%s*</strike>' % state
title = _("This bill has been amended, this value may not be valid.")
color = PAYMENT_STATE_COLORS.get(bill.payment_state, 'grey')
return '<a href="{url}" style="color:{color}" title="{title}">{name}</a>'.format(
url=url, color=color, name=state, title=title)
display_payment_state.allow_tags = True
display_payment_state.short_description = _("Payment")
def get_queryset(self, request):
qs = super().get_queryset(request)
qs = qs.annotate(
models.Count('lines'),
# FIXME https://code.djangoproject.com/ticket/10060
approx_total=Coalesce(Sum(
(F('lines__subtotal') + Coalesce('lines__sublines__total', 0)) * (1+F('lines__tax')/100),
), 0),
)
qs = qs.prefetch_related(
Prefetch('amends', queryset=Bill.objects.filter(is_open=False), to_attr='closed_amends')
)
return qs.defer('html')
class AmendInline(BillAdminMixin, admin.TabularInline):
model = Bill
fields = (
'self_link', 'type', 'display_total_with_subtotals', 'display_payment_state', 'is_open',
'is_sent'
)
readonly_fields = fields
verbose_name_plural = _("Amends")
can_delete = False
extra = 0
self_link = admin_link('__str__')
def has_add_permission(self, *args, **kwargs):
return False
class BillAdmin(BillAdminMixin, ExtendedModelAdmin):
list_display = ( list_display = (
'number', 'type_link', 'account_link', 'closed_on_display', 'updated_on_display', 'number', 'type_link', 'account_link', 'closed_on_display', 'updated_on_display',
'num_lines', 'display_total', 'display_payment_state', 'is_sent' 'num_lines', 'display_total', 'display_payment_state', 'is_sent'
@ -256,9 +324,8 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
change_list_template = 'admin/bills/bill/change_list.html' change_list_template = 'admin/bills/bill/change_list.html'
fieldsets = ( fieldsets = (
(None, { (None, {
'fields': ['number', 'type', 'amend_of_link', 'account_link', 'fields': ['number', 'type', (), 'account_link', 'display_total_with_subtotals',
'display_total_with_subtotals', 'display_payment_state', 'display_payment_state', 'is_sent', 'comments'],
'is_sent', 'comments'],
}), }),
(_("Dates"), { (_("Dates"), {
'classes': ('collapse',), 'classes': ('collapse',),
@ -281,31 +348,26 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
actions.amend_bills, actions.bill_report, actions.service_report, actions.amend_bills, actions.bill_report, actions.service_report,
actions.close_send_download_bills, list_accounts, actions.close_send_download_bills, list_accounts,
] ]
change_readonly_fields = ( change_readonly_fields = ('account_link', 'type', 'is_open', 'amend_of_link')
'account_link', 'type', 'is_open', 'amend_of_link', 'amend_links'
)
readonly_fields = ( readonly_fields = (
'number', 'display_total', 'is_sent', 'display_payment_state', 'created_on_display', 'number', 'display_total', 'is_sent', 'display_payment_state', 'created_on_display',
'closed_on_display', 'updated_on_display', 'display_total_with_subtotals', 'closed_on_display', 'updated_on_display', 'display_total_with_subtotals',
) )
inlines = [BillLineInline, ClosedBillLineInline]
date_hierarchy = 'closed_on' date_hierarchy = 'closed_on'
# TODO when merged https://github.com/django/django/pull/5213
#approximate_date_hierarchy = admin.ApproximateWith.MONTHS
created_on_display = admin_date('created_on', short_description=_("Created")) created_on_display = admin_date('created_on', short_description=_("Created"))
closed_on_display = admin_date('closed_on', short_description=_("Closed")) closed_on_display = admin_date('closed_on', short_description=_("Closed"))
updated_on_display = admin_date('updated_on', short_description=_("Updated")) updated_on_display = admin_date('updated_on', short_description=_("Updated"))
amend_of_link = admin_link('amend_of') amend_of_link = admin_link('amend_of')
def amend_links(self, bill): # def amend_links(self, bill):
links = [] # links = []
for amend in bill.amends.all(): # for amend in bill.amends.all():
url = reverse('admin:bills_bill_change', args=(amend.id,)) # url = reverse('admin:bills_bill_change', args=(amend.id,))
links.append('<a href="{url}">{num}</a>'.format(url=url, num=amend.number)) # links.append('<a href="{url}">{num}</a>'.format(url=url, num=amend.number))
return '<br>'.join(links) # return '<br>'.join(links)
amend_links.short_description = _("Amends") # amend_links.short_description = _("Amends")
amend_links.allow_tags = True # amend_links.allow_tags = True
def num_lines(self, bill): def num_lines(self, bill):
return bill.lines__count return bill.lines__count
@ -319,18 +381,6 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
display_total.short_description = _("total") display_total.short_description = _("total")
display_total.admin_order_field = 'approx_total' display_total.admin_order_field = 'approx_total'
def display_total_with_subtotals(self, bill):
currency = settings.BILLS_CURRENCY.lower()
subtotals = []
for tax, subtotal in bill.compute_subtotals().items():
subtotals.append(_("Subtotal %s%% VAT %s &%s;") % (tax, subtotal[0], currency))
subtotals.append(_("Taxes %s%% VAT %s &%s;") % (tax, subtotal[1], currency))
subtotals = '\n'.join(subtotals)
return '<span title="%s">%s &%s;</span>' % (subtotals, bill.compute_total(), currency)
display_total_with_subtotals.allow_tags = True
display_total_with_subtotals.short_description = _("total")
display_total_with_subtotals.admin_order_field = 'approx_total'
def type_link(self, bill): def type_link(self, bill):
bill_type = bill.type.lower() bill_type = bill.type.lower()
url = reverse('admin:bills_%s_changelist' % bill_type) url = reverse('admin:bills_%s_changelist' % bill_type)
@ -339,27 +389,6 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
type_link.short_description = _("type") type_link.short_description = _("type")
type_link.admin_order_field = 'type' type_link.admin_order_field = 'type'
def display_payment_state(self, bill):
t_opts = bill.transactions.model._meta
transactions = bill.transactions.all()
if len(transactions) == 1:
args = (transactions[0].pk,)
view = 'admin:%s_%s_change' % (t_opts.app_label, t_opts.model_name)
url = reverse(view, args=args)
else:
url = reverse('admin:%s_%s_changelist' % (t_opts.app_label, t_opts.model_name))
url += '?bill=%i' % bill.pk
state = bill.get_payment_state_display().upper()
title = ''
if bill.closed_amends:
state = '<strike>%s*</strike>' % state
title = _("This bill has been amended, this value may not be valid.")
color = PAYMENT_STATE_COLORS.get(bill.payment_state, 'grey')
return '<a href="{url}" style="color:{color}" title="{title}">{name}</a>'.format(
url=url, color=color, name=state, title=title)
display_payment_state.allow_tags = True
display_payment_state.short_description = _("Payment")
def get_urls(self): def get_urls(self):
""" Hook bill lines management URLs on bill admin """ """ Hook bill lines management URLs on bill admin """
urls = super().get_urls() urls = super().get_urls()
@ -381,10 +410,11 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
fieldsets = super().get_fieldsets(request, obj) fieldsets = super().get_fieldsets(request, obj)
if obj: if obj:
# Switches between amend_of_link and amend_links fields # Switches between amend_of_link and amend_links fields
fields = fieldsets[0][1]['fields']
if obj.amend_of_id: if obj.amend_of_id:
fieldsets[0][1]['fields'][2] = 'amend_of_link' fields[2] = 'amend_of_link'
else: else:
fieldsets[0][1]['fields'][2] = 'amend_links' fields[2] = ()
if obj.is_open: if obj.is_open:
fieldsets = fieldsets[0:-1] fieldsets = fieldsets[0:-1]
return fieldsets return fieldsets
@ -400,10 +430,15 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
return [action for action in actions if action.__name__ not in exclude] return [action for action in actions if action.__name__ not in exclude]
def get_inline_instances(self, request, obj=None): def get_inline_instances(self, request, obj=None):
inlines = super().get_inline_instances(request, obj) cls = type(self)
if obj and not obj.is_open: if obj and not obj.is_open:
return [inline for inline in inlines if type(inline) != BillLineInline] if obj.amends.all():
return [inline for inline in inlines if type(inline) != ClosedBillLineInline] cls.inlines = [AmendInline, ClosedBillLineInline]
else:
cls.inlines = [ClosedBillLineInline]
else:
cls.inlines = [BillLineInline]
return super().get_inline_instances(request, obj)
def formfield_for_dbfield(self, db_field, **kwargs): def formfield_for_dbfield(self, db_field, **kwargs):
""" Make value input widget bigger """ """ Make value input widget bigger """
@ -416,20 +451,6 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
formfield.queryset = formfield.queryset.filter(is_open=False) formfield.queryset = formfield.queryset.filter(is_open=False)
return formfield return formfield
def get_queryset(self, request):
qs = super().get_queryset(request)
qs = qs.annotate(
models.Count('lines'),
# FIXME https://code.djangoproject.com/ticket/10060
approx_total=Coalesce(Sum(
(F('lines__subtotal') + Coalesce('lines__sublines__total', 0)) * (1+F('lines__tax')/100),
), 0),
)
qs = qs.prefetch_related(
Prefetch('amends', queryset=Bill.objects.filter(is_open=False), to_attr='closed_amends')
)
return qs.defer('html')
def change_view(self, request, object_id, **kwargs): def change_view(self, request, object_id, **kwargs):
# TODO raise404, here and everywhere # TODO raise404, here and everywhere
bill = self.get_object(request, unquote(object_id)) bill = self.get_object(request, unquote(object_id))

View File

@ -140,20 +140,21 @@ class AmendedListFilter(SimpleListFilter):
def lookups(self, request, model_admin): def lookups(self, request, model_admin):
return ( return (
('1', _("Closed amends")), ('1', _("Amended")),
('-1', _("Open or closed amends")), ('2', _("Open amends")),
('0', _("No closed amends")), ('3', _("Closed amends")),
('-0', _("No amends")), ('0', _("No amends")),
) )
def queryset(self, request, queryset): def queryset(self, request, queryset):
if self.value() is None: if self.value() is None:
return queryset return queryset
amended = queryset.filter(type__in=Bill.AMEND_MAP.values(), amend_of__isnull=False) amended = queryset.filter(amends__isnull=False)
if not self.value().startswith('-'): if self.value() == '1':
amended = amended.filter(is_open=False) return amended.distinct()
amended_ids = amended.distinct().values_list('amend_of_id', flat=True) elif self.value() == '2':
if self.value().endswith('1'): return amended.filter(amends__is_open=True).distinct()
return queryset.filter(id__in=amended_ids) elif self.value() == '3':
else: return amended.filter(amends__is_open=False).distinct()
return queryset.exclude(id__in=amended_ids) elif self.value() == '0':
return queryset.filter(amends__isnull=True).distinct()

View File

@ -1,6 +1,7 @@
import datetime import datetime
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
from django.core.urlresolvers import reverse
from django.core.validators import ValidationError, RegexValidator from django.core.validators import ValidationError, RegexValidator
from django.db import models from django.db import models
from django.db.models import F, Sum from django.db.models import F, Sum
@ -254,6 +255,9 @@ class Bill(models.Model):
return now + payment.get_due_delta() return now + payment.get_due_delta()
return now + relativedelta(months=1) return now + relativedelta(months=1)
def get_absolute_url(self):
return reverse('admin:bills_bill_view', args=(self.pk,))
def close(self, payment=False): def close(self, payment=False):
if not self.is_open: if not self.is_open:
raise TypeError("Bill not in Open state.") raise TypeError("Bill not in Open state.")

View File

@ -7,3 +7,7 @@
{% endwith %} {% endwith %}
</style> </style>
{% endblock %} {% endblock %}
{% block payment %}
{% endblock %}

View File

@ -140,6 +140,7 @@
</div> </div>
</div> </div>
<div id="footer-column-2"> <div id="footer-column-2">
{% block payment %}
<div id="payment"> <div id="payment">
<span class="title">{% trans "PAYMENT" %}</span> <span class="title">{% trans "PAYMENT" %}</span>
{% if payment.message %} {% if payment.message %}
@ -153,6 +154,8 @@
<strong>{{ seller_info.bank_account }}</strong> <strong>{{ seller_info.bank_account }}</strong>
{% endif %} {% endif %}
</div> </div>
{% endblock %}
{% block questions %}
<div id="questions"> <div id="questions">
<span class="title">{% trans "QUESTIONS" %}</span> <span class="title">{% trans "QUESTIONS" %}</span>
{% blocktrans with type=bill.get_type_display.lower email=seller_info.email %} {% blocktrans with type=bill.get_type_display.lower email=seller_info.email %}
@ -161,6 +164,7 @@
your message. your message.
{% endblocktrans %} {% endblocktrans %}
</div> </div>
{% endblock %}
</div> </div>
</div> </div>
</div> </div>

View File

@ -109,7 +109,6 @@ class TransactionAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
search_fields = ('bill__number', 'bill__account__username', 'id') search_fields = ('bill__number', 'bill__account__username', 'id')
actions = change_view_actions + (actions.report, list_accounts,) actions = change_view_actions + (actions.report, list_accounts,)
filter_by_account_fields = ('bill', 'source') filter_by_account_fields = ('bill', 'source')
change_readonly_fields = ('amount', 'currency')
readonly_fields = ( readonly_fields = (
'bill_link', 'display_state', 'process_link', 'account_link', 'source_link', 'bill_link', 'display_state', 'process_link', 'account_link', 'source_link',
'display_created_at', 'display_modified_at' 'display_created_at', 'display_modified_at'
@ -133,6 +132,11 @@ class TransactionAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
del actions['delete_selected'] del actions['delete_selected']
return actions return actions
def get_change_readonly_fields(self, request, obj):
if obj.state in (Transaction.WAITTING_PROCESSING, Transaction.WAITTING_EXECUTION):
return ()
return ('amount', 'currency')
def get_change_view_actions(self, obj=None): def get_change_view_actions(self, obj=None):
actions = super(TransactionAdmin, self).get_change_view_actions() actions = super(TransactionAdmin, self).get_change_view_actions()
exclude = [] exclude = []

View File

@ -10,21 +10,21 @@
<table id="result_list"> <table id="result_list">
<thead> <thead>
<tr> <tr>
<th scope="col"><div class="text"><span>Action</span</div></th> <th scope="col"><div class="text">Action</div></th>
<th scope="col"><div class="text"><span>ID</span</div></th> <th scope="col"><div class="text">ID</div></th>
<th scope="col"><div class="text"><span>Service</span</div></th> <th scope="col"><div class="text">Service</div></th>
<th scope="col"><div class="text"><span>Account</span</div></th> <th scope="col"><div class="text">Account</div></th>
<th scope="col"><div class="text"><span>Content object</span</div></th> <th scope="col"><div class="text">Content object</div></th>
<th scope="col"><div class="text"><span>Registered on</span</div></th> <th scope="col"><div class="text">Registered on</div></th>
<th scope="col"><div class="text"><span>Billed until</span</div></th> <th scope="col"><div class="text">Billed until</div></th>
<th scope="col"><div class="text"><span>Cancelled on</span</div> <th scope="col"><div class="text">Cancelled on</div></th>
</th> <th scope="col"><div class="text">Ignored</div></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for order, action in updates %} {% for order, action in updates %}
<tr class="{% if forloop.counter|divisibleby:2 %}row2{% else %}row1{% endif %}"> <tr class="{% if forloop.counter|divisibleby:2 %}row2{% else %}row1{% endif %}">
<th>{{ action }}</th> <th>{{ action.capitalize }}</th>
<td>{% if order.pk %}<a href="{{ order | admin_url }}">{{ order.id }}</a>{% endif %}</td> <td>{% if order.pk %}<a href="{{ order | admin_url }}">{{ order.id }}</a>{% endif %}</td>
<td><a href="{{ order.service | admin_url }}">{{ order.service }}</a></td> <td><a href="{{ order.service | admin_url }}">{{ order.service }}</a></td>
<td><a href="{{ order.account | admin_url }}">{{ order.account }}</a></td> <td><a href="{{ order.account | admin_url }}">{{ order.account }}</a></td>
@ -32,6 +32,7 @@
<td><span title="{{ order.registered_on }}">{{ order.registered_on }}</span></td> <td><span title="{{ order.registered_on }}">{{ order.registered_on }}</span></td>
<td><span title="{{ order.billed_unitl }}">{{ order.billed_unitl }}</span></td> <td><span title="{{ order.billed_unitl }}">{{ order.billed_unitl }}</span></td>
<td>{{ order.canncelled_on }}</td> <td>{{ order.canncelled_on }}</td>
<td><img src="/static/admin/img/icon-{% if order.ignore %}yes{% else %}no{% endif %}.svg"></td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>

View File

@ -262,7 +262,7 @@ class UNIXUserDisk(ServiceMonitor):
super(UNIXUserDisk, self).prepare() super(UNIXUserDisk, self).prepare()
self.append(textwrap.dedent("""\ self.append(textwrap.dedent("""\
function monitor () { function monitor () {
{ du -bs "$1" || echo 0; } | awk {'print $1'} { SIZE=$(du -bs "$1") && echo $SIZE || echo 0; } | awk {'print $1'}
}""" }"""
)) ))

View File

@ -27,7 +27,7 @@ class DetailListFilter(SimpleListFilter):
parameter_name = 'detail' parameter_name = 'detail'
def lookups(self, request, model_admin): def lookups(self, request, model_admin):
ret = set() ret = set([('empty', _("Empty"))])
lookup_map = {} lookup_map = {}
for apptype in AppType.get_plugins(): for apptype in AppType.get_plugins():
for field, values in apptype.get_detail_lookups().items(): for field, values in apptype.get_detail_lookups().items():
@ -35,11 +35,13 @@ class DetailListFilter(SimpleListFilter):
lookup_map[value[0]] = field lookup_map[value[0]] = field
ret.add(value) ret.add(value)
self.lookup_map = lookup_map self.lookup_map = lookup_map
return sorted(list(ret)) return sorted(list(ret), key=lambda e: e[1])
def queryset(self, request, queryset): def queryset(self, request, queryset):
value = self.value() value = self.value()
if value: if value:
if value == 'empty':
return queryset.filter(data={})
try: try:
field = self.lookup_map[value] field = self.lookup_map[value]
except KeyError: except KeyError: