Merge pull request #8 from ribaguifi/dev/django2.1-admin
Refactor admin code to support Django 2.1
This commit is contained in:
commit
7b59931bcf
|
@ -5,7 +5,7 @@ from django import forms
|
|||
from django.contrib.admin import helpers
|
||||
from django.core import validators
|
||||
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 orchestra.forms.widgets import SpanWidget
|
||||
|
@ -28,9 +28,9 @@ class AdminFormMixin(object):
|
|||
' {% include "admin/includes/fieldset.html" %}'
|
||||
'{% endfor %}'
|
||||
)
|
||||
context = Context({
|
||||
context = {
|
||||
'adminform': adminform
|
||||
})
|
||||
}
|
||||
return template.render(context)
|
||||
|
||||
|
||||
|
@ -71,9 +71,9 @@ class AdminFormSet(BaseModelFormSet):
|
|||
</div>
|
||||
</div>""")
|
||||
)
|
||||
context = Context({
|
||||
context = {
|
||||
'formset': self
|
||||
})
|
||||
}
|
||||
return template.render(context)
|
||||
|
||||
|
||||
|
@ -93,7 +93,7 @@ class AdminPasswordChangeForm(forms.Form):
|
|||
required=False, validators=[validate_password])
|
||||
password2 = forms.CharField(label=_("Password (again)"), widget=forms.PasswordInput,
|
||||
required=False)
|
||||
|
||||
|
||||
def __init__(self, user, *args, **kwargs):
|
||||
self.related = kwargs.pop('related', [])
|
||||
self.raw = kwargs.pop('raw', False)
|
||||
|
@ -109,7 +109,7 @@ class AdminPasswordChangeForm(forms.Form):
|
|||
self.fields['password2_%i' % ix] = forms.CharField(label=_("Password (again)"),
|
||||
widget=forms.PasswordInput, required=False)
|
||||
setattr(self, 'clean_password2_%i' % ix, partial(self.clean_password2, ix=ix))
|
||||
|
||||
|
||||
def clean_password2(self, ix=''):
|
||||
if ix != '':
|
||||
ix = '_%i' % ix
|
||||
|
@ -129,7 +129,7 @@ class AdminPasswordChangeForm(forms.Form):
|
|||
code='password_mismatch',
|
||||
)
|
||||
return password2
|
||||
|
||||
|
||||
def clean_password(self, ix=''):
|
||||
if ix != '':
|
||||
ix = '_%i' % ix
|
||||
|
@ -146,14 +146,14 @@ class AdminPasswordChangeForm(forms.Form):
|
|||
code='bad_hash',
|
||||
)
|
||||
return password
|
||||
|
||||
|
||||
def clean(self):
|
||||
if not self.password_provided:
|
||||
raise forms.ValidationError(
|
||||
self.error_messages['password_missing'],
|
||||
code='password_missing',
|
||||
)
|
||||
|
||||
|
||||
def save(self, commit=True):
|
||||
"""
|
||||
Saves the new password.
|
||||
|
@ -182,7 +182,7 @@ class AdminPasswordChangeForm(forms.Form):
|
|||
if commit:
|
||||
rel.save(update_fields=['password'])
|
||||
return self.user
|
||||
|
||||
|
||||
def _get_changed_data(self):
|
||||
data = super().changed_data
|
||||
for name in self.fields.keys():
|
||||
|
@ -202,7 +202,7 @@ class SendEmailForm(forms.Form):
|
|||
widget=forms.TextInput(attrs={'size': '118'}))
|
||||
message = forms.CharField(label=_("Message"),
|
||||
widget=forms.Textarea(attrs={'cols': 118, 'rows': 15}))
|
||||
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
initial = kwargs.get('initial')
|
||||
|
@ -210,7 +210,7 @@ class SendEmailForm(forms.Form):
|
|||
self.fields['to'].widget = SpanWidget(original=initial['to'])
|
||||
else:
|
||||
self.fields.pop('to')
|
||||
|
||||
|
||||
def clean_comma_separated_emails(self, value):
|
||||
clean_value = []
|
||||
for email in value.split(','):
|
||||
|
@ -222,7 +222,7 @@ class SendEmailForm(forms.Form):
|
|||
raise validators.ValidationError("Comma separated email addresses.")
|
||||
clean_value.append(email)
|
||||
return clean_value
|
||||
|
||||
|
||||
def clean_extra_to(self):
|
||||
extra_to = self.cleaned_data['extra_to']
|
||||
return self.clean_comma_separated_emails(extra_to)
|
||||
|
|
|
@ -10,7 +10,7 @@ from django.urls import reverse, NoReverseMatch
|
|||
from django.db import models
|
||||
from django.shortcuts import redirect
|
||||
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 orchestra.models.utils import get_field_value
|
||||
|
@ -113,21 +113,21 @@ def admin_link(*args, **kwargs):
|
|||
return '---'
|
||||
if not getattr(obj, 'pk', None):
|
||||
return '---'
|
||||
display = kwargs.get('display')
|
||||
if display:
|
||||
display = getattr(obj, display, display)
|
||||
display_ = kwargs.get('display')
|
||||
if display_:
|
||||
display_ = getattr(obj, display_, display_)
|
||||
else:
|
||||
display = obj
|
||||
display_ = obj
|
||||
try:
|
||||
url = change_url(obj)
|
||||
except NoReverseMatch:
|
||||
# Does not has admin
|
||||
return str(display)
|
||||
return str(display_)
|
||||
extra = ''
|
||||
if kwargs['popup']:
|
||||
extra = 'onclick="return showAddAnotherPopup(this);"'
|
||||
extra = mark_safe('onclick="return showAddAnotherPopup(this);"')
|
||||
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
|
||||
|
@ -158,7 +158,7 @@ def admin_date(*args, **kwargs):
|
|||
date = date.strftime("%Y-%m-%d %H:%M:%S %Z")
|
||||
else:
|
||||
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):
|
||||
|
|
|
@ -175,7 +175,7 @@ def delete_related_services(modeladmin, request, queryset):
|
|||
for model, objs in collector.model_objs.items():
|
||||
count = 0
|
||||
# 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
|
||||
# Discount account
|
||||
elif model is not modeladmin.model and model in registered_services:
|
||||
|
|
|
@ -158,6 +158,7 @@ class AccountListAdmin(AccountAdmin):
|
|||
actions = None
|
||||
change_list_template = 'admin/accounts/account/select_account_list.html'
|
||||
|
||||
@mark_safe
|
||||
def select_account(self, instance):
|
||||
# TODO get query string from request.META['QUERY_STRING'] to preserve filters
|
||||
context = {
|
||||
|
@ -167,7 +168,6 @@ class AccountListAdmin(AccountAdmin):
|
|||
}
|
||||
return _('<a href="%(url)s">%(plus)s Add to %(name)s</a>') % context
|
||||
select_account.short_description = _("account")
|
||||
select_account.allow_tags = True
|
||||
select_account.admin_order_field = 'username'
|
||||
|
||||
def changelist_view(self, request, extra_context=None):
|
||||
|
@ -207,6 +207,7 @@ class AccountAdminMixin(object):
|
|||
account = None
|
||||
list_select_related = ('account',)
|
||||
|
||||
@mark_safe
|
||||
def display_active(self, instance):
|
||||
if not instance.is_active:
|
||||
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 src="%s" alt="False">' % static('admin/img/icon-yes.svg')
|
||||
display_active.short_description = _("active")
|
||||
display_active.allow_tags = True
|
||||
display_active.admin_order_field = 'is_active'
|
||||
|
||||
def account_link(self, instance):
|
||||
account = instance.account if instance.pk else self.account
|
||||
return admin_link()(account)
|
||||
account_link.short_description = _("account")
|
||||
account_link.allow_tags = True
|
||||
account_link.admin_order_field = 'account__username'
|
||||
|
||||
def get_form(self, request, obj=None, **kwargs):
|
||||
|
|
|
@ -47,7 +47,7 @@ def create_account_creation_form():
|
|||
# Previous validation error
|
||||
return
|
||||
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():
|
||||
errors['username'] = _("A system user with this name already exists.")
|
||||
for model, key, related_kwargs, __ in create_related:
|
||||
|
|
|
@ -7,6 +7,7 @@ from django.db import models
|
|||
from django.db.models import F, Sum, Prefetch
|
||||
from django.db.models.functions import Coalesce
|
||||
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_lazy as _
|
||||
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.contrib.accounts.actions import list_accounts
|
||||
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 .filters import (BillTypeListFilter, HasBillContactListFilter, TotalListFilter,
|
||||
|
@ -67,6 +68,7 @@ class BillLineInline(admin.TabularInline):
|
|||
|
||||
order_link = admin_link('order', display='pk')
|
||||
|
||||
@mark_safe
|
||||
def display_total(self, line):
|
||||
if line.pk:
|
||||
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">%s</a>' % (url, total)
|
||||
display_total.short_description = _("Total")
|
||||
display_total.allow_tags = True
|
||||
|
||||
def formfield_for_dbfield(self, db_field, **kwargs):
|
||||
""" Make value input widget bigger """
|
||||
|
@ -104,27 +105,26 @@ class ClosedBillLineInline(BillLineInline):
|
|||
readonly_fields = fields
|
||||
can_delete = False
|
||||
|
||||
@mark_safe
|
||||
def display_description(self, line):
|
||||
descriptions = [line.description]
|
||||
for subline in line.sublines.all():
|
||||
descriptions.append(' '*4+subline.description)
|
||||
descriptions.append(' ' * 4 + subline.description)
|
||||
return '<br>'.join(descriptions)
|
||||
display_description.short_description = _("Description")
|
||||
display_description.allow_tags = True
|
||||
|
||||
@mark_safe
|
||||
def display_subtotal(self, line):
|
||||
subtotals = [' ' + str(line.subtotal)]
|
||||
for subline in line.sublines.all():
|
||||
subtotals.append(str(subline.total))
|
||||
return '<br>'.join(subtotals)
|
||||
display_subtotal.short_description = _("Subtotal")
|
||||
display_subtotal.allow_tags = True
|
||||
|
||||
def display_total(self, line):
|
||||
if line.pk:
|
||||
return line.compute_total()
|
||||
display_total.short_description = _("Total")
|
||||
display_total.allow_tags = True
|
||||
|
||||
def has_add_permission(self, request):
|
||||
return False
|
||||
|
@ -242,6 +242,7 @@ class BillLineManagerAdmin(BillLineAdmin):
|
|||
|
||||
|
||||
class BillAdminMixin(AccountAdminMixin):
|
||||
@mark_safe
|
||||
def display_total_with_subtotals(self, bill):
|
||||
if bill.pk:
|
||||
currency = settings.BILLS_CURRENCY.lower()
|
||||
|
@ -251,10 +252,10 @@ class BillAdminMixin(AccountAdminMixin):
|
|||
subtotals.append(_("Taxes %s%% VAT %s &%s;") % (tax, subtotal[1], currency))
|
||||
subtotals = '\n'.join(subtotals)
|
||||
return '<span title="%s">%s &%s;</span>' % (subtotals, bill.compute_total(), currency)
|
||||
display_total_with_subtotals.allow_tags = True
|
||||
display_total_with_subtotals.short_description = _("total")
|
||||
display_total_with_subtotals.admin_order_field = 'approx_total'
|
||||
|
||||
@mark_safe
|
||||
def display_payment_state(self, bill):
|
||||
if bill.pk:
|
||||
t_opts = bill.transactions.model._meta
|
||||
|
@ -276,7 +277,6 @@ class BillAdminMixin(AccountAdminMixin):
|
|||
color = PAYMENT_STATE_COLORS.get(bill.payment_state, 'grey')
|
||||
return '<a href="{url}" style="color:{color}" title="{title}">{name}</a>'.format(
|
||||
url=url, color=color, name=state, title=title)
|
||||
display_payment_state.allow_tags = True
|
||||
display_payment_state.short_description = _("Payment")
|
||||
|
||||
def get_queryset(self, request):
|
||||
|
@ -376,16 +376,14 @@ class BillAdmin(BillAdminMixin, ExtendedModelAdmin):
|
|||
|
||||
def display_total(self, bill):
|
||||
currency = settings.BILLS_CURRENCY.lower()
|
||||
return '%s &%s;' % (bill.compute_total(), currency)
|
||||
display_total.allow_tags = True
|
||||
return format_html('{} &{};', bill.compute_total(), currency)
|
||||
display_total.short_description = _("total")
|
||||
display_total.admin_order_field = 'approx_total'
|
||||
|
||||
def type_link(self, bill):
|
||||
bill_type = bill.type.lower()
|
||||
url = reverse('admin:bills_%s_changelist' % bill_type)
|
||||
return '<a href="%s">%s</a>' % (url, bill.get_type_display())
|
||||
type_link.allow_tags = True
|
||||
return format_html('<a href="{}">{}</a>', url, bill.get_type_display())
|
||||
type_link.short_description = _("type")
|
||||
type_link.admin_order_field = 'type'
|
||||
|
||||
|
@ -479,7 +477,7 @@ class BillContactInline(admin.StackedInline):
|
|||
if db_field.name == 'address':
|
||||
kwargs['widget'] = forms.Textarea(attrs={'cols': 70, 'rows': 2})
|
||||
if db_field.name == 'email_usage':
|
||||
kwargs['widget'] = paddingCheckboxSelectMultiple(45)
|
||||
kwargs['widget'] = PaddingCheckboxSelectMultiple(45)
|
||||
return super().formfield_for_dbfield(db_field, **kwargs)
|
||||
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ def validate_contact(request, bill, error=True):
|
|||
message = msg.format(relation=_("Related"), account=account, url=url)
|
||||
send(request, mark_safe(message))
|
||||
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'):
|
||||
account = force_text(main)
|
||||
url = reverse('admin:accounts_account_change', args=(main.id,))
|
||||
|
|
|
@ -6,7 +6,7 @@ from django.core.validators import ValidationError, RegexValidator
|
|||
from django.db import models
|
||||
from django.db.models import F, Sum
|
||||
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.encoding import force_text
|
||||
from django.utils.functional import cached_property
|
||||
|
@ -303,7 +303,7 @@ class Bill(models.Model):
|
|||
with translation.override(language or self.account.language):
|
||||
if payment is False:
|
||||
payment = self.account.paymentsources.get_default()
|
||||
context = Context({
|
||||
context = {
|
||||
'bill': self,
|
||||
'lines': self.lines.all().prefetch_related('sublines'),
|
||||
'seller': self.seller,
|
||||
|
@ -318,7 +318,7 @@ class Bill(models.Model):
|
|||
'payment': payment and payment.get_bill_context(),
|
||||
'default_due_date': self.get_due_date(payment=payment),
|
||||
'now': timezone.now(),
|
||||
})
|
||||
}
|
||||
template_name = 'BILLS_%s_TEMPLATE' % self.get_type()
|
||||
template = getattr(settings, template_name, settings.BILLS_DEFAULT_TEMPLATE)
|
||||
bill_template = loader.get_template(template)
|
||||
|
|
|
@ -7,7 +7,7 @@ from orchestra.admin.actions import SendEmail
|
|||
from orchestra.admin.utils import insertattr, change_url
|
||||
from orchestra.contrib.accounts.actions import list_accounts
|
||||
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 .models import Contact
|
||||
|
@ -61,18 +61,18 @@ class ContactAdmin(AccountAdminMixin, ExtendedModelAdmin):
|
|||
}),
|
||||
)
|
||||
actions = (SendEmail(), list_accounts)
|
||||
|
||||
|
||||
def dispaly_name(self, contact):
|
||||
return str(contact)
|
||||
dispaly_name.short_description = _("Name")
|
||||
dispaly_name.admin_order_field = 'short_name'
|
||||
|
||||
|
||||
def formfield_for_dbfield(self, db_field, **kwargs):
|
||||
""" Make value input widget bigger """
|
||||
if db_field.name == 'address':
|
||||
kwargs['widget'] = forms.Textarea(attrs={'cols': 70, 'rows': 2})
|
||||
if db_field.name == 'email_usage':
|
||||
kwargs['widget'] = paddingCheckboxSelectMultiple(130)
|
||||
kwargs['widget'] = PaddingCheckboxSelectMultiple(130)
|
||||
return super(ContactAdmin, self).formfield_for_dbfield(db_field, **kwargs)
|
||||
|
||||
|
||||
|
@ -86,14 +86,14 @@ class ContactInline(admin.StackedInline):
|
|||
fields = (
|
||||
('short_name', 'full_name'), 'email', 'email_usage', ('phone', 'phone2'),
|
||||
)
|
||||
|
||||
|
||||
def get_extra(self, request, obj=None, **kwargs):
|
||||
return 0 if obj and obj.contacts.exists() else 1
|
||||
|
||||
|
||||
def get_view_on_site_url(self, obj=None):
|
||||
if obj:
|
||||
return change_url(obj)
|
||||
|
||||
|
||||
def formfield_for_dbfield(self, db_field, **kwargs):
|
||||
""" Make value input widget bigger """
|
||||
if db_field.name == 'short_name':
|
||||
|
@ -101,7 +101,7 @@ class ContactInline(admin.StackedInline):
|
|||
if db_field.name == 'address':
|
||||
kwargs['widget'] = forms.Textarea(attrs={'cols': 70, 'rows': 2})
|
||||
if db_field.name == 'email_usage':
|
||||
kwargs['widget'] = paddingCheckboxSelectMultiple(45)
|
||||
kwargs['widget'] = PaddingCheckboxSelectMultiple(45)
|
||||
return super(ContactInline, self).formfield_for_dbfield(db_field, **kwargs)
|
||||
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
from django.conf.urls import url
|
||||
from django.contrib import admin
|
||||
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 orchestra.admin import ExtendedModelAdmin, ChangePasswordAdminMixin
|
||||
|
@ -49,17 +51,17 @@ class DatabaseAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
|
|||
filter_by_account_fields = ('users',)
|
||||
list_prefetch_related = ('users',)
|
||||
actions = (list_accounts, save_selected)
|
||||
|
||||
|
||||
@mark_safe
|
||||
def display_users(self, db):
|
||||
links = []
|
||||
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)
|
||||
return '<br>'.join(links)
|
||||
display_users.short_description = _("Users")
|
||||
display_users.allow_tags = True
|
||||
display_users.admin_order_field = 'users__username'
|
||||
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
super(DatabaseAdmin, self).save_model(request, obj, form, change)
|
||||
if not change:
|
||||
|
@ -98,24 +100,24 @@ class DatabaseUserAdmin(SelectAccountAdminMixin, ChangePasswordAdminMixin, Exten
|
|||
filter_by_account_fields = ('databases',)
|
||||
list_prefetch_related = ('databases',)
|
||||
actions = (list_accounts, save_selected)
|
||||
|
||||
|
||||
@mark_safe
|
||||
def display_databases(self, user):
|
||||
links = []
|
||||
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)
|
||||
return '<br>'.join(links)
|
||||
display_databases.short_description = _("Databases")
|
||||
display_databases.allow_tags = True
|
||||
display_databases.admin_order_field = 'databases__name'
|
||||
|
||||
|
||||
def get_urls(self):
|
||||
useradmin = UserAdmin(DatabaseUser, self.admin_site)
|
||||
return [
|
||||
url(r'^(\d+)/password/$',
|
||||
self.admin_site.admin_view(useradmin.user_change_password))
|
||||
] + super(DatabaseUserAdmin, self).get_urls()
|
||||
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
""" set password """
|
||||
if not change:
|
||||
|
|
|
@ -17,11 +17,11 @@ class DatabaseUserCreationForm(forms.ModelForm):
|
|||
password2 = forms.CharField(label=_("Password confirmation"), required=False,
|
||||
widget=forms.PasswordInput,
|
||||
help_text=_("Enter the same password as above, for verification."))
|
||||
|
||||
|
||||
class Meta:
|
||||
model = DatabaseUser
|
||||
fields = ('username', 'account', 'type')
|
||||
|
||||
|
||||
def clean_password2(self):
|
||||
password1 = self.cleaned_data.get("password1")
|
||||
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 "
|
||||
"@/./+/-/_ characters.")})
|
||||
user = forms.ModelChoiceField(required=False, queryset=DatabaseUser.objects)
|
||||
|
||||
|
||||
class Meta:
|
||||
model = Database
|
||||
fields = ('username', 'account', 'type')
|
||||
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(DatabaseCreationForm, self).__init__(*args, **kwargs)
|
||||
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 ]
|
||||
self.fields['user'].queryset = qs
|
||||
self.fields['user'].choices = [(None, '--------'),] + choices
|
||||
|
||||
|
||||
def clean_username(self):
|
||||
username = self.cleaned_data.get('username')
|
||||
if DatabaseUser.objects.filter(username=username).exists():
|
||||
raise ValidationError("Provided username already exists.")
|
||||
return username
|
||||
|
||||
|
||||
def clean_password2(self):
|
||||
username = self.cleaned_data.get('username')
|
||||
password1 = self.cleaned_data.get('password1')
|
||||
|
@ -70,14 +70,14 @@ class DatabaseCreationForm(DatabaseUserCreationForm):
|
|||
msg = _("The two password fields didn't match.")
|
||||
raise ValidationError(msg)
|
||||
return password2
|
||||
|
||||
|
||||
def clean_user(self):
|
||||
user = self.cleaned_data.get('user')
|
||||
if user and user.type != self.cleaned_data.get('type'):
|
||||
msg = _("Database type and user type doesn't match")
|
||||
raise ValidationError(msg)
|
||||
return user
|
||||
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super(DatabaseCreationForm, self).clean()
|
||||
if 'user' in cleaned_data and 'username' in cleaned_data:
|
||||
|
@ -91,7 +91,7 @@ class DatabaseCreationForm(DatabaseUserCreationForm):
|
|||
|
||||
class ReadOnlySQLPasswordHashField(ReadOnlyPasswordHashField):
|
||||
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)
|
||||
if 'Invalid' not in original:
|
||||
return original
|
||||
|
@ -114,10 +114,10 @@ class DatabaseUserChangeForm(forms.ModelForm):
|
|||
"this user's password, but you can change the password "
|
||||
"using <a href='../password/'>this form</a>. "
|
||||
"<a onclick='return showAddAnotherPopup(this);' href='../hash/'>Show hash</a>."))
|
||||
|
||||
|
||||
class Meta:
|
||||
model = DatabaseUser
|
||||
fields = ('username', 'password', 'type', 'account')
|
||||
|
||||
|
||||
def clean_password(self):
|
||||
return self.initial["password"]
|
||||
|
|
|
@ -3,6 +3,8 @@ from django.urls import reverse
|
|||
from django.db import models
|
||||
from django.db.models.functions import Concat, Coalesce
|
||||
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 orchestra.admin import ExtendedModelAdmin
|
||||
|
@ -72,9 +74,8 @@ class DomainAdmin(AccountAdminMixin, ExtendedModelAdmin):
|
|||
def structured_name(self, domain):
|
||||
if domain.is_top:
|
||||
return domain.name
|
||||
return ' '*4 + domain.name
|
||||
return mark_safe(' '*4 + domain.name)
|
||||
structured_name.short_description = _("name")
|
||||
structured_name.allow_tags = True
|
||||
structured_name.admin_order_field = 'structured_name'
|
||||
|
||||
def display_is_top(self, domain):
|
||||
|
@ -83,6 +84,7 @@ class DomainAdmin(AccountAdminMixin, ExtendedModelAdmin):
|
|||
display_is_top.boolean = True
|
||||
display_is_top.admin_order_field = 'top'
|
||||
|
||||
@mark_safe
|
||||
def display_websites(self, domain):
|
||||
if apps.isinstalled('orchestra.contrib.websites'):
|
||||
websites = domain.websites.all()
|
||||
|
@ -92,22 +94,22 @@ class DomainAdmin(AccountAdminMixin, ExtendedModelAdmin):
|
|||
site_link = get_on_site_link(website.get_absolute_url())
|
||||
admin_url = change_url(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)
|
||||
links.append(link)
|
||||
return '<br>'.join(links)
|
||||
add_url = reverse('admin:websites_website_add')
|
||||
add_url += '?account=%i&domains=%i' % (domain.account_id, domain.pk)
|
||||
image = '<img src="%s"></img>' % static('orchestra/images/add.png')
|
||||
add_link = '<a href="%s" title="%s">%s</a>' % (
|
||||
add_url, _("Add website"), image
|
||||
add_link = format_html(
|
||||
'<a href="{}" title="{}"><img src="{}" /></a>', add_url,
|
||||
_("Add website"), static('orchestra/images/add.png'),
|
||||
)
|
||||
return _("No website %s") % (add_link)
|
||||
return '---'
|
||||
display_websites.admin_order_field = 'websites__name'
|
||||
display_websites.short_description = _("Websites")
|
||||
display_websites.allow_tags = True
|
||||
|
||||
@mark_safe
|
||||
def display_addresses(self, domain):
|
||||
if apps.isinstalled('orchestra.contrib.mailboxes'):
|
||||
add_url = reverse('admin:mailboxes_address_add')
|
||||
|
@ -126,10 +128,9 @@ class DomainAdmin(AccountAdminMixin, ExtendedModelAdmin):
|
|||
return '---'
|
||||
display_addresses.short_description = _("Addresses")
|
||||
display_addresses.admin_order_field = 'addresses__count'
|
||||
display_addresses.allow_tags = True
|
||||
|
||||
@mark_safe
|
||||
def implicit_records(self, domain):
|
||||
defaults = []
|
||||
types = set(domain.records.values_list('type', flat=True))
|
||||
ttl = settings.DOMAINS_DEFAULT_TTL
|
||||
lines = []
|
||||
|
@ -141,14 +142,13 @@ class DomainAdmin(AccountAdminMixin, ExtendedModelAdmin):
|
|||
value=record.value
|
||||
)
|
||||
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:
|
||||
lines.insert(0, line)
|
||||
else:
|
||||
lines.append(line)
|
||||
return '<br>'.join(lines)
|
||||
implicit_records.short_description = _("Implicit records")
|
||||
implicit_records.allow_tags = True
|
||||
|
||||
def get_fieldsets(self, request, obj=None):
|
||||
""" Add SOA fields when domain is top """
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
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_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):
|
||||
|
@ -34,11 +36,12 @@ class LogEntryAdmin(admin.ModelAdmin):
|
|||
user_link = admin_link('user')
|
||||
display_action_time = admin_date('action_time', short_description=_("Time"))
|
||||
|
||||
@mark_safe
|
||||
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,)),
|
||||
'img': static('admin/img/icon-changelink.svg'),
|
||||
}
|
||||
})
|
||||
if log.is_addition():
|
||||
return _('Added "%(link)s". %(edit)s') % {
|
||||
'link': self.content_object_link(log),
|
||||
|
@ -57,7 +60,6 @@ class LogEntryAdmin(admin.ModelAdmin):
|
|||
}
|
||||
display_message.short_description = _("Message")
|
||||
display_message.admin_order_field = 'action_flag'
|
||||
display_message.allow_tags = True
|
||||
|
||||
def display_action(self, log):
|
||||
if log.is_addition():
|
||||
|
@ -75,10 +77,9 @@ class LogEntryAdmin(admin.ModelAdmin):
|
|||
url = reverse(view, args=(log.object_id,))
|
||||
except NoReverseMatch:
|
||||
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.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):
|
||||
""" Add rel_opts and object to context """
|
||||
|
|
|
@ -5,7 +5,8 @@ from django.urls import reverse
|
|||
from django.db import models
|
||||
from django.http import HttpResponse
|
||||
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 markdown import markdown
|
||||
|
||||
|
@ -50,6 +51,7 @@ class MessageReadOnlyInline(admin.TabularInline):
|
|||
'all': ('orchestra/css/hide-inline-id.css',)
|
||||
}
|
||||
|
||||
@mark_safe
|
||||
def content_html(self, msg):
|
||||
context = {
|
||||
'number': msg.number,
|
||||
|
@ -58,12 +60,13 @@ class MessageReadOnlyInline(admin.TabularInline):
|
|||
}
|
||||
summary = _("#%(number)i Updated by %(author)s about %(time)s") % context
|
||||
header = '<strong style="color:#666;">%s</strong><hr />' % summary
|
||||
|
||||
content = markdown(msg.content)
|
||||
content = content.replace('>\n', '>')
|
||||
content = '<div style="padding-left:20px;">%s</div>' % content
|
||||
|
||||
return header + content
|
||||
content_html.short_description = _("Content")
|
||||
content_html.allow_tags = True
|
||||
|
||||
def has_add_permission(self, request):
|
||||
return False
|
||||
|
@ -111,10 +114,10 @@ class TicketInline(admin.TabularInline):
|
|||
colored_state = admin_colored('state', colors=STATE_COLORS, bold=False)
|
||||
colored_priority = admin_colored('priority', colors=PRIORITY_COLORS, bold=False)
|
||||
|
||||
@mark_safe
|
||||
def ticket_id(self, instance):
|
||||
return '<b>%s</b>' % admin_link()(instance)
|
||||
ticket_id.short_description = '#'
|
||||
ticket_id.allow_tags = True
|
||||
|
||||
|
||||
class TicketAdmin(ExtendedModelAdmin):
|
||||
|
@ -192,6 +195,7 @@ class TicketAdmin(ExtendedModelAdmin):
|
|||
display_state = admin_colored('state', colors=STATE_COLORS, bold=False)
|
||||
display_priority = admin_colored('priority', colors=PRIORITY_COLORS, bold=False)
|
||||
|
||||
@mark_safe
|
||||
def display_summary(self, ticket):
|
||||
context = {
|
||||
'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
|
||||
return '<h4>Added by %(creator)s about %(created)s%(updated)s</h4>' % context
|
||||
display_summary.short_description = 'Summary'
|
||||
display_summary.allow_tags = True
|
||||
|
||||
def unbold_id(self, ticket):
|
||||
""" Unbold id if ticket is read """
|
||||
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
|
||||
unbold_id.allow_tags = True
|
||||
unbold_id.short_description = "#"
|
||||
unbold_id.admin_order_field = 'id'
|
||||
|
||||
|
@ -222,8 +224,7 @@ class TicketAdmin(ExtendedModelAdmin):
|
|||
""" Bold subject when tickets are unread for request.user """
|
||||
if ticket.is_read_by(self.user):
|
||||
return ticket.subject
|
||||
return "<strong class='unread'>%s</strong>" % ticket.subject
|
||||
bold_subject.allow_tags = True
|
||||
return format_html("<strong class='unread'>{}</strong>", ticket.subject)
|
||||
bold_subject.short_description = _("Subject")
|
||||
bold_subject.admin_order_field = 'subject'
|
||||
|
||||
|
@ -297,10 +298,9 @@ class QueueAdmin(admin.ModelAdmin):
|
|||
num = queue.tickets__count
|
||||
url = reverse('admin:issues_ticket_changelist')
|
||||
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.admin_order_field = 'tickets__count'
|
||||
num_tickets.allow_tags = True
|
||||
|
||||
def get_list_display(self, request):
|
||||
""" show notifications """
|
||||
|
|
|
@ -13,7 +13,7 @@ from .models import Queue, Ticket
|
|||
|
||||
class MarkDownWidget(forms.Textarea):
|
||||
""" MarkDown textarea widget with syntax preview """
|
||||
|
||||
|
||||
markdown_url = static('issues/markdown_syntax.html')
|
||||
markdown_help_text = (
|
||||
'<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)
|
||||
)
|
||||
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
|
||||
textarea = super(MarkDownWidget, self).render(name, value, attrs)
|
||||
preview = ('<a class="load-preview" href="#" data-field="{0}">preview</a>'\
|
||||
|
@ -35,18 +35,18 @@ class MessageInlineForm(forms.ModelForm):
|
|||
""" Add message form """
|
||||
created_on = forms.CharField(label="Created On", required=False)
|
||||
content = forms.CharField(widget=MarkDownWidget(), required=False)
|
||||
|
||||
|
||||
class Meta:
|
||||
fields = ('author', 'author_name', 'created_on', 'content')
|
||||
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(MessageInlineForm, self).__init__(*args, **kwargs)
|
||||
self.fields['created_on'].widget = SpanWidget(display='')
|
||||
|
||||
|
||||
def clean_content(self):
|
||||
""" clean HTML tags """
|
||||
return strip_tags(self.cleaned_data['content'])
|
||||
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if self.instance.pk is None:
|
||||
self.instance.author = self.user
|
||||
|
@ -58,7 +58,7 @@ class UsersIterator(forms.models.ModelChoiceIterator):
|
|||
def __init__(self, *args, **kwargs):
|
||||
self.ticket = kwargs.pop('ticket', False)
|
||||
super(forms.models.ModelChoiceIterator, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
def __iter__(self):
|
||||
yield ('', '---------')
|
||||
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):
|
||||
display_description = forms.CharField(label=_("Description"), required=False)
|
||||
description = forms.CharField(widget=MarkDownWidget(attrs={'class':'vLargeTextField'}))
|
||||
|
||||
|
||||
class Meta:
|
||||
model = Ticket
|
||||
fields = (
|
||||
'creator', 'creator_name', 'owner', 'queue', 'subject', 'description',
|
||||
'priority', 'state', 'cc', 'display_description'
|
||||
)
|
||||
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(TicketForm, self).__init__(*args, **kwargs)
|
||||
ticket = kwargs.get('instance', False)
|
||||
|
@ -101,7 +101,7 @@ class TicketForm(forms.ModelForm):
|
|||
description = '<div style="padding-left: 95px;">%s</div>' % description
|
||||
widget = SpanWidget(display=description)
|
||||
self.fields['display_description'].widget = widget
|
||||
|
||||
|
||||
def clean_description(self):
|
||||
""" clean HTML tags """
|
||||
return strip_tags(self.cleaned_data['description'])
|
||||
|
|
|
@ -12,7 +12,7 @@ from .models import List
|
|||
|
||||
class RelatedDomainSerializer(AccountSerializerMixin, RelatedHyperlinkedModelSerializer):
|
||||
class Meta:
|
||||
model = List.address_domain.field.model
|
||||
model = List.address_domain.field.related_model
|
||||
fields = ('url', 'id', 'name')
|
||||
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ from django.contrib import admin, messages
|
|||
from django.urls import reverse
|
||||
from django.db.models import F, Count, Value as V
|
||||
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.translation import ugettext_lazy as _
|
||||
|
||||
|
@ -82,6 +83,7 @@ class MailboxAdmin(ChangePasswordAdminMixin, SelectAccountAdminMixin, ExtendedMo
|
|||
if settings.MAILBOXES_LOCAL_DOMAIN:
|
||||
type(self).actions = self.actions + (SendMailboxEmail(),)
|
||||
|
||||
@mark_safe
|
||||
def display_addresses(self, mailbox):
|
||||
# Get from forwards
|
||||
cache = caches.get_request_cache()
|
||||
|
@ -93,7 +95,7 @@ class MailboxAdmin(ChangePasswordAdminMixin, SelectAccountAdminMixin, ExtendedMo
|
|||
qs = qs.values_list('id', 'email', 'forward')
|
||||
for addr_id, email, mbox in qs:
|
||||
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:
|
||||
cached_forwards[mbox].append(link)
|
||||
except KeyError:
|
||||
|
@ -107,26 +109,23 @@ class MailboxAdmin(ChangePasswordAdminMixin, SelectAccountAdminMixin, ExtendedMo
|
|||
addresses = []
|
||||
for addr in mailbox.addresses.all():
|
||||
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)
|
||||
display_addresses.short_description = _("Addresses")
|
||||
display_addresses.allow_tags = True
|
||||
|
||||
def display_forwards(self, mailbox):
|
||||
forwards = []
|
||||
for addr in mailbox.get_forwards():
|
||||
url = change_url(addr)
|
||||
forwards.append('<a href="%s">%s</a>' % (url, addr.email))
|
||||
return '<br>'.join(forwards)
|
||||
forwards = mailbox.get_forwards()
|
||||
return format_html_join(
|
||||
'<br>', '<a href="{}">{}</a>',
|
||||
[(change_url(addr), addr.email) for addr in forwards]
|
||||
)
|
||||
display_forwards.short_description = _("Forward from")
|
||||
display_forwards.allow_tags = True
|
||||
|
||||
@mark_safe
|
||||
def display_filtering(self, mailbox):
|
||||
""" becacuse of allow_tags = True """
|
||||
return mailbox.get_filtering_display()
|
||||
display_filtering.short_description = _("Filtering")
|
||||
display_filtering.admin_order_field = 'filtering'
|
||||
display_filtering.allow_tags = True
|
||||
|
||||
def formfield_for_dbfield(self, db_field, **kwargs):
|
||||
if db_field.name == 'filtering':
|
||||
|
@ -217,7 +216,7 @@ class MailboxAdmin(ChangePasswordAdminMixin, SelectAccountAdminMixin, ExtendedMo
|
|||
elif obj.custom_filtering:
|
||||
messages.warning(request, msg)
|
||||
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):
|
||||
|
@ -247,29 +246,27 @@ class AddressAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
|
|||
|
||||
def email_link(self, 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.allow_tags = True
|
||||
|
||||
def display_mailboxes(self, address):
|
||||
boxes = []
|
||||
for mailbox in address.mailboxes.all():
|
||||
url = change_url(mailbox)
|
||||
boxes.append('<a href="%s">%s</a>' % (url, mailbox.name))
|
||||
return '<br>'.join(boxes)
|
||||
boxes = address.mailboxes.all()
|
||||
return format_html_join(
|
||||
'<br>', '<a href="{}">{}</a>',
|
||||
[(change_url(mailbox), mailbox.name) for mailbox in boxes]
|
||||
)
|
||||
display_mailboxes.short_description = _("Mailboxes")
|
||||
display_mailboxes.allow_tags = True
|
||||
display_mailboxes.admin_order_field = 'mailboxes__count'
|
||||
|
||||
def display_all_mailboxes(self, address):
|
||||
boxes = []
|
||||
for mailbox in address.get_mailboxes():
|
||||
url = change_url(mailbox)
|
||||
boxes.append('<a href="%s">%s</a>' % (url, mailbox.name))
|
||||
return '<br>'.join(boxes)
|
||||
boxes = address.get_mailboxes()
|
||||
return format_html_join(
|
||||
'<br>', '<a href="{}">{}</a>',
|
||||
[(change_url(mailbox), mailbox.name) for mailbox in boxes]
|
||||
)
|
||||
display_all_mailboxes.short_description = _("Mailboxes links")
|
||||
display_all_mailboxes.allow_tags = True
|
||||
|
||||
@mark_safe
|
||||
def display_forward(self, address):
|
||||
forward_mailboxes = {m.name: m for m in address.get_forward_mailboxes()}
|
||||
values = []
|
||||
|
@ -281,7 +278,6 @@ class AddressAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
|
|||
values.append(forward)
|
||||
return '<br>'.join(values)
|
||||
display_forward.short_description = _("Forward")
|
||||
display_forward.allow_tags = True
|
||||
display_forward.admin_order_field = 'forward'
|
||||
|
||||
def formfield_for_dbfield(self, db_field, **kwargs):
|
||||
|
|
|
@ -44,7 +44,7 @@ class Mailbox(models.Model):
|
|||
def active(self):
|
||||
try:
|
||||
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
|
||||
|
||||
def disable(self):
|
||||
|
|
|
@ -6,6 +6,8 @@ from django.contrib import admin
|
|||
from django.urls import reverse
|
||||
from django.db.models import Count
|
||||
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 orchestra.admin import ExtendedModelAdmin
|
||||
|
@ -60,11 +62,10 @@ class MessageAdmin(ExtendedModelAdmin):
|
|||
def display_subject(self, instance):
|
||||
subject = instance.subject
|
||||
if len(subject) > 64:
|
||||
return subject[:64] + '…'
|
||||
return mark_safe(subject[:64] + '…')
|
||||
return subject
|
||||
display_subject.short_description = _("Subject")
|
||||
display_subject.admin_order_field = 'subject'
|
||||
display_subject.allow_tags = True
|
||||
|
||||
def display_retries(self, instance):
|
||||
num_logs = instance.logs__count
|
||||
|
@ -74,10 +75,9 @@ class MessageAdmin(ExtendedModelAdmin):
|
|||
else:
|
||||
url = reverse('admin:mailer_smtplog_changelist')
|
||||
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.admin_order_field = 'retries'
|
||||
display_retries.allow_tags = True
|
||||
|
||||
def display_content(self, instance):
|
||||
part = email.message_from_string(instance.content)
|
||||
|
@ -99,9 +99,8 @@ class MessageAdmin(ExtendedModelAdmin):
|
|||
payload = payload.decode(charset)
|
||||
if part.get_content_type() == 'text/plain':
|
||||
payload = payload.replace('\n', '<br>').replace(' ', ' ')
|
||||
return payload
|
||||
return mark_safe(payload)
|
||||
display_content.short_description = _("Content")
|
||||
display_content.allow_tags = True
|
||||
|
||||
def display_full_subject(self, instance):
|
||||
return instance.subject
|
||||
|
|
|
@ -2,6 +2,7 @@ from django import forms
|
|||
from django.contrib import admin
|
||||
from django.urls import reverse
|
||||
from django.db import models
|
||||
from django.utils.html import format_html
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
@ -38,15 +39,13 @@ class MiscServiceAdmin(ExtendedModelAdmin):
|
|||
actions = (disable, enable)
|
||||
|
||||
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.allow_tags = True
|
||||
display_name.admin_order_field = 'name'
|
||||
|
||||
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.allow_tags = True
|
||||
display_verbose_name.admin_order_field = 'verbose_name'
|
||||
|
||||
def num_instances(self, misc):
|
||||
|
|
|
@ -51,19 +51,18 @@ class RouteAdmin(ExtendedModelAdmin):
|
|||
|
||||
def display_model(self, route):
|
||||
try:
|
||||
return escape(route.backend_class.model)
|
||||
return route.backend_class.model
|
||||
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.allow_tags = True
|
||||
|
||||
@mark_safe
|
||||
def display_actions(self, route):
|
||||
try:
|
||||
return '<br>'.join(route.backend_class.get_actions())
|
||||
except KeyError:
|
||||
return "<span style='color: red;'>NOT AVAILABLE</span>"
|
||||
display_actions.short_description = _("actions")
|
||||
display_actions.allow_tags = True
|
||||
|
||||
def formfield_for_dbfield(self, db_field, **kwargs):
|
||||
""" 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(
|
||||
(escape(operation.content_type), escape(operation.object_id))))
|
||||
return link
|
||||
instance_link.allow_tags = True
|
||||
instance_link.short_description = _("Instance")
|
||||
|
||||
def has_add_permission(self, *args, **kwargs):
|
||||
|
@ -179,14 +177,12 @@ class ServerAdmin(ExtendedModelAdmin):
|
|||
change_view_actions = actions
|
||||
|
||||
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.allow_tags = True
|
||||
|
||||
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.allow_tags = True
|
||||
|
||||
def get_queryset(self, request):
|
||||
""" Order by structured name and imporve performance """
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from django import forms
|
||||
|
||||
from orchestra.forms.widgets import SpanWidget, paddingCheckboxSelectMultiple
|
||||
from orchestra.forms.widgets import SpanWidget, PaddingCheckboxSelectMultiple
|
||||
|
||||
|
||||
class RouteForm(forms.ModelForm):
|
||||
|
@ -16,5 +16,5 @@ class RouteForm(forms.ModelForm):
|
|||
else:
|
||||
self.fields['backend'].widget = SpanWidget()
|
||||
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)
|
||||
|
|
|
@ -51,8 +51,9 @@ class Server(models.Model):
|
|||
|
||||
def clean(self):
|
||||
self.name = self.name.strip()
|
||||
self.address = self.address.strip()
|
||||
if self.name and not self.address:
|
||||
if self.address:
|
||||
self.address = self.address.strip()
|
||||
elif self.name:
|
||||
validate = OrValidator(validate_ip_address, validate_hostname)
|
||||
validate_hostname(self.name)
|
||||
try:
|
||||
|
|
|
@ -14,7 +14,12 @@ def retrieve_state(servers):
|
|||
state = {}
|
||||
for server, ping, uptime in zip(servers, pings, uptimes):
|
||||
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'):
|
||||
ping = '%s ms' % ping.split('/')[4]
|
||||
else:
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
from datetime import datetime
|
||||
from django import forms
|
||||
from django.contrib import admin
|
||||
from django.urls import reverse, NoReverseMatch
|
||||
from django.db.models import Prefetch
|
||||
from django.utils import timezone
|
||||
from django.utils.html import escape
|
||||
from django.utils.html import escape, format_html
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
@ -112,9 +113,8 @@ class OrderAdmin(AccountAdminMixin, ExtendedModelAdmin):
|
|||
display_cancelled_on = admin_date('cancelled_on')
|
||||
|
||||
def display_description(self, order):
|
||||
return order.description[:64]
|
||||
return format_html(order.description[:64])
|
||||
display_description.short_description = _("Description")
|
||||
display_description.allow_tags = True
|
||||
display_description.admin_order_field = 'description'
|
||||
|
||||
def content_object_link(self, order):
|
||||
|
@ -125,13 +125,13 @@ class OrderAdmin(AccountAdminMixin, ExtendedModelAdmin):
|
|||
# Does not has admin
|
||||
return order.content_object_repr
|
||||
description = str(order.content_object)
|
||||
return '<a href="{url}">{description}</a>'.format(
|
||||
return format_html('<a href="{url}">{description}</a>',
|
||||
url=url, description=description)
|
||||
return order.content_object_repr
|
||||
content_object_link.short_description = _("Content object")
|
||||
content_object_link.allow_tags = True
|
||||
content_object_link.admin_order_field = 'content_object_repr'
|
||||
|
||||
@mark_safe
|
||||
def bills_links(self, order):
|
||||
bills = []
|
||||
make_link = admin_link()
|
||||
|
@ -139,7 +139,6 @@ class OrderAdmin(AccountAdminMixin, ExtendedModelAdmin):
|
|||
bills.append(make_link(line.bill))
|
||||
return '<br>'.join(bills)
|
||||
bills_links.short_description = _("Bills")
|
||||
bills_links.allow_tags = True
|
||||
|
||||
def display_billed_until(self, order):
|
||||
billed_until = order.billed_until
|
||||
|
@ -156,12 +155,12 @@ class OrderAdmin(AccountAdminMixin, ExtendedModelAdmin):
|
|||
red = True
|
||||
elif billed_until < timezone.now().date():
|
||||
red = True
|
||||
color = 'style="color:red;"' if red else ''
|
||||
return '<span title="{raw}" {color}>{human}</span>'.format(
|
||||
color = mark_safe('style="color:red;"') if red else ''
|
||||
return format_html(
|
||||
'<span title="{raw}" {color}>{human}</span>',
|
||||
raw=escape(str(billed_until)), color=color, human=human,
|
||||
)
|
||||
display_billed_until.short_description = _("billed until")
|
||||
display_billed_until.allow_tags = True
|
||||
display_billed_until.admin_order_field = 'billed_until'
|
||||
|
||||
def display_metric(self, order):
|
||||
|
|
|
@ -15,7 +15,7 @@ def cancel_orders(sender, **kwargs):
|
|||
if sender._meta.app_label not in settings.ORDERS_EXCLUDED_APPS:
|
||||
instance = kwargs['instance']
|
||||
# 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
|
||||
if type(instance) in services:
|
||||
for order in Order.objects.by_object(instance).active():
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
from django.contrib import admin
|
||||
from django.urls import reverse
|
||||
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 orchestra.admin import ChangeViewActionsMixin, ExtendedModelAdmin
|
||||
|
@ -154,6 +156,7 @@ class TransactionAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
|
|||
return []
|
||||
return [action for action in actions if action.__name__ not in exclude]
|
||||
|
||||
@mark_safe
|
||||
def display_state(self, obj):
|
||||
state = admin_colored('state', colors=STATE_COLORS)(obj)
|
||||
help_text = obj.get_state_help()
|
||||
|
@ -161,7 +164,6 @@ class TransactionAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
|
|||
return state
|
||||
display_state.admin_order_field = 'state'
|
||||
display_state.short_description = _("State")
|
||||
display_state.allow_tags = True
|
||||
|
||||
|
||||
class TransactionProcessAdmin(ChangeViewActionsMixin, admin.ModelAdmin):
|
||||
|
@ -184,10 +186,10 @@ class TransactionProcessAdmin(ChangeViewActionsMixin, admin.ModelAdmin):
|
|||
|
||||
def file_url(self, process):
|
||||
if process.file:
|
||||
return '<a href="%s">%s</a>' % (process.file.url, process.file.name)
|
||||
file_url.allow_tags = True
|
||||
return format_html('<a href="{}">{}</a>', process.file.url, process.file.name)
|
||||
file_url.admin_order_field = 'file'
|
||||
|
||||
@mark_safe
|
||||
def display_transactions(self, process):
|
||||
ids = []
|
||||
lines = []
|
||||
|
@ -207,7 +209,6 @@ class TransactionProcessAdmin(ChangeViewActionsMixin, admin.ModelAdmin):
|
|||
url += '?process_id=%i' % process.id
|
||||
return '<a href="%s">%s</a>' % (url, transactions)
|
||||
display_transactions.short_description = _("Transactions")
|
||||
display_transactions.allow_tags = True
|
||||
|
||||
def has_add_permission(self, *args, **kwargs):
|
||||
return False
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
from django.contrib import admin
|
||||
from django.urls import reverse
|
||||
from django.db import models
|
||||
from django.utils.html import format_html
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from orchestra.admin import ExtendedModelAdmin
|
||||
|
@ -33,10 +34,9 @@ class PlanAdmin(ExtendedModelAdmin):
|
|||
num = plan.contracts__count
|
||||
url = reverse('admin:plans_contractedplan_changelist')
|
||||
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.admin_order_field = 'contracts__count'
|
||||
num_contracts.allow_tags = True
|
||||
|
||||
def get_queryset(self, request):
|
||||
qs = super(PlanAdmin, self).get_queryset(request)
|
||||
|
|
|
@ -11,6 +11,7 @@ from django.db.models import Q
|
|||
from django.shortcuts import redirect
|
||||
from django.templatetags.static import static
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.html import format_html
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import ungettext, ugettext_lazy as _
|
||||
|
||||
|
@ -105,10 +106,9 @@ class ResourceAdmin(ExtendedModelAdmin):
|
|||
def content_object_link(data):
|
||||
ct = data.content_type
|
||||
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.admin_order_field = 'content_object_repr'
|
||||
content_object_link.allow_tags = True
|
||||
|
||||
|
||||
class ResourceDataAdmin(ExtendedModelAdmin):
|
||||
|
@ -155,10 +155,9 @@ class ResourceDataAdmin(ExtendedModelAdmin):
|
|||
if rdata.used is None:
|
||||
return ''
|
||||
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.admin_order_field = 'used'
|
||||
display_used.allow_tags = True
|
||||
|
||||
def has_add_permission(self, *args, **kwargs):
|
||||
return False
|
||||
|
@ -304,6 +303,7 @@ def resource_inline_factory(resources):
|
|||
self.verbose_name_plural = mark_safe(_("Resources") + ' ' + link)
|
||||
return super(ResourceInline, self).get_fieldsets(request, obj)
|
||||
|
||||
@mark_safe
|
||||
def display_used(self, rdata):
|
||||
update = ''
|
||||
history = ''
|
||||
|
@ -329,7 +329,6 @@ def resource_inline_factory(resources):
|
|||
return _("Unknonw %s %s") % (update, history)
|
||||
return _("No monitor")
|
||||
display_used.short_description = _("Used")
|
||||
display_used.allow_tags = True
|
||||
|
||||
def has_add_permission(self, *args, **kwargs):
|
||||
""" Hidde add another """
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
from django.contrib import admin
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from orchestra.admin import ExtendedModelAdmin, ChangePasswordAdminMixin
|
||||
|
@ -26,7 +27,8 @@ class SaaSAdmin(SelectPluginAdminMixin, ChangePasswordAdminMixin, AccountAdminMi
|
|||
plugin_field = 'service'
|
||||
plugin_title = 'Software as a Service'
|
||||
actions = (disable, enable, list_accounts)
|
||||
|
||||
|
||||
@mark_safe
|
||||
def display_url(self, saas):
|
||||
site_domain = saas.get_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)
|
||||
return '<br>'.join(links)
|
||||
display_url.short_description = _("URL")
|
||||
display_url.allow_tags = True
|
||||
display_url.admin_order_field = 'name'
|
||||
|
||||
|
||||
def get_fields(self, *args, **kwargs):
|
||||
fields = super(SaaSAdmin, self).get_fields(*args, **kwargs)
|
||||
if not self.plugin_instance.allow_custom_url:
|
||||
|
|
|
@ -42,7 +42,7 @@ def clean_custom_url(saas):
|
|||
)
|
||||
except Website.DoesNotExist:
|
||||
# get or create domain
|
||||
Domain = Website.domains.field.model
|
||||
Domain = Website.domains.field.related_model
|
||||
try:
|
||||
domain = Domain.objects.get(name=url.netloc)
|
||||
except Domain.DoesNotExist:
|
||||
|
@ -110,7 +110,7 @@ def create_or_update_directive(saas):
|
|||
account=account,
|
||||
)
|
||||
except Website.DoesNotExist:
|
||||
Domain = Website.domains.field.model
|
||||
Domain = Website.domains.field.related_model
|
||||
domain = Domain.objects.get(name=url.netloc)
|
||||
# Create new website for custom_url
|
||||
tgt_server = Server.objects.get(name='web.pangea.lan')
|
||||
|
|
|
@ -4,6 +4,7 @@ from django.contrib import admin
|
|||
from django.urls import reverse
|
||||
from django.template.response import TemplateResponse
|
||||
from django.utils import timezone
|
||||
from django.utils.html import format_html
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from orchestra.admin import ChangeViewActionsMixin
|
||||
|
@ -69,10 +70,9 @@ class ServiceAdmin(ChangeViewActionsMixin, admin.ModelAdmin):
|
|||
num = service.orders__count
|
||||
url = reverse('admin:orders_order_changelist')
|
||||
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.admin_order_field = 'orders__count'
|
||||
num_orders.allow_tags = True
|
||||
|
||||
def get_queryset(self, request):
|
||||
qs = super(ServiceAdmin, self).get_queryset(request)
|
||||
|
|
|
@ -61,7 +61,7 @@ class SystemUser(models.Model):
|
|||
def active(self):
|
||||
try:
|
||||
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
|
||||
|
||||
@cached_property
|
||||
|
|
|
@ -2,6 +2,7 @@ from django import forms
|
|||
from django.contrib import admin
|
||||
from django.urls import reverse
|
||||
from django.utils.encoding import force_text
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import ugettext, ugettext_lazy as _
|
||||
|
||||
from orchestra.admin import ExtendedModelAdmin
|
||||
|
@ -66,6 +67,7 @@ class WebAppAdmin(SelectPluginAdminMixin, AccountAdminMixin, ExtendedModelAdmin)
|
|||
|
||||
display_type = display_plugin_field('type')
|
||||
|
||||
@mark_safe
|
||||
def display_websites(self, webapp):
|
||||
websites = []
|
||||
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")))
|
||||
return '<br>'.join(websites)
|
||||
display_websites.short_description = _("web sites")
|
||||
display_websites.allow_tags = True
|
||||
|
||||
def display_detail(self, webapp):
|
||||
try:
|
||||
return webapp.type_instance.get_detail()
|
||||
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.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)
|
||||
|
|
|
@ -2,7 +2,7 @@ import os
|
|||
import textwrap
|
||||
from collections import OrderedDict
|
||||
|
||||
from django.template import Template, Context
|
||||
from django.template import Template
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from orchestra.contrib.orchestration import ServiceController
|
||||
|
@ -17,7 +17,7 @@ class PHPController(WebAppServiceMixin, ServiceController):
|
|||
It handles switching between these two PHP process management systemes.
|
||||
"""
|
||||
MERGE = settings.WEBAPPS_MERGE_PHP_WEBAPPS
|
||||
|
||||
|
||||
verbose_name = _("PHP FPM/FCGID")
|
||||
default_route_match = "webapp.type.endswith('php')"
|
||||
doc_settings = (settings, (
|
||||
|
@ -30,7 +30,7 @@ class PHPController(WebAppServiceMixin, ServiceController):
|
|||
'WEBAPPS_PHPFPM_POOL_PATH',
|
||||
'WEBAPPS_PHP_MAX_REQUESTS',
|
||||
))
|
||||
|
||||
|
||||
def save(self, webapp):
|
||||
self.delete_old_config(webapp)
|
||||
context = self.get_context(webapp)
|
||||
|
@ -81,7 +81,7 @@ class PHPController(WebAppServiceMixin, ServiceController):
|
|||
}
|
||||
""") % context
|
||||
)
|
||||
|
||||
|
||||
def save_fcgid(self, webapp, context):
|
||||
self.append("mkdir -p %(wrapper_dir)s" % context)
|
||||
self.append(textwrap.dedent("""
|
||||
|
@ -118,7 +118,7 @@ class PHPController(WebAppServiceMixin, ServiceController):
|
|||
)
|
||||
else:
|
||||
self.append("rm -f %(cmd_options_path)s\n" % context)
|
||||
|
||||
|
||||
def delete(self, webapp):
|
||||
context = self.get_context(webapp)
|
||||
self.delete_old_config(webapp)
|
||||
|
@ -127,13 +127,13 @@ class PHPController(WebAppServiceMixin, ServiceController):
|
|||
# elif webapp.type_instance.is_fcgid:
|
||||
# self.delete_fcgid(webapp, context)
|
||||
self.delete_webapp_dir(context)
|
||||
|
||||
|
||||
def has_sibilings(self, webapp, context):
|
||||
return type(webapp).objects.filter(
|
||||
account=webapp.account_id,
|
||||
data__contains='"php_version":"%s"' % context['php_version'],
|
||||
).exclude(id=webapp.pk).exists()
|
||||
|
||||
|
||||
def all_versions_to_delete(self, webapp, context, preserve=False):
|
||||
context_copy = dict(context)
|
||||
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
|
||||
if not self.MERGE or not self.has_sibilings(webapp, context_copy):
|
||||
yield context_copy
|
||||
|
||||
|
||||
def delete_fpm(self, webapp, context, preserve=False):
|
||||
""" delete all pools in order to efectively support changing php-fpm version """
|
||||
for context_copy in self.all_versions_to_delete(webapp, context, preserve):
|
||||
context_copy['fpm_path'] = settings.WEBAPPS_PHPFPM_POOL_PATH % context_copy
|
||||
self.append("rm -f %(fpm_path)s" % context_copy)
|
||||
|
||||
|
||||
def delete_fcgid(self, webapp, context, preserve=False):
|
||||
""" delete all pools in order to efectively support changing php-fcgid version """
|
||||
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 %(cmd_options_path)s" % context_copy)
|
||||
|
||||
|
||||
def prepare(self):
|
||||
super(PHPController, self).prepare()
|
||||
self.append(textwrap.dedent("""
|
||||
BACKEND="PHPController"
|
||||
echo "$BACKEND" >> /dev/shm/reload.apache2
|
||||
|
||||
|
||||
function coordinate_apache_reload () {
|
||||
# Coordinate Apache reload with other concurrent backends (e.g. Apache2Controller)
|
||||
is_last=0
|
||||
|
@ -203,7 +203,7 @@ class PHPController(WebAppServiceMixin, ServiceController):
|
|||
fi
|
||||
}""")
|
||||
)
|
||||
|
||||
|
||||
def commit(self):
|
||||
context = {
|
||||
'reload_pool': settings.WEBAPPS_PHPFPM_RELOAD_POOL,
|
||||
|
@ -217,7 +217,7 @@ class PHPController(WebAppServiceMixin, ServiceController):
|
|||
""") % context
|
||||
)
|
||||
super(PHPController, self).commit()
|
||||
|
||||
|
||||
def get_fpm_config(self, webapp, context):
|
||||
options = webapp.type_instance.get_options()
|
||||
context.update({
|
||||
|
@ -231,11 +231,11 @@ class PHPController(WebAppServiceMixin, ServiceController):
|
|||
[{{ user }}-{{app_name}}]
|
||||
user = {{ user }}
|
||||
group = {{ group }}
|
||||
|
||||
|
||||
listen = {{ fpm_listen | safe }}
|
||||
listen.owner = {{ user }}
|
||||
listen.group = {{ group }}
|
||||
|
||||
|
||||
pm = ondemand
|
||||
pm.max_requests = {{ max_requests }}
|
||||
pm.max_children = {{ max_children }}
|
||||
|
@ -245,8 +245,8 @@ class PHPController(WebAppServiceMixin, ServiceController):
|
|||
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):
|
||||
opt = webapp.type_instance
|
||||
# Format PHP init vars
|
||||
|
@ -268,7 +268,7 @@ class PHPController(WebAppServiceMixin, ServiceController):
|
|||
export PHP_INI_SCAN_DIR=%(php_ini_scan)s
|
||||
export PHP_FCGI_MAX_REQUESTS=%(max_requests)s
|
||||
exec %(php_binary_path)s%(php_init_vars)s""") % context
|
||||
|
||||
|
||||
def get_fcgid_cmd_options(self, webapp, context):
|
||||
options = webapp.type_instance.get_options()
|
||||
maps = OrderedDict(
|
||||
|
@ -288,7 +288,7 @@ class PHPController(WebAppServiceMixin, ServiceController):
|
|||
) % context
|
||||
cmd_options.insert(0, head)
|
||||
return ' \\\n '.join(cmd_options)
|
||||
|
||||
|
||||
def update_fcgid_context(self, webapp, context):
|
||||
wrapper_path = settings.WEBAPPS_FCGID_WRAPPER_PATH % context
|
||||
context.update({
|
||||
|
@ -301,14 +301,14 @@ class PHPController(WebAppServiceMixin, ServiceController):
|
|||
'cmd_options_path': settings.WEBAPPS_FCGID_CMD_OPTIONS_PATH % context,
|
||||
})
|
||||
return context
|
||||
|
||||
|
||||
def update_fpm_context(self, webapp, context):
|
||||
context.update({
|
||||
'fpm_config': self.get_fpm_config(webapp, context),
|
||||
'fpm_path': settings.WEBAPPS_PHPFPM_POOL_PATH % context,
|
||||
})
|
||||
return context
|
||||
|
||||
|
||||
def get_context(self, webapp):
|
||||
context = super().get_context(webapp)
|
||||
context.update({
|
||||
|
|
|
@ -3,6 +3,8 @@ from django.contrib import admin
|
|||
from django.urls import resolve
|
||||
from django.db.models import Q
|
||||
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 orchestra.admin import ExtendedModelAdmin
|
||||
|
@ -78,6 +80,7 @@ class WebsiteAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
|
|||
search_fields = ('name', 'account__username', 'domains__name', 'content__webapp__name')
|
||||
actions = (disable, enable, list_accounts)
|
||||
|
||||
@mark_safe
|
||||
def display_domains(self, website):
|
||||
domains = []
|
||||
for domain in website.domains.all():
|
||||
|
@ -85,9 +88,9 @@ class WebsiteAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
|
|||
domains.append('<a href="%s">%s</a>' % (url, url))
|
||||
return '<br>'.join(domains)
|
||||
display_domains.short_description = _("domains")
|
||||
display_domains.allow_tags = True
|
||||
display_domains.admin_order_field = 'domains'
|
||||
|
||||
@mark_safe
|
||||
def display_webapps(self, website):
|
||||
webapps = []
|
||||
for content in website.content_set.all():
|
||||
|
@ -100,9 +103,9 @@ class WebsiteAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
|
|||
pass
|
||||
url = change_url(webapp)
|
||||
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)
|
||||
display_webapps.allow_tags = True
|
||||
display_webapps.short_description = _("Web apps")
|
||||
|
||||
def formfield_for_dbfield(self, db_field, **kwargs):
|
||||
|
|
|
@ -2,7 +2,7 @@ import os
|
|||
import re
|
||||
import textwrap
|
||||
|
||||
from django.template import Template, Context
|
||||
from django.template import Template
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from orchestra.contrib.orchestration import ServiceController
|
||||
|
@ -20,7 +20,7 @@ class Apache2Controller(ServiceController):
|
|||
"""
|
||||
HTTP_PORT = 80
|
||||
HTTPS_PORT = 443
|
||||
|
||||
|
||||
model = 'websites.Website'
|
||||
related_models = (
|
||||
('websites.Content', 'website'),
|
||||
|
@ -37,7 +37,7 @@ class Apache2Controller(ServiceController):
|
|||
'WEBSITES_DEFAULT_IPS',
|
||||
'WEBSITES_SAAS_DIRECTIVES',
|
||||
))
|
||||
|
||||
|
||||
def get_extra_conf(self, site, context, ssl=False):
|
||||
extra_conf = self.get_content_directives(site, context)
|
||||
directives = site.get_directives()
|
||||
|
@ -53,7 +53,7 @@ class Apache2Controller(ServiceController):
|
|||
# Order extra conf directives based on directives (longer first)
|
||||
extra_conf = sorted(extra_conf, key=lambda a: len(a[0]), reverse=True)
|
||||
return '\n'.join([conf for location, conf in extra_conf])
|
||||
|
||||
|
||||
def render_virtual_host(self, site, context, ssl=False):
|
||||
context.update({
|
||||
'port': self.HTTPS_PORT if ssl else self.HTTP_PORT,
|
||||
|
@ -78,8 +78,8 @@ class Apache2Controller(ServiceController):
|
|||
{{ line | safe }}{% endfor %}
|
||||
</VirtualHost>
|
||||
""")
|
||||
).render(Context(context))
|
||||
|
||||
).render(context)
|
||||
|
||||
def render_redirect_https(self, context):
|
||||
context['port'] = self.HTTP_PORT
|
||||
return Template(textwrap.dedent("""
|
||||
|
@ -96,8 +96,8 @@ class Apache2Controller(ServiceController):
|
|||
RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI}
|
||||
</VirtualHost>
|
||||
""")
|
||||
).render(Context(context))
|
||||
|
||||
).render(context)
|
||||
|
||||
def save(self, site):
|
||||
context = self.get_context(site)
|
||||
if context['server_name']:
|
||||
|
@ -133,7 +133,7 @@ class Apache2Controller(ServiceController):
|
|||
[[ $(a2dissite %(site_unique_name)s) =~ "already disabled" ]] || UPDATED_APACHE=1\
|
||||
""") % context
|
||||
)
|
||||
|
||||
|
||||
def delete(self, site):
|
||||
context = self.get_context(site)
|
||||
self.append(textwrap.dedent("""
|
||||
|
@ -142,14 +142,14 @@ class Apache2Controller(ServiceController):
|
|||
rm -f %(sites_available)s\
|
||||
""") % context
|
||||
)
|
||||
|
||||
|
||||
def prepare(self):
|
||||
super(Apache2Controller, self).prepare()
|
||||
# Coordinate apache restart with php backend in order not to overdo it
|
||||
self.append(textwrap.dedent("""
|
||||
BACKEND="Apache2Controller"
|
||||
echo "$BACKEND" >> /dev/shm/reload.apache2
|
||||
|
||||
|
||||
function coordinate_apache_reload () {
|
||||
# Coordinate Apache reload with other concurrent backends (e.g. PHPController)
|
||||
is_last=0
|
||||
|
@ -186,12 +186,12 @@ class Apache2Controller(ServiceController):
|
|||
fi
|
||||
}""")
|
||||
)
|
||||
|
||||
|
||||
def commit(self):
|
||||
""" reload Apache2 if necessary """
|
||||
self.append("coordinate_apache_reload")
|
||||
super(Apache2Controller, self).commit()
|
||||
|
||||
|
||||
def get_directives(self, directive, context):
|
||||
method, args = directive[0], directive[1:]
|
||||
try:
|
||||
|
@ -200,7 +200,7 @@ class Apache2Controller(ServiceController):
|
|||
context = (self.__class__.__name__, method)
|
||||
raise AttributeError("%s does not has suport for '%s' directive." % context)
|
||||
return method(context, *args)
|
||||
|
||||
|
||||
def get_content_directives(self, site, context):
|
||||
directives = []
|
||||
for content in site.content_set.all():
|
||||
|
@ -208,19 +208,19 @@ class Apache2Controller(ServiceController):
|
|||
self.set_content_context(content, context)
|
||||
directives += self.get_directives(directive, context)
|
||||
return directives
|
||||
|
||||
|
||||
def get_static_directives(self, context, app_path):
|
||||
context['app_path'] = os.path.normpath(app_path % context)
|
||||
directive = self.get_location_filesystem_map(context)
|
||||
return [
|
||||
(context['location'], directive),
|
||||
]
|
||||
|
||||
|
||||
def get_location_filesystem_map(self, context):
|
||||
if not context['location']:
|
||||
return 'DocumentRoot %(app_path)s' % context
|
||||
return 'Alias %(location)s %(app_path)s' % context
|
||||
|
||||
|
||||
def get_fpm_directives(self, context, socket, app_path):
|
||||
if ':' in socket:
|
||||
# TCP socket
|
||||
|
@ -243,7 +243,7 @@ class Apache2Controller(ServiceController):
|
|||
return [
|
||||
(context['location'], directives),
|
||||
]
|
||||
|
||||
|
||||
def get_fcgid_directives(self, context, app_path, wrapper_path):
|
||||
context.update({
|
||||
'app_path': os.path.normpath(app_path),
|
||||
|
@ -274,7 +274,7 @@ class Apache2Controller(ServiceController):
|
|||
return [
|
||||
(context['location'], directives),
|
||||
]
|
||||
|
||||
|
||||
def get_uwsgi_directives(self, context, socket):
|
||||
# requires apache2 mod_proxy_uwsgi
|
||||
context['socket'] = socket
|
||||
|
@ -283,7 +283,7 @@ class Apache2Controller(ServiceController):
|
|||
return [
|
||||
(context['location'], directives),
|
||||
]
|
||||
|
||||
|
||||
def get_ssl(self, directives):
|
||||
cert = directives.get('ssl-cert')
|
||||
key = directives.get('ssl-key')
|
||||
|
@ -305,7 +305,7 @@ class Apache2Controller(ServiceController):
|
|||
return [
|
||||
('', '\n'.join(ssl_config)),
|
||||
]
|
||||
|
||||
|
||||
def get_security(self, directives):
|
||||
rules = []
|
||||
location = '/'
|
||||
|
@ -329,7 +329,7 @@ class Apache2Controller(ServiceController):
|
|||
</IfModule>""") % '\n '.join(rules)
|
||||
security.append((location, rules))
|
||||
return security
|
||||
|
||||
|
||||
def get_redirects(self, directives):
|
||||
redirects = []
|
||||
for redirect in directives.get('redirect', []):
|
||||
|
@ -342,7 +342,7 @@ class Apache2Controller(ServiceController):
|
|||
(location, redirect)
|
||||
)
|
||||
return redirects
|
||||
|
||||
|
||||
def get_proxies(self, directives):
|
||||
proxies = []
|
||||
for proxy in directives.get('proxy', []):
|
||||
|
@ -360,7 +360,7 @@ class Apache2Controller(ServiceController):
|
|||
(location, proxy)
|
||||
)
|
||||
return proxies
|
||||
|
||||
|
||||
def get_saas(self, directives):
|
||||
saas = []
|
||||
for name, values in directives.items():
|
||||
|
@ -372,20 +372,20 @@ class Apache2Controller(ServiceController):
|
|||
directive = settings.WEBSITES_SAAS_DIRECTIVES[name]
|
||||
saas += self.get_directives(directive, context)
|
||||
return saas
|
||||
|
||||
|
||||
def get_username(self, site):
|
||||
option = site.get_directives().get('user_group')
|
||||
if option:
|
||||
return option[0]
|
||||
return site.get_username()
|
||||
|
||||
|
||||
def get_groupname(self, site):
|
||||
option = site.get_directives().get('user_group')
|
||||
if option and ' ' in option:
|
||||
user, group = option.split()
|
||||
return group
|
||||
return site.get_groupname()
|
||||
|
||||
|
||||
def get_server_names(self, site):
|
||||
server_name = None
|
||||
server_alias = []
|
||||
|
@ -395,7 +395,7 @@ class Apache2Controller(ServiceController):
|
|||
else:
|
||||
server_alias.append(domain.name)
|
||||
return server_name, server_alias
|
||||
|
||||
|
||||
def get_context(self, site):
|
||||
base_apache_conf = settings.WEBSITES_BASE_APACHE_CONF
|
||||
sites_available = os.path.join(base_apache_conf, 'sites-available')
|
||||
|
@ -419,7 +419,7 @@ class Apache2Controller(ServiceController):
|
|||
if not context['ips']:
|
||||
raise ValueError("WEBSITES_DEFAULT_IPS is empty.")
|
||||
return context
|
||||
|
||||
|
||||
def set_content_context(self, content, context):
|
||||
content_context = {
|
||||
'type': content.webapp.type,
|
||||
|
@ -442,7 +442,7 @@ class Apache2Traffic(ServiceMonitor):
|
|||
doc_settings = (settings,
|
||||
('WEBSITES_TRAFFIC_IGNORE_HOSTS',)
|
||||
)
|
||||
|
||||
|
||||
def prepare(self):
|
||||
super(Apache2Traffic, self).prepare()
|
||||
ignore_hosts = '\\|'.join(settings.WEBSITES_TRAFFIC_IGNORE_HOSTS)
|
||||
|
@ -490,11 +490,11 @@ class Apache2Traffic(ServiceMonitor):
|
|||
}' || [[ $? == 1 ]] && true
|
||||
} | xargs echo ${OBJECT_ID}
|
||||
}""") % context)
|
||||
|
||||
|
||||
def monitor(self, site):
|
||||
context = self.get_context(site)
|
||||
self.append('monitor {object_id} "{last_date}" {log_file}'.format(**context))
|
||||
|
||||
|
||||
def get_context(self, site):
|
||||
return {
|
||||
'log_file': '%s{,.1}' % site.get_www_access_log_path(),
|
||||
|
|
|
@ -13,13 +13,13 @@ from .validators import validate_domain_protocol
|
|||
|
||||
class RelatedDomainSerializer(AccountSerializerMixin, RelatedHyperlinkedModelSerializer):
|
||||
class Meta:
|
||||
model = Website.domains.field.model
|
||||
model = Website.domains.field.related_model
|
||||
fields = ('url', 'id', 'name')
|
||||
|
||||
|
||||
class RelatedWebAppSerializer(AccountSerializerMixin, RelatedHyperlinkedModelSerializer):
|
||||
class Meta:
|
||||
model = Content.webapp.field.model
|
||||
model = Content.webapp.field.related_model
|
||||
fields = ('url', 'id', 'name', 'type')
|
||||
|
||||
|
||||
|
|
|
@ -17,9 +17,9 @@ class SpanWidget(forms.Widget):
|
|||
self.original = kwargs.pop('original', '')
|
||||
self.display = kwargs.pop('display', None)
|
||||
super(SpanWidget, self).__init__(*args, **kwargs)
|
||||
|
||||
def render(self, name, value, attrs=None):
|
||||
final_attrs = self.build_attrs(attrs, name=name)
|
||||
|
||||
def render(self, name, value, attrs=None, renderer=None):
|
||||
final_attrs = self.build_attrs(attrs, extra_attrs={'name':name})
|
||||
original = self.original or value
|
||||
display = original if self.display is None else self.display
|
||||
# Display icon
|
||||
|
@ -29,25 +29,25 @@ class SpanWidget(forms.Widget):
|
|||
tag = 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))
|
||||
|
||||
|
||||
def value_from_datadict(self, data, files, name):
|
||||
return self.original
|
||||
|
||||
|
||||
def _has_changed(self, initial, data):
|
||||
return False
|
||||
|
||||
|
||||
def paddingCheckboxSelectMultiple(padding):
|
||||
class PaddingCheckboxSelectMultiple(forms.CheckboxSelectMultiple):
|
||||
""" Ugly hack to render this widget nicely on Django admin """
|
||||
widget = forms.CheckboxSelectMultiple()
|
||||
old_render = widget.render
|
||||
def __init__(self, padding, attrs=None, choices=()):
|
||||
super().__init__(attrs=attrs, choices=choices)
|
||||
self.padding = padding
|
||||
|
||||
def render(self, *args, **kwargs):
|
||||
value = old_render(self, *args, **kwargs)
|
||||
value = super().render(*args, **kwargs)
|
||||
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)
|
||||
widget.render = render
|
||||
return widget
|
||||
|
||||
|
||||
class DynamicHelpTextSelect(forms.Select):
|
||||
|
@ -61,7 +61,7 @@ class DynamicHelpTextSelect(forms.Select):
|
|||
attrs.update(kwargs.get('attrs', {}))
|
||||
kwargs['attrs'] = attrs
|
||||
super(DynamicHelpTextSelect, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
def get_dynamic_help_text(self, target, help_text):
|
||||
return textwrap.dedent("""\
|
||||
siteoptions = {help_text};
|
||||
|
|
|
@ -90,7 +90,7 @@ class RelatedPermission(Permission):
|
|||
if obj is None:
|
||||
parent = cls
|
||||
for relation in relations:
|
||||
parent = getattr(parent, relation).field.model
|
||||
parent = getattr(parent, relation).field.related_model
|
||||
else:
|
||||
parent = functools.reduce(getattr, relations, obj)
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import textwrap
|
||||
|
||||
from django.templatetags.static import static
|
||||
from django.utils.html import format_html
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from orchestra.utils.sys import run
|
||||
|
@ -31,6 +32,6 @@ def get_on_site_link(url):
|
|||
context = {
|
||||
'title': _("View on site %s") % 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)
|
||||
|
|
|
@ -2,7 +2,6 @@ from urllib.parse import urlparse
|
|||
|
||||
from django.core.mail import EmailMultiAlternatives
|
||||
from django.template.loader import render_to_string
|
||||
from django.template import Context
|
||||
|
||||
|
||||
def render_email_template(template, context):
|
||||
|
@ -10,12 +9,9 @@ def render_email_template(template, context):
|
|||
Renders an email template with this format:
|
||||
{% if subject %}Subject{% 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:
|
||||
from orchestra import settings
|
||||
url = urlparse(settings.ORCHESTRA_SITE_URL)
|
||||
|
|
|
@ -12,7 +12,7 @@ ecdsa==0.11
|
|||
Pygments==1.6
|
||||
django-filter==2.2.0
|
||||
jsonfield==0.9.22
|
||||
python-dateutil==2.2
|
||||
python-dateutil>=2.7.0
|
||||
https://github.com/glic3rinu/passlib/archive/master.zip
|
||||
django-iban==0.3.0
|
||||
requests
|
||||
|
|
Loading…
Reference in a new issue