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)
@ -93,7 +93,7 @@ class AdminPasswordChangeForm(forms.Form):
required=False, validators=[validate_password]) required=False, validators=[validate_password])
password2 = forms.CharField(label=_("Password (again)"), widget=forms.PasswordInput, password2 = forms.CharField(label=_("Password (again)"), widget=forms.PasswordInput,
required=False) required=False)
def __init__(self, user, *args, **kwargs): def __init__(self, user, *args, **kwargs):
self.related = kwargs.pop('related', []) self.related = kwargs.pop('related', [])
self.raw = kwargs.pop('raw', False) self.raw = kwargs.pop('raw', False)
@ -109,7 +109,7 @@ class AdminPasswordChangeForm(forms.Form):
self.fields['password2_%i' % ix] = forms.CharField(label=_("Password (again)"), self.fields['password2_%i' % ix] = forms.CharField(label=_("Password (again)"),
widget=forms.PasswordInput, required=False) widget=forms.PasswordInput, required=False)
setattr(self, 'clean_password2_%i' % ix, partial(self.clean_password2, ix=ix)) setattr(self, 'clean_password2_%i' % ix, partial(self.clean_password2, ix=ix))
def clean_password2(self, ix=''): def clean_password2(self, ix=''):
if ix != '': if ix != '':
ix = '_%i' % ix ix = '_%i' % ix
@ -129,7 +129,7 @@ class AdminPasswordChangeForm(forms.Form):
code='password_mismatch', code='password_mismatch',
) )
return password2 return password2
def clean_password(self, ix=''): def clean_password(self, ix=''):
if ix != '': if ix != '':
ix = '_%i' % ix ix = '_%i' % ix
@ -146,14 +146,14 @@ class AdminPasswordChangeForm(forms.Form):
code='bad_hash', code='bad_hash',
) )
return password return password
def clean(self): def clean(self):
if not self.password_provided: if not self.password_provided:
raise forms.ValidationError( raise forms.ValidationError(
self.error_messages['password_missing'], self.error_messages['password_missing'],
code='password_missing', code='password_missing',
) )
def save(self, commit=True): def save(self, commit=True):
""" """
Saves the new password. Saves the new password.
@ -182,7 +182,7 @@ class AdminPasswordChangeForm(forms.Form):
if commit: if commit:
rel.save(update_fields=['password']) rel.save(update_fields=['password'])
return self.user return self.user
def _get_changed_data(self): def _get_changed_data(self):
data = super().changed_data data = super().changed_data
for name in self.fields.keys(): for name in self.fields.keys():
@ -202,7 +202,7 @@ class SendEmailForm(forms.Form):
widget=forms.TextInput(attrs={'size': '118'})) widget=forms.TextInput(attrs={'size': '118'}))
message = forms.CharField(label=_("Message"), message = forms.CharField(label=_("Message"),
widget=forms.Textarea(attrs={'cols': 118, 'rows': 15})) widget=forms.Textarea(attrs={'cols': 118, 'rows': 15}))
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
initial = kwargs.get('initial') initial = kwargs.get('initial')
@ -210,7 +210,7 @@ class SendEmailForm(forms.Form):
self.fields['to'].widget = SpanWidget(original=initial['to']) self.fields['to'].widget = SpanWidget(original=initial['to'])
else: else:
self.fields.pop('to') self.fields.pop('to')
def clean_comma_separated_emails(self, value): def clean_comma_separated_emails(self, value):
clean_value = [] clean_value = []
for email in value.split(','): for email in value.split(','):
@ -222,7 +222,7 @@ class SendEmailForm(forms.Form):
raise validators.ValidationError("Comma separated email addresses.") raise validators.ValidationError("Comma separated email addresses.")
clean_value.append(email) clean_value.append(email)
return clean_value return clean_value
def clean_extra_to(self): def clean_extra_to(self):
extra_to = self.cleaned_data['extra_to'] extra_to = self.cleaned_data['extra_to']
return self.clean_comma_separated_emails(extra_to) return self.clean_comma_separated_emails(extra_to)

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
@ -61,18 +61,18 @@ class ContactAdmin(AccountAdminMixin, ExtendedModelAdmin):
}), }),
) )
actions = (SendEmail(), list_accounts) actions = (SendEmail(), list_accounts)
def dispaly_name(self, contact): def dispaly_name(self, contact):
return str(contact) return str(contact)
dispaly_name.short_description = _("Name") dispaly_name.short_description = _("Name")
dispaly_name.admin_order_field = 'short_name' dispaly_name.admin_order_field = 'short_name'
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 """
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)
@ -86,14 +86,14 @@ class ContactInline(admin.StackedInline):
fields = ( fields = (
('short_name', 'full_name'), 'email', 'email_usage', ('phone', 'phone2'), ('short_name', 'full_name'), 'email', 'email_usage', ('phone', 'phone2'),
) )
def get_extra(self, request, obj=None, **kwargs): def get_extra(self, request, obj=None, **kwargs):
return 0 if obj and obj.contacts.exists() else 1 return 0 if obj and obj.contacts.exists() else 1
def get_view_on_site_url(self, obj=None): def get_view_on_site_url(self, obj=None):
if obj: if obj:
return change_url(obj) return change_url(obj)
def formfield_for_dbfield(self, db_field, **kwargs): def formfield_for_dbfield(self, db_field, **kwargs):
""" Make value input widget bigger """ """ Make value input widget bigger """
if db_field.name == 'short_name': if db_field.name == 'short_name':
@ -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
@ -49,17 +51,17 @@ class DatabaseAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
filter_by_account_fields = ('users',) filter_by_account_fields = ('users',)
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):
super(DatabaseAdmin, self).save_model(request, obj, form, change) super(DatabaseAdmin, self).save_model(request, obj, form, change)
if not change: if not change:
@ -98,24 +100,24 @@ class DatabaseUserAdmin(SelectAccountAdminMixin, ChangePasswordAdminMixin, Exten
filter_by_account_fields = ('databases',) filter_by_account_fields = ('databases',)
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):
useradmin = UserAdmin(DatabaseUser, self.admin_site) useradmin = UserAdmin(DatabaseUser, self.admin_site)
return [ return [
url(r'^(\d+)/password/$', url(r'^(\d+)/password/$',
self.admin_site.admin_view(useradmin.user_change_password)) self.admin_site.admin_view(useradmin.user_change_password))
] + super(DatabaseUserAdmin, self).get_urls() ] + super(DatabaseUserAdmin, self).get_urls()
def save_model(self, request, obj, form, change): def save_model(self, request, obj, form, change):
""" set password """ """ set password """
if not change: if not change:

View File

@ -17,11 +17,11 @@ class DatabaseUserCreationForm(forms.ModelForm):
password2 = forms.CharField(label=_("Password confirmation"), required=False, password2 = forms.CharField(label=_("Password confirmation"), required=False,
widget=forms.PasswordInput, widget=forms.PasswordInput,
help_text=_("Enter the same password as above, for verification.")) help_text=_("Enter the same password as above, for verification."))
class Meta: class Meta:
model = DatabaseUser model = DatabaseUser
fields = ('username', 'account', 'type') fields = ('username', 'account', 'type')
def clean_password2(self): def clean_password2(self):
password1 = self.cleaned_data.get("password1") password1 = self.cleaned_data.get("password1")
password2 = self.cleaned_data.get("password2") password2 = self.cleaned_data.get("password2")
@ -40,11 +40,11 @@ class DatabaseCreationForm(DatabaseUserCreationForm):
'invalid': _("This value may contain 16 characters or fewer, only letters, numbers and " 'invalid': _("This value may contain 16 characters or fewer, only letters, numbers and "
"@/./+/-/_ characters.")}) "@/./+/-/_ characters.")})
user = forms.ModelChoiceField(required=False, queryset=DatabaseUser.objects) user = forms.ModelChoiceField(required=False, queryset=DatabaseUser.objects)
class Meta: class Meta:
model = Database model = Database
fields = ('username', 'account', 'type') fields = ('username', 'account', 'type')
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(DatabaseCreationForm, self).__init__(*args, **kwargs) super(DatabaseCreationForm, self).__init__(*args, **kwargs)
account_id = self.initial.get('account', self.initial_account) account_id = self.initial.get('account', self.initial_account)
@ -53,13 +53,13 @@ class DatabaseCreationForm(DatabaseUserCreationForm):
choices = [ (u.pk, "%s (%s)" % (u, u.get_type_display())) for u in qs ] choices = [ (u.pk, "%s (%s)" % (u, u.get_type_display())) for u in qs ]
self.fields['user'].queryset = qs self.fields['user'].queryset = qs
self.fields['user'].choices = [(None, '--------'),] + choices self.fields['user'].choices = [(None, '--------'),] + choices
def clean_username(self): def clean_username(self):
username = self.cleaned_data.get('username') username = self.cleaned_data.get('username')
if DatabaseUser.objects.filter(username=username).exists(): if DatabaseUser.objects.filter(username=username).exists():
raise ValidationError("Provided username already exists.") raise ValidationError("Provided username already exists.")
return username return username
def clean_password2(self): def clean_password2(self):
username = self.cleaned_data.get('username') username = self.cleaned_data.get('username')
password1 = self.cleaned_data.get('password1') password1 = self.cleaned_data.get('password1')
@ -70,14 +70,14 @@ class DatabaseCreationForm(DatabaseUserCreationForm):
msg = _("The two password fields didn't match.") msg = _("The two password fields didn't match.")
raise ValidationError(msg) raise ValidationError(msg)
return password2 return password2
def clean_user(self): def clean_user(self):
user = self.cleaned_data.get('user') user = self.cleaned_data.get('user')
if user and user.type != self.cleaned_data.get('type'): if user and user.type != self.cleaned_data.get('type'):
msg = _("Database type and user type doesn't match") msg = _("Database type and user type doesn't match")
raise ValidationError(msg) raise ValidationError(msg)
return user return user
def clean(self): def clean(self):
cleaned_data = super(DatabaseCreationForm, self).clean() cleaned_data = super(DatabaseCreationForm, self).clean()
if 'user' in cleaned_data and 'username' in cleaned_data: if 'user' in cleaned_data and 'username' in cleaned_data:
@ -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
@ -114,10 +114,10 @@ class DatabaseUserChangeForm(forms.ModelForm):
"this user's password, but you can change the password " "this user's password, but you can change the password "
"using <a href='../password/'>this form</a>. " "using <a href='../password/'>this form</a>. "
"<a onclick='return showAddAnotherPopup(this);' href='../hash/'>Show hash</a>.")) "<a onclick='return showAddAnotherPopup(this);' href='../hash/'>Show hash</a>."))
class Meta: class Meta:
model = DatabaseUser model = DatabaseUser
fields = ('username', 'password', 'type', 'account') fields = ('username', 'password', 'type', 'account')
def clean_password(self): def clean_password(self):
return self.initial["password"] return self.initial["password"]

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

@ -13,7 +13,7 @@ from .models import Queue, Ticket
class MarkDownWidget(forms.Textarea): class MarkDownWidget(forms.Textarea):
""" MarkDown textarea widget with syntax preview """ """ MarkDown textarea widget with syntax preview """
markdown_url = static('issues/markdown_syntax.html') markdown_url = static('issues/markdown_syntax.html')
markdown_help_text = ( markdown_help_text = (
'<a href="%s" onclick=\'window.open("%s", "", "resizable=yes, ' '<a href="%s" onclick=\'window.open("%s", "", "resizable=yes, '
@ -21,8 +21,8 @@ class MarkDownWidget(forms.Textarea):
'return false;\'>markdown format</a>' % (markdown_url, markdown_url) 'return false;\'>markdown format</a>' % (markdown_url, markdown_url)
) )
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>'\
@ -35,18 +35,18 @@ class MessageInlineForm(forms.ModelForm):
""" Add message form """ """ Add message form """
created_on = forms.CharField(label="Created On", required=False) created_on = forms.CharField(label="Created On", required=False)
content = forms.CharField(widget=MarkDownWidget(), required=False) content = forms.CharField(widget=MarkDownWidget(), required=False)
class Meta: class Meta:
fields = ('author', 'author_name', 'created_on', 'content') fields = ('author', 'author_name', 'created_on', 'content')
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(MessageInlineForm, self).__init__(*args, **kwargs) super(MessageInlineForm, self).__init__(*args, **kwargs)
self.fields['created_on'].widget = SpanWidget(display='') self.fields['created_on'].widget = SpanWidget(display='')
def clean_content(self): def clean_content(self):
""" clean HTML tags """ """ clean HTML tags """
return strip_tags(self.cleaned_data['content']) return strip_tags(self.cleaned_data['content'])
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
if self.instance.pk is None: if self.instance.pk is None:
self.instance.author = self.user self.instance.author = self.user
@ -58,7 +58,7 @@ class UsersIterator(forms.models.ModelChoiceIterator):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.ticket = kwargs.pop('ticket', False) self.ticket = kwargs.pop('ticket', False)
super(forms.models.ModelChoiceIterator, self).__init__(*args, **kwargs) super(forms.models.ModelChoiceIterator, self).__init__(*args, **kwargs)
def __iter__(self): def __iter__(self):
yield ('', '---------') yield ('', '---------')
users = get_user_model().objects.exclude(is_active=False).order_by('name') users = get_user_model().objects.exclude(is_active=False).order_by('name')
@ -73,14 +73,14 @@ class UsersIterator(forms.models.ModelChoiceIterator):
class TicketForm(forms.ModelForm): class TicketForm(forms.ModelForm):
display_description = forms.CharField(label=_("Description"), required=False) display_description = forms.CharField(label=_("Description"), required=False)
description = forms.CharField(widget=MarkDownWidget(attrs={'class':'vLargeTextField'})) description = forms.CharField(widget=MarkDownWidget(attrs={'class':'vLargeTextField'}))
class Meta: class Meta:
model = Ticket model = Ticket
fields = ( fields = (
'creator', 'creator_name', 'owner', 'queue', 'subject', 'description', 'creator', 'creator_name', 'owner', 'queue', 'subject', 'description',
'priority', 'state', 'cc', 'display_description' 'priority', 'state', 'cc', 'display_description'
) )
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(TicketForm, self).__init__(*args, **kwargs) super(TicketForm, self).__init__(*args, **kwargs)
ticket = kwargs.get('instance', False) ticket = kwargs.get('instance', False)
@ -101,7 +101,7 @@ class TicketForm(forms.ModelForm):
description = '<div style="padding-left: 95px;">%s</div>' % description description = '<div style="padding-left: 95px;">%s</div>' % description
widget = SpanWidget(display=description) widget = SpanWidget(display=description)
self.fields['display_description'].widget = widget self.fields['display_description'].widget = widget
def clean_description(self): def clean_description(self):
""" clean HTML tags """ """ clean HTML tags """
return strip_tags(self.cleaned_data['description']) return strip_tags(self.cleaned_data['description'])

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()
self.address = self.address.strip() if self.address:
if self.name and not self.address: self.address = self.address.strip()
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)
ping = ping.stdout.splitlines()[-1].decode()
try:
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
@ -26,7 +27,8 @@ class SaaSAdmin(SelectPluginAdminMixin, ChangePasswordAdminMixin, AccountAdminMi
plugin_field = 'service' plugin_field = 'service'
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,9 +48,8 @@ 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):
fields = super(SaaSAdmin, self).get_fields(*args, **kwargs) fields = super(SaaSAdmin, self).get_fields(*args, **kwargs)
if not self.plugin_instance.allow_custom_url: if not self.plugin_instance.allow_custom_url:

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
@ -17,7 +17,7 @@ class PHPController(WebAppServiceMixin, ServiceController):
It handles switching between these two PHP process management systemes. It handles switching between these two PHP process management systemes.
""" """
MERGE = settings.WEBAPPS_MERGE_PHP_WEBAPPS MERGE = settings.WEBAPPS_MERGE_PHP_WEBAPPS
verbose_name = _("PHP FPM/FCGID") verbose_name = _("PHP FPM/FCGID")
default_route_match = "webapp.type.endswith('php')" default_route_match = "webapp.type.endswith('php')"
doc_settings = (settings, ( doc_settings = (settings, (
@ -30,7 +30,7 @@ class PHPController(WebAppServiceMixin, ServiceController):
'WEBAPPS_PHPFPM_POOL_PATH', 'WEBAPPS_PHPFPM_POOL_PATH',
'WEBAPPS_PHP_MAX_REQUESTS', 'WEBAPPS_PHP_MAX_REQUESTS',
)) ))
def save(self, webapp): def save(self, webapp):
self.delete_old_config(webapp) self.delete_old_config(webapp)
context = self.get_context(webapp) context = self.get_context(webapp)
@ -81,7 +81,7 @@ class PHPController(WebAppServiceMixin, ServiceController):
} }
""") % context """) % context
) )
def save_fcgid(self, webapp, context): def save_fcgid(self, webapp, context):
self.append("mkdir -p %(wrapper_dir)s" % context) self.append("mkdir -p %(wrapper_dir)s" % context)
self.append(textwrap.dedent(""" self.append(textwrap.dedent("""
@ -118,7 +118,7 @@ class PHPController(WebAppServiceMixin, ServiceController):
) )
else: else:
self.append("rm -f %(cmd_options_path)s\n" % context) self.append("rm -f %(cmd_options_path)s\n" % context)
def delete(self, webapp): def delete(self, webapp):
context = self.get_context(webapp) context = self.get_context(webapp)
self.delete_old_config(webapp) self.delete_old_config(webapp)
@ -127,13 +127,13 @@ class PHPController(WebAppServiceMixin, ServiceController):
# elif webapp.type_instance.is_fcgid: # elif webapp.type_instance.is_fcgid:
# self.delete_fcgid(webapp, context) # self.delete_fcgid(webapp, context)
self.delete_webapp_dir(context) self.delete_webapp_dir(context)
def has_sibilings(self, webapp, context): def has_sibilings(self, webapp, context):
return type(webapp).objects.filter( return type(webapp).objects.filter(
account=webapp.account_id, account=webapp.account_id,
data__contains='"php_version":"%s"' % context['php_version'], data__contains='"php_version":"%s"' % context['php_version'],
).exclude(id=webapp.pk).exists() ).exclude(id=webapp.pk).exists()
def all_versions_to_delete(self, webapp, context, preserve=False): def all_versions_to_delete(self, webapp, context, preserve=False):
context_copy = dict(context) context_copy = dict(context)
for php_version, verbose in settings.WEBAPPS_PHP_VERSIONS: for php_version, verbose in settings.WEBAPPS_PHP_VERSIONS:
@ -144,13 +144,13 @@ class PHPController(WebAppServiceMixin, ServiceController):
context_copy['php_version_number'] = php_version_number context_copy['php_version_number'] = php_version_number
if not self.MERGE or not self.has_sibilings(webapp, context_copy): if not self.MERGE or not self.has_sibilings(webapp, context_copy):
yield context_copy yield context_copy
def delete_fpm(self, webapp, context, preserve=False): def delete_fpm(self, webapp, context, preserve=False):
""" delete all pools in order to efectively support changing php-fpm version """ """ delete all pools in order to efectively support changing php-fpm version """
for context_copy in self.all_versions_to_delete(webapp, context, preserve): for context_copy in self.all_versions_to_delete(webapp, context, preserve):
context_copy['fpm_path'] = settings.WEBAPPS_PHPFPM_POOL_PATH % context_copy context_copy['fpm_path'] = settings.WEBAPPS_PHPFPM_POOL_PATH % context_copy
self.append("rm -f %(fpm_path)s" % context_copy) self.append("rm -f %(fpm_path)s" % context_copy)
def delete_fcgid(self, webapp, context, preserve=False): def delete_fcgid(self, webapp, context, preserve=False):
""" delete all pools in order to efectively support changing php-fcgid version """ """ delete all pools in order to efectively support changing php-fcgid version """
for context_copy in self.all_versions_to_delete(webapp, context, preserve): for context_copy in self.all_versions_to_delete(webapp, context, preserve):
@ -160,13 +160,13 @@ class PHPController(WebAppServiceMixin, ServiceController):
}) })
self.append("rm -f %(wrapper_path)s" % context_copy) self.append("rm -f %(wrapper_path)s" % context_copy)
self.append("rm -f %(cmd_options_path)s" % context_copy) self.append("rm -f %(cmd_options_path)s" % context_copy)
def prepare(self): def prepare(self):
super(PHPController, self).prepare() super(PHPController, self).prepare()
self.append(textwrap.dedent(""" self.append(textwrap.dedent("""
BACKEND="PHPController" BACKEND="PHPController"
echo "$BACKEND" >> /dev/shm/reload.apache2 echo "$BACKEND" >> /dev/shm/reload.apache2
function coordinate_apache_reload () { function coordinate_apache_reload () {
# Coordinate Apache reload with other concurrent backends (e.g. Apache2Controller) # Coordinate Apache reload with other concurrent backends (e.g. Apache2Controller)
is_last=0 is_last=0
@ -203,7 +203,7 @@ class PHPController(WebAppServiceMixin, ServiceController):
fi fi
}""") }""")
) )
def commit(self): def commit(self):
context = { context = {
'reload_pool': settings.WEBAPPS_PHPFPM_RELOAD_POOL, 'reload_pool': settings.WEBAPPS_PHPFPM_RELOAD_POOL,
@ -217,7 +217,7 @@ class PHPController(WebAppServiceMixin, ServiceController):
""") % context """) % context
) )
super(PHPController, self).commit() super(PHPController, self).commit()
def get_fpm_config(self, webapp, context): def get_fpm_config(self, webapp, context):
options = webapp.type_instance.get_options() options = webapp.type_instance.get_options()
context.update({ context.update({
@ -231,11 +231,11 @@ class PHPController(WebAppServiceMixin, ServiceController):
[{{ user }}-{{app_name}}] [{{ user }}-{{app_name}}]
user = {{ user }} user = {{ user }}
group = {{ group }} group = {{ group }}
listen = {{ fpm_listen | safe }} listen = {{ fpm_listen | safe }}
listen.owner = {{ user }} listen.owner = {{ user }}
listen.group = {{ group }} listen.group = {{ group }}
pm = ondemand pm = ondemand
pm.max_requests = {{ max_requests }} pm.max_requests = {{ max_requests }}
pm.max_children = {{ max_children }} pm.max_children = {{ max_children }}
@ -245,8 +245,8 @@ 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
# Format PHP init vars # Format PHP init vars
@ -268,7 +268,7 @@ class PHPController(WebAppServiceMixin, ServiceController):
export PHP_INI_SCAN_DIR=%(php_ini_scan)s export PHP_INI_SCAN_DIR=%(php_ini_scan)s
export PHP_FCGI_MAX_REQUESTS=%(max_requests)s export PHP_FCGI_MAX_REQUESTS=%(max_requests)s
exec %(php_binary_path)s%(php_init_vars)s""") % context exec %(php_binary_path)s%(php_init_vars)s""") % context
def get_fcgid_cmd_options(self, webapp, context): def get_fcgid_cmd_options(self, webapp, context):
options = webapp.type_instance.get_options() options = webapp.type_instance.get_options()
maps = OrderedDict( maps = OrderedDict(
@ -288,7 +288,7 @@ class PHPController(WebAppServiceMixin, ServiceController):
) % context ) % context
cmd_options.insert(0, head) cmd_options.insert(0, head)
return ' \\\n '.join(cmd_options) return ' \\\n '.join(cmd_options)
def update_fcgid_context(self, webapp, context): def update_fcgid_context(self, webapp, context):
wrapper_path = settings.WEBAPPS_FCGID_WRAPPER_PATH % context wrapper_path = settings.WEBAPPS_FCGID_WRAPPER_PATH % context
context.update({ context.update({
@ -301,14 +301,14 @@ class PHPController(WebAppServiceMixin, ServiceController):
'cmd_options_path': settings.WEBAPPS_FCGID_CMD_OPTIONS_PATH % context, 'cmd_options_path': settings.WEBAPPS_FCGID_CMD_OPTIONS_PATH % context,
}) })
return context return context
def update_fpm_context(self, webapp, context): def update_fpm_context(self, webapp, context):
context.update({ context.update({
'fpm_config': self.get_fpm_config(webapp, context), 'fpm_config': self.get_fpm_config(webapp, context),
'fpm_path': settings.WEBAPPS_PHPFPM_POOL_PATH % context, 'fpm_path': settings.WEBAPPS_PHPFPM_POOL_PATH % context,
}) })
return context return context
def get_context(self, webapp): def get_context(self, webapp):
context = super().get_context(webapp) context = super().get_context(webapp)
context.update({ context.update({

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
@ -20,7 +20,7 @@ class Apache2Controller(ServiceController):
""" """
HTTP_PORT = 80 HTTP_PORT = 80
HTTPS_PORT = 443 HTTPS_PORT = 443
model = 'websites.Website' model = 'websites.Website'
related_models = ( related_models = (
('websites.Content', 'website'), ('websites.Content', 'website'),
@ -37,7 +37,7 @@ class Apache2Controller(ServiceController):
'WEBSITES_DEFAULT_IPS', 'WEBSITES_DEFAULT_IPS',
'WEBSITES_SAAS_DIRECTIVES', 'WEBSITES_SAAS_DIRECTIVES',
)) ))
def get_extra_conf(self, site, context, ssl=False): def get_extra_conf(self, site, context, ssl=False):
extra_conf = self.get_content_directives(site, context) extra_conf = self.get_content_directives(site, context)
directives = site.get_directives() directives = site.get_directives()
@ -53,7 +53,7 @@ class Apache2Controller(ServiceController):
# Order extra conf directives based on directives (longer first) # Order extra conf directives based on directives (longer first)
extra_conf = sorted(extra_conf, key=lambda a: len(a[0]), reverse=True) extra_conf = sorted(extra_conf, key=lambda a: len(a[0]), reverse=True)
return '\n'.join([conf for location, conf in extra_conf]) return '\n'.join([conf for location, conf in extra_conf])
def render_virtual_host(self, site, context, ssl=False): def render_virtual_host(self, site, context, ssl=False):
context.update({ context.update({
'port': self.HTTPS_PORT if ssl else self.HTTP_PORT, 'port': self.HTTPS_PORT if ssl else self.HTTP_PORT,
@ -78,8 +78,8 @@ 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
return Template(textwrap.dedent(""" return Template(textwrap.dedent("""
@ -96,8 +96,8 @@ 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)
if context['server_name']: if context['server_name']:
@ -133,7 +133,7 @@ class Apache2Controller(ServiceController):
[[ $(a2dissite %(site_unique_name)s) =~ "already disabled" ]] || UPDATED_APACHE=1\ [[ $(a2dissite %(site_unique_name)s) =~ "already disabled" ]] || UPDATED_APACHE=1\
""") % context """) % context
) )
def delete(self, site): def delete(self, site):
context = self.get_context(site) context = self.get_context(site)
self.append(textwrap.dedent(""" self.append(textwrap.dedent("""
@ -142,14 +142,14 @@ class Apache2Controller(ServiceController):
rm -f %(sites_available)s\ rm -f %(sites_available)s\
""") % context """) % context
) )
def prepare(self): def prepare(self):
super(Apache2Controller, self).prepare() super(Apache2Controller, self).prepare()
# Coordinate apache restart with php backend in order not to overdo it # Coordinate apache restart with php backend in order not to overdo it
self.append(textwrap.dedent(""" self.append(textwrap.dedent("""
BACKEND="Apache2Controller" BACKEND="Apache2Controller"
echo "$BACKEND" >> /dev/shm/reload.apache2 echo "$BACKEND" >> /dev/shm/reload.apache2
function coordinate_apache_reload () { function coordinate_apache_reload () {
# Coordinate Apache reload with other concurrent backends (e.g. PHPController) # Coordinate Apache reload with other concurrent backends (e.g. PHPController)
is_last=0 is_last=0
@ -186,12 +186,12 @@ class Apache2Controller(ServiceController):
fi fi
}""") }""")
) )
def commit(self): def commit(self):
""" reload Apache2 if necessary """ """ reload Apache2 if necessary """
self.append("coordinate_apache_reload") self.append("coordinate_apache_reload")
super(Apache2Controller, self).commit() super(Apache2Controller, self).commit()
def get_directives(self, directive, context): def get_directives(self, directive, context):
method, args = directive[0], directive[1:] method, args = directive[0], directive[1:]
try: try:
@ -200,7 +200,7 @@ class Apache2Controller(ServiceController):
context = (self.__class__.__name__, method) context = (self.__class__.__name__, method)
raise AttributeError("%s does not has suport for '%s' directive." % context) raise AttributeError("%s does not has suport for '%s' directive." % context)
return method(context, *args) return method(context, *args)
def get_content_directives(self, site, context): def get_content_directives(self, site, context):
directives = [] directives = []
for content in site.content_set.all(): for content in site.content_set.all():
@ -208,19 +208,19 @@ class Apache2Controller(ServiceController):
self.set_content_context(content, context) self.set_content_context(content, context)
directives += self.get_directives(directive, context) directives += self.get_directives(directive, context)
return directives return directives
def get_static_directives(self, context, app_path): def get_static_directives(self, context, app_path):
context['app_path'] = os.path.normpath(app_path % context) context['app_path'] = os.path.normpath(app_path % context)
directive = self.get_location_filesystem_map(context) directive = self.get_location_filesystem_map(context)
return [ return [
(context['location'], directive), (context['location'], directive),
] ]
def get_location_filesystem_map(self, context): def get_location_filesystem_map(self, context):
if not context['location']: if not context['location']:
return 'DocumentRoot %(app_path)s' % context return 'DocumentRoot %(app_path)s' % context
return 'Alias %(location)s %(app_path)s' % context return 'Alias %(location)s %(app_path)s' % context
def get_fpm_directives(self, context, socket, app_path): def get_fpm_directives(self, context, socket, app_path):
if ':' in socket: if ':' in socket:
# TCP socket # TCP socket
@ -243,7 +243,7 @@ class Apache2Controller(ServiceController):
return [ return [
(context['location'], directives), (context['location'], directives),
] ]
def get_fcgid_directives(self, context, app_path, wrapper_path): def get_fcgid_directives(self, context, app_path, wrapper_path):
context.update({ context.update({
'app_path': os.path.normpath(app_path), 'app_path': os.path.normpath(app_path),
@ -274,7 +274,7 @@ class Apache2Controller(ServiceController):
return [ return [
(context['location'], directives), (context['location'], directives),
] ]
def get_uwsgi_directives(self, context, socket): def get_uwsgi_directives(self, context, socket):
# requires apache2 mod_proxy_uwsgi # requires apache2 mod_proxy_uwsgi
context['socket'] = socket context['socket'] = socket
@ -283,7 +283,7 @@ class Apache2Controller(ServiceController):
return [ return [
(context['location'], directives), (context['location'], directives),
] ]
def get_ssl(self, directives): def get_ssl(self, directives):
cert = directives.get('ssl-cert') cert = directives.get('ssl-cert')
key = directives.get('ssl-key') key = directives.get('ssl-key')
@ -305,7 +305,7 @@ class Apache2Controller(ServiceController):
return [ return [
('', '\n'.join(ssl_config)), ('', '\n'.join(ssl_config)),
] ]
def get_security(self, directives): def get_security(self, directives):
rules = [] rules = []
location = '/' location = '/'
@ -329,7 +329,7 @@ class Apache2Controller(ServiceController):
</IfModule>""") % '\n '.join(rules) </IfModule>""") % '\n '.join(rules)
security.append((location, rules)) security.append((location, rules))
return security return security
def get_redirects(self, directives): def get_redirects(self, directives):
redirects = [] redirects = []
for redirect in directives.get('redirect', []): for redirect in directives.get('redirect', []):
@ -342,7 +342,7 @@ class Apache2Controller(ServiceController):
(location, redirect) (location, redirect)
) )
return redirects return redirects
def get_proxies(self, directives): def get_proxies(self, directives):
proxies = [] proxies = []
for proxy in directives.get('proxy', []): for proxy in directives.get('proxy', []):
@ -360,7 +360,7 @@ class Apache2Controller(ServiceController):
(location, proxy) (location, proxy)
) )
return proxies return proxies
def get_saas(self, directives): def get_saas(self, directives):
saas = [] saas = []
for name, values in directives.items(): for name, values in directives.items():
@ -372,20 +372,20 @@ class Apache2Controller(ServiceController):
directive = settings.WEBSITES_SAAS_DIRECTIVES[name] directive = settings.WEBSITES_SAAS_DIRECTIVES[name]
saas += self.get_directives(directive, context) saas += self.get_directives(directive, context)
return saas return saas
def get_username(self, site): def get_username(self, site):
option = site.get_directives().get('user_group') option = site.get_directives().get('user_group')
if option: if option:
return option[0] return option[0]
return site.get_username() return site.get_username()
def get_groupname(self, site): def get_groupname(self, site):
option = site.get_directives().get('user_group') option = site.get_directives().get('user_group')
if option and ' ' in option: if option and ' ' in option:
user, group = option.split() user, group = option.split()
return group return group
return site.get_groupname() return site.get_groupname()
def get_server_names(self, site): def get_server_names(self, site):
server_name = None server_name = None
server_alias = [] server_alias = []
@ -395,7 +395,7 @@ class Apache2Controller(ServiceController):
else: else:
server_alias.append(domain.name) server_alias.append(domain.name)
return server_name, server_alias return server_name, server_alias
def get_context(self, site): def get_context(self, site):
base_apache_conf = settings.WEBSITES_BASE_APACHE_CONF base_apache_conf = settings.WEBSITES_BASE_APACHE_CONF
sites_available = os.path.join(base_apache_conf, 'sites-available') sites_available = os.path.join(base_apache_conf, 'sites-available')
@ -419,7 +419,7 @@ class Apache2Controller(ServiceController):
if not context['ips']: if not context['ips']:
raise ValueError("WEBSITES_DEFAULT_IPS is empty.") raise ValueError("WEBSITES_DEFAULT_IPS is empty.")
return context return context
def set_content_context(self, content, context): def set_content_context(self, content, context):
content_context = { content_context = {
'type': content.webapp.type, 'type': content.webapp.type,
@ -442,7 +442,7 @@ class Apache2Traffic(ServiceMonitor):
doc_settings = (settings, doc_settings = (settings,
('WEBSITES_TRAFFIC_IGNORE_HOSTS',) ('WEBSITES_TRAFFIC_IGNORE_HOSTS',)
) )
def prepare(self): def prepare(self):
super(Apache2Traffic, self).prepare() super(Apache2Traffic, self).prepare()
ignore_hosts = '\\|'.join(settings.WEBSITES_TRAFFIC_IGNORE_HOSTS) ignore_hosts = '\\|'.join(settings.WEBSITES_TRAFFIC_IGNORE_HOSTS)
@ -490,11 +490,11 @@ class Apache2Traffic(ServiceMonitor):
}' || [[ $? == 1 ]] && true }' || [[ $? == 1 ]] && true
} | xargs echo ${OBJECT_ID} } | xargs echo ${OBJECT_ID}
}""") % context) }""") % context)
def monitor(self, site): def monitor(self, site):
context = self.get_context(site) context = self.get_context(site)
self.append('monitor {object_id} "{last_date}" {log_file}'.format(**context)) self.append('monitor {object_id} "{last_date}" {log_file}'.format(**context))
def get_context(self, site): def get_context(self, site):
return { return {
'log_file': '%s{,.1}' % site.get_www_access_log_path(), 'log_file': '%s{,.1}' % site.get_www_access_log_path(),

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

@ -17,9 +17,9 @@ class SpanWidget(forms.Widget):
self.original = kwargs.pop('original', '') self.original = kwargs.pop('original', '')
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
@ -29,25 +29,25 @@ class SpanWidget(forms.Widget):
tag = self.tag[:-1] tag = self.tag[:-1]
endtag = '/'.join((self.tag[0], self.tag[1:])) endtag = '/'.join((self.tag[0], self.tag[1:]))
return mark_safe('%s%s >%s%s' % (tag, forms.utils.flatatt(final_attrs), display, endtag)) return mark_safe('%s%s >%s%s' % (tag, forms.utils.flatatt(final_attrs), display, endtag))
def value_from_datadict(self, data, files, name): def value_from_datadict(self, data, files, name):
return self.original return self.original
def _has_changed(self, initial, data): def _has_changed(self, initial, data):
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):
@ -61,7 +61,7 @@ class DynamicHelpTextSelect(forms.Select):
attrs.update(kwargs.get('attrs', {})) attrs.update(kwargs.get('attrs', {}))
kwargs['attrs'] = attrs kwargs['attrs'] = attrs
super(DynamicHelpTextSelect, self).__init__(*args, **kwargs) super(DynamicHelpTextSelect, self).__init__(*args, **kwargs)
def get_dynamic_help_text(self, target, help_text): def get_dynamic_help_text(self, target, help_text):
return textwrap.dedent("""\ return textwrap.dedent("""\
siteoptions = {help_text}; siteoptions = {help_text};

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):
@ -10,12 +9,9 @@ def render_email_template(template, context):
Renders an email template with this format: Renders an email template with this format:
{% 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