Merge pull request #8 from ribaguifi/dev/django2.1-admin

Refactor admin code to support Django 2.1
This commit is contained in:
Santiago L 2021-05-24 12:55:35 +02:00 committed by GitHub
commit 7b59931bcf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 278 additions and 296 deletions

View File

@ -5,7 +5,7 @@ from django import forms
from django.contrib.admin import helpers from django.contrib.admin import helpers
from django.core import validators from django.core import validators
from django.forms.models import modelformset_factory, BaseModelFormSet from django.forms.models import modelformset_factory, BaseModelFormSet
from django.template import Template, Context from django.template import Template
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from orchestra.forms.widgets import SpanWidget from orchestra.forms.widgets import SpanWidget
@ -28,9 +28,9 @@ class AdminFormMixin(object):
' {% include "admin/includes/fieldset.html" %}' ' {% include "admin/includes/fieldset.html" %}'
'{% endfor %}' '{% endfor %}'
) )
context = Context({ context = {
'adminform': adminform 'adminform': adminform
}) }
return template.render(context) return template.render(context)
@ -71,9 +71,9 @@ class AdminFormSet(BaseModelFormSet):
</div> </div>
</div>""") </div>""")
) )
context = Context({ context = {
'formset': self 'formset': self
}) }
return template.render(context) return template.render(context)

View File

@ -10,7 +10,7 @@ from django.urls import reverse, NoReverseMatch
from django.db import models from django.db import models
from django.shortcuts import redirect from django.shortcuts import redirect
from django.utils import timezone from django.utils import timezone
from django.utils.html import escape from django.utils.html import escape, format_html
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from orchestra.models.utils import get_field_value from orchestra.models.utils import get_field_value
@ -113,21 +113,21 @@ def admin_link(*args, **kwargs):
return '---' return '---'
if not getattr(obj, 'pk', None): if not getattr(obj, 'pk', None):
return '---' return '---'
display = kwargs.get('display') display_ = kwargs.get('display')
if display: if display_:
display = getattr(obj, display, display) display_ = getattr(obj, display_, display_)
else: else:
display = obj display_ = obj
try: try:
url = change_url(obj) url = change_url(obj)
except NoReverseMatch: except NoReverseMatch:
# Does not has admin # Does not has admin
return str(display) return str(display_)
extra = '' extra = ''
if kwargs['popup']: if kwargs['popup']:
extra = 'onclick="return showAddAnotherPopup(this);"' extra = mark_safe('onclick="return showAddAnotherPopup(this);"')
title = "Change %s" % obj._meta.verbose_name title = "Change %s" % obj._meta.verbose_name
return mark_safe('<a href="%s" title="%s" %s>%s</a>' % (url, title, extra, display)) return format_html('<a href="{}" title="{}" {}>{}</a>', url, title, extra, display_)
@admin_field @admin_field
@ -158,7 +158,7 @@ def admin_date(*args, **kwargs):
date = date.strftime("%Y-%m-%d %H:%M:%S %Z") date = date.strftime("%Y-%m-%d %H:%M:%S %Z")
else: else:
date = date.strftime("%Y-%m-%d") date = date.strftime("%Y-%m-%d")
return '<span title="{0}">{1}</span>'.format(date, escape(natural)) return format_html('<span title="{0}">{1}</span>', date, natural)
def get_object_from_url(modeladmin, request): def get_object_from_url(modeladmin, request):

View File

@ -175,7 +175,7 @@ def delete_related_services(modeladmin, request, queryset):
for model, objs in collector.model_objs.items(): for model, objs in collector.model_objs.items():
count = 0 count = 0
# discount main systemuser # discount main systemuser
if model is modeladmin.model.main_systemuser.field.model: if model is modeladmin.model.main_systemuser.field.related_model:
count = len(objs) - 1 count = len(objs) - 1
# Discount account # Discount account
elif model is not modeladmin.model and model in registered_services: elif model is not modeladmin.model and model in registered_services:

View File

@ -158,6 +158,7 @@ class AccountListAdmin(AccountAdmin):
actions = None actions = None
change_list_template = 'admin/accounts/account/select_account_list.html' change_list_template = 'admin/accounts/account/select_account_list.html'
@mark_safe
def select_account(self, instance): def select_account(self, instance):
# TODO get query string from request.META['QUERY_STRING'] to preserve filters # TODO get query string from request.META['QUERY_STRING'] to preserve filters
context = { context = {
@ -167,7 +168,6 @@ class AccountListAdmin(AccountAdmin):
} }
return _('<a href="%(url)s">%(plus)s Add to %(name)s</a>') % context return _('<a href="%(url)s">%(plus)s Add to %(name)s</a>') % context
select_account.short_description = _("account") select_account.short_description = _("account")
select_account.allow_tags = True
select_account.admin_order_field = 'username' select_account.admin_order_field = 'username'
def changelist_view(self, request, extra_context=None): def changelist_view(self, request, extra_context=None):
@ -207,6 +207,7 @@ class AccountAdminMixin(object):
account = None account = None
list_select_related = ('account',) list_select_related = ('account',)
@mark_safe
def display_active(self, instance): def display_active(self, instance):
if not instance.is_active: if not instance.is_active:
return '<img src="%s" alt="False">' % static('admin/img/icon-no.svg') return '<img src="%s" alt="False">' % static('admin/img/icon-no.svg')
@ -215,14 +216,12 @@ class AccountAdminMixin(object):
return '<img style="width:13px" src="%s" alt="False" title="%s">' % (static('admin/img/inline-delete.svg'), msg) return '<img style="width:13px" src="%s" alt="False" title="%s">' % (static('admin/img/inline-delete.svg'), msg)
return '<img src="%s" alt="False">' % static('admin/img/icon-yes.svg') return '<img src="%s" alt="False">' % static('admin/img/icon-yes.svg')
display_active.short_description = _("active") display_active.short_description = _("active")
display_active.allow_tags = True
display_active.admin_order_field = 'is_active' display_active.admin_order_field = 'is_active'
def account_link(self, instance): def account_link(self, instance):
account = instance.account if instance.pk else self.account account = instance.account if instance.pk else self.account
return admin_link()(account) return admin_link()(account)
account_link.short_description = _("account") account_link.short_description = _("account")
account_link.allow_tags = True
account_link.admin_order_field = 'account__username' account_link.admin_order_field = 'account__username'
def get_form(self, request, obj=None, **kwargs): def get_form(self, request, obj=None, **kwargs):

View File

@ -47,7 +47,7 @@ def create_account_creation_form():
# Previous validation error # Previous validation error
return return
errors = {} errors = {}
systemuser_model = Account.main_systemuser.field.model systemuser_model = Account.main_systemuser.field.related_model
if systemuser_model.objects.filter(username=account.username).exists(): if systemuser_model.objects.filter(username=account.username).exists():
errors['username'] = _("A system user with this name already exists.") errors['username'] = _("A system user with this name already exists.")
for model, key, related_kwargs, __ in create_related: for model, key, related_kwargs, __ in create_related:

View File

