Random fixes
This commit is contained in:
parent
a0dbf96c8a
commit
2490ff83c8
6
TODO.md
6
TODO.md
|
@ -430,7 +430,6 @@ mkhomedir_helper or create ssh homes with bash.rc and such
|
|||
# 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
|
||||
|
||||
# upgrade to django 1.9 and make margins wider
|
||||
# lets encrypt: DNS vs HTTP challange
|
||||
# lets enctypt: autorenew
|
||||
|
||||
|
@ -459,8 +458,7 @@ with open(file) as handler:
|
|||
os.unlink(file)
|
||||
|
||||
|
||||
# change filter By PHP version: by detail
|
||||
|
||||
# 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)
|
||||
|
|
|
@ -142,6 +142,7 @@ class ChangeViewActionsMixin(object):
|
|||
view.tool_description = tool_description
|
||||
view.css_class = getattr(action, 'css_class', 'historylink')
|
||||
view.help_text = getattr(action, 'help_text', '')
|
||||
view.hidden = getattr(action, 'hidden', False)
|
||||
views.append(view)
|
||||
return views
|
||||
|
||||
|
@ -150,7 +151,7 @@ class ChangeViewActionsMixin(object):
|
|||
kwargs['extra_context'] = {}
|
||||
obj = self.get_object(request, unquote(object_id))
|
||||
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)
|
||||
|
||||
|
|
|
@ -32,6 +32,7 @@ def view_bill(modeladmin, request, queryset):
|
|||
return HttpResponse(html)
|
||||
view_bill.tool_description = _("View")
|
||||
view_bill.url_name = 'view'
|
||||
view_bill.hidden = True
|
||||
|
||||
|
||||
@transaction.atomic
|
||||
|
|
|
@ -102,6 +102,7 @@ class ClosedBillLineInline(BillLineInline):
|
|||
'display_subtotal', 'display_total'
|
||||
)
|
||||
readonly_fields = fields
|
||||
can_delete = False
|
||||
|
||||
def display_description(self, line):
|
||||
descriptions = [line.description]
|
||||
|
@ -128,9 +129,6 @@ class ClosedBillLineInline(BillLineInline):
|
|||
def has_add_permission(self, request):
|
||||
return False
|
||||
|
||||
def has_delete_permission(self, request, obj=None):
|
||||
return False
|
||||
|
||||
|
||||
class BillLineAdmin(admin.ModelAdmin):
|
||||
list_display = (
|
||||
|
@ -243,7 +241,77 @@ class BillLineManagerAdmin(BillLineAdmin):
|
|||
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 = (
|
||||
'number', 'type_link', 'account_link', 'closed_on_display', 'updated_on_display',
|
||||
'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'
|
||||
fieldsets = (
|
||||
(None, {
|
||||
'fields': ['number', 'type', 'amend_of_link', 'account_link',
|
||||
'display_total_with_subtotals', 'display_payment_state',
|
||||
'is_sent', 'comments'],
|
||||
'fields': ['number', 'type', (), 'account_link', 'display_total_with_subtotals',
|
||||
'display_payment_state', 'is_sent', 'comments'],
|
||||
}),
|
||||
(_("Dates"), {
|
||||
'classes': ('collapse',),
|
||||
|
@ -281,31 +348,26 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
|
|||
actions.amend_bills, actions.bill_report, actions.service_report,
|
||||
actions.close_send_download_bills, list_accounts,
|
||||
]
|
||||
change_readonly_fields = (
|
||||
'account_link', 'type', 'is_open', 'amend_of_link', 'amend_links'
|
||||
)
|
||||
change_readonly_fields = ('account_link', 'type', 'is_open', 'amend_of_link')
|
||||
readonly_fields = (
|
||||
'number', 'display_total', 'is_sent', 'display_payment_state', 'created_on_display',
|
||||
'closed_on_display', 'updated_on_display', 'display_total_with_subtotals',
|
||||
)
|
||||
inlines = [BillLineInline, ClosedBillLineInline]
|
||||
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"))
|
||||
closed_on_display = admin_date('closed_on', short_description=_("Closed"))
|
||||
updated_on_display = admin_date('updated_on', short_description=_("Updated"))
|
||||
amend_of_link = admin_link('amend_of')
|
||||
|
||||
def amend_links(self, bill):
|
||||
links = []
|
||||
for amend in bill.amends.all():
|
||||
url = reverse('admin:bills_bill_change', args=(amend.id,))
|
||||
links.append('<a href="{url}">{num}</a>'.format(url=url, num=amend.number))
|
||||
return '<br>'.join(links)
|
||||
amend_links.short_description = _("Amends")
|
||||
amend_links.allow_tags = True
|
||||
# def amend_links(self, bill):
|
||||
# links = []
|
||||
# for amend in bill.amends.all():
|
||||
# url = reverse('admin:bills_bill_change', args=(amend.id,))
|
||||
# links.append('<a href="{url}">{num}</a>'.format(url=url, num=amend.number))
|
||||
# return '<br>'.join(links)
|
||||
# amend_links.short_description = _("Amends")
|
||||
# amend_links.allow_tags = True
|
||||
|
||||
def num_lines(self, bill):
|
||||
return bill.lines__count
|
||||
|
@ -319,18 +381,6 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
|
|||
display_total.short_description = _("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):
|
||||
bill_type = bill.type.lower()
|
||||
url = reverse('admin:bills_%s_changelist' % bill_type)
|
||||
|
@ -339,27 +389,6 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
|
|||
type_link.short_description = _("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):
|
||||
""" Hook bill lines management URLs on bill admin """
|
||||
urls = super().get_urls()
|
||||
|
@ -381,10 +410,11 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
|
|||
fieldsets = super().get_fieldsets(request, obj)
|
||||
if obj:
|
||||
# Switches between amend_of_link and amend_links fields
|
||||
fields = fieldsets[0][1]['fields']
|
||||
if obj.amend_of_id:
|
||||
fieldsets[0][1]['fields'][2] = 'amend_of_link'
|
||||
fields[2] = 'amend_of_link'
|
||||
else:
|
||||
fieldsets[0][1]['fields'][2] = 'amend_links'
|
||||
fields[2] = ()
|
||||
if obj.is_open:
|
||||
fieldsets = fieldsets[0:-1]
|
||||
return fieldsets
|
||||
|
@ -400,10 +430,15 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
|
|||
return [action for action in actions if action.__name__ not in exclude]
|
||||
|
||||
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:
|
||||
return [inline for inline in inlines if type(inline) != BillLineInline]
|
||||
return [inline for inline in inlines if type(inline) != ClosedBillLineInline]
|
||||
if obj.amends.all():
|
||||
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):
|
||||
""" Make value input widget bigger """
|
||||
|
@ -416,20 +451,6 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
|
|||
formfield.queryset = formfield.queryset.filter(is_open=False)
|
||||
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):
|
||||
# TODO raise404, here and everywhere
|
||||
bill = self.get_object(request, unquote(object_id))
|
||||
|
|
|
@ -140,20 +140,21 @@ class AmendedListFilter(SimpleListFilter):
|
|||
|
||||
def lookups(self, request, model_admin):
|
||||
return (
|
||||
('1', _("Closed amends")),
|
||||
('-1', _("Open or closed amends")),
|
||||
('0', _("No closed amends")),
|
||||
('-0', _("No amends")),
|
||||
('1', _("Amended")),
|
||||
('2', _("Open amends")),
|
||||
('3', _("Closed amends")),
|
||||
('0', _("No amends")),
|
||||
)
|
||||
|
||||
def queryset(self, request, queryset):
|
||||
if self.value() is None:
|
||||
return queryset
|
||||
amended = queryset.filter(type__in=Bill.AMEND_MAP.values(), amend_of__isnull=False)
|
||||
if not self.value().startswith('-'):
|
||||
amended = amended.filter(is_open=False)
|
||||
amended_ids = amended.distinct().values_list('amend_of_id', flat=True)
|
||||
if self.value().endswith('1'):
|
||||
return queryset.filter(id__in=amended_ids)
|
||||
else:
|
||||
return queryset.exclude(id__in=amended_ids)
|
||||
amended = queryset.filter(amends__isnull=False)
|
||||
if self.value() == '1':
|
||||
return amended.distinct()
|
||||
elif self.value() == '2':
|
||||
return amended.filter(amends__is_open=True).distinct()
|
||||
elif self.value() == '3':
|
||||
return amended.filter(amends__is_open=False).distinct()
|
||||
elif self.value() == '0':
|
||||
return queryset.filter(amends__isnull=True).distinct()
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import datetime
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.core.validators import ValidationError, RegexValidator
|
||||
from django.db import models
|
||||
from django.db.models import F, Sum
|
||||
|
@ -254,6 +255,9 @@ class Bill(models.Model):
|
|||
return now + payment.get_due_delta()
|
||||
return now + relativedelta(months=1)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('admin:bills_bill_view', args=(self.pk,))
|
||||
|
||||
def close(self, payment=False):
|
||||
if not self.is_open:
|
||||
raise TypeError("Bill not in Open state.")
|
||||
|
|
|
@ -7,3 +7,7 @@
|
|||
{% endwith %}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block payment %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -140,6 +140,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div id="footer-column-2">
|
||||
{% block payment %}
|
||||
<div id="payment">
|
||||
<span class="title">{% trans "PAYMENT" %}</span>
|
||||
{% if payment.message %}
|
||||
|
@ -153,6 +154,8 @@
|
|||
<strong>{{ seller_info.bank_account }}</strong>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block questions %}
|
||||
<div id="questions">
|
||||
<span class="title">{% trans "QUESTIONS" %}</span>
|
||||
{% blocktrans with type=bill.get_type_display.lower email=seller_info.email %}
|
||||
|
@ -161,6 +164,7 @@
|
|||
your message.
|
||||
{% endblocktrans %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -109,7 +109,6 @@ class TransactionAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
|
|||
search_fields = ('bill__number', 'bill__account__username', 'id')
|
||||
actions = change_view_actions + (actions.report, list_accounts,)
|
||||
filter_by_account_fields = ('bill', 'source')
|
||||
change_readonly_fields = ('amount', 'currency')
|
||||
readonly_fields = (
|
||||
'bill_link', 'display_state', 'process_link', 'account_link', 'source_link',
|
||||
'display_created_at', 'display_modified_at'
|
||||
|
@ -133,6 +132,11 @@ class TransactionAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
|
|||
del actions['delete_selected']
|
||||
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):
|
||||
actions = super(TransactionAdmin, self).get_change_view_actions()
|
||||
exclude = []
|
||||
|
|
|
@ -10,21 +10,21 @@
|
|||
<table id="result_list">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col"><div class="text"><span>Action</span</div></th>
|
||||
<th scope="col"><div class="text"><span>ID</span</div></th>
|
||||
<th scope="col"><div class="text"><span>Service</span</div></th>
|
||||
<th scope="col"><div class="text"><span>Account</span</div></th>
|
||||
<th scope="col"><div class="text"><span>Content object</span</div></th>
|
||||
<th scope="col"><div class="text"><span>Registered on</span</div></th>
|
||||
<th scope="col"><div class="text"><span>Billed until</span</div></th>
|
||||
<th scope="col"><div class="text"><span>Cancelled on</span</div>
|
||||
</th>
|
||||
<th scope="col"><div class="text">Action</div></th>
|
||||
<th scope="col"><div class="text">ID</div></th>
|
||||
<th scope="col"><div class="text">Service</div></th>
|
||||
<th scope="col"><div class="text">Account</div></th>
|
||||
<th scope="col"><div class="text">Content object</div></th>
|
||||
<th scope="col"><div class="text">Registered on</div></th>
|
||||
<th scope="col"><div class="text">Billed until</div></th>
|
||||
<th scope="col"><div class="text">Cancelled on</div></th>
|
||||
<th scope="col"><div class="text">Ignored</div></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for order, action in updates %}
|
||||
<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><a href="{{ order.service | admin_url }}">{{ order.service }}</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.billed_unitl }}">{{ order.billed_unitl }}</span></td>
|
||||
<td>{{ order.canncelled_on }}</td>
|
||||
<td><img src="/static/admin/img/icon-{% if order.ignore %}yes{% else %}no{% endif %}.svg"></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
|
|
@ -262,7 +262,7 @@ class UNIXUserDisk(ServiceMonitor):
|
|||
super(UNIXUserDisk, self).prepare()
|
||||
self.append(textwrap.dedent("""\
|
||||
function monitor () {
|
||||
{ du -bs "$1" || echo 0; } | awk {'print $1'}
|
||||
{ SIZE=$(du -bs "$1") && echo $SIZE || echo 0; } | awk {'print $1'}
|
||||
}"""
|
||||
))
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ class DetailListFilter(SimpleListFilter):
|
|||
parameter_name = 'detail'
|
||||
|
||||
def lookups(self, request, model_admin):
|
||||
ret = set()
|
||||
ret = set([('empty', _("Empty"))])
|
||||
lookup_map = {}
|
||||
for apptype in AppType.get_plugins():
|
||||
for field, values in apptype.get_detail_lookups().items():
|
||||
|
@ -35,11 +35,13 @@ class DetailListFilter(SimpleListFilter):
|
|||
lookup_map[value[0]] = field
|
||||
ret.add(value)
|
||||
self.lookup_map = lookup_map
|
||||
return sorted(list(ret))
|
||||
return sorted(list(ret), key=lambda e: e[1])
|
||||
|
||||
def queryset(self, request, queryset):
|
||||
value = self.value()
|
||||
if value:
|
||||
if value == 'empty':
|
||||
return queryset.filter(data={})
|
||||
try:
|
||||
field = self.lookup_map[value]
|
||||
except KeyError:
|
||||
|
|
Loading…
Reference in a new issue