from datetime import datetime from django import forms from django.contrib import admin from django.urls import reverse, NoReverseMatch from django.db.models import Prefetch from django.utils import timezone from django.utils.html import escape, format_html from django.utils.safestring import mark_safe from django.utils.translation import ugettext_lazy as _ from orchestra.admin import ExtendedModelAdmin from orchestra.admin.utils import admin_link, admin_date, change_url from orchestra.contrib.accounts.actions import list_accounts from orchestra.contrib.accounts.admin import AccountAdminMixin from orchestra.utils.humanize import naturaldate from .actions import BillSelectedOrders, mark_as_ignored, mark_as_not_ignored, report from .filters import IgnoreOrderListFilter, ActiveOrderListFilter, BilledOrderListFilter from .models import Order, MetricStorage class MetricStorageInline(admin.TabularInline): model = MetricStorage readonly_fields = ('value', 'created_on', 'updated_on') extra = 0 def has_add_permission(self, request, obj=None): return False def get_fieldsets(self, request, obj=None): if obj: url = reverse('admin:orders_metricstorage_changelist') url += '?order=%i' % obj.pk title = _('Metric storage, last 10 entries, (See all)') self.verbose_name_plural = mark_safe(title % url) return super(MetricStorageInline, self).get_fieldsets(request, obj) def get_queryset(self, request): qs = super(MetricStorageInline, self).get_queryset(request) change_view = bool(self.parent_object and self.parent_object.pk) if change_view: qs = qs.order_by('-id') parent_id = self.parent_object.pk try: tenth_id = qs.filter(order_id=parent_id).values_list('id', flat=True)[9] except IndexError: pass else: return qs.filter(pk__gte=tenth_id) return qs class OrderAdmin(AccountAdminMixin, ExtendedModelAdmin): list_display = ( 'display_description', 'service_link', 'account_link', 'content_object_link', 'display_registered_on', 'display_billed_until', 'display_cancelled_on', 'display_metric' ) list_filter = ( ActiveOrderListFilter, IgnoreOrderListFilter, BilledOrderListFilter, 'account__type', 'service', ) default_changelist_filters = ( ('ignore', '0'), ) actions = ( BillSelectedOrders(), mark_as_ignored, mark_as_not_ignored, report, list_accounts ) change_view_actions = (BillSelectedOrders(), mark_as_ignored, mark_as_not_ignored) date_hierarchy = 'registered_on' inlines = (MetricStorageInline,) add_inlines = () search_fields = ('account__username', 'content_object_repr', 'description',) list_prefetch_related = ( 'content_object', Prefetch('metrics', queryset=MetricStorage.objects.order_by('-id')), ) list_select_related = ('account', 'service') add_fieldsets = ( (None, { 'fields': ('account', 'service') }), (_("Object"), { 'fields': ('content_type', 'object_id',), }), (_("State"), { 'fields': ('registered_on', 'cancelled_on', 'billed_on', 'billed_metric', 'billed_until' ) }), (None, { 'fields': ('description', 'ignore',), }), ) fieldsets = ( (None, { 'fields': ('account_link', 'service_link', 'content_object_link'), }), (_("State"), { 'fields': ('registered_on', 'cancelled_on', 'billed_on', 'billed_metric', 'billed_until' ) }), (None, { 'fields': ('description', 'ignore', 'bills_links'), }), ) readonly_fields = ( 'content_object_repr', 'content_object_link', 'bills_links', 'account_link', 'service_link' ) service_link = admin_link('service') display_registered_on = admin_date('registered_on') display_cancelled_on = admin_date('cancelled_on') def display_description(self, order): return format_html(order.description[:64]) display_description.short_description = _("Description") display_description.admin_order_field = 'description' def content_object_link(self, order): if order.content_object: try: url = change_url(order.content_object) except NoReverseMatch: # Does not has admin return order.content_object_repr description = str(order.content_object) return format_html('{description}', url=url, description=description) return order.content_object_repr content_object_link.short_description = _("Content object") content_object_link.admin_order_field = 'content_object_repr' @mark_safe def bills_links(self, order): bills = [] make_link = admin_link() for line in order.lines.select_related('bill').distinct('bill'): bills.append(make_link(line.bill)) return '
'.join(bills) bills_links.short_description = _("Bills") def display_billed_until(self, order): billed_until = order.billed_until red = False human = escape(naturaldate(billed_until)) if billed_until: if order.cancelled_on and order.cancelled_on <= billed_until: pass elif order.service.billing_period == order.service.NEVER: human = _("Forever") elif order.service.payment_style == order.service.POSTPAY: boundary = order.service.handler.get_billing_point(order) if billed_until < boundary: red = True elif billed_until < timezone.now().date(): red = True color = mark_safe('style="color:red;"') if red else '' return format_html( '{human}', raw=escape(str(billed_until)), color=color, human=human, ) display_billed_until.short_description = _("billed until") display_billed_until.admin_order_field = 'billed_until' def display_metric(self, order): """ dispalys latest metric value, don't uses latest() because not loosing prefetch_related """ try: metric = order.metrics.all()[0] except IndexError: return '' return metric.value display_metric.short_description = _("Metric") def formfield_for_dbfield(self, db_field, **kwargs): """ Make value input widget bigger """ if db_field.name == 'description': kwargs['widget'] = forms.Textarea(attrs={'cols': 70, 'rows': 2}) return super().formfield_for_dbfield(db_field, **kwargs) # def get_changelist(self, request, **kwargs): # ChangeList = super(OrderAdmin, self).get_changelist(request, **kwargs) # class OrderFilterChangeList(ChangeList): # def get_filters(self, request): # filters = super(OrderFilterChangeList, self).get_filters(request) # tail = [] # filters_copy = [] # for list_filter in filters[0]: # if getattr(list_filter, 'apply_last', False): # tail.append(list_filter) # else: # filters_copy.append(list_filter) # filters = ((filters_copy+tail),) + filters[1:] # return filters # return OrderFilterChangeList class MetricStorageAdmin(admin.ModelAdmin): list_display = ('order', 'value', 'created_on', 'updated_on') list_filter = ('order__service',) raw_id_fields = ('order',) admin.site.register(Order, OrderAdmin) admin.site.register(MetricStorage, MetricStorageAdmin)