def display_summary(self, ticket): context = { 'creator': admin_link('creator')(self, ticket) if ticket.creator else ticket.creator_name, 'created': admin_date('created_at')(ticket), 'updated': '', } msg = ticket.messages.last() if msg: context.update({ 'updated': admin_date('created_at')(msg), 'updater': admin_link('author')(self, msg) if msg.author else msg.author_name, }) context['updated'] = '. Updated by %(updater)s about %(updated)s' % context return '<h4>Added by %(creator)s about %(created)s%(updated)s</h4>' % context
def display_summary(self, ticket): context = { 'creator': admin_link('creator')(self, ticket) if ticket.creator else ticket.creator_name, 'created': admin_date('created_on')(ticket), 'updated': '', } msg = ticket.messages.last() if msg: context.update({ 'updated': admin_date('created_on')(msg), 'updater': admin_link('author')(self, msg) if msg.author else msg.author_name, }) context['updated'] = '. Updated by %(updater)s about %(updated)s' % context return '<h4>Added by %(creator)s about %(created)s%(updated)s</h4>' % context
def instance_link(self, operation): link = admin_link("instance")(self, operation) if link == "---": return _("Deleted {0}").format( operation.instance_repr or "-".join((escape(operation.content_type), escape(operation.object_id))) ) return link
class BillLineInline(admin.TabularInline): model = BillLine fields = ( 'description', 'order_link', 'start_on', 'end_on', 'rate', 'quantity', 'tax', 'subtotal', 'display_total', ) readonly_fields = ('display_total', 'order_link') order_link = admin_link('order', display='pk') @mark_safe def display_total(self, line): if line.pk: total = line.compute_total() sublines = line.sublines.all() url = change_url(line) if sublines: content = '\n'.join(['%s: %s' % (sub.description, sub.total) for sub in sublines]) img = static('admin/img/icon-alert.svg') return '<a href="%s" title="%s">%s <img src="%s"></img></a>' % (url, content, total, img) return '<a href="%s">%s</a>' % (url, total) display_total.short_description = _("Total") def formfield_for_dbfield(self, db_field, **kwargs): """ Make value input widget bigger """ if db_field.name == 'description': kwargs['widget'] = forms.TextInput(attrs={'size':'50'}) elif db_field.name not in ('start_on', 'end_on'): kwargs['widget'] = forms.TextInput(attrs={'size':'6'}) return super().formfield_for_dbfield(db_field, **kwargs) def get_queryset(self, request): qs = super().get_queryset(request) return qs.prefetch_related('sublines').select_related('order')
def instance_link(self, operation): try: return admin_link('instance')(self, operation) except: return _("deleted {0} {1}").format( escape(operation.content_type), escape(operation.object_id) )
class ContractedPlanAdmin(AccountAdminMixin, admin.ModelAdmin): list_display = ('id', 'plan_link', 'account_link') list_filter = ('plan__name',) list_select_related = ('plan', 'account') search_fields = ('account__username', 'plan__name', 'id') actions = (list_accounts,) plan_link = admin_link('plan')
def __init__(self, *args, **kwargs): super(SelectSourceForm, self).__init__(*args, **kwargs) bill = kwargs.get('instance') if bill: total = bill.compute_total() sources = bill.account.paymentsources.filter(is_active=True) recharge = bool(total < 0) choices = [(None, '-----------')] for source in sources: if not recharge or source.method_class().allow_recharge: choices.append((source.pk, str(source))) self.fields['source'].choices = choices self.fields['source'].initial = choices[-1][0] self.fields['show_total'].widget.display = total self.fields['bill_link'].widget.display = admin_link('__str__')(bill) self.fields['display_type'].widget.display = bill.get_type_display() self.fields['account_link'].widget.display = admin_link('account')(bill)
def __init__(self, *args, **kwargs): super(PluginForm, self).__init__(*args, **kwargs) if self.plugin_field in self.fields: # Provide a link to the related DB object change view value = self.plugin.related_instance.pk link = admin_link()(self.plugin.related_instance) display = '%s <a href=".">change</a>' % link self.fields[self.plugin_field].widget = SpanWidget(original=value, display=display) help_text = self.fields[self.plugin_field].help_text
class SMTPLogAdmin(admin.ModelAdmin): list_display = ('id', 'message_link', 'colored_result', 'date_delta', 'log_message') list_filter = ('result', ) fields = ('message_link', 'colored_result', 'date_delta', 'log_message') readonly_fields = fields message_link = admin_link('message') colored_result = admin_colored('result', colors=COLORS, bold=False) date_delta = admin_date('date')
def display_forward(self, address): forward_mailboxes = {m.name: m for m in address.get_forward_mailboxes()} values = [] for forward in address.forward.split(): mbox = forward_mailboxes.get(forward) if mbox: values.append(admin_link()(mbox)) else: values.append(forward) return '<br>'.join(values)
def __init__(self, *args, **kwargs): super(SelectSourceForm, self).__init__(*args, **kwargs) bill = kwargs.get('instance') if bill: total = bill.compute_total() sources = bill.account.paymentsources.filter(is_active=True) recharge = bool(total < 0) choices = [(None, '-----------')] for source in sources: if not recharge or source.method_class().allow_recharge: choices.append((source.pk, str(source))) self.fields['source'].choices = choices self.fields['source'].initial = choices[-1][0] self.fields['show_total'].widget.display = total self.fields['bill_link'].widget.display = admin_link('__str__')( bill) self.fields['display_type'].widget.display = bill.get_type_display( ) self.fields['account_link'].widget.display = admin_link('account')( bill)
def content_html(self, msg): context = { 'number': msg.number, 'time': admin_date('created_on')(msg), 'author': admin_link('author')(msg) if msg.author else msg.author_name, } summary = _("#%(number)i Updated by %(author)s about %(time)s") % context header = '<strong style="color:#666;">%s</strong><hr />' % summary content = markdown(msg.content) content = content.replace('>\n', '>') content = '<div style="padding-left:20px;">%s</div>' % content return header + content
def display_forward(self, address): forward_mailboxes = { m.name: m for m in address.get_forward_mailboxes() } values = [] for forward in address.forward.split(): mbox = forward_mailboxes.get(forward) if mbox: values.append(admin_link()(mbox)) else: values.append(forward) return '<br>'.join(values)
class TransactionInline(admin.TabularInline): model = Transaction can_delete = False extra = 0 fields = ('transaction_link', 'bill_link', 'source_link', 'display_state', 'amount', 'currency') readonly_fields = fields transaction_link = admin_link('__str__', short_description=_("ID")) bill_link = admin_link('bill') source_link = admin_link('source') display_state = admin_colored('state', colors=STATE_COLORS) class Media: css = {'all': ('orchestra/css/hide-inline-id.css', )} def has_add_permission(self, *args, **kwargs): return False def get_queryset(self, *args, **kwargs): qs = super().get_queryset(*args, **kwargs) return qs.select_related('source', 'bill')
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
def content_html(self, msg): context = { 'number': msg.number, 'time': admin_date('created_at')(msg), 'author': admin_link('author')(msg) if msg.author else msg.author_name, } summary = _("#%(number)i Updated by %(author)s about %(time)s") % context header = '<strong style="color:#666;">%s</strong><hr />' % summary content = markdown(msg.content) content = content.replace('>\n', '>') content = '<div style="padding-left:20px;">%s</div>' % content return header + content
class DomainInline(admin.TabularInline): model = Domain fields = ('domain_link', 'display_records', 'account_link') readonly_fields = ('domain_link', 'display_records', 'account_link') extra = 0 verbose_name_plural = _("Subdomains") domain_link = admin_link('__str__') domain_link.short_description = _("Name") account_link = admin_link('account') def display_records(self, domain): return ', '.join([record.type for record in domain.records.all()]) display_records.short_description = _("Declared records") def has_add_permission(self, *args, **kwargs): return False def get_queryset(self, request): """ Order by structured name and imporve performance """ qs = super(DomainInline, self).get_queryset(request) return qs.select_related('account').prefetch_related('records')
def display_websites(self, webapp): websites = [] for content in webapp.content_set.all(): site_url = content.get_absolute_url() site_link = get_on_site_link(site_url) website = content.website name = "%s on %s %s" % (website.name, content.path, site_link) link = admin_link(display=name)(website) websites.append(link) if not websites: add_url = reverse('admin:websites_website_add') add_url += '?account=%s' % webapp.account_id plus = '<strong style="color:green; font-size:12px">+</strong>' websites.append('<a href="%s">%s%s</a>' % (add_url, plus, ugettext("Add website"))) return '<br>'.join(websites)
class TicketInline(admin.TabularInline): fields = ( 'ticket_id', 'subject', 'creator_link', 'owner_link', 'colored_state', 'colored_priority', 'created', 'updated' ) readonly_fields = ( 'ticket_id', 'subject', 'creator_link', 'owner_link', 'colored_state', 'colored_priority', 'created', 'updated' ) model = Ticket extra = 0 max_num = 0 creator_link = admin_link('creator') owner_link = admin_link('owner') created = admin_link('created_at') updated = admin_link('updated_at') colored_state = admin_colored('state', colors=STATE_COLORS, bold=False) colored_priority = admin_colored('priority', colors=PRIORITY_COLORS, bold=False) @mark_safe def ticket_id(self, instance): return '<b>%s</b>' % admin_link()(instance) ticket_id.short_description = '#'
class ContentInline(AccountAdminMixin, admin.TabularInline): model = Content extra = 1 fields = ('webapp', 'webapp_link', 'webapp_type', 'path') readonly_fields = ('webapp_link', 'webapp_type') filter_by_account_fields = ['webapp'] webapp_link = admin_link('webapp', popup=True) webapp_link.short_description = _("Web App") def webapp_type(self, content): if not content.pk: return '' return content.webapp.get_type_display() webapp_type.short_description = _("Web App type")
def set_soa(modeladmin, request, queryset): if queryset.filter(top__isnull=False).exists(): msg = _("Set SOA on subdomains is not possible.") modeladmin.message_user(request, msg, messages.ERROR) return form = SOAForm() if request.POST.get('post') == 'generic_confirmation': form = SOAForm(request.POST) if form.is_valid(): updates = { name: value for name, value in form.cleaned_data.items() if value } change_message = _("SOA set %s") % str(updates)[1:-1] for domain in queryset: for name, value in updates.items(): if name.startswith('clear_'): name = name.replace('clear_', '') value = '' setattr(domain, name, value) modeladmin.log_change(request, domain, change_message) domain.save() num = len(queryset) msg = ungettext( _("SOA record for one domain has been updated."), _("SOA record for %s domains has been updated.") % num, num) modeladmin.message_user(request, msg) return opts = modeladmin.model._meta context = { 'title': _("Set SOA for selected domains"), 'content_message': '', 'action_name': _("Set SOA"), 'action_value': 'set_soa', 'display_objects': [admin_link('__str__')(domain) for domain in queryset], 'queryset': queryset, 'opts': opts, 'app_label': opts.app_label, 'action_checkbox_name': helpers.ACTION_CHECKBOX_NAME, 'form': form, 'obj': get_object_from_url(modeladmin, request), } return render(request, 'admin/orchestra/generic_confirmation.html', context)
def set_soa(modeladmin, request, queryset): if queryset.filter(top__isnull=False).exists(): msg = _("Set SOA on subdomains is not possible.") modeladmin.message_user(request, msg, messages.ERROR) return form = SOAForm() if request.POST.get('post') == 'generic_confirmation': form = SOAForm(request.POST) if form.is_valid(): updates = {name: value for name, value in form.cleaned_data.items() if value} change_message = _("SOA set %s") % str(updates)[1:-1] for domain in queryset: for name, value in updates.items(): if name.startswith('clear_'): name = name.replace('clear_', '') value = '' setattr(domain, name, value) modeladmin.log_change(request, domain, change_message) domain.save() num = len(queryset) msg = ungettext( _("SOA record for one domain has been updated."), _("SOA record for %s domains has been updated.") % num, num ) modeladmin.message_user(request, msg) return opts = modeladmin.model._meta context = { 'title': _("Set SOA for selected domains"), 'content_message': '', 'action_name': _("Set SOA"), 'action_value': 'set_soa', 'display_objects': [admin_link('__str__')(domain) for domain in queryset], 'queryset': queryset, 'opts': opts, 'app_label': opts.app_label, 'action_checkbox_name': helpers.ACTION_CHECKBOX_NAME, 'form': form, 'obj': get_object_from_url(modeladmin, request), } return render(request, 'admin/orchestra/generic_confirmation.html', context)
class BackendLogAdmin(ChangeViewActionsMixin, admin.ModelAdmin): list_display = ( 'id', 'backend', 'server_link', 'display_state', 'exit_code', 'display_created', 'execution_time', ) list_display_links = ('id', 'backend') list_filter = ('state', 'server', 'backend', 'operations__action') search_fields = ('script', ) date_hierarchy = 'created_at' inlines = (BackendOperationInline, ) fields = ('backend', 'server_link', 'state', 'display_script', 'mono_stdout', 'mono_stderr', 'mono_traceback', 'exit_code', 'task_id', 'display_created', 'execution_time') readonly_fields = fields actions = (retry_backend, ) change_view_actions = actions server_link = admin_link('server') display_created = admin_date('created_at', short_description=_("Created")) display_state = admin_colored('state', colors=STATE_COLORS) display_script = display_code('script') mono_stdout = display_mono('stdout') mono_stderr = display_mono('stderr') mono_traceback = display_mono('traceback') class Media: css = {'all': ('orchestra/css/pygments/github.css', )} def get_queryset(self, request): """ Order by structured name and imporve performance """ qs = super(BackendLogAdmin, self).get_queryset(request) return qs.select_related('server').defer('script', 'stdout') def has_add_permission(self, *args, **kwargs): return False
def instance_link(self, operation): link = admin_link('instance')(self, operation) if link == '---': return _("Deleted {0}").format(operation.instance_repr or '-'.join( (escape(operation.content_type), escape(operation.object_id)))) return link
class ResourceDataAdmin(ExtendedModelAdmin): list_display = ('id', 'resource_link', content_object_link, 'allocated', 'display_used', 'display_updated') list_filter = ('resource', ) fields = ( 'resource_link', 'content_type', content_object_link, 'display_updated', 'display_used', 'allocated', ) search_fields = ('content_object_repr', ) readonly_fields = fields actions = (run_monitor, show_history) change_view_actions = actions ordering = ('-updated_at', ) list_select_related = ('resource__content_type', 'content_type') resource_link = admin_link('resource') display_updated = admin_date('updated_at', short_description=_("Updated")) def get_urls(self): """Returns the additional urls for the change view links""" urls = super(ResourceDataAdmin, self).get_urls() admin_site = self.admin_site opts = self.model._meta return [ url('^(\d+)/used-monitordata/$', admin_site.admin_view(self.used_monitordata_view), name='%s_%s_used_monitordata' % (opts.app_label, opts.model_name)), url('^history_data/$', admin_site.admin_view(history_data), name='%s_%s_history_data' % (opts.app_label, opts.model_name)), url('^list-related/(.+)/(.+)/(\d+)/$', admin_site.admin_view(self.list_related_view), name='%s_%s_list_related' % (opts.app_label, opts.model_name)), ] + urls def display_used(self, rdata): if rdata.used is None: return '' url = reverse('admin:resources_resourcedata_used_monitordata', args=(rdata.pk, )) return format_html('<a href="{}">{} {}</a>', url, rdata.used, rdata.unit) display_used.short_description = _("Used") display_used.admin_order_field = 'used' def has_add_permission(self, *args, **kwargs): return False def used_monitordata_view(self, request, object_id): url = reverse('admin:resources_monitordata_changelist') url += '?resource_data=%s' % object_id return redirect(url) def list_related_view(self, request, app_name, model_name, object_id): resources = Resource.objects.select_related('content_type') resource_models = { r.content_type.model_class(): r.content_type_id for r in resources } # Self model = apps.get_model(app_name, model_name) obj = model.objects.get(id=int(object_id)) ct_id = resource_models[model] qset = Q(content_type_id=ct_id, object_id=obj.id, resource__is_active=True) # Related for field, rel in obj._meta.fields_map.items(): try: ct_id = resource_models[rel.related_model] except KeyError: pass else: manager = getattr(obj, field) ids = manager.values_list('id', flat=True) qset = Q(qset) | Q(content_type_id=ct_id, object_id__in=ids, resource__is_active=True) related = ResourceData.objects.filter(qset) related_ids = related.values_list('id', flat=True) related_ids = ','.join(map(str, related_ids)) url = reverse('admin:resources_resourcedata_changelist') url += '?id__in=%s' % related_ids return redirect(url)
def letsencrypt(modeladmin, request, queryset): wildcards = set() domains = set() content_error = '' contentless = queryset.exclude(content__path='/').distinct() if contentless: content_error = ungettext( ugettext( "Selected website %s doesn't have a webapp mounted on <tt>/</tt>." ), ugettext( "Selected websites %s don't have a webapp mounted on <tt>/</tt>." ), len(contentless), ) content_error += ugettext( "<br>Websites need a webapp (e.g. static) mounted on </tt>/</tt> " "for let's encrypt HTTP-01 challenge to work.") content_error = content_error % ', '.join( (admin_link()(website) for website in contentless)) content_error = '<ul class="errorlist"><li>%s</li></ul>' % content_error queryset = queryset.prefetch_related('domains') for website in queryset: for domain in website.domains.all(): if domain.name.startswith('*.'): wildcards.add(domain.name) else: domains.add(domain.name) form = LetsEncryptForm(domains, wildcards, initial={'domains': '\n'.join(domains)}) action_value = 'letsencrypt' if request.POST.get('post') == 'generic_confirmation': form = LetsEncryptForm(domains, wildcards, request.POST) if not content_error and form.is_valid(): cleaned_data = form.cleaned_data domains = set(cleaned_data['domains']) operations = [] for website in queryset: website_domains = [d.name for d in website.domains.all()] encrypt_domains = set() for domain in domains: if is_valid_domain(domain, website_domains, wildcards): encrypt_domains.add(domain) website.encrypt_domains = encrypt_domains operations.extend( Operation.create_for_action(website, 'encrypt')) modeladmin.log_change(request, website, _("Encrypted!")) if not operations: messages.error(request, _("No backend operation has been executed.")) else: logs = Operation.execute(operations) helpers.message_user(request, logs) live_lineages = read_live_lineages(logs) errors = 0 successes = 0 no_https = 0 for website in queryset: try: configure_cert(website, live_lineages) except LookupError: errors += 1 messages.error( request, _("No lineage found for website %s") % website.name) else: if website.protocol == website.HTTP: no_https += 1 website.save(update_fields=('name', )) successes += 1 context = { 'name': website.name, 'errors': errors, 'successes': successes, 'no_https': no_https } if errors: msg = ungettext( _("No lineages found for websites {name}."), _("No lineages found for {errors} websites."), errors) messages.error(request, msg % context) if successes: msg = ungettext( _("{name} website has successfully been encrypted."), _("{successes} websites have been successfully encrypted." ), successes) messages.success(request, msg.format(**context)) if no_https: msg = ungettext( _("{name} website does not have <b>HTTPS protocol</b> enabled." ), _("{no_https} websites do not have <b>HTTPS protocol</b> enabled." ), no_https) messages.warning(request, mark_safe(msg.format(**context))) return opts = modeladmin.model._meta app_label = opts.app_label context = { 'title': _("Let's encrypt!"), 'action_name': _("Encrypt"), 'content_message': ugettext( "You are going to request certificates for the following domains.<br>" "This operation is safe to run multiple times, " "existing certificates will not be regenerated. " "Also notice that let's encrypt does not currently support wildcard certificates." ) + content_error, 'action_value': action_value, 'queryset': queryset, 'opts': opts, 'obj': website if len(queryset) == 1 else None, 'app_label': app_label, 'action_checkbox_name': admin.helpers.ACTION_CHECKBOX_NAME, 'form': form, } return TemplateResponse(request, 'admin/orchestra/generic_confirmation.html', context)
class ListAdmin(ChangePasswordAdminMixin, SelectAccountAdminMixin, ExtendedModelAdmin): list_display = ('name', 'address_name', 'address_domain_link', 'account_link', 'display_active') add_fieldsets = ( (None, { 'classes': ('wide', ), 'fields': ('account_link', 'name', 'is_active') }), (_("Address"), { 'classes': ('wide', ), 'fields': (('address_name', 'address_domain'), ) }), (_("Admin"), { 'classes': ('wide', ), 'fields': ('admin_email', 'password1', 'password2'), }), ) fieldsets = ( (None, { 'classes': ('wide', ), 'fields': ('account_link', 'name', 'is_active') }), (_("Address"), { 'classes': ('wide', ), 'description': _("Additional address besides the default <name>@%s") % settings.LISTS_DEFAULT_DOMAIN, 'fields': (('address_name', 'address_domain'), ) }), (_("Admin"), { 'classes': ('wide', ), 'fields': ('password', ), }), ) search_fields = ('name', 'address_name', 'address_domain__name', 'account__username') list_filter = (IsActiveListFilter, HasCustomAddressListFilter) readonly_fields = ('account_link', ) change_readonly_fields = ('name', ) form = NonStoredUserChangeForm add_form = UserCreationForm list_select_related = ( 'account', 'address_domain', ) filter_by_account_fields = ['address_domain'] actions = (disable, enable, list_accounts) address_domain_link = admin_link('address_domain', order='address_domain__name') def get_urls(self): useradmin = UserAdmin(List, self.admin_site) return [ url(r'^(\d+)/password/$', self.admin_site.admin_view(useradmin.user_change_password)) ] + super(ListAdmin, self).get_urls() def save_model(self, request, obj, form, change): """ set password """ if not change: obj.set_password(form.cleaned_data["password1"]) super(ListAdmin, self).save_model(request, obj, form, change)
class AddressAdmin(SelectAccountAdminMixin, ExtendedModelAdmin): list_display = ( 'display_email', 'account_link', 'domain_link', 'display_mailboxes', 'display_forward', ) list_filter = (HasMailboxListFilter, HasForwardListFilter) fields = ('account_link', 'email_link', 'mailboxes', 'forward', 'display_all_mailboxes') add_fields = ('account_link', ('name', 'domain'), 'mailboxes', 'forward') # inlines = [AutoresponseInline] search_fields = ('forward', 'mailboxes__name', 'account__username', 'computed_email', 'domain__name') readonly_fields = ('account_link', 'domain_link', 'email_link', 'display_all_mailboxes') actions = (SendAddressEmail(), ) filter_by_account_fields = ('domain', 'mailboxes') filter_horizontal = ['mailboxes'] form = AddressForm list_prefetch_related = ('mailboxes', 'domain') domain_link = admin_link('domain', order='domain__name') def display_email(self, address): return address.computed_email display_email.short_description = _("Email") display_email.admin_order_field = 'computed_email' def email_link(self, address): link = self.domain_link(address) return "%s@%s" % (address.name, link) email_link.short_description = _("Email") email_link.allow_tags = True def display_mailboxes(self, address): boxes = [] for mailbox in address.mailboxes.all(): url = change_url(mailbox) boxes.append('<a href="%s">%s</a>' % (url, mailbox.name)) return '<br>'.join(boxes) display_mailboxes.short_description = _("Mailboxes") display_mailboxes.allow_tags = True display_mailboxes.admin_order_field = 'mailboxes__count' def display_all_mailboxes(self, address): boxes = [] for mailbox in address.get_mailboxes(): url = change_url(mailbox) boxes.append('<a href="%s">%s</a>' % (url, mailbox.name)) return '<br>'.join(boxes) display_all_mailboxes.short_description = _("Mailboxes links") display_all_mailboxes.allow_tags = True def display_forward(self, address): forward_mailboxes = { m.name: m for m in address.get_forward_mailboxes() } values = [] for forward in address.forward.split(): mbox = forward_mailboxes.get(forward) if mbox: values.append(admin_link()(mbox)) else: values.append(forward) return '<br>'.join(values) display_forward.short_description = _("Forward") display_forward.allow_tags = True display_forward.admin_order_field = 'forward' def formfield_for_dbfield(self, db_field, **kwargs): if db_field.name == 'forward': kwargs['widget'] = forms.TextInput(attrs={'size': '118'}) return super(AddressAdmin, self).formfield_for_dbfield(db_field, **kwargs) def get_fields(self, request, obj=None): """ Remove mailboxes field when creating address from a popup i.e. from mailbox add form """ fields = super(AddressAdmin, self).get_fields(request, obj) if '_to_field' in parse_qs(request.META['QUERY_STRING']): # Add address popup fields = list(fields) fields.remove('mailboxes') return fields def get_queryset(self, request): qs = super(AddressAdmin, self).get_queryset(request) qs = qs.annotate( computed_email=Concat(F('name'), V('@'), F('domain__name'))) return qs.annotate(Count('mailboxes')) def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None): if not add: self.check_matching_mailbox(request, obj) return super(AddressAdmin, self).render_change_form(request, context, add, change, form_url, obj) def log_addition(self, request, object, *args, **kwargs): self.check_matching_mailbox(request, object) return super(AddressAdmin, self).log_addition(request, object, *args, **kwargs) def check_matching_mailbox(self, request, obj): # Check if new addresse matches with a mbox because of having a local domain if obj.name and obj.domain and obj.domain.name == settings.MAILBOXES_LOCAL_DOMAIN: if obj.name not in obj.forward.split() and Mailbox.objects.filter( name=obj.name).exists(): for mailbox in obj.mailboxes.all(): if mailbox.name == obj.name: return msg = _( "Address '%s' matches mailbox '%s' local address, please consider " "if makes sense adding the mailbox on the mailboxes or forward field." ) % (obj, obj.name) if msg not in (m.message for m in messages.get_messages(request)): self.message_user(request, msg, level=messages.WARNING)
def ticket_id(self, instance): return '<b>%s</b>' % admin_link()(instance)
class MiscellaneousAdmin(SelectPluginAdminMixin, AccountAdminMixin, ExtendedModelAdmin): list_display = ( '__str__', 'service_link', 'amount', 'account_link', 'dispaly_active' ) list_filter = ('service__name', 'is_active') list_select_related = ('service', 'account') readonly_fields = ('account_link', 'service_link') add_fields = ('service', 'account', 'description', 'is_active') fields = ('service_link', 'account', 'description', 'is_active') change_readonly_fields = ('identifier', 'service') search_fields = ('identifier', 'description', 'account__username') actions = (disable, enable) plugin_field = 'service' plugin = MiscServicePlugin service_link = admin_link('service') def dispaly_active(self, instance): return instance.active dispaly_active.short_description = _("Active") dispaly_active.boolean = True dispaly_active.admin_order_field = 'is_active' def get_service(self, obj): if obj is None: return self.plugin.get(self.plugin_value).related_instance else: return obj.service def get_fieldsets(self, request, obj=None): fieldsets = super().get_fieldsets(request, obj) fields = list(fieldsets[0][1]['fields']) service = self.get_service(obj) if obj: fields.insert(1, 'account_link') if service.has_amount: fields.insert(-1, 'amount') if service.has_identifier: fields.insert(2, 'identifier') fieldsets[0][1]['fields'] = fields return fieldsets def get_form(self, request, obj=None, **kwargs): if obj: plugin = self.plugin.get(obj.service.name)() else: plugin = self.plugin.get(self.plugin_value)() self.form = plugin.get_form() self.plugin_instance = plugin service = self.get_service(obj) form = super(SelectPluginAdminMixin, self).get_form(request, obj, **kwargs) def clean_identifier(self, service=service): identifier = self.cleaned_data['identifier'] validator_path = settings.MISCELLANEOUS_IDENTIFIER_VALIDATORS.get(service.name, None) if validator_path: validator = import_class(validator_path) validator(identifier) return identifier form.clean_identifier = clean_identifier return form 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': 4}) return super(MiscellaneousAdmin, self).formfield_for_dbfield(db_field, **kwargs) def save_model(self, request, obj, form, change): if not change: plugin = self.plugin kwargs = { plugin.name_field: self.plugin_value } setattr(obj, self.plugin_field, plugin.model.objects.get(**kwargs)) obj.save()
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') list_filter = ( BillTypeListFilter, 'is_open', 'is_sent', TotalListFilter, PaymentStateListFilter, AmendedListFilter, 'account__is_active', ) add_fields = ('account', 'type', 'amend_of', 'is_open', 'due_on', 'comments') change_list_template = 'admin/bills/bill/change_list.html' fieldsets = ( (None, { 'fields': [ 'number', 'type', (), 'account_link', 'display_total_with_subtotals', 'display_payment_state', 'is_sent', 'comments' ], }), (_("Dates"), { 'classes': ('collapse', ), 'fields': ('created_on_display', 'closed_on_display', 'updated_on_display', 'due_on'), }), (_("Raw"), { 'classes': ('collapse', ), 'fields': ('html', ), }), ) list_prefetch_related = ('transactions', 'lines__sublines') search_fields = ('number', 'account__username', 'comments') change_view_actions = [ actions.manage_lines, actions.view_bill, actions.download_bills, actions.send_bills, actions.close_bills, actions.amend_bills, actions.close_send_download_bills, ] actions = [ actions.manage_lines, actions.download_bills, actions.close_bills, actions.send_bills, 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') readonly_fields = ( 'number', 'display_total', 'is_sent', 'display_payment_state', 'created_on_display', 'closed_on_display', 'updated_on_display', 'display_total_with_subtotals', ) date_hierarchy = 'closed_on' 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 num_lines(self, bill): return bill.lines__count num_lines.admin_order_field = 'lines__count' num_lines.short_description = _("lines") def display_total(self, bill): currency = settings.BILLS_CURRENCY.lower() return '%s &%s;' % (bill.compute_total(), currency) display_total.allow_tags = True display_total.short_description = _("total") display_total.admin_order_field = 'approx_total' def type_link(self, bill): bill_type = bill.type.lower() url = reverse('admin:bills_%s_changelist' % bill_type) return '<a href="%s">%s</a>' % (url, bill.get_type_display()) type_link.allow_tags = True type_link.short_description = _("type") type_link.admin_order_field = 'type' def get_urls(self): """ Hook bill lines management URLs on bill admin """ urls = super().get_urls() admin_site = self.admin_site extra_urls = [ url("^manage-lines/$", admin_site.admin_view( BillLineManagerAdmin(BillLine, admin_site).changelist_view), name='bills_bill_manage_lines'), ] return extra_urls + urls def get_readonly_fields(self, request, obj=None): fields = super().get_readonly_fields(request, obj) if obj and not obj.is_open: fields += self.add_fields return fields def get_fieldsets(self, request, obj=None): 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: fields[2] = 'amend_of_link' else: fields[2] = () if obj.is_open: fieldsets = fieldsets[0:-1] return fieldsets def get_change_view_actions(self, obj=None): actions = super().get_change_view_actions(obj) exclude = [] if obj: if not obj.is_open: exclude += ['close_bills', 'close_send_download_bills'] if obj.type not in obj.AMEND_MAP: exclude += ['amend_bills'] return [action for action in actions if action.__name__ not in exclude] def get_inline_instances(self, request, obj=None): cls = type(self) if obj and not obj.is_open: 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 """ if db_field.name == 'comments': kwargs['widget'] = forms.Textarea(attrs={'cols': 70, 'rows': 4}) elif db_field.name == 'html': kwargs['widget'] = forms.Textarea(attrs={'cols': 150, 'rows': 20}) formfield = super().formfield_for_dbfield(db_field, **kwargs) if db_field.name == 'amend_of': formfield.queryset = formfield.queryset.filter(is_open=False) return formfield def change_view(self, request, object_id, **kwargs): # TODO raise404, here and everywhere bill = self.get_object(request, unquote(object_id)) actions.validate_contact(request, bill, error=False) return super().change_view(request, object_id, **kwargs)
def letsencrypt(modeladmin, request, queryset): wildcards = set() domains = set() content_error = '' contentless = queryset.exclude(content__path='/').distinct() if contentless: content_error = ungettext( ugettext("Selected website %s doesn't have a webapp mounted on <tt>/</tt>."), ugettext("Selected websites %s don't have a webapp mounted on <tt>/</tt>."), len(contentless), ) content_error += ugettext("<br>Websites need a webapp (e.g. static) mounted on </tt>/</tt> " "for let's encrypt HTTP-01 challenge to work.") content_error = content_error % ', '.join((admin_link()(website) for website in contentless)) content_error = '<ul class="errorlist"><li>%s</li></ul>' % content_error queryset = queryset.prefetch_related('domains') for website in queryset: for domain in website.domains.all(): if domain.name.startswith('*.'): wildcards.add(domain.name) else: domains.add(domain.name) form = LetsEncryptForm(domains, wildcards, initial={'domains': '\n'.join(domains)}) action_value = 'letsencrypt' if request.POST.get('post') == 'generic_confirmation': form = LetsEncryptForm(domains, wildcards, request.POST) if not content_error and form.is_valid(): cleaned_data = form.cleaned_data domains = set(cleaned_data['domains']) operations = [] for website in queryset: website_domains = [d.name for d in website.domains.all()] encrypt_domains = set() for domain in domains: if is_valid_domain(domain, website_domains, wildcards): encrypt_domains.add(domain) website.encrypt_domains = encrypt_domains operations.extend(Operation.create_for_action(website, 'encrypt')) modeladmin.log_change(request, website, _("Encrypted!")) if not operations: messages.error(request, _("No backend operation has been executed.")) else: logs = Operation.execute(operations) helpers.message_user(request, logs) live_lineages = read_live_lineages(logs) errors = 0 successes = 0 no_https = 0 for website in queryset: try: configure_cert(website, live_lineages) except LookupError: errors += 1 messages.error(request, _("No lineage found for website %s") % website.name) else: if website.protocol == website.HTTP: no_https += 1 website.save(update_fields=('name',)) successes += 1 context = { 'name': website.name, 'errors': errors, 'successes': successes, 'no_https': no_https } if errors: msg = ungettext( _("No lineages found for websites {name}."), _("No lineages found for {errors} websites."), errors) messages.error(request, msg % context) if successes: msg = ungettext( _("{name} website has successfully been encrypted."), _("{successes} websites have been successfully encrypted."), successes) messages.success(request, msg.format(**context)) if no_https: msg = ungettext( _("{name} website does not have <b>HTTPS protocol</b> enabled."), _("{no_https} websites do not have <b>HTTPS protocol</b> enabled."), no_https) messages.warning(request, mark_safe(msg.format(**context))) return opts = modeladmin.model._meta app_label = opts.app_label context = { 'title': _("Let's encrypt!"), 'action_name': _("Encrypt"), 'content_message': ugettext("You are going to request certificates for the following domains.<br>" "This operation is safe to run multiple times, " "existing certificates will not be regenerated. " "Also notice that let's encrypt does not currently support wildcard certificates.") + content_error, 'action_value': action_value, 'queryset': queryset, 'opts': opts, 'obj': website if len(queryset) == 1 else None, 'app_label': app_label, 'action_checkbox_name': admin.helpers.ACTION_CHECKBOX_NAME, 'form': form, } return TemplateResponse(request, 'admin/orchestra/generic_confirmation.html', context)
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 '<br>'.join(bills)
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 order.description[:64] display_description.short_description = _("Description") display_description.allow_tags = True 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 '<a href="{url}">{description}</a>'.format( url=url, description=description) return order.content_object_repr content_object_link.short_description = _("Content object") content_object_link.allow_tags = True content_object_link.admin_order_field = 'content_object_repr' 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 '<br>'.join(bills) bills_links.short_description = _("Bills") bills_links.allow_tags = True 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 = 'style="color:red;"' if red else '' return '<span title="{raw}" {color}>{human}</span>'.format( raw=escape(str(billed_until)), color=color, human=human, ) display_billed_until.short_description = _("billed until") display_billed_until.allow_tags = True 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)
class DomainAdmin(AccountAdminMixin, ExtendedModelAdmin): list_display = ('structured_name', 'display_is_top', 'display_websites', 'display_addresses', 'account_link') add_fields = ('name', 'account') fields = ('name', 'account_link', 'display_websites', 'display_addresses', 'dns2136_address_match_list') readonly_fields = ('account_link', 'top_link', 'display_websites', 'display_addresses', 'implicit_records') inlines = (RecordInline, DomainInline) list_filter = (TopDomainListFilter, HasWebsiteFilter, HasAddressFilter) change_readonly_fields = ('name', 'serial') search_fields = ('name', 'account__username', 'records__value') add_form = BatchDomainCreationAdminForm actions = (edit_records, set_soa, list_accounts) change_view_actions = (view_zone, edit_records) top_link = admin_link('top') def structured_name(self, domain): if domain.is_top: return domain.name return mark_safe(' ' * 4 + domain.name) structured_name.short_description = _("name") structured_name.admin_order_field = 'structured_name' def display_is_top(self, domain): return domain.is_top display_is_top.short_description = _("Is top") display_is_top.boolean = True display_is_top.admin_order_field = 'top' @mark_safe def display_websites(self, domain): if apps.isinstalled('orchestra.contrib.websites'): websites = domain.websites.all() if websites: links = [] for website in websites: site_link = get_on_site_link(website.get_absolute_url()) admin_url = change_url(website) title = _("Edit website") link = format_html('<a href="{}" title="{}">{} {}</a>', admin_url, title, website.name, site_link) links.append(link) return '<br>'.join(links) add_url = reverse('admin:websites_website_add') add_url += '?account=%i&domains=%i' % (domain.account_id, domain.pk) add_link = format_html( '<a href="{}" title="{}"><img src="{}" /></a>', add_url, _("Add website"), static('orchestra/images/add.png'), ) return _("No website %s") % (add_link) return '---' display_websites.admin_order_field = 'websites__name' display_websites.short_description = _("Websites") @mark_safe def display_addresses(self, domain): if apps.isinstalled('orchestra.contrib.mailboxes'): add_url = reverse('admin:mailboxes_address_add') add_url += '?account=%i&domain=%i' % (domain.account_id, domain.pk) image = '<img src="%s"></img>' % static('orchestra/images/add.png') add_link = '<a href="%s" title="%s">%s</a>' % ( add_url, _("Add address"), image) addresses = domain.addresses.all() if addresses: url = reverse('admin:mailboxes_address_changelist') url += '?domain=%i' % addresses[0].domain_id title = '\n'.join([address.email for address in addresses]) return '<a href="%s" title="%s">%s</a> %s' % ( url, title, len(addresses), add_link) return _("No address %s") % (add_link) return '---' display_addresses.short_description = _("Addresses") display_addresses.admin_order_field = 'addresses__count' @mark_safe def implicit_records(self, domain): types = set(domain.records.values_list('type', flat=True)) ttl = settings.DOMAINS_DEFAULT_TTL lines = [] for record in domain.get_default_records(): line = '{name} {ttl} IN {type} {value}'.format(name=domain.name, ttl=ttl, type=record.type, value=record.value) if not domain.record_is_implicit(record, types): line = format_html('<strike>{}</strike>', line) if record.type is Record.SOA: lines.insert(0, line) else: lines.append(line) return '<br>'.join(lines) implicit_records.short_description = _("Implicit records") def get_fieldsets(self, request, obj=None): """ Add SOA fields when domain is top """ fieldsets = super(DomainAdmin, self).get_fieldsets(request, obj) if obj: fieldsets += ((_("Implicit records"), { 'classes': ('collapse', ), 'fields': ('implicit_records', ), }), ) if obj.is_top: fieldsets += ((_("SOA"), { 'classes': ('collapse', ), 'description': _("SOA (Start of Authority) records are used to determine how the " "zone propagates to the secondary nameservers."), 'fields': ('serial', 'refresh', 'retry', 'expire', 'min_ttl'), }), ) else: existing = fieldsets[0][1]['fields'] if 'top_link' not in existing: fieldsets[0][1]['fields'].insert(2, 'top_link') return fieldsets def get_inline_instances(self, request, obj=None): inlines = super(DomainAdmin, self).get_inline_instances(request, obj) if not obj or not obj.is_top: return [ inline for inline in inlines if type(inline) != DomainInline ] return inlines def get_queryset(self, request): """ Order by structured name and imporve performance """ qs = super(DomainAdmin, self).get_queryset(request) qs = qs.select_related('top', 'account') if request.method == 'GET': qs = qs.annotate(structured_id=Coalesce('top__id', 'id'), structured_name=Concat('top__name', 'name')).order_by( '-structured_id', 'structured_name') if apps.isinstalled('orchestra.contrib.websites'): qs = qs.prefetch_related('websites__domains') if apps.isinstalled('orchestra.contrib.mailboxes'): qs = qs.annotate(models.Count('addresses')) return qs def save_model(self, request, obj, form, change): """ batch domain creation support """ super(DomainAdmin, self).save_model(request, obj, form, change) self.extra_domains = [] if not change: for name in form.extra_names: domain = Domain.objects.create(name=name, account_id=obj.account_id) self.extra_domains.append(domain) def save_related(self, request, form, formsets, change): """ batch domain creation support """ super(DomainAdmin, self).save_related(request, form, formsets, change) if not change: # Clone records to extra_domains, if any for formset in formsets: if formset.model is Record: for domain in self.extra_domains: # Reset pk value of the record instances to force creation of new ones for record_form in formset.forms: record = record_form.instance if record.pk: record.pk = None formset.instance = domain form.instance = domain self.save_formset(request, form, formset, change)
class LogEntryAdmin(admin.ModelAdmin): list_display = ( 'display_action_time', 'user_link', 'display_message', ) list_filter = ( 'action_flag', ('user', admin.RelatedOnlyFieldListFilter), ('content_type', admin.RelatedOnlyFieldListFilter), ) date_hierarchy = 'action_time' search_fields = ('object_repr', 'change_message', 'user__username') fields = ('user_link', 'content_object_link', 'display_action_time', 'display_action', 'change_message') readonly_fields = ( 'user_link', 'content_object_link', 'display_action_time', 'display_action', ) actions = None list_select_related = ('user', 'content_type') list_display_links = None user_link = admin_link('user') display_action_time = admin_date('action_time', short_description=_("Time")) @mark_safe def display_message(self, log): edit = format_html( '<a href="{url}"><img src="{img}"></img></a>', **{ 'url': reverse('admin:admin_logentry_change', args=(log.pk, )), 'img': static('admin/img/icon-changelink.svg'), }) if log.is_addition(): return _('Added "%(link)s". %(edit)s') % { 'link': self.content_object_link(log), 'edit': edit } elif log.is_change(): return _('Changed "%(link)s" - %(changes)s %(edit)s') % { 'link': self.content_object_link(log), 'changes': log.get_change_message(), 'edit': edit, } elif log.is_deletion(): return _('Deleted "%(object)s." %(edit)s') % { 'object': log.object_repr, 'edit': edit, } display_message.short_description = _("Message") display_message.admin_order_field = 'action_flag' def display_action(self, log): if log.is_addition(): return _("Added") elif log.is_change(): return _("Changed") return _("Deleted") display_action.short_description = _("Action") display_action.admin_order_field = 'action_flag' def content_object_link(self, log): ct = log.content_type view = 'admin:%s_%s_change' % (ct.app_label, ct.model) try: url = reverse(view, args=(log.object_id, )) except NoReverseMatch: return log.object_repr return format_html('<a href="{}">{}</a>', url, log.object_repr) content_object_link.short_description = _("Content object") content_object_link.admin_order_field = 'object_repr' def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None): """ Add rel_opts and object to context """ if not add and 'edit' in request.GET.urlencode(): context.update({ 'rel_opts': obj.content_type.model_class()._meta, 'object': obj, }) return super(LogEntryAdmin, self).render_change_form(request, context, add, change, form_url, obj) def response_change(self, request, obj): """ save and continue preserve edit query string """ response = super(LogEntryAdmin, self).response_change(request, obj) if 'edit' in request.GET.urlencode() and 'edit' not in response.url: return HttpResponseRedirect(response.url + '?edit=True') return response def response_post_save_change(self, request, obj): """ save redirect to object history """ if 'edit' in request.GET.urlencode(): opts = obj.content_type.model_class()._meta view = 'admin:%s_%s_history' % (opts.app_label, opts.model_name) post_url = reverse(view, args=(obj.object_id, )) preserved_filters = self.get_preserved_filters(request) post_url = add_preserved_filters( { 'preserved_filters': preserved_filters, 'opts': opts }, post_url) return HttpResponseRedirect(post_url) return super(LogEntryAdmin, self).response_post_save_change(request, obj) def has_add_permission(self, *args, **kwargs): return False def has_delete_permission(self, *args, **kwargs): return False def log_addition(self, *args, **kwargs): pass def log_change(self, *args, **kwargs): pass def log_deletion(self, *args, **kwargs): pass
def account_link(self, instance): account = instance.account if instance.pk else self.account return admin_link()(account)
class BillLineAdmin(admin.ModelAdmin): list_display = ('description', 'bill_link', 'display_is_open', 'account_link', 'rate', 'quantity', 'tax', 'subtotal', 'display_sublinetotal', 'display_total') actions = ( actions.undo_billing, actions.move_lines, actions.copy_lines, actions.service_report, actions.list_bills, ) fieldsets = ( (None, { 'fields': ('bill_link', 'description', 'tax', 'start_on', 'end_on', 'amended_line_link') }), (_("Totals"), { 'fields': ('rate', ('quantity', 'verbose_quantity'), 'subtotal', 'display_sublinetotal', 'display_total'), }), (_("Order"), { 'fields': ( 'order_link', 'order_billed_on', 'order_billed_until', ) }), ) readonly_fields = ('bill_link', 'order_link', 'amended_line_link', 'display_sublinetotal', 'display_total') list_filter = ('tax', 'bill__is_open', 'order__service') list_select_related = ('bill', 'bill__account') search_fields = ('description', 'bill__number') inlines = (BillSublineInline, ) account_link = admin_link('bill__account') bill_link = admin_link('bill') order_link = admin_link('order') amended_line_link = admin_link('amended_line') def display_is_open(self, instance): return instance.bill.is_open display_is_open.short_description = _("Is open") display_is_open.boolean = True def display_sublinetotal(self, instance): total = instance.subline_total return total if total is not None else '---' display_sublinetotal.short_description = _("Sublines") display_sublinetotal.admin_order_field = 'subline_total' def display_total(self, instance): return round(instance.computed_total or 0, 2) display_total.short_description = _("Total") display_total.admin_order_field = 'computed_total' def get_readonly_fields(self, request, obj=None): fields = super().get_readonly_fields(request, obj) if obj and not obj.bill.is_open: return list(fields) + [ 'description', 'tax', 'start_on', 'end_on', 'rate', 'quantity', 'verbose_quantity', 'subtotal', 'order_billed_on', 'order_billed_until' ] return fields def get_queryset(self, request): qs = super().get_queryset(request) qs = qs.annotate( subline_total=Sum('sublines__total'), computed_total=(F('subtotal') + Sum(Coalesce('sublines__total', 0))) * (1 + F('tax') / 100), ) return qs def has_delete_permission(self, request, obj=None): if obj and not obj.bill.is_open: return False return super().has_delete_permission(request, obj)
class AccountAdmin(ChangePasswordAdminMixin, auth.UserAdmin, ExtendedModelAdmin): list_display = ('username', 'full_name', 'type', 'is_active') list_filter = ( 'type', 'is_active', ) add_fieldsets = ( (_("User"), { 'fields': ( 'username', 'password1', 'password2', ), }), (_("Personal info"), { 'fields': ('short_name', 'full_name', 'email', ('type', 'language'), 'comments'), }), (_("Permissions"), { 'fields': ('is_superuser', ) }), ) fieldsets = ( (_("User"), { 'fields': ('username', 'password', 'main_systemuser_link') }), (_("Personal info"), { 'fields': ('short_name', 'full_name', 'email', ('type', 'language'), 'comments'), }), (_("Permissions"), { 'fields': ('is_superuser', 'is_active') }), (_("Important dates"), { 'classes': ('collapse', ), 'fields': ('last_login', 'date_joined') }), ) search_fields = ('username', 'short_name', 'full_name') add_form = AccountCreationForm form = UserChangeForm filter_horizontal = () change_readonly_fields = ('username', 'main_systemuser_link', 'is_active') change_form_template = 'admin/accounts/account/change_form.html' actions = (disable_selected, enable_selected, delete_related_services, list_contacts, service_report, SendEmail()) change_view_actions = (disable_selected, service_report, enable_selected) ordering = () main_systemuser_link = admin_link('main_systemuser') 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}) return super(AccountAdmin, self).formfield_for_dbfield(db_field, **kwargs) def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None): if not add: if request.method == 'GET' and not obj.is_active: messages.warning(request, 'This account is disabled.') context.update({ 'services': sorted([ model._meta for model in services.get() if model is not Account ], key=lambda i: i.verbose_name_plural.lower()), 'accounts': sorted([ model._meta for model in accounts.get() if model is not Account ], key=lambda i: i.verbose_name_plural.lower()) }) return super(AccountAdmin, self).render_change_form(request, context, add, change, form_url, obj) def get_fieldsets(self, request, obj=None): fieldsets = super(AccountAdmin, self).get_fieldsets(request, obj) if not obj: fields = AccountCreationForm.create_related_fields if fields: fieldsets = copy.deepcopy(fieldsets) fieldsets = list(fieldsets) fieldsets.insert(1, (_("Related services"), { 'fields': fields })) return fieldsets def save_model(self, request, obj, form, change): if not change: form.save_model(obj) form.save_related(obj) else: if isinstalled('orchestra.contrib.orders') and isinstalled( 'orchestra.contrib.services'): if 'type' in form.changed_data: old_type = Account.objects.get(pk=obj.pk).type new_type = form.cleaned_data['type'] context = { 'from': old_type.lower(), 'to': new_type.lower(), 'url': reverse('admin:orders_order_changelist'), } msg = '' if old_type in SERVICES_IGNORE_ACCOUNT_TYPE and new_type not in SERVICES_IGNORE_ACCOUNT_TYPE: context['url'] += '?account=%i&ignore=1' % obj.pk msg = _( "Account type has been changed from <i>%(from)s</i> to <i>%(to)s</i>. " "You may want to mark <a href='%(url)s'>existing ignored orders</a> as not ignored." ) elif old_type not in SERVICES_IGNORE_ACCOUNT_TYPE and new_type in SERVICES_IGNORE_ACCOUNT_TYPE: context['url'] += '?account=%i&ignore=0' % obj.pk msg = _( "Account type has been changed from <i>%(from)s</i> to <i>%(to)s</i>. " "You may want to ignore <a href='%(url)s'>existing not ignored orders</a>." ) if msg: messages.warning(request, mark_safe(msg % context)) super(AccountAdmin, self).save_model(request, obj, form, change) def get_change_view_actions(self, obj=None): views = super().get_change_view_actions(obj=obj) if obj is not None: if obj.is_active: return [view for view in views if view.url_name != 'enable'] return [view for view in views if view.url_name != 'disable'] return views def get_actions(self, request): actions = super().get_actions(request) if 'delete_selected' in actions: del actions['delete_selected'] return actions