Random fixes
This commit is contained in:
parent
04b9ee51cb
commit
0e65c65433
|
@ -252,14 +252,8 @@ class ChangePasswordAdminMixin(object):
|
|||
related.append(user.account)
|
||||
else:
|
||||
account = user
|
||||
# TODO plugability
|
||||
if user._meta.model_name != 'systemuser':
|
||||
rel = account.systemusers.filter(username=username).first()
|
||||
if rel:
|
||||
related.append(rel)
|
||||
if user._meta.model_name != 'mailbox':
|
||||
rel = account.mailboxes.filter(name=username).first()
|
||||
if rel:
|
||||
for rel in account.get_related_passwords():
|
||||
if not isinstance(user, type(rel)):
|
||||
related.append(rel)
|
||||
|
||||
if request.method == 'POST':
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
import copy
|
||||
import re
|
||||
|
||||
from django import forms
|
||||
from django.conf.urls import patterns, url
|
||||
from django.contrib import admin, messages
|
||||
from django.contrib.admin.util import unquote
|
||||
from django.contrib.auth import admin as auth
|
||||
from django.db.models.loading import get_model
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.six.moves.urllib.parse import parse_qsl
|
||||
|
@ -13,6 +17,7 @@ from orchestra.admin.utils import wrap_admin_view, admin_link, set_url_query, ch
|
|||
from orchestra.core import services, accounts
|
||||
from orchestra.forms import UserChangeForm
|
||||
|
||||
from . import settings
|
||||
from .actions import disable
|
||||
from .filters import HasMainUserListFilter
|
||||
from .forms import AccountCreationForm
|
||||
|
@ -84,12 +89,27 @@ class AccountAdmin(ChangePasswordAdminMixin, auth.UserAdmin, ExtendedModelAdmin)
|
|||
return super(AccountAdmin, self).change_view(request, object_id,
|
||||
form_url=form_url, extra_context=context)
|
||||
|
||||
def get_fieldsets(self, request, obj=None):
|
||||
fieldsets = super(AccountAdmin, self).get_fieldsets(request, obj=obj)
|
||||
if not obj:
|
||||
fields = AccountCreationForm.create_related_fields
|
||||
if fields:
|
||||
fieldsets = copy.deepcopy(fieldsets)
|
||||
fieldsets = list(fieldsets)
|
||||
fieldsets.insert(1, (_("Related services"), {'fields': fields}))
|
||||
return fieldsets
|
||||
|
||||
def get_queryset(self, request):
|
||||
""" Select related for performance """
|
||||
qs = super(AccountAdmin, self).get_queryset(request)
|
||||
related = ('invoicecontact',)
|
||||
return qs.select_related(*related)
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
super(AccountAdmin, self).save_model(request, obj, form, change)
|
||||
if not change:
|
||||
form.save_related(obj)
|
||||
|
||||
|
||||
admin.site.register(Account, AccountAdmin)
|
||||
|
||||
|
@ -162,6 +182,7 @@ class AccountAdminMixin(object):
|
|||
def render(*args, **kwargs):
|
||||
output = old_render(*args, **kwargs)
|
||||
output = output.replace('/add/"', '/add/?account=%s"' % self.account.pk)
|
||||
output = re.sub(r'/add/\?([^".]*)"', r'/add/?\1&account=%s"' % self.account.pk, output)
|
||||
return mark_safe(output)
|
||||
formfield.widget.render = render
|
||||
# Filter related object by account
|
||||
|
|
|
@ -1,21 +1,60 @@
|
|||
from django import forms
|
||||
from django.contrib import auth
|
||||
from django.db.models.loading import get_model
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from orchestra.core.validators import validate_password
|
||||
from orchestra.forms import UserCreationForm
|
||||
from orchestra.forms.widgets import ReadOnlyWidget
|
||||
|
||||
from . import settings
|
||||
from .models import Account
|
||||
|
||||
|
||||
def create_account_creation_form():
|
||||
fields = {}
|
||||
for model, key, kwargs, help_text in settings.ACCOUNTS_CREATE_RELATED:
|
||||
model = get_model(model)
|
||||
field_name = 'create_%s' % model._meta.model_name
|
||||
label = _("Create related %s") % model._meta.verbose_name
|
||||
fields[field_name] = forms.BooleanField(initial=True, required=False, label=label,
|
||||
help_text=help_text)
|
||||
|
||||
class AccountCreationForm(UserCreationForm):
|
||||
def clean_username(self):
|
||||
# Since model.clean() will check this, this is redundant,
|
||||
# but it sets a nicer error message than the ORM and avoids conflicts with contrib.auth
|
||||
username = self.cleaned_data["username"]
|
||||
account_model = self._meta.model
|
||||
if hasattr(account_model, 'systemusers'):
|
||||
systemuser_model = account_model.systemusers.related.model
|
||||
if systemuser_model.objects.filter(username=username).exists():
|
||||
raise forms.ValidationError(self.error_messages['duplicate_username'])
|
||||
return username
|
||||
def clean(self):
|
||||
""" unique usernames between accounts and system users """
|
||||
cleaned_data = UserCreationForm.clean(self)
|
||||
try:
|
||||
account = Account(
|
||||
username=cleaned_data['username'],
|
||||
password=cleaned_data['password1']
|
||||
)
|
||||
except KeyError:
|
||||
# Previous validation error
|
||||
return
|
||||
for model, key, related_kwargs, __ in settings.ACCOUNTS_CREATE_RELATED:
|
||||
model = get_model(model)
|
||||
kwargs = {
|
||||
key: eval(related_kwargs[key], {'account': account})
|
||||
}
|
||||
if model.objects.filter(**kwargs).exists():
|
||||
verbose_name = model._meta.verbose_name
|
||||
raise forms.ValidationError(
|
||||
_("A %s with this name already exists") % verbose_name
|
||||
)
|
||||
|
||||
def save_related(self, account):
|
||||
for model, key, related_kwargs, __ in settings.ACCOUNTS_CREATE_RELATED:
|
||||
model = get_model(model)
|
||||
field_name = 'create_%s' % model._meta.model_name
|
||||
if self.cleaned_data[field_name]:
|
||||
for key, value in related_kwargs.iteritems():
|
||||
related_kwargs[key] = eval(value, {'account': account})
|
||||
model.objects.create(account=account, **related_kwargs)
|
||||
|
||||
fields.update({
|
||||
'create_related_fields': fields.keys(),
|
||||
'clean': clean,
|
||||
'save_related': save_related,
|
||||
})
|
||||
|
||||
return type('AccountCreationForm', (UserCreationForm,), fields)
|
||||
|
||||
|
||||
AccountCreationForm = create_account_creation_form()
|
||||
|
|
|
@ -2,6 +2,7 @@ from django.contrib.auth import models as auth
|
|||
from django.conf import settings as djsettings
|
||||
from django.core import validators
|
||||
from django.db import models
|
||||
from django.db.models.loading import get_model
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
@ -57,19 +58,6 @@ class Account(auth.AbstractBaseUser):
|
|||
def get_main(cls):
|
||||
return cls.objects.get(pk=settings.ACCOUNTS_MAIN_PK)
|
||||
|
||||
def clean(self):
|
||||
""" unique usernames between accounts and system users """
|
||||
if not self.pk and hasattr(self, 'systemusers'):
|
||||
if self.systemusers.model.objects.filter(username=self.username).exists():
|
||||
raise validators.ValidationError(_("A user with this name already exists"))
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
created = not self.pk
|
||||
super(Account, self).save(*args, **kwargs)
|
||||
if created and hasattr(self, 'systemusers'):
|
||||
self.systemusers.create(username=self.username, account=self,
|
||||
password=self.password, is_main=True)
|
||||
|
||||
def disable(self):
|
||||
self.is_active = False
|
||||
self.save(update_fields=['is_active'])
|
||||
|
@ -133,5 +121,21 @@ class Account(auth.AbstractBaseUser):
|
|||
return True
|
||||
return auth._user_has_module_perms(self, app_label)
|
||||
|
||||
def get_related_passwords(self):
|
||||
related = []
|
||||
for model, key, kwargs, __ in settings.ACCOUNTS_CREATE_RELATED:
|
||||
if 'password' not in kwargs:
|
||||
continue
|
||||
model = get_model(model)
|
||||
kwargs = {
|
||||
key: eval(kwargs[key], {'account': self})
|
||||
}
|
||||
try:
|
||||
rel = model.objects.get(account=self, **kwargs)
|
||||
except model.DoesNotExist:
|
||||
continue
|
||||
related.append(rel)
|
||||
return related
|
||||
|
||||
|
||||
services.register(Account, menu=False)
|
||||
|
|
|
@ -22,3 +22,32 @@ ACCOUNTS_DEFAULT_LANGUAGE = getattr(settings, 'ACCOUNTS_DEFAULT_LANGUAGE', 'en')
|
|||
|
||||
|
||||
ACCOUNTS_MAIN_PK = getattr(settings, 'ACCOUNTS_MAIN_PK', 1)
|
||||
|
||||
|
||||
ACCOUNTS_CREATE_RELATED = getattr(settings, 'ACCOUNTS_CREATE_RELATED', (
|
||||
# <model>, <key field>, <kwargs>, <help_text>
|
||||
('systemusers.SystemUser',
|
||||
'username',
|
||||
{
|
||||
'username': 'account.username',
|
||||
'password': 'account.password',
|
||||
'is_main': 'True',
|
||||
},
|
||||
_("Designates whether to creates a related system users with the same username and password or not."),
|
||||
),
|
||||
('mailboxes.Mailbox',
|
||||
'name',
|
||||
{
|
||||
'name': 'account.username',
|
||||
'password': 'account.password',
|
||||
},
|
||||
_("Designates whether to creates a related mailbox with the same name and password or not."),
|
||||
),
|
||||
('domains.Domain',
|
||||
'name',
|
||||
{
|
||||
'name': '"%s.orchestra.lan" % account.username'
|
||||
},
|
||||
_("Designates whether to creates a related subdomain <username>.orchestra.lan or not."),
|
||||
),
|
||||
))
|
||||
|
|
|
@ -11,14 +11,14 @@ def validate_contact(request, bill, error=True):
|
|||
'You should <a href="{url}#invoicecontact-group">provide one</a>')
|
||||
valid = True
|
||||
send = messages.error if error else messages.warning
|
||||
if not hasattr(bill.account, 'invoicecontact'):
|
||||
if not hasattr(bill.account, 'billcontact'):
|
||||
account = force_text(bill.account)
|
||||
url = reverse('admin:accounts_account_change', args=(bill.account_id,))
|
||||
message = msg.format(relation=_("Related"), account=account, url=url)
|
||||
send(request, mark_safe(message))
|
||||
valid = False
|
||||
main = type(bill).account.field.rel.to.get_main()
|
||||
if not hasattr(main, 'invoicecontact'):
|
||||
if not hasattr(main, 'billcontact'):
|
||||
account = force_text(main)
|
||||
url = reverse('admin:accounts_account_change', args=(main.id,))
|
||||
message = msg.format(relation=_("Main"), account=account, url=url)
|
||||
|
|
|
@ -5,7 +5,7 @@ from django.utils.safestring import mark_safe
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from orchestra.admin import ExtendedModelAdmin, ChangePasswordAdminMixin
|
||||
from orchestra.admin.utils import admin_link
|
||||
from orchestra.admin.utils import admin_link, change_url
|
||||
from orchestra.apps.accounts.admin import AccountAdminMixin, SelectAccountAdminMixin
|
||||
|
||||
from .forms import DatabaseCreationForm, DatabaseUserChangeForm, DatabaseUserCreationForm
|
||||
|
@ -13,7 +13,7 @@ from .models import Database, DatabaseUser
|
|||
|
||||
|
||||
class DatabaseAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
|
||||
list_display = ('name', 'type', 'account_link')
|
||||
list_display = ('name', 'type', 'display_users', 'account_link')
|
||||
list_filter = ('type',)
|
||||
search_fields = ['name']
|
||||
change_readonly_fields = ('name', 'type')
|
||||
|
@ -21,7 +21,7 @@ class DatabaseAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
|
|||
fieldsets = (
|
||||
(None, {
|
||||
'classes': ('extrapretty',),
|
||||
'fields': ('account_link', 'name', 'type', 'users'),
|
||||
'fields': ('account_link', 'name', 'type', 'users', 'display_users'),
|
||||
}),
|
||||
)
|
||||
add_fieldsets = (
|
||||
|
@ -39,6 +39,18 @@ class DatabaseAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
|
|||
}),
|
||||
)
|
||||
add_form = DatabaseCreationForm
|
||||
readonly_fields = ('account_link', 'display_users',)
|
||||
filter_horizontal = ['users']
|
||||
|
||||
def display_users(self, db):
|
||||
links = []
|
||||
for user in db.users.all():
|
||||
link = '<a href="%s">%s</a>' % (change_url(user), user.username)
|
||||
links.append(link)
|
||||
return ', '.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)
|
||||
|
@ -56,7 +68,7 @@ class DatabaseAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
|
|||
|
||||
|
||||
class DatabaseUserAdmin(SelectAccountAdminMixin, ChangePasswordAdminMixin, ExtendedModelAdmin):
|
||||
list_display = ('username', 'type', 'account_link')
|
||||
list_display = ('username', 'type', 'display_databases', 'account_link')
|
||||
list_filter = ('type',)
|
||||
search_fields = ['username']
|
||||
form = DatabaseUserChangeForm
|
||||
|
@ -65,7 +77,7 @@ class DatabaseUserAdmin(SelectAccountAdminMixin, ChangePasswordAdminMixin, Exten
|
|||
fieldsets = (
|
||||
(None, {
|
||||
'classes': ('extrapretty',),
|
||||
'fields': ('account_link', 'username', 'password', 'type')
|
||||
'fields': ('account_link', 'username', 'password', 'type', 'display_databases')
|
||||
}),
|
||||
)
|
||||
add_fieldsets = (
|
||||
|
@ -74,6 +86,17 @@ class DatabaseUserAdmin(SelectAccountAdminMixin, ChangePasswordAdminMixin, Exten
|
|||
'fields': ('account_link', 'username', 'password1', 'password2', 'type')
|
||||
}),
|
||||
)
|
||||
readonly_fields = ('account_link', 'display_databases',)
|
||||
|
||||
def display_databases(self, user):
|
||||
links = []
|
||||
for db in user.databases.all():
|
||||
link = '<a href="%s">%s</a>' % (change_url(db), db.name)
|
||||
links.append(link)
|
||||
return ', '.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)
|
||||
|
|
|
@ -39,7 +39,7 @@ class Bind9MasterDomainBackend(ServiceController):
|
|||
def update_conf(self, context):
|
||||
self.append(textwrap.dedent("""\
|
||||
sed '/zone "%(name)s".*/,/^\s*};\s*$/!d' %(conf_path)s | diff -B -I"^\s*//" - <(echo '%(conf)s') || {
|
||||
sed -i -e '/zone "%(name)s".*/,/^\s*};/d' \\
|
||||
sed -i -e '/zone\s\s*"%(name)s".*/,/^\s*};/d' \\
|
||||
-e 'N; /^\\n$/d; P; D' %(conf_path)s
|
||||
echo '%(conf)s' >> %(conf_path)s
|
||||
UPDATED=1
|
||||
|
@ -47,7 +47,7 @@ class Bind9MasterDomainBackend(ServiceController):
|
|||
))
|
||||
# Delete ex-top-domains that are now subdomains
|
||||
self.append(textwrap.dedent("""\
|
||||
sed -i -e '/zone ".*\.%(name)s".*/,/^\s*};\s*$/d' \\
|
||||
sed -i -e '/zone\s\s*".*\.%(name)s".*/,/^\s*};\s*$/d' \\
|
||||
-e 'N; /^\\n$/d; P; D' %(conf_path)s""" % context
|
||||
))
|
||||
if 'zone_path' in context:
|
||||
|
@ -64,7 +64,7 @@ class Bind9MasterDomainBackend(ServiceController):
|
|||
# These can never be top level domains
|
||||
return
|
||||
self.append(textwrap.dedent("""\
|
||||
sed -e '/zone ".*\.%(name)s".*/,/^\s*};\s*$/d' \\
|
||||
sed -e '/zone\s\s*"%(name)s".*/,/^\s*};\s*$/d' \\
|
||||
-e 'N; /^\\n$/d; P; D' %(conf_path)s > %(conf_path)s.tmp""" % context
|
||||
))
|
||||
self.append('diff -B -I"^\s*//" %(conf_path)s.tmp %(conf_path)s || UPDATED=1' % context)
|
||||
|
|
|
@ -21,6 +21,17 @@ class Domain(models.Model):
|
|||
def __unicode__(self):
|
||||
return self.name
|
||||
|
||||
@classmethod
|
||||
def get_top_domain(cls, name):
|
||||
split = name.split('.')
|
||||
top = None
|
||||
for i in range(1, len(split)-1):
|
||||
name = '.'.join(split[i:])
|
||||
domain = Domain.objects.filter(name=name)
|
||||
if domain:
|
||||
top = domain.get()
|
||||
return top
|
||||
|
||||
@property
|
||||
def origin(self):
|
||||
return self.top or self
|
||||
|
@ -39,14 +50,7 @@ class Domain(models.Model):
|
|||
return self.origin.subdomains.all()
|
||||
|
||||
def get_top(self):
|
||||
split = self.name.split('.')
|
||||
top = None
|
||||
for i in range(1, len(split)-1):
|
||||
name = '.'.join(split[i:])
|
||||
domain = Domain.objects.filter(name=name)
|
||||
if domain:
|
||||
top = domain.get()
|
||||
return top
|
||||
return type(self).get_top_domain(self.name)
|
||||
|
||||
def render_zone(self):
|
||||
origin = self.origin
|
||||
|
|
|
@ -27,6 +27,14 @@ class DomainSerializer(AccountSerializerMixin, HyperlinkedModelSerializer):
|
|||
fields = ('url', 'name', 'records')
|
||||
postonly_fields = ('name',)
|
||||
|
||||
def clean_name(self, attrs, source):
|
||||
""" prevent users creating subdomains of other users domains """
|
||||
name = attrs[source]
|
||||
top = Domain.get_top_domain(name)
|
||||
if top and top.account != self.account:
|
||||
raise ValidationError(_("Can not create subdomains of other users domains"))
|
||||
return attrs
|
||||
|
||||
def full_clean(self, instance):
|
||||
""" Checks if everything is consistent """
|
||||
instance = super(DomainSerializer, self).full_clean(instance)
|
||||
|
|
|
@ -124,7 +124,7 @@ class MailmanBackend(ServiceController):
|
|||
'name': mail_list.name,
|
||||
'password': mail_list.password,
|
||||
'domain': mail_list.address_domain or settings.LISTS_DEFAULT_DOMAIN,
|
||||
'address_name': mail_list.address_name,
|
||||
'address_name': mail_list.get_address_name,
|
||||
'address_domain': mail_list.address_domain,
|
||||
'admin': mail_list.admin_email,
|
||||
'mailman_root': settings.LISTS_MAILMAN_ROOT_PATH,
|
||||
|
|
|
@ -12,9 +12,6 @@ class CleanAddressMixin(object):
|
|||
if name and not domain:
|
||||
msg = _("Domain should be selected for provided address name")
|
||||
raise forms.ValidationError(msg)
|
||||
elif not name and domain:
|
||||
msg = _("Address name should be provided for this selected domain")
|
||||
raise forms.ValidationError(msg)
|
||||
return domain
|
||||
|
||||
|
||||
|
|
|
@ -35,6 +35,9 @@ class List(models.Model):
|
|||
return "%s@%s" % (self.address_name, self.address_domain)
|
||||
return ''
|
||||
|
||||
def get_address_name(self):
|
||||
return self.address_name or self.name
|
||||
|
||||
def get_username(self):
|
||||
return self.name
|
||||
|
||||
|
|
|
@ -40,15 +40,15 @@ class ListSerializer(AccountSerializerMixin, HyperlinkedModelSerializer):
|
|||
raise serializers.ValidationError(_("Password required"))
|
||||
return attrs
|
||||
|
||||
def validate(self, attrs):
|
||||
address_domain = attrs.get('address_domain')
|
||||
address_name = attrs.get('address_name', )
|
||||
def validate_address_domain(self, attrs, source):
|
||||
address_domain = attrs.get(source)
|
||||
address_name = attrs.get('address_name')
|
||||
if self.object:
|
||||
address_domain = address_domain or self.object.address_domain
|
||||
address_name = address_name or self.object.address_name
|
||||
if bool(address_domain) != bool(address_name):
|
||||
if address_name and not address_domain:
|
||||
raise serializers.ValidationError(
|
||||
_("address_name and address_domain should go in tandem"))
|
||||
_("address_domains should should be provided when providing an addres_name"))
|
||||
return attrs
|
||||
|
||||
def save_object(self, obj, **kwargs):
|
||||
|
|
|
@ -4,7 +4,7 @@ from django.utils.translation import ugettext_lazy as _
|
|||
|
||||
from orchestra.admin import ExtendedModelAdmin
|
||||
from orchestra.admin.utils import change_url
|
||||
from orchestra.apps.accounts.admin import SelectAccountAdminMixin
|
||||
from orchestra.apps.accounts.admin import AccountAdminMixin
|
||||
|
||||
from .models import WebApp, WebAppOption
|
||||
|
||||
|
@ -25,10 +25,11 @@ class WebAppOptionInline(admin.TabularInline):
|
|||
return super(WebAppOptionInline, self).formfield_for_dbfield(db_field, **kwargs)
|
||||
|
||||
|
||||
class WebAppAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
|
||||
fields = ('account_link', 'name', 'type')
|
||||
class WebAppAdmin(AccountAdminMixin, ExtendedModelAdmin):
|
||||
list_display = ('name', 'type', 'display_websites', 'account_link')
|
||||
list_filter = ('type',)
|
||||
add_fields = ('account', 'name', 'type')
|
||||
fields = ('account_link', 'name', 'type')
|
||||
inlines = [WebAppOptionInline]
|
||||
readonly_fields = ('account_link',)
|
||||
change_readonly_fields = ('name', 'type')
|
||||
|
|
|
@ -41,7 +41,7 @@ def validate_name(value):
|
|||
"""
|
||||
A single non-empty line of free-form text with no whitespace.
|
||||
"""
|
||||
validators.RegexValidator('^[\.\w]+$',
|
||||
validators.RegexValidator('^[\.\w\-]+$',
|
||||
_("Enter a valid name (text without whitspaces)."), 'invalid')(value)
|
||||
|
||||
|
||||
|
|
|
@ -38,6 +38,7 @@ class UserCreationForm(forms.ModelForm):
|
|||
"""
|
||||
error_messages = {
|
||||
'password_mismatch': _("The two password fields didn't match."),
|
||||
'duplicate_username': _("A user with that username already exists."),
|
||||
}
|
||||
password1 = forms.CharField(label=_("Password"),
|
||||
widget=forms.PasswordInput, validators=[validate_password])
|
||||
|
|
Loading…
Reference in New Issue