@ -7,6 +7,7 @@ from django.db import models
from django.db.models import F, Sum, Prefetch from django.db.models import F, Sum, Prefetch
from django.db.models.functions import Coalesce from django.db.models.functions import Coalesce
from django.templatetags.static import static from django.templatetags.static import static
from django.utils.html import format_html
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.shortcuts import redirect from django.shortcuts import redirect
@ -15,7 +16,7 @@ from orchestra.admin import ExtendedModelAdmin
from orchestra.admin.utils import admin_date, insertattr, admin_link, change_url from orchestra.admin.utils import admin_date, insertattr, admin_link, change_url
from orchestra.contrib.accounts.actions import list_accounts from orchestra.contrib.accounts.actions import list_accounts
from orchestra.contrib.accounts.admin import AccountAdminMixin, AccountAdmin from orchestra.contrib.accounts.admin import AccountAdminMixin, AccountAdmin
from orchestra.forms.widgets import paddingCheckboxSelectMultiple from orchestra.forms.widgets import PaddingCheckboxSelectMultiple
from . import settings, actions from . import settings, actions
from .filters import (BillTypeListFilter, HasBillContactListFilter, TotalListFilter, from .filters import (BillTypeListFilter, HasBillContactListFilter, TotalListFilter,
@ -67,6 +68,7 @@ class BillLineInline(admin.TabularInline):
order_link = admin_link('order', display='pk') order_link = admin_link('order', display='pk')
@mark_safe
def display_total(self, line): def display_total(self, line):
if line.pk: if line.pk:
total = line.compute_total() total = line.compute_total()
@ -78,7 +80,6 @@ class BillLineInline(admin.TabularInline):
return '<a href="%s" title="%s">%s <img src="%s"></img></a>' % (url, content, total, img) return '<a href="%s" title="%s">%s <img src="%s"></img></a>' % (url, content, total, img)
return '<a href="%s">%s</a>' % (url, total) return '<a href="%s">%s</a>' % (url, total)
display_total.short_description = _("Total") display_total.short_description = _("Total")
display_total.allow_tags = True
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 """
@ -104,27 +105,26 @@ class ClosedBillLineInline(BillLineInline):
readonly_fields = fields readonly_fields = fields
can_delete = False can_delete = False
@mark_safe
def display_description(self, line): def display_description(self, line):
descriptions = [line.description] descriptions = [line.description]
for subline in line.sublines.all(): for subline in line.sublines.all():
descriptions.append('&nbsp;' * 4 + subline.description) descriptions.append('&nbsp;' * 4 + subline.description)
return '<br>'.join(descriptions) return '<br>'.join(descriptions)
display_description.short_description = _("Description") display_description.short_description = _("Description")
display_description.allow_tags = True
@mark_safe
def display_subtotal(self, line): def display_subtotal(self, line):
subtotals = ['&nbsp;' + str(line.subtotal)] subtotals = ['&nbsp;' + str(line.subtotal)]
for subline in line.sublines.all(): for subline in line.sublines.all():
subtotals.append(str(subline.total)) subtotals.append(str(subline.total))
return '<br>'.join(subtotals) return '<br>'.join(subtotals)
display_subtotal.short_description = _("Subtotal") display_subtotal.short_description = _("Subtotal")
display_subtotal.allow_tags = True
def display_total(self, line): def display_total(self, line):
if line.pk: if line.pk:
return line.compute_total() return line.compute_total()
display_total.short_description = _("Total") display_total.short_description = _("Total")
display_total.allow_tags = True
def has_add_permission(self, request): def has_add_permission(self, request):
return False return False
@ -242,6 +242,7 @@ class BillLineManagerAdmin(BillLineAdmin):
class BillAdminMixin(AccountAdminMixin): class BillAdminMixin(AccountAdminMixin):
@mark_safe
def display_total_with_subtotals(self, bill): def display_total_with_subtotals(self, bill):
if bill.pk: if bill.pk:
currency = settings.BILLS_CURRENCY.lower() currency = settings.BILLS_CURRENCY.lower()
@ -251,10 +252,10 @@ class BillAdminMixin(AccountAdminMixin):
subtotals.append(_("Taxes %s%% VAT %s &%s;") % (tax, subtotal[1], currency)) subtotals.append(_("Taxes %s%% VAT %s &%s;") % (tax, subtotal[1], currency))
subtotals = '\n'.join(subtotals) subtotals = '\n'.join(subtotals)
return '<span title="%s">%s &%s;</span>' % (subtotals, bill.compute_total(), currency) 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.short_description = _("total")
display_total_with_subtotals.admin_order_field = 'approx_total' display_total_with_subtotals.admin_order_field = 'approx_total'
@mark_safe
def display_payment_state(self, bill): def display_payment_state(self, bill):
if bill.pk: if bill.pk:
t_opts = bill.transactions.model._meta t_opts = bill.transactions.model._meta
@ -276,7 +277,6 @@ class BillAdminMixin(AccountAdminMixin):
color = PAYMENT_STATE_COLORS.get(bill.payment_state, 'grey') color = PAYMENT_STATE_COLORS.get(bill.payment_state, 'grey')
return '<a href="{url}" style="color:{color}" title="{title}">{name}</a>'.format( return '<a href="{url}" style="color:{color}" title="{title}">{name}</a>'.format(
url=url, color=color, name=state, title=title) url=url, color=color, name=state, title=title)
display_payment_state.allow_tags = True
display_payment_state.short_description = _("Payment") display_payment_state.short_description = _("Payment")
def get_queryset(self, request): def get_queryset(self, request):
@ -376,16 +376,14 @@ class BillAdmin(BillAdminMixin, ExtendedModelAdmin):
def display_total(self, bill): def display_total(self, bill):
currency = settings.BILLS_CURRENCY.lower() currency = settings.BILLS_CURRENCY.lower()
return '%s &%s;' % (bill.compute_total(), currency) return format_html('{} &{};', bill.compute_total(), currency)
display_total.allow_tags = True
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 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)
return '<a href="%s">%s</a>' % (url, bill.get_type_display()) return format_html('<a href="{}">{}</a>', url, bill.get_type_display())
type_link.allow_tags = True
type_link.short_description = _("type") type_link.short_description = _("type")
type_link.admin_order_field = 'type' type_link.admin_order_field = 'type'
@ -479,7 +477,7 @@ class BillContactInline(admin.StackedInline):
if db_field.name == 'address': if db_field.name == 'address':
kwargs['widget'] = forms.Textarea(attrs={'cols': 70, 'rows': 2}) kwargs['widget'] = forms.Textarea(attrs={'cols': 70, 'rows': 2})
if db_field.name == 'email_usage': if db_field.name == 'email_usage':
kwargs['widget'] = paddingCheckboxSelectMultiple(45) kwargs['widget'] = PaddingCheckboxSelectMultiple(45)
return super().formfield_for_dbfield(db_field, **kwargs) return super().formfield_for_dbfield(db_field, **kwargs)

View File

@ -21,7 +21,7 @@ def validate_contact(request, bill, error=True):
message = msg.format(relation=_("Related"), account=account, url=url) message = msg.format(relation=_("Related"), account=account, url=url)
send(request, mark_safe(message)) send(request, mark_safe(message))
valid = False valid = False
main = type(bill).account.field.model.objects.get_main() main = type(bill).account.field.related_model.objects.get_main()
if not hasattr(main, 'billcontact'): if not hasattr(main, 'billcontact'):
account = force_text(main) account = force_text(main)
url = reverse('admin:accounts_account_change', args=(main.id,)) url = reverse('admin:accounts_account_change', args=(main.id,))

View File

@ -6,7 +6,7 @@ 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
from django.db.models.functions import Coalesce from django.db.models.functions import Coalesce
from django.template import loader, Context from django.template import loader
from django.utils import timezone, translation from django.utils import timezone, translation
from django.utils.encoding import force_text from django.utils.encoding import force_text
from django.utils.functional import cached_property from django.utils.functional import cached_property
@ -303,7 +303,7 @@ class Bill(models.Model):
with translation.override(language or self.account.language): with translation.override(language or self.account.language):
if payment is False: if payment is False:
payment = self.account.paymentsources.get_default() payment = self.account.paymentsources.get_default()
context = Context({ context = {
'bill': self, 'bill': self,
'lines': self.lines.all().prefetch_related('sublines'), 'lines': self.lines.all().prefetch_related('sublines'),
'seller': self.seller, 'seller': self.seller,
@ -318,7 +318,7 @@ class Bill(models.Model):
'payment': payment and payment.get_bill_context(), 'payment': payment and payment.get_bill_context(),
'default_due_date': self.get_due_date(payment=payment), 'default_due_date': self.get_due_date(payment=payment),
'now': timezone.now(), 'now': timezone.now(),
}) }
template_name = 'BILLS_%s_TEMPLATE' % self.get_type() template_name = 'BILLS_%s_TEMPLATE' % self.get_type()
template = getattr(settings, template_name, settings.BILLS_DEFAULT_TEMPLATE) template = getattr(settings, template_name, settings.BILLS_DEFAULT_TEMPLATE)
bill_template = loader.get_template(template) bill_template = loader.get_template(template)

View File

@ -7,7 +7,7 @@ from orchestra.admin.actions import SendEmail
from orchestra.admin.utils import insertattr, change_url from orchestra.admin.utils import insertattr, change_url
from orchestra.contrib.accounts.actions import list_accounts from orchestra.contrib.accounts.actions import list_accounts
from orchestra.contrib.accounts.admin import AccountAdmin, AccountAdminMixin from orchestra.contrib.accounts.admin import AccountAdmin, AccountAdminMixin
from orchestra.forms.widgets import paddingCheckboxSelectMultiple from orchestra.forms.widgets import PaddingCheckboxSelectMultiple
from .filters import EmailUsageListFilter from .filters import EmailUsageListFilter
from .models import Contact from .models import Contact
@ -72,7 +72,7 @@ class ContactAdmin(AccountAdminMixin, ExtendedModelAdmin):
if db_field.name == 'address': if db_field.name == 'address':
kwargs['widget'] = forms.Textarea(attrs={'cols': 70, 'rows': 2}) kwargs['widget'] = forms.Textarea(attrs={'cols': 70, 'rows': 2})
if db_field.name == 'email_usage': if db_field.name == 'email_usage':
kwargs['widget'] = paddingCheckboxSelectMultiple(130) kwargs['widget'] = PaddingCheckboxSelectMultiple(130)
return super(ContactAdmin, self).formfield_for_dbfield(db_field, **kwargs) return super(ContactAdmin, self).formfield_for_dbfield(db_field, **kwargs)
@ -101,7 +101,7 @@ class ContactInline(admin.StackedInline):
if db_field.name == 'address': if db_field.name == 'address':
kwargs['widget'] = forms.Textarea(attrs={'cols': 70, 'rows': 2}) kwargs['widget'] = forms.Textarea(attrs={'cols': 70, 'rows': 2})
if db_field.name == 'email_usage': if db_field.name == 'email_usage':
kwargs['widget'] = paddingCheckboxSelectMultiple(45) kwargs['widget'] = PaddingCheckboxSelectMultiple(45)
return super(ContactInline, self).formfield_for_dbfield(db_field, **kwargs) return super(ContactInline, self).formfield_for_dbfield(db_field, **kwargs)

View File

@ -1,6 +1,8 @@
from django.conf.urls import url from django.conf.urls import url
from django.contrib import admin from django.contrib import admin
from django.contrib.auth.admin import UserAdmin from django.contrib.auth.admin import UserAdmin
from django.utils.html import format_html
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from orchestra.admin import ExtendedModelAdmin, ChangePasswordAdminMixin from orchestra.admin import ExtendedModelAdmin, ChangePasswordAdminMixin
@ -50,14 +52,14 @@ class DatabaseAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
list_prefetch_related = ('users',) list_prefetch_related = ('users',)
actions = (list_accounts, save_selected) actions = (list_accounts, save_selected)
@mark_safe
def display_users(self, db): def display_users(self, db):
links = [] links = []
for user in db.users.all(): for user in db.users.all():
link = '<a href="%s">%s</a>' % (change_url(user), user.username) link = format_html('<a href="{}">{}</a>', change_url(user), user.username)
links.append(link) links.append(link)
return '<br>'.join(links) return '<br>'.join(links)
display_users.short_description = _("Users") display_users.short_description = _("Users")
display_users.allow_tags = True
display_users.admin_order_field = 'users__username' display_users.admin_order_field = 'users__username'
def save_model(self, request, obj, form, change): def save_model(self, request, obj, form, change):
@ -99,14 +101,14 @@ class DatabaseUserAdmin(SelectAccountAdminMixin, ChangePasswordAdminMixin, Exten
list_prefetch_related = ('databases',) list_prefetch_related = ('databases',)
actions = (list_accounts, save_selected) actions = (list_accounts, save_selected)
@mark_safe
def display_databases(self, user): def display_databases(self, user):
links = [] links = []
for db in user.databases.all(): for db in user.databases.all():
link = '<a href="%s">%s</a>' % (change_url(db), db.name) link = format_html('<a href="{}">{}</a>', change_url(db), db.name)
links.append(link) links.append(link)
return '<br>'.join(links) return '<br>'.join(links)
display_databases.short_description = _("Databases") display_databases.short_description = _("Databases")
display_databases.allow_tags = True
display_databases.admin_order_field = 'databases__name' display_databases.admin_order_field = 'databases__name'
def get_urls(self): def get_urls(self):

View File

@ -91,7 +91,7 @@ class DatabaseCreationForm(DatabaseUserCreationForm):
class ReadOnlySQLPasswordHashField(ReadOnlyPasswordHashField): class ReadOnlySQLPasswordHashField(ReadOnlyPasswordHashField):
class ReadOnlyPasswordHashWidget(forms.Widget): class ReadOnlyPasswordHashWidget(forms.Widget):
def render(self, name, value, attrs): def render(self, name, value, attrs, renderer=None):
original = ReadOnlyPasswordHashField.widget().render(name, value, attrs) original = ReadOnlyPasswordHashField.widget().render(name, value, attrs)
if 'Invalid' not in original: if 'Invalid' not in original:
return original return original

View File

@ -3,6 +3,8 @@ from django.urls import reverse
from django.db import models from django.db import models
from django.db.models.functions import Concat, Coalesce from django.db.models.functions import Concat, Coalesce
from django.templatetags.static import static from django.templatetags.static import static
from django.utils.html import format_html
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext, ugettext_lazy as _ from django.utils.translation import ugettext, ugettext_lazy as _
from orchestra.admin import ExtendedModelAdmin from orchestra.admin import ExtendedModelAdmin
@ -72,9 +74,8 @@ class DomainAdmin(AccountAdminMixin, ExtendedModelAdmin):
def structured_name(self, domain): def structured_name(self, domain):
if domain.is_top: if domain.is_top:
return domain.name return domain.name
return '&nbsp;'*4 + domain.name return mark_safe('&nbsp;'*4 + domain.name)
structured_name.short_description = _("name") structured_name.short_description = _("name")
structured_name.allow_tags = True
structured_name.admin_order_field = 'structured_name' structured_name.admin_order_field = 'structured_name'
def display_is_top(self, domain): def display_is_top(self, domain):
@ -83,6 +84,7 @@ class DomainAdmin(AccountAdminMixin, ExtendedModelAdmin):
display_is_top.boolean = True display_is_top.boolean = True
display_is_top.admin_order_field = 'top' display_is_top.admin_order_field = 'top'
@mark_safe
def display_websites(self, domain): def display_websites(self, domain):
if apps.isinstalled('orchestra.contrib.websites'): if apps.isinstalled('orchestra.contrib.websites'):
websites = domain.websites.all() websites = domain.websites.all()
@ -92,22 +94,22 @@ class DomainAdmin(AccountAdminMixin, ExtendedModelAdmin):
site_link = get_on_site_link(website.get_absolute_url()) site_link = get_on_site_link(website.get_absolute_url())
admin_url = change_url(website) admin_url = change_url(website)
title = _("Edit website") title = _("Edit website")
link = '<a href="%s" title="%s">%s %s</a>' % ( link = format_html('<a href="{}" title="{}">{} {}</a>',
admin_url, title, website.name, site_link) admin_url, title, website.name, site_link)
links.append(link) links.append(link)
return '<br>'.join(links) return '<br>'.join(links)
add_url = reverse('admin:websites_website_add') add_url = reverse('admin:websites_website_add')
add_url += '?account=%i&domains=%i' % (domain.account_id, domain.pk) add_url += '?account=%i&domains=%i' % (domain.account_id, domain.pk)
image = '<img src="%s"></img>' % static('orchestra/images/add.png') add_link = format_html(
add_link = '<a href="%s" title="%s">%s</a>' % ( '<a href="{}" title="{}"><img src="{}" /></a>', add_url,
add_url, _("Add website"), image _("Add website"), static('orchestra/images/add.png'),
) )
return _("No website %s") % (add_link) return _("No website %s") % (add_link)
return '---' return '---'
display_websites.admin_order_field = 'websites__name' display_websites.admin_order_field = 'websites__name'
display_websites.short_description = _("Websites") display_websites.short_description = _("Websites")
display_websites.allow_tags = True
@mark_safe
def display_addresses(self, domain): def display_addresses(self, domain):
if apps.isinstalled('orchestra.contrib.mailboxes'): if apps.isinstalled('orchestra.contrib.mailboxes'):
add_url = reverse('admin:mailboxes_address_add') add_url = reverse('admin:mailboxes_address_add')
@ -126,10 +128,9 @@ class DomainAdmin(AccountAdminMixin, ExtendedModelAdmin):
return '---' return '---'
display_addresses.short_description = _("Addresses") display_addresses.short_description = _("Addresses")
display_addresses.admin_order_field = 'addresses__count' display_addresses.admin_order_field = 'addresses__count'
display_addresses.allow_tags = True
@mark_safe
def implicit_records(self, domain): def implicit_records(self, domain):
defaults = []
types = set(domain.records.values_list('type', flat=True)) types = set(domain.records.values_list('type', flat=True))
ttl = settings.DOMAINS_DEFAULT_TTL ttl = settings.DOMAINS_DEFAULT_TTL
lines = [] lines = []
@ -141,14 +142,13 @@ class DomainAdmin(AccountAdminMixin, ExtendedModelAdmin):
value=record.value value=record.value
) )
if not domain.record_is_implicit(record, types): if not domain.record_is_implicit(record, types):
line = '<strike>%s</strike>' % line line = format_html('<strike>{}</strike>', line)
if record.type is Record.SOA: if record.type is Record.SOA:
lines.insert(0, line) lines.insert(0, line)
else: else:
lines.append(line) lines.append(line)
return '<br>'.join(lines) return '<br>'.join(lines)
implicit_records.short_description = _("Implicit records") implicit_records.short_description = _("Implicit records")
implicit_records.allow_tags = True
def get_fieldsets(self, request, obj=None): def get_fieldsets(self, request, obj=None):
""" Add SOA fields when domain is top """ """ Add SOA fields when domain is top """

View File

@ -1,12 +1,14 @@
from django.contrib import admin from django.contrib import admin
from django.utils.translation import ugettext_lazy as _
from django.urls import reverse, NoReverseMatch
from django.contrib.admin.templatetags.admin_urls import add_preserved_filters
from django.http import HttpResponseRedirect
from django.contrib.admin.utils import unquote
from django.contrib.admin.templatetags.admin_static import static from django.contrib.admin.templatetags.admin_static import static
from django.contrib.admin.templatetags.admin_urls import add_preserved_filters
from django.contrib.admin.utils import unquote
from django.http import HttpResponseRedirect
from django.urls import NoReverseMatch, reverse
from django.utils.html import format_html
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _
from orchestra.admin.utils import admin_link, admin_date from orchestra.admin.utils import admin_date, admin_link
class LogEntryAdmin(admin.ModelAdmin): class LogEntryAdmin(admin.ModelAdmin):
@ -34,11 +36,12 @@ class LogEntryAdmin(admin.ModelAdmin):
user_link = admin_link('user') user_link = admin_link('user')
display_action_time = admin_date('action_time', short_description=_("Time")) display_action_time = admin_date('action_time', short_description=_("Time"))
@mark_safe
def display_message(self, log): def display_message(self, log):
edit = '<a href="%(url)s"><img src="%(img)s"></img></a>' % { edit = format_html('<a href="{url}"><img src="{img}"></img></a>', **{
'url': reverse('admin:admin_logentry_change', args=(log.pk,)), 'url': reverse('admin:admin_logentry_change', args=(log.pk,)),
'img': static('admin/img/icon-changelink.svg'), 'img': static('admin/img/icon-changelink.svg'),
} })
if log.is_addition(): if log.is_addition():
return _('Added "%(link)s". %(edit)s') % { return _('Added "%(link)s". %(edit)s') % {
'link': self.content_object_link(log), 'link': self.content_object_link(log),
@ -57,7 +60,6 @@ class LogEntryAdmin(admin.ModelAdmin):
} }
display_message.short_description = _("Message") display_message.short_description = _("Message")
display_message.admin_order_field = 'action_flag' display_message.admin_order_field = 'action_flag'
display_message.allow_tags = True
def display_action(self, log): def display_action(self, log):
if log.is_addition(): if log.is_addition():
@ -75,10 +77,9 @@ class LogEntryAdmin(admin.ModelAdmin):
url = reverse(view, args=(log.object_id,)) url = reverse(view, args=(log.object_id,))
except NoReverseMatch: except NoReverseMatch:
return log.object_repr return log.object_repr
return '<a href="%s">%s</a>' % (url, log.object_repr) return format_html('<a href="{}">{}</a>', url, log.object_repr)
content_object_link.short_description = _("Content object") content_object_link.short_description = _("Content object")
content_object_link.admin_order_field = 'object_repr' content_object_link.admin_order_field = 'object_repr'
content_object_link.allow_tags = True
def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None): def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
""" Add rel_opts and object to context """ """ Add rel_opts and object to context """

View File

@ -5,7 +5,8 @@ from django.urls import reverse
from django.db import models from django.db import models
from django.http import HttpResponse from django.http import HttpResponse
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.utils.html import strip_tags from django.utils.html import format_html, strip_tags
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from markdown import markdown from markdown import markdown
@ -50,6 +51,7 @@ class MessageReadOnlyInline(admin.TabularInline):
'all': ('orchestra/css/hide-inline-id.css',) 'all': ('orchestra/css/hide-inline-id.css',)
} }
@mark_safe
def content_html(self, msg): def content_html(self, msg):
context = { context = {
'number': msg.number, 'number': msg.number,
@ -58,12 +60,13 @@ class MessageReadOnlyInline(admin.TabularInline):
} }
summary = _("#%(number)i Updated by %(author)s about %(time)s") % context summary = _("#%(number)i Updated by %(author)s about %(time)s") % context
header = '<strong style="color:#666;">%s</strong><hr />' % summary header = '<strong style="color:#666;">%s</strong><hr />' % summary
content = markdown(msg.content) content = markdown(msg.content)
content = content.replace('>\n', '>') content = content.replace('>\n', '>')
content = '<div style="padding-left:20px;">%s</div>' % content content = '<div style="padding-left:20px;">%s</div>' % content
return header + content return header + content
content_html.short_description = _("Content") content_html.short_description = _("Content")
content_html.allow_tags = True
def has_add_permission(self, request): def has_add_permission(self, request):
return False return False
@ -111,10 +114,10 @@ class TicketInline(admin.TabularInline):
colored_state = admin_colored('state', colors=STATE_COLORS, bold=False) colored_state = admin_colored('state', colors=STATE_COLORS, bold=False)
colored_priority = admin_colored('priority', colors=PRIORITY_COLORS, bold=False) colored_priority = admin_colored('priority', colors=PRIORITY_COLORS, bold=False)
@mark_safe
def ticket_id(self, instance): def ticket_id(self, instance):
return '<b>%s</b>' % admin_link()(instance) return '<b>%s</b>' % admin_link()(instance)
ticket_id.short_description = '#' ticket_id.short_description = '#'
ticket_id.allow_tags = True
class TicketAdmin(ExtendedModelAdmin): class TicketAdmin(ExtendedModelAdmin):
@ -192,6 +195,7 @@ class TicketAdmin(ExtendedModelAdmin):
display_state = admin_colored('state', colors=STATE_COLORS, bold=False) display_state = admin_colored('state', colors=STATE_COLORS, bold=False)
display_priority = admin_colored('priority', colors=PRIORITY_COLORS, bold=False) display_priority = admin_colored('priority', colors=PRIORITY_COLORS, bold=False)
@mark_safe
def display_summary(self, ticket): def display_summary(self, ticket):
context = { context = {
'creator': admin_link('creator')(self, ticket) if ticket.creator else ticket.creator_name, 'creator': admin_link('creator')(self, ticket) if ticket.creator else ticket.creator_name,
@ -207,14 +211,12 @@ class TicketAdmin(ExtendedModelAdmin):
context['updated'] = '. Updated by %(updater)s about %(updated)s' % context context['updated'] = '. Updated by %(updater)s about %(updated)s' % context
return '<h4>Added by %(creator)s about %(created)s%(updated)s</h4>' % context return '<h4>Added by %(creator)s about %(created)s%(updated)s</h4>' % context
display_summary.short_description = 'Summary' display_summary.short_description = 'Summary'
display_summary.allow_tags = True
def unbold_id(self, ticket): def unbold_id(self, ticket):
""" Unbold id if ticket is read """ """ Unbold id if ticket is read """
if ticket.is_read_by(self.user): if ticket.is_read_by(self.user):
return '<span style="font-weight:normal;font-size:11px;">%s</span>' % ticket.pk return format_html('<span style="font-weight:normal;font-size:11px;">{}</span>', ticket.pk)
return ticket.pk return ticket.pk
unbold_id.allow_tags = True
unbold_id.short_description = "#" unbold_id.short_description = "#"
unbold_id.admin_order_field = 'id' unbold_id.admin_order_field = 'id'
@ -222,8 +224,7 @@ class TicketAdmin(ExtendedModelAdmin):
""" Bold subject when tickets are unread for request.user """ """ Bold subject when tickets are unread for request.user """
if ticket.is_read_by(self.user): if ticket.is_read_by(self.user):
return ticket.subject return ticket.subject
return "<strong class='unread'>%s</strong>" % ticket.subject return format_html("<strong class='unread'>{}</strong>", ticket.subject)
bold_subject.allow_tags = True
bold_subject.short_description = _("Subject") bold_subject.short_description = _("Subject")
bold_subject.admin_order_field = 'subject' bold_subject.admin_order_field = 'subject'
@ -297,10 +298,9 @@ class QueueAdmin(admin.ModelAdmin):
num = queue.tickets__count num = queue.tickets__count
url = reverse('admin:issues_ticket_changelist') url = reverse('admin:issues_ticket_changelist')
url += '?queue=%i' % queue.pk url += '?queue=%i' % queue.pk
return '<a href="%s">%d</a>' % (url, num) return format_html('<a href="{}">{}</a>', url, num)
num_tickets.short_description = _("Tickets") num_tickets.short_description = _("Tickets")
num_tickets.admin_order_field = 'tickets__count' num_tickets.admin_order_field = 'tickets__count'
num_tickets.allow_tags = True
def get_list_display(self, request): def get_list_display(self, request):
""" show notifications """ """ show notifications """

View File

@ -22,7 +22,7 @@ class MarkDownWidget(forms.Textarea):
) )
markdown_help_text = 'HTML not allowed, you can use %s' % markdown_help_text markdown_help_text = 'HTML not allowed, you can use %s' % markdown_help_text
def render(self, name, value, attrs): def render(self, name, value, attrs, renderer=None):
widget_id = attrs['id'] if attrs and 'id' in attrs else 'id_%s' % name widget_id = attrs['id'] if attrs and 'id' in attrs else 'id_%s' % name
textarea = super(MarkDownWidget, self).render(name, value, attrs) textarea = super(MarkDownWidget, self).render(name, value, attrs)
preview = ('<a class="load-preview" href="#" data-field="{0}">preview</a>'\ preview = ('<a class="load-preview" href="#" data-field="{0}">preview</a>'\

View File

@ -12,7 +12,7 @@ from .models import List
class RelatedDomainSerializer(AccountSerializerMixin, RelatedHyperlinkedModelSerializer): class RelatedDomainSerializer(AccountSerializerMixin, RelatedHyperlinkedModelSerializer):
class Meta: class Meta:
model = List.address_domain.field.model model = List.address_domain.field.related_model
fields = ('url', 'id', 'name') fields = ('url', 'id', 'name')

View File

@ -6,6 +6,7 @@ from django.contrib import admin, messages
from django.urls import reverse from django.urls import reverse
from django.db.models import F, Count, Value as V from django.db.models import F, Count, Value as V
from django.db.models.functions import Concat from django.db.models.functions import Concat
from django.utils.html import format_html, format_html_join
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
@ -82,6 +83,7 @@ class MailboxAdmin(ChangePasswordAdminMixin, SelectAccountAdminMixin, ExtendedMo
if settings.MAILBOXES_LOCAL_DOMAIN: if settings.MAILBOXES_LOCAL_DOMAIN:
type(self).actions = self.actions + (SendMailboxEmail(),) type(self).actions = self.actions + (SendMailboxEmail(),)
@mark_safe
def display_addresses(self, mailbox): def display_addresses(self, mailbox):
# Get from forwards # Get from forwards
cache = caches.get_request_cache() cache = caches.get_request_cache()
@ -93,7 +95,7 @@ class MailboxAdmin(ChangePasswordAdminMixin, SelectAccountAdminMixin, ExtendedMo
qs = qs.values_list('id', 'email', 'forward') qs = qs.values_list('id', 'email', 'forward')
for addr_id, email, mbox in qs: for addr_id, email, mbox in qs:
url = reverse('admin:mailboxes_address_change', args=(addr_id,)) url = reverse('admin:mailboxes_address_change', args=(addr_id,))
link = '<a href="%s">%s</a>' % (url, email) link = format_html('<a href="{}">{}</a>', url, email)
try: try:
cached_forwards[mbox].append(link) cached_forwards[mbox].append(link)
except KeyError: except KeyError:
@ -107,26 +109,23 @@ class MailboxAdmin(ChangePasswordAdminMixin, SelectAccountAdminMixin, ExtendedMo
addresses = [] addresses = []
for addr in mailbox.addresses.all(): for addr in mailbox.addresses.all():
url = change_url(addr) url = change_url(addr)
addresses.append('<a href="%s">%s</a>' % (url, addr.email)) addresses.append(format_html('<a href="{}">{}</a>', url, addr.email))
return '<br>'.join(addresses+forwards) return '<br>'.join(addresses+forwards)
display_addresses.short_description = _("Addresses") display_addresses.short_description = _("Addresses")
display_addresses.allow_tags = True
def display_forwards(self, mailbox): def display_forwards(self, mailbox):
forwards = [] forwards = mailbox.get_forwards()
for addr in mailbox.get_forwards(): return format_html_join(
url = change_url(addr) '<br>', '<a href="{}">{}</a>',
forwards.append('<a href="%s">%s</a>' % (url, addr.email)) [(change_url(addr), addr.email) for addr in forwards]
return '<br>'.join(forwards) )
display_forwards.short_description = _("Forward from") display_forwards.short_description = _("Forward from")
display_forwards.allow_tags = True
@mark_safe
def display_filtering(self, mailbox): def display_filtering(self, mailbox):
""" becacuse of allow_tags = True """
return mailbox.get_filtering_display() return mailbox.get_filtering_display()
display_filtering.short_description = _("Filtering") display_filtering.short_description = _("Filtering")
display_filtering.admin_order_field = 'filtering' display_filtering.admin_order_field = 'filtering'
display_filtering.allow_tags = True
def formfield_for_dbfield(self, db_field, **kwargs): def formfield_for_dbfield(self, db_field, **kwargs):
if db_field.name == 'filtering': if db_field.name == 'filtering':
@ -217,7 +216,7 @@ class MailboxAdmin(ChangePasswordAdminMixin, SelectAccountAdminMixin, ExtendedMo
elif obj.custom_filtering: elif obj.custom_filtering:
messages.warning(request, msg) messages.warning(request, msg)
super(MailboxAdmin, self).save_model(request, obj, form, change) super(MailboxAdmin, self).save_model(request, obj, form, change)
obj.addresses = form.cleaned_data['addresses'] obj.addresses.set(form.cleaned_data['addresses'])
class AddressAdmin(SelectAccountAdminMixin, ExtendedModelAdmin): class AddressAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
@ -247,29 +246,27 @@ class AddressAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
def email_link(self, address): def email_link(self, address):
link = self.domain_link(address) link = self.domain_link(address)
return "%s@%s" % (address.name, link) return format_html("{}@{}", address.name, link)
email_link.short_description = _("Email") email_link.short_description = _("Email")
email_link.allow_tags = True
def display_mailboxes(self, address): def display_mailboxes(self, address):
boxes = [] boxes = address.mailboxes.all()
for mailbox in address.mailboxes.all(): return format_html_join(
url = change_url(mailbox) '<br>', '<a href="{}">{}</a>',
boxes.append('<a href="%s">%s</a>' % (url, mailbox.name)) [(change_url(mailbox), mailbox.name) for mailbox in boxes]
return '<br>'.join(boxes) )
display_mailboxes.short_description = _("Mailboxes") display_mailboxes.short_description = _("Mailboxes")
display_mailboxes.allow_tags = True
display_mailboxes.admin_order_field = 'mailboxes__count' display_mailboxes.admin_order_field = 'mailboxes__count'
def display_all_mailboxes(self, address): def display_all_mailboxes(self, address):
boxes = [] boxes = address.get_mailboxes()
for mailbox in address.get_mailboxes(): return format_html_join(
url = change_url(mailbox) '<br>', '<a href="{}">{}</a>',
boxes.append('<a href="%s">%s</a>' % (url, mailbox.name)) [(change_url(mailbox), mailbox.name) for mailbox in boxes]
return '<br>'.join(boxes) )
display_all_mailboxes.short_description = _("Mailboxes links") display_all_mailboxes.short_description = _("Mailboxes links")
display_all_mailboxes.allow_tags = True
@mark_safe
def display_forward(self, address): def display_forward(self, address):
forward_mailboxes = {m.name: m for m in address.get_forward_mailboxes()} forward_mailboxes = {m.name: m for m in address.get_forward_mailboxes()}
values = [] values = []
@ -281,7 +278,6 @@ class AddressAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
values.append(forward) values.append(forward)
return '<br>'.join(values) return '<br>'.join(values)
display_forward.short_description = _("Forward") display_forward.short_description = _("Forward")
display_forward.allow_tags = True
display_forward.admin_order_field = 'forward' display_forward.admin_order_field = 'forward'
def formfield_for_dbfield(self, db_field, **kwargs): def formfield_for_dbfield(self, db_field, **kwargs):

View File

@ -44,7 +44,7 @@ class Mailbox(models.Model):
def active(self): def active(self):
try: try:
return self.is_active and self.account.is_active return self.is_active and self.account.is_active
except type(self).account.field.model.DoesNotExist: except type(self).account.field.related_model.DoesNotExist:
return self.is_active return self.is_active
def disable(self): def disable(self):

View File

@ -6,6 +6,8 @@ from django.contrib import admin
from django.urls import reverse from django.urls import reverse
from django.db.models import Count from django.db.models import Count
from django.shortcuts import redirect from django.shortcuts import redirect
from django.utils.html import format_html
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from orchestra.admin import ExtendedModelAdmin from orchestra.admin import ExtendedModelAdmin
@ -60,11 +62,10 @@ class MessageAdmin(ExtendedModelAdmin):
def display_subject(self, instance): def display_subject(self, instance):
subject = instance.subject subject = instance.subject
if len(subject) > 64: if len(subject) > 64:
return subject[:64] + '&hellip;' return mark_safe(subject[:64] + '&hellip;')
return subject return subject
display_subject.short_description = _("Subject") display_subject.short_description = _("Subject")
display_subject.admin_order_field = 'subject' display_subject.admin_order_field = 'subject'
display_subject.allow_tags = True
def display_retries(self, instance): def display_retries(self, instance):
num_logs = instance.logs__count num_logs = instance.logs__count
@ -74,10 +75,9 @@ class MessageAdmin(ExtendedModelAdmin):
else: else:
url = reverse('admin:mailer_smtplog_changelist') url = reverse('admin:mailer_smtplog_changelist')
url += '?&message=%i' % instance.pk url += '?&message=%i' % instance.pk
return '<a href="%s" onclick="return showAddAnotherPopup(this);">%d</a>' % (url, instance.retries) return format_html('<a href="{}" onclick="return showAddAnotherPopup(this);">{}</a>', url, instance.retries)
display_retries.short_description = _("Retries") display_retries.short_description = _("Retries")
display_retries.admin_order_field = 'retries' display_retries.admin_order_field = 'retries'
display_retries.allow_tags = True
def display_content(self, instance): def display_content(self, instance):
part = email.message_from_string(instance.content) part = email.message_from_string(instance.content)
@ -99,9 +99,8 @@ class MessageAdmin(ExtendedModelAdmin):
payload = payload.decode(charset) payload = payload.decode(charset)
if part.get_content_type() == 'text/plain': if part.get_content_type() == 'text/plain':
payload = payload.replace('\n', '<br>').replace(' ', '&nbsp;') payload = payload.replace('\n', '<br>').replace(' ', '&nbsp;')
return payload return mark_safe(payload)
display_content.short_description = _("Content") display_content.short_description = _("Content")
display_content.allow_tags = True
def display_full_subject(self, instance): def display_full_subject(self, instance):
return instance.subject return instance.subject

View File

@ -2,6 +2,7 @@ from django import forms
from django.contrib import admin from django.contrib import admin
from django.urls import reverse from django.urls import reverse
from django.db import models from django.db import models
from django.utils.html import format_html
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
@ -38,15 +39,13 @@ class MiscServiceAdmin(ExtendedModelAdmin):
actions = (disable, enable) actions = (disable, enable)
def display_name(self, misc): def display_name(self, misc):
return '<span title="%s">%s</span>' % (misc.description, misc.name) return format_html('<span title="{}">{}</span>', misc.description, misc.name)
display_name.short_description = _("name") display_name.short_description = _("name")
display_name.allow_tags = True
display_name.admin_order_field = 'name' display_name.admin_order_field = 'name'
def display_verbose_name(self, misc): def display_verbose_name(self, misc):
return '<span title="%s">%s</span>' % (misc.description, misc.verbose_name) return format_html('<span title="{}">{}</span>', misc.description, misc.verbose_name)
display_verbose_name.short_description = _("verbose name") display_verbose_name.short_description = _("verbose name")
display_verbose_name.allow_tags = True
display_verbose_name.admin_order_field = 'verbose_name' display_verbose_name.admin_order_field = 'verbose_name'
def num_instances(self, misc): def num_instances(self, misc):

View File

@ -51,19 +51,18 @@ class RouteAdmin(ExtendedModelAdmin):
def display_model(self, route): def display_model(self, route):
try: try:
return escape(route.backend_class.model) return route.backend_class.model
except KeyError: except KeyError:
return "<span style='color: red;'>NOT AVAILABLE</span>" return mark_safe("<span style='color: red;'>NOT AVAILABLE</span>")
display_model.short_description = _("model") display_model.short_description = _("model")
display_model.allow_tags = True
@mark_safe
def display_actions(self, route): def display_actions(self, route):
try: try:
return '<br>'.join(route.backend_class.get_actions()) return '<br>'.join(route.backend_class.get_actions())
except KeyError: except KeyError:
return "<span style='color: red;'>NOT AVAILABLE</span>" return "<span style='color: red;'>NOT AVAILABLE</span>"
display_actions.short_description = _("actions") display_actions.short_description = _("actions")
display_actions.allow_tags = True
def formfield_for_dbfield(self, db_field, **kwargs): def formfield_for_dbfield(self, db_field, **kwargs):
""" Provides dynamic help text on backend form field """ """ Provides dynamic help text on backend form field """
@ -120,7 +119,6 @@ class BackendOperationInline(admin.TabularInline):
return _("Deleted {0}").format(operation.instance_repr or '-'.join( return _("Deleted {0}").format(operation.instance_repr or '-'.join(
(escape(operation.content_type), escape(operation.object_id)))) (escape(operation.content_type), escape(operation.object_id))))
return link return link
instance_link.allow_tags = True
instance_link.short_description = _("Instance") instance_link.short_description = _("Instance")
def has_add_permission(self, *args, **kwargs): def has_add_permission(self, *args, **kwargs):
@ -179,14 +177,12 @@ class ServerAdmin(ExtendedModelAdmin):
change_view_actions = actions change_view_actions = actions
def display_ping(self, instance): def display_ping(self, instance):
return self._remote_state[instance.pk][0] return mark_safe(self._remote_state[instance.pk][0])
display_ping.short_description = _("Ping") display_ping.short_description = _("Ping")
display_ping.allow_tags = True
def display_uptime(self, instance): def display_uptime(self, instance):
return self._remote_state[instance.pk][1] return mark_safe(self._remote_state[instance.pk][1])
display_uptime.short_description = _("Uptime") display_uptime.short_description = _("Uptime")
display_uptime.allow_tags = True
def get_queryset(self, request): def get_queryset(self, request):
""" Order by structured name and imporve performance """ """ Order by structured name and imporve performance """

View File

@ -1,6 +1,6 @@
from django import forms from django import forms
from orchestra.forms.widgets import SpanWidget, paddingCheckboxSelectMultiple from orchestra.forms.widgets import SpanWidget, PaddingCheckboxSelectMultiple
class RouteForm(forms.ModelForm): class RouteForm(forms.ModelForm):
@ -16,5 +16,5 @@ class RouteForm(forms.ModelForm):
else: else:
self.fields['backend'].widget = SpanWidget() self.fields['backend'].widget = SpanWidget()
actions = backend_class.actions actions = backend_class.actions
self.fields['async_actions'].widget = paddingCheckboxSelectMultiple(45) self.fields['async_actions'].widget = PaddingCheckboxSelectMultiple(45)
self.fields['async_actions'].choices = ((action, action) for action in actions) self.fields['async_actions'].choices = ((action, action) for action in actions)

View File

@ -51,8 +51,9 @@ class Server(models.Model):
def clean(self): def clean(self):
self.name = self.name.strip() self.name = self.name.strip()
if self.address:
self.address = self.address.strip() self.address = self.address.strip()
if self.name and not self.address: elif self.name:
validate = OrValidator(validate_ip_address, validate_hostname) validate = OrValidator(validate_ip_address, validate_hostname)
validate_hostname(self.name) validate_hostname(self.name)
try: try:

View File

@ -14,7 +14,12 @@ def retrieve_state(servers):
state = {} state = {}
for server, ping, uptime in zip(servers, pings, uptimes): for server, ping, uptime in zip(servers, pings, uptimes):
ping = join(ping, silent=True) ping = join(ping, silent=True)
try:
ping = ping.stdout.splitlines()[-1].decode() ping = ping.stdout.splitlines()[-1].decode()
except IndexError:
ping = ''
if ping.startswith('rtt'): if ping.startswith('rtt'):
ping = '%s ms' % ping.split('/')[4] ping = '%s ms' % ping.split('/')[4]
else: else:

View File

@ -1,9 +1,10 @@
from datetime import datetime
from django import forms from django import forms
from django.contrib import admin from django.contrib import admin
from django.urls import reverse, NoReverseMatch from django.urls import reverse, NoReverseMatch
from django.db.models import Prefetch from django.db.models import Prefetch
from django.utils import timezone from django.utils import timezone
from django.utils.html import escape from django.utils.html import escape, format_html
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
@ -112,9 +113,8 @@ class OrderAdmin(AccountAdminMixin, ExtendedModelAdmin):
display_cancelled_on = admin_date('cancelled_on') display_cancelled_on = admin_date('cancelled_on')
def display_description(self, order): def display_description(self, order):
return order.description[:64] return format_html(order.description[:64])
display_description.short_description = _("Description") display_description.short_description = _("Description")
display_description.allow_tags = True
display_description.admin_order_field = 'description' display_description.admin_order_field = 'description'
def content_object_link(self, order): def content_object_link(self, order):
@ -125,13 +125,13 @@ class OrderAdmin(AccountAdminMixin, ExtendedModelAdmin):
# Does not has admin # Does not has admin
return order.content_object_repr return order.content_object_repr
description = str(order.content_object) description = str(order.content_object)
return '<a href="{url}">{description}</a>'.format( return format_html('<a href="{url}">{description}</a>',
url=url, description=description) url=url, description=description)
return order.content_object_repr return order.content_object_repr
content_object_link.short_description = _("Content object") content_object_link.short_description = _("Content object")
content_object_link.allow_tags = True
content_object_link.admin_order_field = 'content_object_repr' content_object_link.admin_order_field = 'content_object_repr'
@mark_safe
def bills_links(self, order): def bills_links(self, order):
bills = [] bills = []
make_link = admin_link() make_link = admin_link()
@ -139,7 +139,6 @@ class OrderAdmin(AccountAdminMixin, ExtendedModelAdmin):
bills.append(make_link(line.bill)) bills.append(make_link(line.bill))
return '<br>'.join(bills) return '<br>'.join(bills)
bills_links.short_description = _("Bills") bills_links.short_description = _("Bills")
bills_links.allow_tags = True
def display_billed_until(self, order): def display_billed_until(self, order):
billed_until = order.billed_until billed_until = order.billed_until
@ -156,12 +155,12 @@ class OrderAdmin(AccountAdminMixin, ExtendedModelAdmin):
red = True red = True
elif billed_until < timezone.now().date(): elif billed_until < timezone.now().date():
red = True red = True
color = 'style="color:red;"' if red else '' color = mark_safe('style="color:red;"') if red else ''
return '<span title="{raw}" {color}>{human}</span>'.format( return format_html(
'<span title="{raw}" {color}>{human}</span>',
raw=escape(str(billed_until)), color=color, human=human, raw=escape(str(billed_until)), color=color, human=human,
) )
display_billed_until.short_description = _("billed until") display_billed_until.short_description = _("billed until")
display_billed_until.allow_tags = True
display_billed_until.admin_order_field = 'billed_until' display_billed_until.admin_order_field = 'billed_until'
def display_metric(self, order): def display_metric(self, order):

View File

@ -15,7 +15,7 @@ def cancel_orders(sender, **kwargs):
if sender._meta.app_label not in settings.ORDERS_EXCLUDED_APPS: if sender._meta.app_label not in settings.ORDERS_EXCLUDED_APPS:
instance = kwargs['instance'] instance = kwargs['instance']
# Account delete will delete all related orders, no need to maintain order consistency # Account delete will delete all related orders, no need to maintain order consistency
if isinstance(instance, Order.account.field.model): if isinstance(instance, Order.account.field.related_model):
return return
if type(instance) in services: if type(instance) in services:
for order in Order.objects.by_object(instance).active(): for order in Order.objects.by_object(instance).active():

View File

@ -1,6 +1,8 @@
from django.contrib import admin from django.contrib import admin
from django.urls import reverse from django.urls import reverse
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.utils.html import format_html
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from orchestra.admin import ChangeViewActionsMixin, ExtendedModelAdmin from orchestra.admin import ChangeViewActionsMixin, ExtendedModelAdmin
@ -154,6 +156,7 @@ class TransactionAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
return [] return []
return [action for action in actions if action.__name__ not in exclude] return [action for action in actions if action.__name__ not in exclude]
@mark_safe
def display_state(self, obj): def display_state(self, obj):
state = admin_colored('state', colors=STATE_COLORS)(obj) state = admin_colored('state', colors=STATE_COLORS)(obj)
help_text = obj.get_state_help() help_text = obj.get_state_help()
@ -161,7 +164,6 @@ class TransactionAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
return state return state
display_state.admin_order_field = 'state' display_state.admin_order_field = 'state'
display_state.short_description = _("State") display_state.short_description = _("State")
display_state.allow_tags = True
class TransactionProcessAdmin(ChangeViewActionsMixin, admin.ModelAdmin): class TransactionProcessAdmin(ChangeViewActionsMixin, admin.ModelAdmin):
@ -184,10 +186,10 @@ class TransactionProcessAdmin(ChangeViewActionsMixin, admin.ModelAdmin):
def file_url(self, process): def file_url(self, process):
if process.file: if process.file:
return '<a href="%s">%s</a>' % (process.file.url, process.file.name) return format_html('<a href="{}">{}</a>', process.file.url, process.file.name)
file_url.allow_tags = True
file_url.admin_order_field = 'file' file_url.admin_order_field = 'file'
@mark_safe
def display_transactions(self, process): def display_transactions(self, process):
ids = [] ids = []
lines = [] lines = []
@ -207,7 +209,6 @@ class TransactionProcessAdmin(ChangeViewActionsMixin, admin.ModelAdmin):
url += '?process_id=%i' % process.id url += '?process_id=%i' % process.id
return '<a href="%s">%s</a>' % (url, transactions) return '<a href="%s">%s</a>' % (url, transactions)
display_transactions.short_description = _("Transactions") display_transactions.short_description = _("Transactions")
display_transactions.allow_tags = True
def has_add_permission(self, *args, **kwargs): def has_add_permission(self, *args, **kwargs):
return False return False

View File

@ -1,6 +1,7 @@
from django.contrib import admin from django.contrib import admin
from django.urls import reverse from django.urls import reverse
from django.db import models from django.db import models
from django.utils.html import format_html
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from orchestra.admin import ExtendedModelAdmin from orchestra.admin import ExtendedModelAdmin
@ -33,10 +34,9 @@ class PlanAdmin(ExtendedModelAdmin):
num = plan.contracts__count num = plan.contracts__count
url = reverse('admin:plans_contractedplan_changelist') url = reverse('admin:plans_contractedplan_changelist')
url += '?plan__name={}'.format(plan.name) url += '?plan__name={}'.format(plan.name)
return '<a href="{0}">{1}</a>'.format(url, num) return format_html('<a href="{0}">{1}</a>', url, num)
num_contracts.short_description = _("Contracts") num_contracts.short_description = _("Contracts")
num_contracts.admin_order_field = 'contracts__count' num_contracts.admin_order_field = 'contracts__count'
num_contracts.allow_tags = True
def get_queryset(self, request): def get_queryset(self, request):
qs = super(PlanAdmin, self).get_queryset(request) qs = super(PlanAdmin, self).get_queryset(request)

View File

@ -11,6 +11,7 @@ from django.db.models import Q
from django.shortcuts import redirect from django.shortcuts import redirect
from django.templatetags.static import static from django.templatetags.static import static
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django.utils.html import format_html
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils.translation import ungettext, ugettext_lazy as _ from django.utils.translation import ungettext, ugettext_lazy as _
@ -105,10 +106,9 @@ class ResourceAdmin(ExtendedModelAdmin):
def content_object_link(data): def content_object_link(data):
ct = data.content_type ct = data.content_type
url = reverse('admin:%s_%s_change' % (ct.app_label, ct.model), args=(data.object_id,)) url = reverse('admin:%s_%s_change' % (ct.app_label, ct.model), args=(data.object_id,))
return '<a href="%s">%s</a>' % (url, data.content_object_repr) return format_html('<a href="{}">{}</a>', url, data.content_object_repr)
content_object_link.short_description = _("Content object") content_object_link.short_description = _("Content object")
content_object_link.admin_order_field = 'content_object_repr' content_object_link.admin_order_field = 'content_object_repr'
content_object_link.allow_tags = True
class ResourceDataAdmin(ExtendedModelAdmin): class ResourceDataAdmin(ExtendedModelAdmin):
@ -155,10 +155,9 @@ class ResourceDataAdmin(ExtendedModelAdmin):
if rdata.used is None: if rdata.used is None:
return '' return ''
url = reverse('admin:resources_resourcedata_used_monitordata', args=(rdata.pk,)) url = reverse('admin:resources_resourcedata_used_monitordata', args=(rdata.pk,))
return '<a href="%s">%s %s</a>' % (url, rdata.used, rdata.unit) return format_html('<a href="{}">{} {}</a>', url, rdata.used, rdata.unit)
display_used.short_description = _("Used") display_used.short_description = _("Used")
display_used.admin_order_field = 'used' display_used.admin_order_field = 'used'
display_used.allow_tags = True
def has_add_permission(self, *args, **kwargs): def has_add_permission(self, *args, **kwargs):
return False return False
@ -304,6 +303,7 @@ def resource_inline_factory(resources):
self.verbose_name_plural = mark_safe(_("Resources") + ' ' + link) self.verbose_name_plural = mark_safe(_("Resources") + ' ' + link)
return super(ResourceInline, self).get_fieldsets(request, obj) return super(ResourceInline, self).get_fieldsets(request, obj)
@mark_safe
def display_used(self, rdata): def display_used(self, rdata):
update = '' update = ''
history = '' history = ''
@ -329,7 +329,6 @@ def resource_inline_factory(resources):
return _("Unknonw %s %s") % (update, history) return _("Unknonw %s %s") % (update, history)
return _("No monitor") return _("No monitor")
display_used.short_description = _("Used") display_used.short_description = _("Used")
display_used.allow_tags = True
def has_add_permission(self, *args, **kwargs): def has_add_permission(self, *args, **kwargs):
""" Hidde add another """ """ Hidde add another """

View File

@ -1,5 +1,6 @@
from django.contrib import admin from django.contrib import admin
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from orchestra.admin import ExtendedModelAdmin, ChangePasswordAdminMixin from orchestra.admin import ExtendedModelAdmin, ChangePasswordAdminMixin
@ -27,6 +28,7 @@ class SaaSAdmin(SelectPluginAdminMixin, ChangePasswordAdminMixin, AccountAdminMi
plugin_title = 'Software as a Service' plugin_title = 'Software as a Service'
actions = (disable, enable, list_accounts) actions = (disable, enable, list_accounts)
@mark_safe
def display_url(self, saas): def display_url(self, saas):
site_domain = saas.get_site_domain() site_domain = saas.get_site_domain()
site_link = '<a href="http://%s">%s</a>' % (site_domain, site_domain) site_link = '<a href="http://%s">%s</a>' % (site_domain, site_domain)
@ -46,7 +48,6 @@ class SaaSAdmin(SelectPluginAdminMixin, ChangePasswordAdminMixin, AccountAdminMi
links.append(link) links.append(link)
return '<br>'.join(links) return '<br>'.join(links)
display_url.short_description = _("URL") display_url.short_description = _("URL")
display_url.allow_tags = True
display_url.admin_order_field = 'name' display_url.admin_order_field = 'name'
def get_fields(self, *args, **kwargs): def get_fields(self, *args, **kwargs):

View File

@ -42,7 +42,7 @@ def clean_custom_url(saas):
) )
except Website.DoesNotExist: except Website.DoesNotExist:
# get or create domain # get or create domain
Domain = Website.domains.field.model Domain = Website.domains.field.related_model
try: try:
domain = Domain.objects.get(name=url.netloc) domain = Domain.objects.get(name=url.netloc)
except Domain.DoesNotExist: except Domain.DoesNotExist:
@ -110,7 +110,7 @@ def create_or_update_directive(saas):
account=account, account=account,
) )
except Website.DoesNotExist: except Website.DoesNotExist:
Domain = Website.domains.field.model Domain = Website.domains.field.related_model
domain = Domain.objects.get(name=url.netloc) domain = Domain.objects.get(name=url.netloc)
# Create new website for custom_url # Create new website for custom_url
tgt_server = Server.objects.get(name='web.pangea.lan') tgt_server = Server.objects.get(name='web.pangea.lan')

View File

@ -4,6 +4,7 @@ from django.contrib import admin
from django.urls import reverse from django.urls import reverse
from django.template.response import TemplateResponse from django.template.response import TemplateResponse
from django.utils import timezone from django.utils import timezone
from django.utils.html import format_html
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from orchestra.admin import ChangeViewActionsMixin from orchestra.admin import ChangeViewActionsMixin
@ -69,10 +70,9 @@ class ServiceAdmin(ChangeViewActionsMixin, admin.ModelAdmin):
num = service.orders__count num = service.orders__count
url = reverse('admin:orders_order_changelist') url = reverse('admin:orders_order_changelist')
url += '?service__id__exact=%i&is_active=True' % service.pk url += '?service__id__exact=%i&is_active=True' % service.pk
return '<a href="%s">%d</a>' % (url, num) return format_html('<a href="{}">{}</a>', url, num)
num_orders.short_description = _("Orders") num_orders.short_description = _("Orders")
num_orders.admin_order_field = 'orders__count' num_orders.admin_order_field = 'orders__count'
num_orders.allow_tags = True
def get_queryset(self, request): def get_queryset(self, request):
qs = super(ServiceAdmin, self).get_queryset(request) qs = super(ServiceAdmin, self).get_queryset(request)

View File

@ -61,7 +61,7 @@ class SystemUser(models.Model):
def active(self): def active(self):
try: try:
return self.is_active and self.account.is_active return self.is_active and self.account.is_active
except type(self).account.field.model.DoesNotExist: except type(self).account.field.related_model.DoesNotExist:
return self.is_active return self.is_active
@cached_property @cached_property

View File

@ -2,6 +2,7 @@ from django import forms
from django.contrib import admin from django.contrib import admin
from django.urls import reverse from django.urls import reverse
from django.utils.encoding import force_text from django.utils.encoding import force_text
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext, ugettext_lazy as _ from django.utils.translation import ugettext, ugettext_lazy as _
from orchestra.admin import ExtendedModelAdmin from orchestra.admin import ExtendedModelAdmin
@ -66,6 +67,7 @@ class WebAppAdmin(SelectPluginAdminMixin, AccountAdminMixin, ExtendedModelAdmin)
display_type = display_plugin_field('type') display_type = display_plugin_field('type')
@mark_safe
def display_websites(self, webapp): def display_websites(self, webapp):
websites = [] websites = []
for content in webapp.content_set.all(): for content in webapp.content_set.all():
@ -82,29 +84,13 @@ class WebAppAdmin(SelectPluginAdminMixin, AccountAdminMixin, ExtendedModelAdmin)
websites.append('<a href="%s">%s%s</a>' % (add_url, plus, ugettext("Add website"))) websites.append('<a href="%s">%s%s</a>' % (add_url, plus, ugettext("Add website")))
return '<br>'.join(websites) return '<br>'.join(websites)
display_websites.short_description = _("web sites") display_websites.short_description = _("web sites")
display_websites.allow_tags = True
def display_detail(self, webapp): def display_detail(self, webapp):
try: try:
return webapp.type_instance.get_detail() return webapp.type_instance.get_detail()
except KeyError: except KeyError:
return "<span style='color:red;'>Not available</span>" return mark_safe("<span style='color:red;'>Not available</span>")
display_detail.short_description = _("detail") display_detail.short_description = _("detail")
display_detail.allow_tags = True
# def get_form(self, request, obj=None, **kwargs):
# form = super(WebAppAdmin, self).get_form(request, obj, **kwargs)
# if obj:
#
# def formfield_for_dbfield(self, db_field, **kwargs):
# """ Make value input widget bigger """
# if db_field.name == 'type':
# # Help text based on select widget
# kwargs['widget'] = DynamicHelpTextSelect(
# 'this.id.replace("name", "value")', self.TYPE_HELP_TEXT
# )
# kwargs['help_text'] = self.TYPE_HELP_TEXT.get(db_field.default, '')
# return super(WebAppAdmin, self).formfield_for_dbfield(db_field, **kwargs)
admin.site.register(WebApp, WebAppAdmin) admin.site.register(WebApp, WebAppAdmin)

View File

@ -2,7 +2,7 @@ import os
import textwrap import textwrap
from collections import OrderedDict from collections import OrderedDict
from django.template import Template, Context from django.template import Template
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from orchestra.contrib.orchestration import ServiceController from orchestra.contrib.orchestration import ServiceController
@ -245,7 +245,7 @@ class PHPController(WebAppServiceMixin, ServiceController):
php_admin_value[{{ name | safe }}] = {{ value | safe }}{% endfor %} php_admin_value[{{ name | safe }}] = {{ value | safe }}{% endfor %}
""" """
)) ))
return fpm_config.render(Context(context)) return fpm_config.render(context)
def get_fcgid_wrapper(self, webapp, context): def get_fcgid_wrapper(self, webapp, context):
opt = webapp.type_instance opt = webapp.type_instance

View File

@ -3,6 +3,8 @@ from django.contrib import admin
from django.urls import resolve from django.urls import resolve
from django.db.models import Q from django.db.models import Q
from django.utils.encoding import force_text from django.utils.encoding import force_text
from django.utils.html import format_html
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from orchestra.admin import ExtendedModelAdmin from orchestra.admin import ExtendedModelAdmin
@ -78,6 +80,7 @@ class WebsiteAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
search_fields = ('name', 'account__username', 'domains__name', 'content__webapp__name') search_fields = ('name', 'account__username', 'domains__name', 'content__webapp__name')
actions = (disable, enable, list_accounts) actions = (disable, enable, list_accounts)
@mark_safe
def display_domains(self, website): def display_domains(self, website):
domains = [] domains = []
for domain in website.domains.all(): for domain in website.domains.all():
@ -85,9 +88,9 @@ class WebsiteAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
domains.append('<a href="%s">%s</a>' % (url, url)) domains.append('<a href="%s">%s</a>' % (url, url))
return '<br>'.join(domains) return '<br>'.join(domains)
display_domains.short_description = _("domains") display_domains.short_description = _("domains")
display_domains.allow_tags = True
display_domains.admin_order_field = 'domains' display_domains.admin_order_field = 'domains'
@mark_safe
def display_webapps(self, website): def display_webapps(self, website):
webapps = [] webapps = []
for content in website.content_set.all(): for content in website.content_set.all():
@ -100,9 +103,9 @@ class WebsiteAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
pass pass
url = change_url(webapp) url = change_url(webapp)
name = "%s on %s" % (webapp.name, content.path or '/') name = "%s on %s" % (webapp.name, content.path or '/')
webapps.append('<a href="%s" title="%s">%s %s</a>' % (url, detail, name, site_link)) webapp_info = format_html('<a href="{}" title="{}">{}</a> {}', url, detail, name, site_link)
webapps.append(webapp_info)
return '<br>'.join(webapps) return '<br>'.join(webapps)
display_webapps.allow_tags = True
display_webapps.short_description = _("Web apps") display_webapps.short_description = _("Web apps")
def formfield_for_dbfield(self, db_field, **kwargs): def formfield_for_dbfield(self, db_field, **kwargs):

View File

@ -2,7 +2,7 @@ import os
import re import re
import textwrap import textwrap
from django.template import Template, Context from django.template import Template
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from orchestra.contrib.orchestration import ServiceController from orchestra.contrib.orchestration import ServiceController
@ -78,7 +78,7 @@ class Apache2Controller(ServiceController):
{{ line | safe }}{% endfor %} {{ line | safe }}{% endfor %}
</VirtualHost> </VirtualHost>
""") """)
).render(Context(context)) ).render(context)
def render_redirect_https(self, context): def render_redirect_https(self, context):
context['port'] = self.HTTP_PORT context['port'] = self.HTTP_PORT
@ -96,7 +96,7 @@ class Apache2Controller(ServiceController):
RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI}
</VirtualHost> </VirtualHost>
""") """)
).render(Context(context)) ).render(context)
def save(self, site): def save(self, site):
context = self.get_context(site) context = self.get_context(site)

View File

@ -13,13 +13,13 @@ from .validators import validate_domain_protocol
class RelatedDomainSerializer(AccountSerializerMixin, RelatedHyperlinkedModelSerializer): class RelatedDomainSerializer(AccountSerializerMixin, RelatedHyperlinkedModelSerializer):
class Meta: class Meta:
model = Website.domains.field.model model = Website.domains.field.related_model
fields = ('url', 'id', 'name') fields = ('url', 'id', 'name')
class RelatedWebAppSerializer(AccountSerializerMixin, RelatedHyperlinkedModelSerializer): class RelatedWebAppSerializer(AccountSerializerMixin, RelatedHyperlinkedModelSerializer):
class Meta: class Meta:
model = Content.webapp.field.model model = Content.webapp.field.related_model
fields = ('url', 'id', 'name', 'type') fields = ('url', 'id', 'name', 'type')

View File

@ -18,8 +18,8 @@ class SpanWidget(forms.Widget):
self.display = kwargs.pop('display', None) self.display = kwargs.pop('display', None)
super(SpanWidget, self).__init__(*args, **kwargs) super(SpanWidget, self).__init__(*args, **kwargs)
def render(self, name, value, attrs=None): def render(self, name, value, attrs=None, renderer=None):
final_attrs = self.build_attrs(attrs, name=name) final_attrs = self.build_attrs(attrs, extra_attrs={'name':name})
original = self.original or value original = self.original or value
display = original if self.display is None else self.display display = original if self.display is None else self.display
# Display icon # Display icon
@ -37,17 +37,17 @@ class SpanWidget(forms.Widget):
return False return False
def paddingCheckboxSelectMultiple(padding): class PaddingCheckboxSelectMultiple(forms.CheckboxSelectMultiple):
""" Ugly hack to render this widget nicely on Django admin """ """ Ugly hack to render this widget nicely on Django admin """
widget = forms.CheckboxSelectMultiple() def __init__(self, padding, attrs=None, choices=()):
old_render = widget.render super().__init__(attrs=attrs, choices=choices)
self.padding = padding
def render(self, *args, **kwargs): def render(self, *args, **kwargs):
value = old_render(self, *args, **kwargs) value = super().render(*args, **kwargs)
value = re.sub(r'^<ul id=([^>]+)>', value = re.sub(r'^<ul id=([^>]+)>',
r'<ul id=\1 style="padding-left:%ipx">' % padding, value, 1) r'<ul id=\1 style="padding-left:%ipx">' % self.padding, value, 1)
return mark_safe(value) return mark_safe(value)
widget.render = render
return widget
class DynamicHelpTextSelect(forms.Select): class DynamicHelpTextSelect(forms.Select):

View File

@ -90,7 +90,7 @@ class RelatedPermission(Permission):
if obj is None: if obj is None:
parent = cls parent = cls
for relation in relations: for relation in relations:
parent = getattr(parent, relation).field.model parent = getattr(parent, relation).field.related_model
else: else:
parent = functools.reduce(getattr, relations, obj) parent = functools.reduce(getattr, relations, obj)

View File

@ -1,6 +1,7 @@
import textwrap import textwrap
from django.templatetags.static import static from django.templatetags.static import static
from django.utils.html import format_html
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from orchestra.utils.sys import run from orchestra.utils.sys import run
@ -31,6 +32,6 @@ def get_on_site_link(url):
context = { context = {
'title': _("View on site %s") % url, 'title': _("View on site %s") % url,
'url': url, 'url': url,
'image': '<img src="%s"></img>' % static('orchestra/images/view-on-site.png'), 'image': format_html('<img src="{}"></img>', static('orchestra/images/view-on-site.png')),
} }
return '<a href="%(url)s" title="%(title)s">%(image)s</a>' % context return format_html('<a href="{url}" title="{title}">{image}</a>', **context)

View File

@ -2,7 +2,6 @@ from urllib.parse import urlparse
from django.core.mail import EmailMultiAlternatives from django.core.mail import EmailMultiAlternatives
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.template import Context
def render_email_template(template, context): def render_email_template(template, context):
@ -11,11 +10,8 @@ def render_email_template(template, context):
{% if subject %}Subject{% endif %} {% if subject %}Subject{% endif %}
{% if message %}Email body{% endif %} {% if message %}Email body{% endif %}
context can be a dictionary or a template.Context instance context must be a dict
""" """
if isinstance(context, dict):
context = Context(context)
if not 'site' in context: if not 'site' in context:
from orchestra import settings from orchestra import settings
url = urlparse(settings.ORCHESTRA_SITE_URL) url = urlparse(settings.ORCHESTRA_SITE_URL)

View File

@ -12,7 +12,7 @@ ecdsa==0.11
Pygments==1.6 Pygments==1.6
django-filter==2.2.0 django-filter==2.2.0
jsonfield==0.9.22 jsonfield==0.9.22
python-dateutil==2.2 python-dateutil>=2.7.0
https://github.com/glic3rinu/passlib/archive/master.zip https://github.com/glic3rinu/passlib/archive/master.zip
django-iban==0.3.0 django-iban==0.3.0
requests requests