Partially implemented BSCW support
This commit is contained in:
parent
8cce7b58f6
commit
9ed4be4d2e
10
TODO.md
10
TODO.md
|
@ -169,8 +169,6 @@ Remember that, as always with QuerySets, any subsequent chained methods which im
|
||||||
|
|
||||||
* validate address.forward: if mailbox in account.mailboxes then: _("Please use mailboxes field") or consider removing mailbox support on forward (user@pangea.org instead)
|
* validate address.forward: if mailbox in account.mailboxes then: _("Please use mailboxes field") or consider removing mailbox support on forward (user@pangea.org instead)
|
||||||
|
|
||||||
* reespell systemuser to system_user
|
|
||||||
|
|
||||||
* remove order in account admin and others admininlines
|
* remove order in account admin and others admininlines
|
||||||
|
|
||||||
* Databases.User add reverse M2M databases widget (like mailbox.addresses)
|
* Databases.User add reverse M2M databases widget (like mailbox.addresses)
|
||||||
|
@ -194,3 +192,11 @@ Remember that, as always with QuerySets, any subsequent chained methods which im
|
||||||
* resource min max allocation with validation
|
* resource min max allocation with validation
|
||||||
|
|
||||||
* mailman needs both aliases when address_name is provided (default messages and bounces and all)
|
* mailman needs both aliases when address_name is provided (default messages and bounces and all)
|
||||||
|
|
||||||
|
* domain validation parse named-checzone output to assign errors to fields
|
||||||
|
|
||||||
|
|
||||||
|
* Directory Protection on webapp and use webapp path as base path (validate)
|
||||||
|
* User [Group] webapp/website option (validation) which overrides default mainsystemuser
|
||||||
|
|
||||||
|
* validate systemuser.home
|
||||||
|
|
|
@ -25,7 +25,7 @@ from .models import Account
|
||||||
|
|
||||||
|
|
||||||
class AccountAdmin(ChangePasswordAdminMixin, auth.UserAdmin, ExtendedModelAdmin):
|
class AccountAdmin(ChangePasswordAdminMixin, auth.UserAdmin, ExtendedModelAdmin):
|
||||||
list_display = ('username', 'type', 'is_active')
|
list_display = ('username', 'full_name', 'type', 'is_active')
|
||||||
list_filter = (
|
list_filter = (
|
||||||
'type', 'is_active', HasMainUserListFilter
|
'type', 'is_active', HasMainUserListFilter
|
||||||
)
|
)
|
||||||
|
@ -55,7 +55,7 @@ class AccountAdmin(ChangePasswordAdminMixin, auth.UserAdmin, ExtendedModelAdmin)
|
||||||
'fields': ('last_login', 'date_joined')
|
'fields': ('last_login', 'date_joined')
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
search_fields = ('username',)
|
search_fields = ('username', 'short_name', 'full_name')
|
||||||
add_form = AccountCreationForm
|
add_form = AccountCreationForm
|
||||||
form = UserChangeForm
|
form = UserChangeForm
|
||||||
filter_horizontal = ()
|
filter_horizontal = ()
|
||||||
|
@ -64,6 +64,7 @@ class AccountAdmin(ChangePasswordAdminMixin, auth.UserAdmin, ExtendedModelAdmin)
|
||||||
actions = [disable]
|
actions = [disable]
|
||||||
change_view_actions = actions
|
change_view_actions = actions
|
||||||
list_select_related = ('billcontact',)
|
list_select_related = ('billcontact',)
|
||||||
|
ordering = ()
|
||||||
|
|
||||||
main_systemuser_link = admin_link('main_systemuser')
|
main_systemuser_link = admin_link('main_systemuser')
|
||||||
|
|
||||||
|
@ -115,10 +116,8 @@ admin.site.register(Account, AccountAdmin)
|
||||||
|
|
||||||
class AccountListAdmin(AccountAdmin):
|
class AccountListAdmin(AccountAdmin):
|
||||||
""" Account list to allow account selection when creating new services """
|
""" Account list to allow account selection when creating new services """
|
||||||
list_display = ('select_account', 'type', 'username')
|
list_display = ('select_account', 'username', 'type', 'username')
|
||||||
actions = None
|
actions = None
|
||||||
search_fields = ['username',]
|
|
||||||
ordering = ('username',)
|
|
||||||
|
|
||||||
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
|
||||||
|
|
|
@ -68,9 +68,6 @@ class DomainAdmin(AccountAdminMixin, ExtendedModelAdmin):
|
||||||
list_filter = [TopDomainListFilter]
|
list_filter = [TopDomainListFilter]
|
||||||
change_readonly_fields = ('name',)
|
change_readonly_fields = ('name',)
|
||||||
search_fields = ['name',]
|
search_fields = ['name',]
|
||||||
default_changelist_filters = (
|
|
||||||
('top_domain', 'True'),
|
|
||||||
)
|
|
||||||
add_form = CreateDomainAdminForm
|
add_form = CreateDomainAdminForm
|
||||||
change_view_actions = [view_zone]
|
change_view_actions = [view_zone]
|
||||||
|
|
||||||
|
|
|
@ -11,25 +11,8 @@ class TopDomainListFilter(SimpleListFilter):
|
||||||
def lookups(self, request, model_admin):
|
def lookups(self, request, model_admin):
|
||||||
return (
|
return (
|
||||||
('True', _("Top domains")),
|
('True', _("Top domains")),
|
||||||
('False', _("All")),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def queryset(self, request, queryset):
|
def queryset(self, request, queryset):
|
||||||
if self.value() == 'True':
|
if self.value() == 'True':
|
||||||
return queryset.filter(top__isnull=True)
|
return queryset.filter(top__isnull=True)
|
||||||
|
|
||||||
def choices(self, cl):
|
|
||||||
""" Enable default selection different than All """
|
|
||||||
for lookup, title in self.lookup_choices:
|
|
||||||
title = title._proxy____args[0]
|
|
||||||
selected = self.value() == force_text(lookup)
|
|
||||||
if not selected and title == "Top domains" and self.value() is None:
|
|
||||||
selected = True
|
|
||||||
# end of workaround
|
|
||||||
yield {
|
|
||||||
'selected': selected,
|
|
||||||
'query_string': cl.get_query_string({
|
|
||||||
self.parameter_name: lookup,
|
|
||||||
}, []),
|
|
||||||
'display': title,
|
|
||||||
}
|
|
||||||
|
|
|
@ -100,7 +100,7 @@ class AddressAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
|
||||||
list_filter = (HasMailboxListFilter, HasForwardListFilter)
|
list_filter = (HasMailboxListFilter, HasForwardListFilter)
|
||||||
fields = ('account_link', ('name', 'domain'), 'mailboxes', 'forward')
|
fields = ('account_link', ('name', 'domain'), 'mailboxes', 'forward')
|
||||||
inlines = [AutoresponseInline]
|
inlines = [AutoresponseInline]
|
||||||
search_fields = ('name', 'domain__name',)
|
search_fields = ('name', 'domain__name', 'forward', 'mailboxes__name', 'account__username')
|
||||||
readonly_fields = ('account_link', 'domain_link', 'email_link')
|
readonly_fields = ('account_link', 'domain_link', 'email_link')
|
||||||
filter_by_account_fields = ('domain', 'mailboxes')
|
filter_by_account_fields = ('domain', 'mailboxes')
|
||||||
filter_horizontal = ['mailboxes']
|
filter_horizontal = ['mailboxes']
|
||||||
|
|
|
@ -42,6 +42,7 @@ class MailboxForm(forms.ModelForm):
|
||||||
self.fields['addresses'].initial = self.instance.addresses.all()
|
self.fields['addresses'].initial = self.instance.addresses.all()
|
||||||
|
|
||||||
def clean_custom_filtering(self):
|
def clean_custom_filtering(self):
|
||||||
|
# TODO move to model.clean?
|
||||||
filtering = self.cleaned_data['filtering']
|
filtering = self.cleaned_data['filtering']
|
||||||
custom_filtering = self.cleaned_data['custom_filtering']
|
custom_filtering = self.cleaned_data['custom_filtering']
|
||||||
if filtering == self._meta.model.CUSTOM and not custom_filtering:
|
if filtering == self._meta.model.CUSTOM and not custom_filtering:
|
||||||
|
@ -50,6 +51,10 @@ class MailboxForm(forms.ModelForm):
|
||||||
})
|
})
|
||||||
return custom_filtering
|
return custom_filtering
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
if not self.cleaned_data['mailboxes'] and not self.cleaned_data['forward']:
|
||||||
|
raise ValidationError("A mailbox or forward address should be provided.")
|
||||||
|
|
||||||
|
|
||||||
class MailboxChangeForm(UserChangeForm, MailboxForm):
|
class MailboxChangeForm(UserChangeForm, MailboxForm):
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -81,5 +81,5 @@ class AddressSerializer(AccountSerializerMixin, serializers.HyperlinkedModelSeri
|
||||||
|
|
||||||
def validate(self, attrs):
|
def validate(self, attrs):
|
||||||
if not attrs['mailboxes'] and not attrs['forward']:
|
if not attrs['mailboxes'] and not attrs['forward']:
|
||||||
raise serializers.ValidationError("mailboxes or forward addresses should be provided")
|
raise serializers.ValidationError("A mailbox or forward address should be provided.")
|
||||||
return attrs
|
return attrs
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
from dateutil import relativedelta
|
from dateutil import relativedelta
|
||||||
from django import forms
|
from django import forms
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
|
||||||
from orchestra.utils import plugins
|
from orchestra.utils import plugins
|
||||||
from orchestra.utils.functional import cached
|
from orchestra.utils.functional import cached
|
||||||
|
@ -26,8 +27,12 @@ class PaymentMethod(plugins.Plugin):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def clean_data(cls, data):
|
def clean_data(cls, data):
|
||||||
""" model clean """
|
""" model clean, uses cls.serializer by default """
|
||||||
return data
|
serializer = cls.serializer(data=data)
|
||||||
|
if not serializer.is_valid():
|
||||||
|
serializer.errors.pop('non_field_errors', None)
|
||||||
|
raise ValidationError(serializer.errors)
|
||||||
|
return serializer.data
|
||||||
|
|
||||||
def get_form(self):
|
def get_form(self):
|
||||||
self.form.plugin = self
|
self.form.plugin = self
|
||||||
|
|
|
@ -19,7 +19,7 @@ from .options import PaymentMethod
|
||||||
|
|
||||||
|
|
||||||
class SEPADirectDebitForm(PluginDataForm):
|
class SEPADirectDebitForm(PluginDataForm):
|
||||||
iban = IBANFormField(label='IBAN',
|
iban = forms.CharField(label='IBAN',
|
||||||
widget=forms.TextInput(attrs={'size': '50'}))
|
widget=forms.TextInput(attrs={'size': '50'}))
|
||||||
name = forms.CharField(max_length=128, label=_("Name"),
|
name = forms.CharField(max_length=128, label=_("Name"),
|
||||||
widget=forms.TextInput(attrs={'size': '50'}))
|
widget=forms.TextInput(attrs={'size': '50'}))
|
||||||
|
@ -30,6 +30,11 @@ class SEPADirectDebitSerializer(serializers.Serializer):
|
||||||
min_length=min(IBAN_COUNTRY_CODE_LENGTH.values()), max_length=34)
|
min_length=min(IBAN_COUNTRY_CODE_LENGTH.values()), max_length=34)
|
||||||
name = serializers.CharField(label=_("Name"), max_length=128)
|
name = serializers.CharField(label=_("Name"), max_length=128)
|
||||||
|
|
||||||
|
def validate(self, data):
|
||||||
|
data['iban'] = data['iban'].strip()
|
||||||
|
data['name'] = data['name'].strip()
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
class SEPADirectDebit(PaymentMethod):
|
class SEPADirectDebit(PaymentMethod):
|
||||||
verbose_name = _("SEPA Direct Debit")
|
verbose_name = _("SEPA Direct Debit")
|
||||||
|
@ -44,13 +49,6 @@ class SEPADirectDebit(PaymentMethod):
|
||||||
return _("This bill will been automatically charged to your bank account "
|
return _("This bill will been automatically charged to your bank account "
|
||||||
" with IBAN number<br><strong>%s</strong>.") % source.number
|
" with IBAN number<br><strong>%s</strong>.") % source.number
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def clean_data(cls, data):
|
|
||||||
data['iban'] = data['iban'].strip()
|
|
||||||
data['name'] = data['name'].strip()
|
|
||||||
IBANValidator()(data['iban'])
|
|
||||||
return data
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def process(cls, transactions):
|
def process(cls, transactions):
|
||||||
debts = []
|
debts = []
|
||||||
|
|
|
@ -128,10 +128,11 @@ class ResourceData(models.Model):
|
||||||
resource = models.ForeignKey(Resource, related_name='dataset', verbose_name=_("resource"))
|
resource = models.ForeignKey(Resource, related_name='dataset', verbose_name=_("resource"))
|
||||||
content_type = models.ForeignKey(ContentType, verbose_name=_("content type"))
|
content_type = models.ForeignKey(ContentType, verbose_name=_("content type"))
|
||||||
object_id = models.PositiveIntegerField(_("object id"))
|
object_id = models.PositiveIntegerField(_("object id"))
|
||||||
used = models.DecimalField(_("used"), max_digits=16, decimal_places=2, null=True)
|
used = models.DecimalField(_("used"), max_digits=16, decimal_places=2, null=True,
|
||||||
updated_at = models.DateTimeField(_("updated"), null=True)
|
editable=False)
|
||||||
allocated = models.PositiveIntegerField(_("allocated"), null=True, blank=True)
|
updated_at = models.DateTimeField(_("updated"), null=True, editable=False)
|
||||||
|
allocated = models.DecimalField(_("allocated"), max_digits=8, decimal_places=2,
|
||||||
|
null=True, blank=True)
|
||||||
content_object = GenericForeignKey()
|
content_object = GenericForeignKey()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -193,6 +194,12 @@ def create_resource_relation():
|
||||||
""" account.resources.web """
|
""" account.resources.web """
|
||||||
def __getattr__(self, attr):
|
def __getattr__(self, attr):
|
||||||
""" get or build ResourceData """
|
""" get or build ResourceData """
|
||||||
|
try:
|
||||||
|
return self.obj.__resource_cache[attr]
|
||||||
|
except AttributeError:
|
||||||
|
self.obj.__resource_cache = {}
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
try:
|
try:
|
||||||
data = self.obj.resource_set.get(resource__name=attr)
|
data = self.obj.resource_set.get(resource__name=attr)
|
||||||
except ResourceData.DoesNotExist:
|
except ResourceData.DoesNotExist:
|
||||||
|
@ -201,6 +208,7 @@ def create_resource_relation():
|
||||||
is_active=True)
|
is_active=True)
|
||||||
data = ResourceData(content_object=self.obj, resource=resource,
|
data = ResourceData(content_object=self.obj, resource=resource,
|
||||||
allocated=resource.default_allocation)
|
allocated=resource.default_allocation)
|
||||||
|
self.obj.__resource_cache[attr] = data
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def __get__(self, obj, cls):
|
def __get__(self, obj, cls):
|
||||||
|
|
|
@ -11,7 +11,8 @@ from .services import SoftwareService
|
||||||
class SaaS(models.Model):
|
class SaaS(models.Model):
|
||||||
service = models.CharField(_("service"), max_length=32,
|
service = models.CharField(_("service"), max_length=32,
|
||||||
choices=SoftwareService.get_plugin_choices())
|
choices=SoftwareService.get_plugin_choices())
|
||||||
account = models.ForeignKey('accounts.Account', verbose_name=_("account"), related_name='saas')
|
account = models.ForeignKey('accounts.Account', verbose_name=_("account"),
|
||||||
|
related_name='saas')
|
||||||
data = JSONField(_("data"))
|
data = JSONField(_("data"))
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -29,5 +30,8 @@ class SaaS(models.Model):
|
||||||
def description(self):
|
def description(self):
|
||||||
return self.service_class().get_description(self.data)
|
return self.service_class().get_description(self.data)
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
self.data = self.service_class().clean_data(self)
|
||||||
|
|
||||||
|
|
||||||
services.register(SaaS)
|
services.register(SaaS)
|
||||||
|
|
|
@ -1,19 +1,53 @@
|
||||||
from django import forms
|
from django import forms
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from orchestra.core import validators
|
||||||
from orchestra.forms import PluginDataForm
|
from orchestra.forms import PluginDataForm
|
||||||
|
|
||||||
from .options import SoftwareService
|
from .options import SoftwareService
|
||||||
|
|
||||||
|
|
||||||
|
# TODO monitor quota since out of sync?
|
||||||
|
|
||||||
class BSCWForm(PluginDataForm):
|
class BSCWForm(PluginDataForm):
|
||||||
username = forms.CharField(label=_("Username"), max_length=64)
|
username = forms.CharField(label=_("Username"), max_length=64)
|
||||||
password = forms.CharField(label=_("Password"), max_length=64)
|
password = forms.CharField(label=_("Password"), max_length=256, required=False)
|
||||||
quota = forms.IntegerField(label=_("Quota"))
|
email = forms.EmailField(label=_("Email"))
|
||||||
|
quota = forms.IntegerField(label=_("Quota"), help_text=_("Disk quota in MB."))
|
||||||
|
|
||||||
|
|
||||||
|
class SEPADirectDebitSerializer(serializers.Serializer):
|
||||||
|
username = serializers.CharField(label=_("Username"), max_length=64,
|
||||||
|
validators=[validators.validate_name])
|
||||||
|
password = serializers.CharField(label=_("Password"), max_length=256, required=False,
|
||||||
|
write_only=True)
|
||||||
|
email = serializers.EmailField(label=_("Email"))
|
||||||
|
quota = serializers.IntegerField(label=_("Quota"), help_text=_("Disk quota in MB."))
|
||||||
|
|
||||||
|
def validate(self, data):
|
||||||
|
data['username'] = data['username'].strip()
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
class BSCWService(SoftwareService):
|
class BSCWService(SoftwareService):
|
||||||
verbose_name = "BSCW"
|
verbose_name = "BSCW"
|
||||||
form = BSCWForm
|
form = BSCWForm
|
||||||
|
serializer = SEPADirectDebitSerializer
|
||||||
description_field = 'username'
|
description_field = 'username'
|
||||||
icon = 'saas/icons/BSCW.png'
|
icon = 'saas/icons/BSCW.png'
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def clean_data(cls, saas):
|
||||||
|
try:
|
||||||
|
data = super(BSCWService, cls).clean_data(saas)
|
||||||
|
except ValidationError, error:
|
||||||
|
if not saas.pk and 'password' not in saas.data:
|
||||||
|
error.error_dict['password'] = [_("Password is required.")]
|
||||||
|
raise error
|
||||||
|
if not saas.pk and 'password' not in saas.data:
|
||||||
|
raise ValidationError({
|
||||||
|
'password': _("Password is required.")
|
||||||
|
})
|
||||||
|
return data
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from django import forms
|
from django import forms
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from orchestra.utils import plugins
|
from orchestra.utils import plugins
|
||||||
|
@ -8,8 +9,10 @@ from orchestra.utils.python import import_class
|
||||||
from .. import settings
|
from .. import settings
|
||||||
|
|
||||||
|
|
||||||
|
# TODO if unique_description: make description_field create only
|
||||||
class SoftwareService(plugins.Plugin):
|
class SoftwareService(plugins.Plugin):
|
||||||
description_field = ''
|
description_field = ''
|
||||||
|
unique_description = True
|
||||||
form = None
|
form = None
|
||||||
serializer = None
|
serializer = None
|
||||||
icon = 'orchestra/icons/apps.png'
|
icon = 'orchestra/icons/apps.png'
|
||||||
|
@ -23,6 +26,21 @@ class SoftwareService(plugins.Plugin):
|
||||||
plugins.append(import_class(cls))
|
plugins.append(import_class(cls))
|
||||||
return plugins
|
return plugins
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def clean_data(cls, saas):
|
||||||
|
""" model clean, uses cls.serizlier by default """
|
||||||
|
if cls.unique_description and not saas.pk:
|
||||||
|
from ..models import SaaS
|
||||||
|
field = cls.description_field
|
||||||
|
if SaaS.objects.filter(data__contains='"%s":"%s"' % (field, saas.data[field])).exists():
|
||||||
|
raise ValidationError({
|
||||||
|
field: _("SaaS service with this %(field)s already exists.")
|
||||||
|
}, params={'field': field})
|
||||||
|
serializer = cls.serializer(data=saas.data)
|
||||||
|
if not serializer.is_valid():
|
||||||
|
raise ValidationError(serializer.errors)
|
||||||
|
return serializer.data
|
||||||
|
|
||||||
def get_form(self):
|
def get_form(self):
|
||||||
self.form.plugin = self
|
self.form.plugin = self
|
||||||
self.form.plugin_field = 'service'
|
self.form.plugin_field = 'service'
|
||||||
|
|
|
@ -27,12 +27,15 @@ class WebApp(models.Model):
|
||||||
verbose_name_plural = _("Web Apps")
|
verbose_name_plural = _("Web Apps")
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return self.name or settings.WEBAPPS_BLANK_NAME
|
return self.get_name()
|
||||||
|
|
||||||
@cached
|
@cached
|
||||||
def get_options(self):
|
def get_options(self):
|
||||||
return { opt.name: opt.value for opt in self.options.all() }
|
return { opt.name: opt.value for opt in self.options.all() }
|
||||||
|
|
||||||
|
def get_name(self):
|
||||||
|
return return self.name or settings.WEBAPPS_BLANK_NAME
|
||||||
|
|
||||||
def get_fpm_port(self):
|
def get_fpm_port(self):
|
||||||
return settings.WEBAPPS_FPM_START_PORT + self.account.pk
|
return settings.WEBAPPS_FPM_START_PORT + self.account.pk
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,6 @@ WEBSITES_DEFAULT_IP = getattr(settings, 'WEBSITES_DEFAULT_IP', '*')
|
||||||
WEBSITES_DOMAIN_MODEL = getattr(settings, 'WEBSITES_DOMAIN_MODEL', 'domains.Domain')
|
WEBSITES_DOMAIN_MODEL = getattr(settings, 'WEBSITES_DOMAIN_MODEL', 'domains.Domain')
|
||||||
|
|
||||||
|
|
||||||
# TODO ssl ca, ssl cert, ssl key
|
|
||||||
WEBSITES_OPTIONS = getattr(settings, 'WEBSITES_OPTIONS', {
|
WEBSITES_OPTIONS = getattr(settings, 'WEBSITES_OPTIONS', {
|
||||||
# { name: ( verbose_name, validation_regex ) }
|
# { name: ( verbose_name, validation_regex ) }
|
||||||
'directory_protection': (
|
'directory_protection': (
|
||||||
|
@ -48,6 +47,10 @@ WEBSITES_OPTIONS = getattr(settings, 'WEBSITES_OPTIONS', {
|
||||||
_("HTTPD - Disable Modsecurity"),
|
_("HTTPD - Disable Modsecurity"),
|
||||||
r'^[\w/_]+$'
|
r'^[\w/_]+$'
|
||||||
),
|
),
|
||||||
|
'user_group': (
|
||||||
|
_("HTTPD - SuexecUserGroup"),
|
||||||
|
r'^[\w/_]+\s[\w/_]+$'
|
||||||
|
),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -7,12 +7,11 @@ from ..core.validators import validate_password
|
||||||
|
|
||||||
|
|
||||||
class PluginDataForm(forms.ModelForm):
|
class PluginDataForm(forms.ModelForm):
|
||||||
class Meta:
|
data = forms.CharField(widget=forms.HiddenInput, required=False)
|
||||||
exclude = ('data',)
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(PluginDataForm, self).__init__(*args, **kwargs)
|
super(PluginDataForm, self).__init__(*args, **kwargs)
|
||||||
# TODO remove it weel
|
# TODO remove it well
|
||||||
try:
|
try:
|
||||||
self.fields[self.plugin_field].widget = forms.HiddenInput()
|
self.fields[self.plugin_field].widget = forms.HiddenInput()
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
@ -23,13 +22,14 @@ class PluginDataForm(forms.ModelForm):
|
||||||
initial = self.fields[field].initial
|
initial = self.fields[field].initial
|
||||||
self.fields[field].initial = instance.data.get(field, initial)
|
self.fields[field].initial = instance.data.get(field, initial)
|
||||||
|
|
||||||
def save(self, commit=True):
|
def clean(self):
|
||||||
plugin = self.plugin
|
data = {}
|
||||||
setattr(self.instance, self.plugin_field, plugin.get_plugin_name())
|
for field in self.declared_fields:
|
||||||
self.instance.data = {
|
try:
|
||||||
field: self.cleaned_data[field] for field in self.declared_fields
|
data[field] = self.cleaned_data[field]
|
||||||
}
|
except KeyError:
|
||||||
return super(PluginDataForm, self).save(commit=commit)
|
data[field] = self.data[field]
|
||||||
|
self.cleaned_data['data'] = data
|
||||||
|
|
||||||
|
|
||||||
class UserCreationForm(forms.ModelForm):
|
class UserCreationForm(forms.ModelForm):
|
||||||
|
|
Loading…
Reference in New Issue