Removed old users app
This commit is contained in:
parent
23d62c2d77
commit
5ed8773acc
|
@ -1,110 +0,0 @@
|
||||||
from django.conf.urls import patterns, url
|
|
||||||
from django.core.urlresolvers import reverse
|
|
||||||
from django.contrib import admin
|
|
||||||
from django.contrib.admin.util import unquote
|
|
||||||
from django.contrib.auth import admin as auth
|
|
||||||
from django.utils.translation import ugettext, ugettext_lazy as _
|
|
||||||
|
|
||||||
from orchestra.admin import ExtendedModelAdmin
|
|
||||||
from orchestra.admin.utils import wrap_admin_view
|
|
||||||
from orchestra.apps.accounts.admin import AccountAdminMixin
|
|
||||||
|
|
||||||
from .forms import UserCreationForm, UserChangeForm
|
|
||||||
from .models import User
|
|
||||||
from .roles.filters import role_list_filter_factory
|
|
||||||
|
|
||||||
|
|
||||||
class UserAdmin(AccountAdminMixin, auth.UserAdmin, ExtendedModelAdmin):
|
|
||||||
list_display = ('username', 'display_is_main')
|
|
||||||
list_filter = ('is_staff', 'is_superuser', 'is_active')
|
|
||||||
fieldsets = (
|
|
||||||
(None, {
|
|
||||||
'fields': ('account', 'username', 'password')
|
|
||||||
}),
|
|
||||||
(_("Personal info"), {
|
|
||||||
'fields': ('first_name', 'last_name', 'email')
|
|
||||||
}),
|
|
||||||
(_("Permissions"), {
|
|
||||||
'fields': ('is_active', 'is_staff', 'is_superuser', 'display_is_main')
|
|
||||||
}),
|
|
||||||
(_("Important dates"), {
|
|
||||||
'fields': ('last_login', 'date_joined')
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
add_fieldsets = (
|
|
||||||
(None, {
|
|
||||||
'classes': ('wide',),
|
|
||||||
'fields': ('username', 'password1', 'password2', 'account'),
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
search_fields = ['username', 'account__user__username']
|
|
||||||
readonly_fields = ('display_is_main', 'account_link')
|
|
||||||
change_readonly_fields = ('username',)
|
|
||||||
filter_horizontal = ()
|
|
||||||
add_form = UserCreationForm
|
|
||||||
form = UserChangeForm
|
|
||||||
roles = []
|
|
||||||
ordering = ('-id',)
|
|
||||||
change_form_template = 'admin/users/user/change_form.html'
|
|
||||||
|
|
||||||
def display_is_main(self, instance):
|
|
||||||
return instance.is_main
|
|
||||||
display_is_main.short_description = _("is main")
|
|
||||||
display_is_main.boolean = True
|
|
||||||
|
|
||||||
def get_urls(self):
|
|
||||||
""" Returns the additional urls for the change view links """
|
|
||||||
urls = super(UserAdmin, self).get_urls()
|
|
||||||
opts = self.model._meta
|
|
||||||
new_urls = patterns("")
|
|
||||||
for role in self.roles:
|
|
||||||
new_urls += patterns("",
|
|
||||||
url('^(\d+)/%s/$' % role.url_name,
|
|
||||||
wrap_admin_view(self, role().change_view),
|
|
||||||
name='%s_%s_%s_change' % (opts.app_label, opts.model_name, role.name)),
|
|
||||||
url('^(\d+)/%s/delete/$' % role.url_name,
|
|
||||||
wrap_admin_view(self, role().delete_view),
|
|
||||||
name='%s_%s_%s_delete' % (opts.app_label, opts.model_name, role.name))
|
|
||||||
)
|
|
||||||
return new_urls + urls
|
|
||||||
|
|
||||||
def get_fieldsets(self, request, obj=None):
|
|
||||||
fieldsets = super(UserAdmin, self).get_fieldsets(request, obj=obj)
|
|
||||||
if obj and obj.account:
|
|
||||||
fieldsets[0][1]['fields'] = ('account_link',) + fieldsets[0][1]['fields'][1:]
|
|
||||||
return fieldsets
|
|
||||||
|
|
||||||
def get_list_display(self, request):
|
|
||||||
roles = []
|
|
||||||
for role in self.roles:
|
|
||||||
def has_role(user, role_class=role):
|
|
||||||
role = role_class(user=user)
|
|
||||||
if role.exists:
|
|
||||||
return '<img src="/static/admin/img/icon-yes.gif" alt="True">'
|
|
||||||
url = reverse('admin:users_user_%s_change' % role.name, args=(user.pk,))
|
|
||||||
false = '<img src="/static/admin/img/icon-no.gif" alt="False">'
|
|
||||||
return '<a href="%s">%s</a>' % (url, false)
|
|
||||||
has_role.short_description = _("Has %s") % role.name
|
|
||||||
has_role.admin_order_field = role.name
|
|
||||||
has_role.allow_tags = True
|
|
||||||
roles.append(has_role)
|
|
||||||
return list(self.list_display) + roles + ['account_link']
|
|
||||||
|
|
||||||
def get_list_filter(self, request):
|
|
||||||
roles = [ role_list_filter_factory(role) for role in self.roles ]
|
|
||||||
return list(self.list_filter) + roles
|
|
||||||
|
|
||||||
def change_view(self, request, object_id, **kwargs):
|
|
||||||
user = self.get_object(User, unquote(object_id))
|
|
||||||
extra_context = kwargs.get('extra_context', {})
|
|
||||||
extra_context['roles'] = [ role(user=user) for role in self.roles ]
|
|
||||||
kwargs['extra_context'] = extra_context
|
|
||||||
return super(UserAdmin, self).change_view(request, object_id, **kwargs)
|
|
||||||
|
|
||||||
def get_queryset(self, request):
|
|
||||||
""" Select related for performance """
|
|
||||||
related = ['account__user'] + [ role.name for role in self.roles ]
|
|
||||||
return super(UserAdmin, self).get_queryset(request).select_related(*related)
|
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(User, UserAdmin)
|
|
|
@ -1,20 +0,0 @@
|
||||||
from django.contrib.auth import get_user_model
|
|
||||||
from rest_framework import viewsets
|
|
||||||
|
|
||||||
from orchestra.api import router, SetPasswordApiMixin
|
|
||||||
from orchestra.apps.accounts.api import AccountApiMixin
|
|
||||||
|
|
||||||
from .serializers import UserSerializer
|
|
||||||
|
|
||||||
|
|
||||||
class UserViewSet(AccountApiMixin, SetPasswordApiMixin, viewsets.ModelViewSet):
|
|
||||||
model = get_user_model()
|
|
||||||
serializer_class = UserSerializer
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
""" select related roles """
|
|
||||||
qs = super(UserViewSet, self).get_queryset()
|
|
||||||
return qs.select_related(*self.inserted)
|
|
||||||
|
|
||||||
|
|
||||||
router.register(r'users', UserViewSet)
|
|
|
@ -1,123 +0,0 @@
|
||||||
import textwrap
|
|
||||||
|
|
||||||
from django.utils import timezone
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
from orchestra.apps.orchestration import ServiceController
|
|
||||||
from orchestra.apps.resources import ServiceMonitor
|
|
||||||
|
|
||||||
from . import settings
|
|
||||||
|
|
||||||
|
|
||||||
class SystemUserBackend(ServiceController):
|
|
||||||
verbose_name = _("System User")
|
|
||||||
model = 'users.User'
|
|
||||||
ignore_fields = ['last_login']
|
|
||||||
|
|
||||||
def save(self, user):
|
|
||||||
context = self.get_context(user)
|
|
||||||
if user.is_main:
|
|
||||||
self.append(textwrap.dedent("""
|
|
||||||
if [[ $( id %(username)s ) ]]; then
|
|
||||||
usermod --password '%(password)s' %(username)s
|
|
||||||
else
|
|
||||||
useradd %(username)s --password '%(password)s' \\
|
|
||||||
--shell /bin/false
|
|
||||||
fi
|
|
||||||
mkdir -p %(home)s
|
|
||||||
chown %(username)s.%(username)s %(home)s""" % context
|
|
||||||
))
|
|
||||||
else:
|
|
||||||
self.delete(user)
|
|
||||||
|
|
||||||
def delete(self, user):
|
|
||||||
context = self.get_context(user)
|
|
||||||
self.append("{ sleep 2 && killall -u %(username)s -s KILL; } &" % context)
|
|
||||||
self.append("killall -u %(username)s" % context)
|
|
||||||
self.append("userdel %(username)s" % context)
|
|
||||||
|
|
||||||
def get_context(self, user):
|
|
||||||
context = {
|
|
||||||
'username': user.username,
|
|
||||||
'password': user.password if user.is_active else '*%s' % user.password,
|
|
||||||
}
|
|
||||||
context['home'] = settings.USERS_SYSTEMUSER_HOME % context
|
|
||||||
return context
|
|
||||||
|
|
||||||
|
|
||||||
class SystemUserDisk(ServiceMonitor):
|
|
||||||
model = 'users.User'
|
|
||||||
resource = ServiceMonitor.DISK
|
|
||||||
verbose_name = _('System user disk')
|
|
||||||
|
|
||||||
def monitor(self, user):
|
|
||||||
context = self.get_context(user)
|
|
||||||
self.append("du -s %(home)s | xargs echo %(object_id)s" % context)
|
|
||||||
|
|
||||||
def get_context(self, user):
|
|
||||||
context = SystemUserBackend().get_context(user)
|
|
||||||
context['object_id'] = user.pk
|
|
||||||
return context
|
|
||||||
|
|
||||||
|
|
||||||
class FTPTraffic(ServiceMonitor):
|
|
||||||
model = 'users.User'
|
|
||||||
resource = ServiceMonitor.TRAFFIC
|
|
||||||
verbose_name = _('FTP traffic')
|
|
||||||
|
|
||||||
def prepare(self):
|
|
||||||
current_date = timezone.localtime(self.current_date)
|
|
||||||
current_date = current_date.strftime("%Y%m%d%H%M%S")
|
|
||||||
self.append(textwrap.dedent("""
|
|
||||||
function monitor () {
|
|
||||||
OBJECT_ID=$1
|
|
||||||
INI_DATE=$2
|
|
||||||
USERNAME="$3"
|
|
||||||
LOG_FILE="$4"
|
|
||||||
grep "UPLOAD\|DOWNLOAD" "${LOG_FILE}" \\
|
|
||||||
| grep " \\[${USERNAME}\\] " \\
|
|
||||||
| awk -v ini="${INI_DATE}" '
|
|
||||||
BEGIN {
|
|
||||||
end = "%s"
|
|
||||||
sum = 0
|
|
||||||
months["Jan"] = "01"
|
|
||||||
months["Feb"] = "02"
|
|
||||||
months["Mar"] = "03"
|
|
||||||
months["Apr"] = "04"
|
|
||||||
months["May"] = "05"
|
|
||||||
months["Jun"] = "06"
|
|
||||||
months["Jul"] = "07"
|
|
||||||
months["Aug"] = "08"
|
|
||||||
months["Sep"] = "09"
|
|
||||||
months["Oct"] = "10"
|
|
||||||
months["Nov"] = "11"
|
|
||||||
months["Dec"] = "12"
|
|
||||||
} {
|
|
||||||
# log: Fri Jul 11 13:23:17 2014
|
|
||||||
split($4, t, ":")
|
|
||||||
# line_date = year month day hour minute second
|
|
||||||
line_date = $5 months[$2] $3 t[1] t[2] t[3]
|
|
||||||
if ( line_date > ini && line_date < end)
|
|
||||||
split($0, l, "\\", ")
|
|
||||||
split(l[3], b, " ")
|
|
||||||
sum += b[1]
|
|
||||||
} END {
|
|
||||||
print sum
|
|
||||||
}
|
|
||||||
' | xargs echo ${OBJECT_ID}
|
|
||||||
}""" % current_date))
|
|
||||||
|
|
||||||
def monitor(self, user):
|
|
||||||
context = self.get_context(user)
|
|
||||||
self.append(
|
|
||||||
'monitor %(object_id)i %(last_date)s "%(username)s" "%(log_file)s"' % context)
|
|
||||||
|
|
||||||
def get_context(self, user):
|
|
||||||
last_date = timezone.localtime(self.get_last_date(user.pk))
|
|
||||||
return {
|
|
||||||
'log_file': settings.USERS_FTP_LOG_PATH,
|
|
||||||
'last_date': last_date.strftime("%Y%m%d%H%M%S"),
|
|
||||||
'object_id': user.pk,
|
|
||||||
'username': user.username,
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,49 +0,0 @@
|
||||||
from django import forms
|
|
||||||
from django.contrib import auth
|
|
||||||
from django.utils.translation import ugettext, ugettext_lazy as _
|
|
||||||
|
|
||||||
from orchestra.core.validators import validate_password
|
|
||||||
|
|
||||||
from .models import User
|
|
||||||
|
|
||||||
|
|
||||||
class UserCreationForm(auth.forms.UserCreationForm):
|
|
||||||
class Meta(auth.forms.UserCreationForm.Meta):
|
|
||||||
model = User
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(UserCreationForm, self).__init__(*args, **kwargs)
|
|
||||||
self.fields['password1'].validators.append(validate_password)
|
|
||||||
|
|
||||||
def clean_username(self):
|
|
||||||
# Since User.username is unique, this check is redundant,
|
|
||||||
# but it sets a nicer error message than the ORM. See #13147.
|
|
||||||
username = self.cleaned_data["username"]
|
|
||||||
try:
|
|
||||||
User._default_manager.get(username=username)
|
|
||||||
except User.DoesNotExist:
|
|
||||||
return username
|
|
||||||
raise forms.ValidationError(self.error_messages['duplicate_username'])
|
|
||||||
|
|
||||||
|
|
||||||
class UserChangeForm(forms.ModelForm):
|
|
||||||
password = auth.forms.ReadOnlyPasswordHashField(label=_("Password"),
|
|
||||||
help_text=_("Raw passwords are not stored, so there is no way to see "
|
|
||||||
"this user's password, but you can change the password "
|
|
||||||
"using <a href=\"password/\">this form</a>."))
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = User
|
|
||||||
fields = '__all__'
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(UserChangeForm, self).__init__(*args, **kwargs)
|
|
||||||
f = self.fields.get('user_permissions', None)
|
|
||||||
if f is not None:
|
|
||||||
f.queryset = f.queryset.select_related('content_type')
|
|
||||||
|
|
||||||
def clean_password(self):
|
|
||||||
# Regardless of what the user provides, return the initial value.
|
|
||||||
# This is done here, rather than on the field, because the
|
|
||||||
# field does not have access to the initial value
|
|
||||||
return self.initial["password"]
|
|
|
@ -1,92 +0,0 @@
|
||||||
from django.contrib.auth import models as auth
|
|
||||||
from django.core import validators
|
|
||||||
from django.core.mail import send_mail
|
|
||||||
from django.db import models
|
|
||||||
from django.utils import timezone
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
from orchestra.core import services
|
|
||||||
|
|
||||||
|
|
||||||
class User(auth.AbstractBaseUser):
|
|
||||||
username = models.CharField(_("username"), max_length=64, unique=True,
|
|
||||||
help_text=_("Required. 30 characters or fewer. Letters, digits and "
|
|
||||||
"./-/_ only."),
|
|
||||||
validators=[validators.RegexValidator(r'^[\w.-]+$',
|
|
||||||
_("Enter a valid username."), 'invalid')])
|
|
||||||
account = models.ForeignKey('accounts.Account', verbose_name=_("Account"),
|
|
||||||
related_name='users')
|
|
||||||
first_name = models.CharField(_("first name"), max_length=30, blank=True)
|
|
||||||
last_name = models.CharField(_("last name"), max_length=30, blank=True)
|
|
||||||
email = models.EmailField(_('email address'), blank=True)
|
|
||||||
is_superuser = models.BooleanField(_("superuser status"), default=False,
|
|
||||||
help_text=_("Designates that this user has all permissions without "
|
|
||||||
"explicitly assigning them."))
|
|
||||||
is_staff = models.BooleanField(_("staff status"), default=False,
|
|
||||||
help_text=_("Designates whether the user can log into this admin "
|
|
||||||
"site."))
|
|
||||||
is_admin = models.BooleanField(_("admin status"), default=False,
|
|
||||||
help_text=_("Designates whether the user can administrate its account."))
|
|
||||||
is_active = models.BooleanField(_("active"), default=True,
|
|
||||||
help_text=_("Designates whether this user should be treated as "
|
|
||||||
"active. Unselect this instead of deleting accounts."))
|
|
||||||
date_joined = models.DateTimeField(_("date joined"), default=timezone.now)
|
|
||||||
|
|
||||||
objects = auth.UserManager()
|
|
||||||
|
|
||||||
USERNAME_FIELD = 'username'
|
|
||||||
REQUIRED_FIELDS = ['email']
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_main(self):
|
|
||||||
return self.account.user == self
|
|
||||||
|
|
||||||
def get_full_name(self):
|
|
||||||
full_name = '%s %s' % (self.first_name, self.last_name)
|
|
||||||
return full_name.strip() or self.username
|
|
||||||
|
|
||||||
def get_short_name(self):
|
|
||||||
""" Returns the short name for the user """
|
|
||||||
return self.first_name
|
|
||||||
|
|
||||||
def email_user(self, subject, message, from_email=None, **kwargs):
|
|
||||||
""" Sends an email to this User """
|
|
||||||
send_mail(subject, message, from_email, [self.email], **kwargs)
|
|
||||||
|
|
||||||
def has_perm(self, perm, obj=None):
|
|
||||||
"""
|
|
||||||
Returns True if the user has the specified permission. This method
|
|
||||||
queries all available auth backends, but returns immediately if any
|
|
||||||
backend returns True. Thus, a user who has permission from a single
|
|
||||||
auth backend is assumed to have permission in general. If an object is
|
|
||||||
provided, permissions for this specific object are checked.
|
|
||||||
"""
|
|
||||||
# Active superusers have all permissions.
|
|
||||||
if self.is_active and self.is_superuser:
|
|
||||||
return True
|
|
||||||
# Otherwise we need to check the backends.
|
|
||||||
return auth._user_has_perm(self, perm, obj)
|
|
||||||
|
|
||||||
def has_perms(self, perm_list, obj=None):
|
|
||||||
"""
|
|
||||||
Returns True if the user has each of the specified permissions. If
|
|
||||||
object is passed, it checks if the user has all required perms for this
|
|
||||||
object.
|
|
||||||
"""
|
|
||||||
for perm in perm_list:
|
|
||||||
if not self.has_perm(perm, obj):
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def has_module_perms(self, app_label):
|
|
||||||
"""
|
|
||||||
Returns True if the user has any permissions in the given app label.
|
|
||||||
Uses pretty much the same logic as has_perm, above.
|
|
||||||
"""
|
|
||||||
# Active superusers have all permissions.
|
|
||||||
if self.is_active and self.is_superuser:
|
|
||||||
return True
|
|
||||||
return auth._user_has_module_perms(self, app_label)
|
|
||||||
|
|
||||||
|
|
||||||
services.register(User, menu=False)
|
|
|
@ -1,27 +0,0 @@
|
||||||
from ..models import User
|
|
||||||
|
|
||||||
|
|
||||||
class Register(object):
|
|
||||||
def __init__(self):
|
|
||||||
self._registry = {}
|
|
||||||
|
|
||||||
def __contains__(self, key):
|
|
||||||
return key in self._registry
|
|
||||||
|
|
||||||
def register(self, name, model):
|
|
||||||
if name in self._registry:
|
|
||||||
raise KeyError("%s already registered" % name)
|
|
||||||
def has_role(user, model=model):
|
|
||||||
try:
|
|
||||||
getattr(user, name)
|
|
||||||
except model.DoesNotExist:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
setattr(User, 'has_%s' % name, has_role)
|
|
||||||
self._registry[name] = model
|
|
||||||
|
|
||||||
def get(self):
|
|
||||||
return self._registry
|
|
||||||
|
|
||||||
|
|
||||||
roles = Register()
|
|
|
@ -1,145 +0,0 @@
|
||||||
from django.contrib import messages
|
|
||||||
from django.contrib.admin.util import unquote, get_deleted_objects
|
|
||||||
from django.contrib.admin.templatetags.admin_urls import add_preserved_filters
|
|
||||||
from django.db import router
|
|
||||||
from django.http import Http404, HttpResponseRedirect
|
|
||||||
from django.template.response import TemplateResponse
|
|
||||||
from django.shortcuts import redirect
|
|
||||||
from django.utils.encoding import force_text
|
|
||||||
from django.utils.html import escape
|
|
||||||
from django.utils.translation import ugettext, ugettext_lazy as _
|
|
||||||
|
|
||||||
from orchestra.admin.utils import get_modeladmin, change_url
|
|
||||||
|
|
||||||
from .forms import role_form_factory
|
|
||||||
from ..models import User
|
|
||||||
|
|
||||||
|
|
||||||
class RoleAdmin(object):
|
|
||||||
model = None
|
|
||||||
name = ''
|
|
||||||
url_name = ''
|
|
||||||
form = None
|
|
||||||
|
|
||||||
def __init__(self, user=None):
|
|
||||||
self.user = user
|
|
||||||
|
|
||||||
@property
|
|
||||||
def exists(self):
|
|
||||||
try:
|
|
||||||
return getattr(self.user, self.name)
|
|
||||||
except self.model.DoesNotExist:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def get_user(self, request, object_id):
|
|
||||||
try:
|
|
||||||
user = User.objects.get(pk=unquote(object_id))
|
|
||||||
except User.DoesNotExist:
|
|
||||||
opts = self.model._meta
|
|
||||||
raise Http404(
|
|
||||||
_('%(name)s object with primary key %(key)r does not exist.') %
|
|
||||||
{'name': force_text(opts.verbose_name), 'key': escape(object_id)}
|
|
||||||
)
|
|
||||||
return user
|
|
||||||
|
|
||||||
def change_view(self, request, object_id):
|
|
||||||
modeladmin = get_modeladmin(User)
|
|
||||||
user = self.get_user(request, object_id)
|
|
||||||
self.user = user
|
|
||||||
obj = None
|
|
||||||
exists = self.exists
|
|
||||||
if exists:
|
|
||||||
obj = getattr(user, self.name)
|
|
||||||
form_class = self.form if self.form else role_form_factory(self)
|
|
||||||
form = form_class(instance=obj)
|
|
||||||
opts = User._meta
|
|
||||||
app_label = opts.app_label
|
|
||||||
title = _("Add %s for user %s" % (self.name, user))
|
|
||||||
action = _("Create")
|
|
||||||
# User has submitted the form
|
|
||||||
if request.method == 'POST':
|
|
||||||
form = form_class(request.POST, instance=obj)
|
|
||||||
form.user = user
|
|
||||||
if form.is_valid():
|
|
||||||
obj = form.save()
|
|
||||||
context = {
|
|
||||||
'name': obj._meta.verbose_name,
|
|
||||||
'obj': obj,
|
|
||||||
'action': _("saved" if exists else "created")
|
|
||||||
}
|
|
||||||
modeladmin.log_change(request, request.user, "%s saved" % self.name.capitalize())
|
|
||||||
msg = _('The role %(name)s for user "%(obj)s" was %(action)s successfully.') % context
|
|
||||||
modeladmin.message_user(request, msg, messages.SUCCESS)
|
|
||||||
if not "_continue" in request.POST:
|
|
||||||
return redirect(change_url(user))
|
|
||||||
exists = True
|
|
||||||
|
|
||||||
if exists:
|
|
||||||
title = _("Change %s %s settings" % (user, self.name))
|
|
||||||
action = _("Save")
|
|
||||||
form = form_class(instance=obj)
|
|
||||||
|
|
||||||
context = {
|
|
||||||
'title': title,
|
|
||||||
'opts': opts,
|
|
||||||
'app_label': app_label,
|
|
||||||
'form': form,
|
|
||||||
'action': action,
|
|
||||||
'role': self,
|
|
||||||
'roles': [ role(user=user) for role in modeladmin.roles ],
|
|
||||||
'media': modeladmin.media
|
|
||||||
}
|
|
||||||
|
|
||||||
template = 'admin/users/user/role.html'
|
|
||||||
app = modeladmin.admin_site.name
|
|
||||||
return TemplateResponse(request, template, context, current_app=app)
|
|
||||||
|
|
||||||
def delete_view(self, request, object_id):
|
|
||||||
"The 'delete' admin view for this model."
|
|
||||||
opts = self.model._meta
|
|
||||||
app_label = opts.app_label
|
|
||||||
modeladmin = get_modeladmin(User)
|
|
||||||
user = self.get_user(request, object_id)
|
|
||||||
obj = getattr(user, self.name)
|
|
||||||
|
|
||||||
using = router.db_for_write(self.model)
|
|
||||||
|
|
||||||
# Populate deleted_objects, a data structure of all related objects that
|
|
||||||
# will also be deleted.
|
|
||||||
(deleted_objects, perms_needed, protected) = get_deleted_objects(
|
|
||||||
[obj], opts, request.user, modeladmin.admin_site, using)
|
|
||||||
|
|
||||||
if request.POST: # The user has already confirmed the deletion.
|
|
||||||
if perms_needed:
|
|
||||||
raise PermissionDenied
|
|
||||||
obj_display = force_text(obj)
|
|
||||||
modeladmin.log_deletion(request, obj, obj_display)
|
|
||||||
modeladmin.delete_model(request, obj)
|
|
||||||
post_url = change_url(user)
|
|
||||||
preserved_filters = modeladmin.get_preserved_filters(request)
|
|
||||||
post_url = add_preserved_filters(
|
|
||||||
{'preserved_filters': preserved_filters, 'opts': opts}, post_url
|
|
||||||
)
|
|
||||||
return HttpResponseRedirect(post_url)
|
|
||||||
|
|
||||||
object_name = force_text(opts.verbose_name)
|
|
||||||
|
|
||||||
if perms_needed or protected:
|
|
||||||
title = _("Cannot delete %(name)s") % {"name": object_name}
|
|
||||||
else:
|
|
||||||
title = _("Are you sure?")
|
|
||||||
|
|
||||||
context = {
|
|
||||||
"title": title,
|
|
||||||
"object_name": object_name,
|
|
||||||
"object": obj,
|
|
||||||
"deleted_objects": deleted_objects,
|
|
||||||
"perms_lacking": perms_needed,
|
|
||||||
"protected": protected,
|
|
||||||
"opts": opts,
|
|
||||||
"app_label": app_label,
|
|
||||||
'preserved_filters': modeladmin.get_preserved_filters(request),
|
|
||||||
'role': self,
|
|
||||||
}
|
|
||||||
return TemplateResponse(request, 'admin/users/user/delete_role.html',
|
|
||||||
context, current_app=modeladmin.admin_site.name)
|
|
|
@ -1,23 +0,0 @@
|
||||||
from django.contrib.admin import SimpleListFilter
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
|
|
||||||
def role_list_filter_factory(role):
|
|
||||||
class RoleListFilter(SimpleListFilter):
|
|
||||||
""" Filter Nodes by group according to request.user """
|
|
||||||
title = _("has %s" % role.name)
|
|
||||||
parameter_name = role.url_name
|
|
||||||
|
|
||||||
def lookups(self, request, model_admin):
|
|
||||||
return (
|
|
||||||
('True', _("Yes")),
|
|
||||||
('False', _("No")),
|
|
||||||
)
|
|
||||||
|
|
||||||
def queryset(self, request, queryset):
|
|
||||||
if self.value() == 'True':
|
|
||||||
return queryset.filter(**{ '%s__isnull' % role.name: False })
|
|
||||||
if self.value() == 'False':
|
|
||||||
return queryset.filter(**{ '%s__isnull' % role.name: True })
|
|
||||||
|
|
||||||
return RoleListFilter
|
|
|
@ -1,17 +0,0 @@
|
||||||
from django import forms
|
|
||||||
|
|
||||||
|
|
||||||
class RoleAdminBaseForm(forms.ModelForm):
|
|
||||||
class Meta:
|
|
||||||
exclude = ('user', )
|
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
|
||||||
self.instance.user = self.user
|
|
||||||
return super(RoleAdminBaseForm, self).save(*args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
def role_form_factory(role):
|
|
||||||
class RoleAdminForm(RoleAdminBaseForm):
|
|
||||||
class Meta(RoleAdminBaseForm.Meta):
|
|
||||||
model = role.model
|
|
||||||
return RoleAdminForm
|
|
|
@ -1,15 +0,0 @@
|
||||||
from django.contrib.auth import get_user_model
|
|
||||||
|
|
||||||
from orchestra.admin.utils import insertattr
|
|
||||||
from orchestra.apps.users.roles.admin import RoleAdmin
|
|
||||||
|
|
||||||
from .models import Jabber
|
|
||||||
|
|
||||||
|
|
||||||
class JabberRoleAdmin(RoleAdmin):
|
|
||||||
model = Jabber
|
|
||||||
name = 'jabber'
|
|
||||||
url_name = 'jabber'
|
|
||||||
|
|
||||||
|
|
||||||
insertattr(get_user_model(), 'roles', JabberRoleAdmin)
|
|
|
@ -1,15 +0,0 @@
|
||||||
from django.db import models
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
from .. import roles
|
|
||||||
|
|
||||||
|
|
||||||
class Jabber(models.Model):
|
|
||||||
user = models.OneToOneField('users.User', verbose_name=_("user"),
|
|
||||||
related_name='jabber')
|
|
||||||
|
|
||||||
def __unicode__(self):
|
|
||||||
return str(self.user)
|
|
||||||
|
|
||||||
|
|
||||||
roles.register('jabber', Jabber)
|
|
|
@ -1,124 +0,0 @@
|
||||||
from django import forms
|
|
||||||
from django.contrib import admin
|
|
||||||
from django.contrib.auth import get_user_model
|
|
||||||
from django.core.urlresolvers import reverse
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
from orchestra.admin import ExtendedModelAdmin
|
|
||||||
from orchestra.admin.utils import insertattr, admin_link
|
|
||||||
from orchestra.apps.accounts.admin import SelectAccountAdminMixin
|
|
||||||
from orchestra.apps.users.roles.admin import RoleAdmin
|
|
||||||
|
|
||||||
from .forms import MailRoleAdminForm
|
|
||||||
from .models import Mailbox, Address, Autoresponse
|
|
||||||
|
|
||||||
|
|
||||||
class AutoresponseInline(admin.StackedInline):
|
|
||||||
model = Autoresponse
|
|
||||||
verbose_name_plural = _("autoresponse")
|
|
||||||
|
|
||||||
def formfield_for_dbfield(self, db_field, **kwargs):
|
|
||||||
if db_field.name == 'subject':
|
|
||||||
kwargs['widget'] = forms.TextInput(attrs={'size':'118'})
|
|
||||||
return super(AutoresponseInline, self).formfield_for_dbfield(db_field, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
#class AddressAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
|
|
||||||
# list_display = ('email', 'domain_link', 'mailboxes', 'forwards', 'account_link')
|
|
||||||
# fields = ('account_link', ('name', 'domain'), 'destination')
|
|
||||||
# inlines = [AutoresponseInline]
|
|
||||||
# search_fields = ('name', 'domain__name',)
|
|
||||||
# readonly_fields = ('account_link', 'domain_link', 'email_link')
|
|
||||||
# filter_by_account_fields = ['domain']
|
|
||||||
#
|
|
||||||
# domain_link = link('domain', order='domain__name')
|
|
||||||
#
|
|
||||||
# def email_link(self, address):
|
|
||||||
# link = self.domain_link(address)
|
|
||||||
# return "%s@%s" % (address.name, link)
|
|
||||||
# email_link.short_description = _("Email")
|
|
||||||
# email_link.allow_tags = True
|
|
||||||
#
|
|
||||||
# def mailboxes(self, address):
|
|
||||||
# boxes = []
|
|
||||||
# for mailbox in address.get_mailboxes():
|
|
||||||
# user = mailbox.user
|
|
||||||
# url = reverse('admin:users_user_mailbox_change', args=(user.pk,))
|
|
||||||
# boxes.append('<a href="%s">%s</a>' % (url, user.username))
|
|
||||||
# return '<br>'.join(boxes)
|
|
||||||
# mailboxes.allow_tags = True
|
|
||||||
#
|
|
||||||
# def forwards(self, address):
|
|
||||||
# values = [ dest for dest in address.destination.split() if '@' in dest ]
|
|
||||||
# return '<br>'.join(values)
|
|
||||||
# forwards.allow_tags = True
|
|
||||||
#
|
|
||||||
# def formfield_for_dbfield(self, db_field, **kwargs):
|
|
||||||
# if db_field.name == 'destination':
|
|
||||||
# kwargs['widget'] = forms.TextInput(attrs={'size':'118'})
|
|
||||||
# return super(AddressAdmin, self).formfield_for_dbfield(db_field, **kwargs)
|
|
||||||
#
|
|
||||||
# def queryset(self, request):
|
|
||||||
# """ Select related for performance """
|
|
||||||
# qs = super(AddressAdmin, self).queryset(request)
|
|
||||||
# return qs.select_related('domain')
|
|
||||||
|
|
||||||
|
|
||||||
class AddressAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
|
|
||||||
list_display = (
|
|
||||||
'email', 'domain_link', 'display_mailboxes', 'display_forward', 'account_link'
|
|
||||||
)
|
|
||||||
fields = ('account_link', ('name', 'domain'), 'mailboxes', 'forward')
|
|
||||||
inlines = [AutoresponseInline]
|
|
||||||
search_fields = ('name', 'domain__name',)
|
|
||||||
readonly_fields = ('account_link', 'domain_link', 'email_link')
|
|
||||||
filter_by_account_fields = ['domain']
|
|
||||||
filter_horizontal = ['mailboxes']
|
|
||||||
|
|
||||||
domain_link = admin_link('domain', order='domain__name')
|
|
||||||
|
|
||||||
def email_link(self, address):
|
|
||||||
link = self.domain_link(address)
|
|
||||||
return "%s@%s" % (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():
|
|
||||||
user = mailbox.user
|
|
||||||
url = reverse('admin:users_user_mailbox_change', args=(user.pk,))
|
|
||||||
boxes.append('<a href="%s">%s</a>' % (url, user.username))
|
|
||||||
return '<br>'.join(boxes)
|
|
||||||
display_mailboxes.short_description = _("Mailboxes")
|
|
||||||
display_mailboxes.allow_tags = True
|
|
||||||
|
|
||||||
def display_forward(self, address):
|
|
||||||
values = [ dest for dest in address.forward.split() ]
|
|
||||||
return '<br>'.join(values)
|
|
||||||
display_forward.short_description = _("Forward")
|
|
||||||
display_forward.allow_tags = True
|
|
||||||
|
|
||||||
def formfield_for_dbfield(self, db_field, **kwargs):
|
|
||||||
if db_field.name == 'forward':
|
|
||||||
kwargs['widget'] = forms.TextInput(attrs={'size':'118'})
|
|
||||||
if db_field.name == 'mailboxes':
|
|
||||||
mailboxes = db_field.rel.to.objects.select_related('user')
|
|
||||||
kwargs['queryset'] = mailboxes.filter(user__account=self.account)
|
|
||||||
return super(AddressAdmin, self).formfield_for_dbfield(db_field, **kwargs)
|
|
||||||
|
|
||||||
def get_queryset(self, request):
|
|
||||||
""" Select related for performance """
|
|
||||||
qs = super(AddressAdmin, self).get_queryset(request)
|
|
||||||
return qs.select_related('domain')
|
|
||||||
|
|
||||||
|
|
||||||
class MailRoleAdmin(RoleAdmin):
|
|
||||||
model = Mailbox
|
|
||||||
name = 'mailbox'
|
|
||||||
url_name = 'mailbox'
|
|
||||||
form = MailRoleAdminForm
|
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(Address, AddressAdmin)
|
|
||||||
insertattr(get_user_model(), 'roles', MailRoleAdmin)
|
|
|
@ -1,27 +0,0 @@
|
||||||
from rest_framework import viewsets
|
|
||||||
|
|
||||||
from orchestra.api import router
|
|
||||||
from orchestra.apps.accounts.api import AccountApiMixin
|
|
||||||
|
|
||||||
from .models import Address, Mailbox
|
|
||||||
from .serializers import AddressSerializer, MailboxSerializer
|
|
||||||
|
|
||||||
|
|
||||||
class AddressViewSet(AccountApiMixin, viewsets.ModelViewSet):
|
|
||||||
model = Address
|
|
||||||
serializer_class = AddressSerializer
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class MailboxViewSet(viewsets.ModelViewSet):
|
|
||||||
model = Mailbox
|
|
||||||
serializer_class = MailboxSerializer
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
qs = super(MailboxViewSet, self).get_queryset()
|
|
||||||
qs = qs.select_related('user')
|
|
||||||
return qs.filter(user__account=self.request.user.account_id)
|
|
||||||
|
|
||||||
|
|
||||||
router.register(r'mailboxes', MailboxViewSet)
|
|
||||||
router.register(r'addresses', AddressViewSet)
|
|
|
@ -1,160 +0,0 @@
|
||||||
import os
|
|
||||||
|
|
||||||
from django.utils import timezone
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
from orchestra.apps.orchestration import ServiceController
|
|
||||||
from orchestra.apps.resources import ServiceMonitor
|
|
||||||
|
|
||||||
from . import settings
|
|
||||||
from .models import Address
|
|
||||||
|
|
||||||
|
|
||||||
class MailSystemUserBackend(ServiceController):
|
|
||||||
verbose_name = _("Mail system user")
|
|
||||||
model = 'mail.Mailbox'
|
|
||||||
# TODO related_models = ('resources__content_type') ??
|
|
||||||
|
|
||||||
DEFAULT_GROUP = 'postfix'
|
|
||||||
|
|
||||||
def create_user(self, context):
|
|
||||||
self.append(
|
|
||||||
"if [[ $( id %(username)s ) ]]; then \n"
|
|
||||||
" usermod -p '%(password)s' %(username)s \n"
|
|
||||||
"else \n"
|
|
||||||
" useradd %(username)s --password '%(password)s' \\\n"
|
|
||||||
" --shell /dev/null \n"
|
|
||||||
"fi" % context
|
|
||||||
)
|
|
||||||
self.append("mkdir -p %(home)s" % context)
|
|
||||||
self.append("chown %(username)s.%(group)s %(home)s" % context)
|
|
||||||
|
|
||||||
def generate_filter(self, mailbox, context):
|
|
||||||
now = timezone.now().strftime("%B %d, %Y, %H:%M")
|
|
||||||
context['filtering'] = (
|
|
||||||
"# Sieve Filter\n"
|
|
||||||
"# Generated by Orchestra %s\n\n" % now
|
|
||||||
)
|
|
||||||
if mailbox.use_custom_filtering:
|
|
||||||
context['filtering'] += mailbox.custom_filtering
|
|
||||||
else:
|
|
||||||
context['filtering'] += settings.EMAILS_DEFAUL_FILTERING
|
|
||||||
context['filter_path'] = os.path.join(context['home'], '.orchestra.sieve')
|
|
||||||
self.append("echo '%(filtering)s' > %(filter_path)s" % context)
|
|
||||||
|
|
||||||
def save(self, mailbox):
|
|
||||||
context = self.get_context(mailbox)
|
|
||||||
self.create_user(context)
|
|
||||||
self.generate_filter(mailbox, context)
|
|
||||||
|
|
||||||
def delete(self, mailbox):
|
|
||||||
context = self.get_context(mailbox)
|
|
||||||
self.append("{ sleep 2 && killall -u %(username)s -s KILL; } &" % context)
|
|
||||||
self.append("killall -u %(username)s" % context)
|
|
||||||
self.append("userdel %(username)s" % context)
|
|
||||||
self.append("rm -fr %(home)s" % context)
|
|
||||||
|
|
||||||
def get_context(self, mailbox):
|
|
||||||
user = mailbox.user
|
|
||||||
context = {
|
|
||||||
'username': user.username,
|
|
||||||
'password': user.password if user.is_active else '*%s' % user.password,
|
|
||||||
'group': self.DEFAULT_GROUP
|
|
||||||
}
|
|
||||||
context['home'] = settings.EMAILS_HOME % context
|
|
||||||
return context
|
|
||||||
|
|
||||||
|
|
||||||
class PostfixAddressBackend(ServiceController):
|
|
||||||
verbose_name = _("Postfix address")
|
|
||||||
model = 'mail.Address'
|
|
||||||
|
|
||||||
def include_virtdomain(self, context):
|
|
||||||
self.append(
|
|
||||||
'[[ $(grep "^\s*%(domain)s\s*$" %(virtdomains)s) ]]'
|
|
||||||
' || { echo "%(domain)s" >> %(virtdomains)s; UPDATED=1; }' % context
|
|
||||||
)
|
|
||||||
|
|
||||||
def exclude_virtdomain(self, context):
|
|
||||||
domain = context['domain']
|
|
||||||
if not Address.objects.filter(domain=domain).exists():
|
|
||||||
self.append('sed -i "s/^%(domain)s//" %(virtdomains)s' % context)
|
|
||||||
|
|
||||||
def update_virtusertable(self, context):
|
|
||||||
self.append(
|
|
||||||
'LINE="%(email)s\t%(destination)s"\n'
|
|
||||||
'if [[ ! $(grep "^%(email)s\s" %(virtusertable)s) ]]; then\n'
|
|
||||||
' echo "$LINE" >> %(virtusertable)s\n'
|
|
||||||
' UPDATED=1\n'
|
|
||||||
'else\n'
|
|
||||||
' if [[ ! $(grep "^${LINE}$" %(virtusertable)s) ]]; then\n'
|
|
||||||
' sed -i "s/^%(email)s\s.*$/${LINE}/" %(virtusertable)s\n'
|
|
||||||
' UPDATED=1\n'
|
|
||||||
' fi\n'
|
|
||||||
'fi' % context
|
|
||||||
)
|
|
||||||
|
|
||||||
def exclude_virtusertable(self, context):
|
|
||||||
self.append(
|
|
||||||
'if [[ $(grep "^%(email)s\s") ]]; then\n'
|
|
||||||
' sed -i "s/^%(email)s\s.*$//" %(virtusertable)s\n'
|
|
||||||
' UPDATED=1\n'
|
|
||||||
'fi'
|
|
||||||
)
|
|
||||||
|
|
||||||
def save(self, address):
|
|
||||||
context = self.get_context(address)
|
|
||||||
self.include_virtdomain(context)
|
|
||||||
self.update_virtusertable(context)
|
|
||||||
|
|
||||||
def delete(self, address):
|
|
||||||
context = self.get_context(address)
|
|
||||||
self.exclude_virtdomain(context)
|
|
||||||
self.exclude_virtusertable(context)
|
|
||||||
|
|
||||||
def commit(self):
|
|
||||||
context = self.get_context_files()
|
|
||||||
self.append('[[ $UPDATED == 1 ]] && { '
|
|
||||||
'postmap %(virtdomains)s;'
|
|
||||||
'postmap %(virtusertable)s;'
|
|
||||||
'}' % context)
|
|
||||||
|
|
||||||
def get_context_files(self):
|
|
||||||
return {
|
|
||||||
'virtdomains': settings.EMAILS_VIRTDOMAINS_PATH,
|
|
||||||
'virtusertable': settings.EMAILS_VIRTUSERTABLE_PATH,
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_context(self, address):
|
|
||||||
context = self.get_context_files()
|
|
||||||
context.update({
|
|
||||||
'domain': address.domain,
|
|
||||||
'email': address.email,
|
|
||||||
'destination': address.destination,
|
|
||||||
})
|
|
||||||
return context
|
|
||||||
|
|
||||||
|
|
||||||
class AutoresponseBackend(ServiceController):
|
|
||||||
verbose_name = _("Mail autoresponse")
|
|
||||||
model = 'mail.Autoresponse'
|
|
||||||
|
|
||||||
|
|
||||||
class MaildirDisk(ServiceMonitor):
|
|
||||||
model = 'email.Mailbox'
|
|
||||||
resource = ServiceMonitor.DISK
|
|
||||||
verbose_name = _("Maildir disk usage")
|
|
||||||
|
|
||||||
def monitor(self, mailbox):
|
|
||||||
context = self.get_context(mailbox)
|
|
||||||
self.append(
|
|
||||||
"SIZE=$(sed -n '2p' %(maildir_path)s | cut -d' ' -f1)\n"
|
|
||||||
"echo %(object_id)s ${SIZE:-0}" % context
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_context(self, mailbox):
|
|
||||||
context = MailSystemUserBackend().get_context(mailbox)
|
|
||||||
context['home'] = settings.EMAILS_HOME % context
|
|
||||||
context['maildir_path'] = os.path.join(context['home'], 'Maildir/maildirsize')
|
|
||||||
context['object_id'] = mailbox.pk
|
|
||||||
return context
|
|
|
@ -1,53 +0,0 @@
|
||||||
from django import forms
|
|
||||||
from django.core.urlresolvers import reverse
|
|
||||||
from django.utils.safestring import mark_safe
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
from orchestra.forms.widgets import ReadOnlyWidget
|
|
||||||
|
|
||||||
from .models import Mailbox
|
|
||||||
from ..forms import RoleAdminBaseForm
|
|
||||||
|
|
||||||
|
|
||||||
class MailRoleAdminForm(RoleAdminBaseForm):
|
|
||||||
class Meta(RoleAdminBaseForm.Meta):
|
|
||||||
model = Mailbox
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(MailRoleAdminForm, self).__init__(*args, **kwargs)
|
|
||||||
instance = kwargs.get('instance')
|
|
||||||
if instance:
|
|
||||||
widget = ReadOnlyWidget(self.addresses(instance))
|
|
||||||
self.fields['addresses'] = forms.CharField(widget=widget,
|
|
||||||
label=_("Addresses"))
|
|
||||||
|
|
||||||
# def addresses(self, mailbox):
|
|
||||||
# account = mailbox.user.account
|
|
||||||
# addresses = account.addresses.filter(destination__contains=mailbox.user.username)
|
|
||||||
# add_url = reverse('admin:mail_address_add')
|
|
||||||
# add_url += '?account=%d&destination=%s' % (account.pk, mailbox.user.username)
|
|
||||||
# img = '<img src="/static/admin/img/icon_addlink.gif" width="10" height="10" alt="Add Another">'
|
|
||||||
# onclick = 'onclick="return showAddAnotherPopup(this);"'
|
|
||||||
# add_link = '<a href="%s" %s>%s Add address</a>' % (add_url, onclick, img)
|
|
||||||
# value = '%s<br><br>' % add_link
|
|
||||||
# for pk, name, domain in addresses.values_list('pk', 'name', 'domain__name'):
|
|
||||||
# url = reverse('admin:mail_address_change', args=(pk,))
|
|
||||||
# name = '%s@%s' % (name, domain)
|
|
||||||
# value += '<li><a href="%s">%s</a></li>' % (url, name)
|
|
||||||
# value = '<ul>%s</ul>' % value
|
|
||||||
# return mark_safe('<div style="padding-left: 100px;">%s</div>' % value)
|
|
||||||
|
|
||||||
def addresses(self, mailbox):
|
|
||||||
account = mailbox.user.account
|
|
||||||
add_url = reverse('admin:mail_address_add')
|
|
||||||
add_url += '?account=%d&mailboxes=%s' % (account.pk, mailbox.pk)
|
|
||||||
img = '<img src="/static/admin/img/icon_addlink.gif" width="10" height="10" alt="Add Another">'
|
|
||||||
onclick = 'onclick="return showAddAnotherPopup(this);"'
|
|
||||||
add_link = '<a href="%s" %s>%s Add address</a>' % (add_url, onclick, img)
|
|
||||||
value = '%s<br><br>' % add_link
|
|
||||||
for pk, name, domain in mailbox.addresses.values_list('pk', 'name', 'domain__name'):
|
|
||||||
url = reverse('admin:mail_address_change', args=(pk,))
|
|
||||||
name = '%s@%s' % (name, domain)
|
|
||||||
value += '<li><a href="%s">%s</a></li>' % (url, name)
|
|
||||||
value = '<ul>%s</ul>' % value
|
|
||||||
return mark_safe('<div style="padding-left: 100px;">%s</div>' % value)
|
|
|
@ -1,110 +0,0 @@
|
||||||
from django.db import models
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
from orchestra.core import services
|
|
||||||
|
|
||||||
from .. import roles
|
|
||||||
|
|
||||||
from . import validators, settings
|
|
||||||
|
|
||||||
|
|
||||||
class Mailbox(models.Model):
|
|
||||||
user = models.OneToOneField('users.User', verbose_name=_("User"),
|
|
||||||
related_name='mailbox')
|
|
||||||
use_custom_filtering = models.BooleanField(_("Use custom filtering"),
|
|
||||||
default=False)
|
|
||||||
custom_filtering = models.TextField(_("filtering"), blank=True,
|
|
||||||
validators=[validators.validate_sieve],
|
|
||||||
help_text=_("Arbitrary email filtering in sieve language."))
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name_plural = _("mailboxes")
|
|
||||||
|
|
||||||
def __unicode__(self):
|
|
||||||
return self.user.username
|
|
||||||
|
|
||||||
# def get_addresses(self):
|
|
||||||
# regex = r'(^|\s)+%s(\s|$)+' % self.user.username
|
|
||||||
# return Address.objects.filter(destination__regex=regex)
|
|
||||||
#
|
|
||||||
# def delete(self, *args, **kwargs):
|
|
||||||
# """ Update related addresses """
|
|
||||||
# regex = re.compile(r'(^|\s)+(\s*%s)(\s|$)+' % self.user.username)
|
|
||||||
# super(Mailbox, self).delete(*args, **kwargs)
|
|
||||||
# for address in self.get_addresses():
|
|
||||||
# address.destination = regex.sub(r'\3', address.destination).strip()
|
|
||||||
# if not address.destination:
|
|
||||||
# address.delete()
|
|
||||||
# else:
|
|
||||||
# address.save()
|
|
||||||
|
|
||||||
|
|
||||||
#class Address(models.Model):
|
|
||||||
# name = models.CharField(_("name"), max_length=64,
|
|
||||||
# validators=[validators.validate_emailname])
|
|
||||||
# domain = models.ForeignKey(settings.EMAILS_DOMAIN_MODEL,
|
|
||||||
# verbose_name=_("domain"),
|
|
||||||
# related_name='addresses')
|
|
||||||
# destination = models.CharField(_("destination"), max_length=256,
|
|
||||||
# validators=[validators.validate_destination],
|
|
||||||
# help_text=_("Space separated mailbox names or email addresses"))
|
|
||||||
# account = models.ForeignKey('accounts.Account', verbose_name=_("Account"),
|
|
||||||
# related_name='addresses')
|
|
||||||
#
|
|
||||||
# class Meta:
|
|
||||||
# verbose_name_plural = _("addresses")
|
|
||||||
# unique_together = ('name', 'domain')
|
|
||||||
#
|
|
||||||
# def __unicode__(self):
|
|
||||||
# return self.email
|
|
||||||
#
|
|
||||||
# @property
|
|
||||||
# def email(self):
|
|
||||||
# return "%s@%s" % (self.name, self.domain)
|
|
||||||
#
|
|
||||||
# def get_mailboxes(self):
|
|
||||||
# for dest in self.destination.split():
|
|
||||||
# if '@' not in dest:
|
|
||||||
# yield Mailbox.objects.select_related('user').get(user__username=dest)
|
|
||||||
|
|
||||||
|
|
||||||
class Address(models.Model):
|
|
||||||
name = models.CharField(_("name"), max_length=64,
|
|
||||||
validators=[validators.validate_emailname])
|
|
||||||
domain = models.ForeignKey(settings.EMAILS_DOMAIN_MODEL,
|
|
||||||
verbose_name=_("domain"),
|
|
||||||
related_name='addresses')
|
|
||||||
mailboxes = models.ManyToManyField('mail.Mailbox',
|
|
||||||
verbose_name=_("mailboxes"),
|
|
||||||
related_name='addresses', blank=True)
|
|
||||||
forward = models.CharField(_("forward"), max_length=256, blank=True,
|
|
||||||
validators=[validators.validate_forward])
|
|
||||||
account = models.ForeignKey('accounts.Account', verbose_name=_("Account"),
|
|
||||||
related_name='addresses')
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name_plural = _("addresses")
|
|
||||||
unique_together = ('name', 'domain')
|
|
||||||
|
|
||||||
def __unicode__(self):
|
|
||||||
return self.email
|
|
||||||
|
|
||||||
@property
|
|
||||||
def email(self):
|
|
||||||
return "%s@%s" % (self.name, self.domain)
|
|
||||||
|
|
||||||
|
|
||||||
class Autoresponse(models.Model):
|
|
||||||
address = models.OneToOneField(Address, verbose_name=_("address"),
|
|
||||||
related_name='autoresponse')
|
|
||||||
# TODO initial_date
|
|
||||||
subject = models.CharField(_("subject"), max_length=256)
|
|
||||||
message = models.TextField(_("message"))
|
|
||||||
enabled = models.BooleanField(_("enabled"), default=False)
|
|
||||||
|
|
||||||
def __unicode__(self):
|
|
||||||
return self.address
|
|
||||||
|
|
||||||
|
|
||||||
services.register(Address)
|
|
||||||
roles.register('mailbox', Mailbox)
|
|
|
@ -1,43 +0,0 @@
|
||||||
from rest_framework import serializers
|
|
||||||
|
|
||||||
from orchestra.api import router
|
|
||||||
from orchestra.apps.accounts.serializers import AccountSerializerMixin
|
|
||||||
|
|
||||||
from .models import Address, Mailbox
|
|
||||||
|
|
||||||
|
|
||||||
#class AddressSerializer(serializers.HyperlinkedModelSerializer):
|
|
||||||
# class Meta:
|
|
||||||
# model = Address
|
|
||||||
# fields = ('url', 'name', 'domain', 'destination')
|
|
||||||
|
|
||||||
|
|
||||||
class NestedMailboxSerializer(serializers.HyperlinkedModelSerializer):
|
|
||||||
class Meta:
|
|
||||||
model = Mailbox
|
|
||||||
fields = ('url', 'use_custom_filtering', 'custom_filtering')
|
|
||||||
|
|
||||||
|
|
||||||
class MailboxSerializer(serializers.HyperlinkedModelSerializer):
|
|
||||||
class Meta:
|
|
||||||
model = Mailbox
|
|
||||||
fields = ('url', 'user', 'use_custom_filtering', 'custom_filtering')
|
|
||||||
|
|
||||||
|
|
||||||
class AddressSerializer(AccountSerializerMixin, serializers.HyperlinkedModelSerializer):
|
|
||||||
class Meta:
|
|
||||||
model = Address
|
|
||||||
fields = ('url', 'name', 'domain', 'mailboxes', 'forward')
|
|
||||||
|
|
||||||
def get_fields(self, *args, **kwargs):
|
|
||||||
fields = super(AddressSerializer, self).get_fields(*args, **kwargs)
|
|
||||||
account = self.context['view'].request.user.account_id
|
|
||||||
mailboxes = fields['mailboxes'].queryset.select_related('user')
|
|
||||||
fields['mailboxes'].queryset = mailboxes.filter(user__account=account)
|
|
||||||
# TODO do it on permissions or in self.filter_by_account_field ?
|
|
||||||
domain = fields['domain'].queryset
|
|
||||||
fields['domain'].queryset = domain .filter(account=account)
|
|
||||||
return fields
|
|
||||||
|
|
||||||
|
|
||||||
router.insert('users', 'mailbox', NestedMailboxSerializer, required=False)
|
|
|
@ -1,29 +0,0 @@
|
||||||
from django.conf import settings
|
|
||||||
|
|
||||||
|
|
||||||
EMAILS_DOMAIN_MODEL = getattr(settings, 'EMAILS_DOMAIN_MODEL', 'domains.Domain')
|
|
||||||
|
|
||||||
EMAILS_HOME = getattr(settings, 'EMAILS_HOME', '/home/%(username)s/')
|
|
||||||
|
|
||||||
EMAILS_SIEVETEST_PATH = getattr(settings, 'EMAILS_SIEVETEST_PATH', '/dev/shm')
|
|
||||||
|
|
||||||
EMAILS_SIEVETEST_BIN_PATH = getattr(settings, 'EMAILS_SIEVETEST_BIN_PATH',
|
|
||||||
'%(orchestra_root)s/bin/sieve-test')
|
|
||||||
|
|
||||||
|
|
||||||
EMAILS_VIRTUSERTABLE_PATH = getattr(settings, 'EMAILS_VIRTUSERTABLE_PATH',
|
|
||||||
'/etc/postfix/virtusertable')
|
|
||||||
|
|
||||||
|
|
||||||
EMAILS_VIRTDOMAINS_PATH = getattr(settings, 'EMAILS_VIRTDOMAINS_PATH',
|
|
||||||
'/etc/postfix/virtdomains')
|
|
||||||
|
|
||||||
|
|
||||||
EMAILS_DEFAUL_FILTERING = getattr(settings, 'EMAILS_DEFAULT_FILTERING',
|
|
||||||
'require ["fileinto","regex","envelope","vacation","reject","relational","comparator-i;ascii-numeric"];\n'
|
|
||||||
'\n'
|
|
||||||
'if header :value "ge" :comparator "i;ascii-numeric" "X-Spam-Score" "5" {\n'
|
|
||||||
' fileinto "Junk";\n'
|
|
||||||
' discard;\n'
|
|
||||||
'}'
|
|
||||||
)
|
|
|
@ -1,62 +0,0 @@
|
||||||
import hashlib
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
|
|
||||||
from django.core.validators import ValidationError, EmailValidator
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
from orchestra.utils import paths
|
|
||||||
from orchestra.utils.system import run
|
|
||||||
|
|
||||||
from . import settings
|
|
||||||
|
|
||||||
|
|
||||||
def validate_emailname(value):
|
|
||||||
msg = _("'%s' is not a correct email name" % value)
|
|
||||||
if '@' in value:
|
|
||||||
raise ValidationError(msg)
|
|
||||||
value += '@localhost'
|
|
||||||
try:
|
|
||||||
EmailValidator(value)
|
|
||||||
except ValidationError:
|
|
||||||
raise ValidationError(msg)
|
|
||||||
|
|
||||||
|
|
||||||
#def validate_destination(value):
|
|
||||||
# """ space separated mailboxes or emails """
|
|
||||||
# for destination in value.split():
|
|
||||||
# msg = _("'%s' is not an existent mailbox" % destination)
|
|
||||||
# if '@' in destination:
|
|
||||||
# if not destination[-1].isalpha():
|
|
||||||
# raise ValidationError(msg)
|
|
||||||
# EmailValidator(destination)
|
|
||||||
# else:
|
|
||||||
# from .models import Mailbox
|
|
||||||
# if not Mailbox.objects.filter(user__username=destination).exists():
|
|
||||||
# raise ValidationError(msg)
|
|
||||||
# validate_emailname(destination)
|
|
||||||
|
|
||||||
|
|
||||||
def validate_forward(value):
|
|
||||||
""" space separated mailboxes or emails """
|
|
||||||
for destination in value.split():
|
|
||||||
EmailValidator(destination)
|
|
||||||
|
|
||||||
|
|
||||||
def validate_sieve(value):
|
|
||||||
sieve_name = '%s.sieve' % hashlib.md5(value).hexdigest()
|
|
||||||
path = os.path.join(settings.EMAILS_SIEVETEST_PATH, sieve_name)
|
|
||||||
with open(path, 'wb') as f:
|
|
||||||
f.write(value)
|
|
||||||
context = {
|
|
||||||
'orchestra_root': paths.get_orchestra_root()
|
|
||||||
}
|
|
||||||
sievetest = settings.EMAILS_SIEVETEST_BIN_PATH % context
|
|
||||||
test = run(' '.join([sievetest, path, '/dev/null']), display=False)
|
|
||||||
if test.return_code:
|
|
||||||
errors = []
|
|
||||||
for line in test.stderr.splitlines():
|
|
||||||
error = re.match(r'^.*(line\s+[0-9]+:.*)', line)
|
|
||||||
if error:
|
|
||||||
errors += error.groups()
|
|
||||||
raise ValidationError(' '.join(errors))
|
|
|
@ -1,15 +0,0 @@
|
||||||
from django.contrib.auth import get_user_model
|
|
||||||
|
|
||||||
from orchestra.admin.utils import insertattr
|
|
||||||
from orchestra.apps.users.roles.admin import RoleAdmin
|
|
||||||
|
|
||||||
from .models import POSIX
|
|
||||||
|
|
||||||
|
|
||||||
class POSIXRoleAdmin(RoleAdmin):
|
|
||||||
model = POSIX
|
|
||||||
name = 'posix'
|
|
||||||
url_name = 'posix'
|
|
||||||
|
|
||||||
|
|
||||||
insertattr(get_user_model(), 'roles', POSIXRoleAdmin)
|
|
|
@ -1,22 +0,0 @@
|
||||||
from django.db import models
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
from .. import roles
|
|
||||||
|
|
||||||
from . import settings
|
|
||||||
|
|
||||||
|
|
||||||
class POSIX(models.Model):
|
|
||||||
user = models.OneToOneField('users.User', verbose_name=_("user"),
|
|
||||||
related_name='posix')
|
|
||||||
home = models.CharField(_("home"), max_length=256, blank=True,
|
|
||||||
help_text=_("Home directory relative to account's ~primary_user"))
|
|
||||||
shell = models.CharField(_("shell"), max_length=32,
|
|
||||||
choices=settings.POSIX_SHELLS, default=settings.POSIX_DEFAULT_SHELL)
|
|
||||||
|
|
||||||
def __unicode__(self):
|
|
||||||
return str(self.user)
|
|
||||||
|
|
||||||
# TODO groups
|
|
||||||
|
|
||||||
roles.register('posix', POSIX)
|
|
|
@ -1,14 +0,0 @@
|
||||||
from rest_framework import serializers
|
|
||||||
|
|
||||||
from orchestra.api import router
|
|
||||||
|
|
||||||
from .models import POSIX
|
|
||||||
|
|
||||||
|
|
||||||
class POSIXSerializer(serializers.ModelSerializer):
|
|
||||||
class Meta:
|
|
||||||
model = POSIX
|
|
||||||
fields = ('home', 'shell')
|
|
||||||
|
|
||||||
|
|
||||||
router.insert('users', 'posix', POSIXSerializer, required=False)
|
|
|
@ -1,11 +0,0 @@
|
||||||
from django.conf import settings
|
|
||||||
from django.utils.translation import ugettext, ugettext_lazy as _
|
|
||||||
|
|
||||||
|
|
||||||
POSIX_SHELLS = getattr(settings, 'POSIX_SHELLS', (
|
|
||||||
('/bin/false', _("FTP/sFTP only")),
|
|
||||||
('/bin/rsync', _("rsync shell")),
|
|
||||||
('/bin/bash', "Bash"),
|
|
||||||
))
|
|
||||||
|
|
||||||
POSIX_DEFAULT_SHELL = getattr(settings, 'POSIX_DEFAULT_SHELL', '/bin/false')
|
|
|
@ -1,35 +0,0 @@
|
||||||
from django.contrib.auth import get_user_model
|
|
||||||
from django.forms import widgets
|
|
||||||
from django.utils.translation import ugettext, ugettext_lazy as _
|
|
||||||
from rest_framework import serializers
|
|
||||||
|
|
||||||
from orchestra.apps.accounts.serializers import AccountSerializerMixin
|
|
||||||
from orchestra.core.validators import validate_password
|
|
||||||
|
|
||||||
|
|
||||||
class UserSerializer(AccountSerializerMixin, serializers.HyperlinkedModelSerializer):
|
|
||||||
password = serializers.CharField(max_length=128, label=_('Password'),
|
|
||||||
validators=[validate_password], write_only=True, required=False,
|
|
||||||
widget=widgets.PasswordInput)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = get_user_model()
|
|
||||||
fields = (
|
|
||||||
'url', 'username', 'password', 'first_name', 'last_name', 'email',
|
|
||||||
'is_admin', 'is_active',
|
|
||||||
)
|
|
||||||
|
|
||||||
def validate_password(self, attrs, source):
|
|
||||||
""" POST only password """
|
|
||||||
if self.object.pk:
|
|
||||||
if 'password' in attrs:
|
|
||||||
raise serializers.ValidationError(_("Can not set password"))
|
|
||||||
elif 'password' not in attrs:
|
|
||||||
raise serializers.ValidationError(_("Password required"))
|
|
||||||
return attrs
|
|
||||||
|
|
||||||
def save_object(self, obj, **kwargs):
|
|
||||||
# FIXME this method will be called when saving nested serializers :(
|
|
||||||
if not obj.pk:
|
|
||||||
obj.set_password(obj.password)
|
|
||||||
super(UserSerializer, self).save_object(obj, **kwargs)
|
|
|
@ -1,6 +0,0 @@
|
||||||
from django.conf import settings
|
|
||||||
|
|
||||||
|
|
||||||
USERS_SYSTEMUSER_HOME = getattr(settings, 'USERES_SYSTEMUSER_HOME', '/home/%(username)s')
|
|
||||||
|
|
||||||
USERS_FTP_LOG_PATH = getattr(settings, 'USERS_FTP_LOG_PATH', '/var/log/vsftpd.log')
|
|
|
@ -1,15 +0,0 @@
|
||||||
{% extends "admin/change_form.html" %}
|
|
||||||
{% load i18n admin_urls %}
|
|
||||||
|
|
||||||
{% block object-tools-items %}
|
|
||||||
<li><a href="." class="historylink">{% trans "User" %}</a></li>
|
|
||||||
{% for item in roles %}
|
|
||||||
<li><a href="{{ item.url_name }}/" class="{% if item.exists %}historylink{% else %}addlink{% endif %}" title="{{ item.description }}">{% if item.exists %}{{ item.name.capitalize }}{% else %}Add {{ item.name }}{% endif %}</a></li>
|
|
||||||
{% endfor %}
|
|
||||||
<li>
|
|
||||||
{% url opts|admin_urlname:'history' original.pk|admin_urlquote as history_url %}
|
|
||||||
<a href="{% add_preserved_filters history_url %}" class="historylink">{% trans "History" %}</a>
|
|
||||||
</li>
|
|
||||||
{% if has_absolute_url %}<li><a href="../../../r/{{ content_type_id }}/{{ object_id }}/" class="viewsitelink">{% trans "View on site" %}</a></li>{% endif%}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
{% extends "admin/delete_confirmation.html" %}
|
|
||||||
{% load i18n admin_urls %}
|
|
||||||
|
|
||||||
|
|
||||||
{% block breadcrumbs %}
|
|
||||||
<div class="breadcrumbs">
|
|
||||||
<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
|
|
||||||
› <a href="{% url 'admin:app_list' app_label=opts.app_label %}">{{ app_label|capfirst }}</a>
|
|
||||||
› <a href="{% url 'admin:users_user_changelist' %}">Users</a>
|
|
||||||
› <a href="{% url 'admin:users_user_change' object.pk|admin_urlquote %}">{{ user|truncatewords:"18" }}</a>
|
|
||||||
› <a href="../">{{ role.name|truncatewords:"18" }}</a>
|
|
||||||
› {% trans 'Delete' %}
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
|
@ -1,70 +0,0 @@
|
||||||
{% extends "admin/base_site.html" %}
|
|
||||||
{% load i18n admin_urls admin_static admin_modify utils %}
|
|
||||||
|
|
||||||
|
|
||||||
{% block extrastyle %}
|
|
||||||
{{ block.super }}
|
|
||||||
<link rel="stylesheet" type="text/css" href="{% static "admin/css/forms.css" %}" />
|
|
||||||
{{ media }}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block coltype %}colM{% endblock %}
|
|
||||||
{% block bodyclass %}{{ block.super }} app-{{ opts.app_label }} model-{{ opts.model_name }} change-form{% endblock %}
|
|
||||||
|
|
||||||
|
|
||||||
{% block breadcrumbs %}
|
|
||||||
<div class="breadcrumbs">
|
|
||||||
<a href="{% url 'admin:index' %}">Home</a>
|
|
||||||
› <a href="{% url 'admin:app_list' app_label=app_label %}">{{ app_label|capfirst|escape }}</a>
|
|
||||||
› <a href="{% url opts|admin_urlname:'changelist' %}">{{ opts.verbose_name_plural|capfirst }}</a>
|
|
||||||
› <a href="{% url opts|admin_urlname:'change' user.pk %}">{{ user|capfirst }}</a>
|
|
||||||
› {{ role.name.capitalize }}
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{% block content %}<div id="content-main">
|
|
||||||
{% block object-tools %}
|
|
||||||
<ul class="object-tools">
|
|
||||||
{% block object-tools-items %}
|
|
||||||
<li><a href=".." class="historylink">{% trans "User" %}</a></li>
|
|
||||||
{% for item in roles %}
|
|
||||||
<li><a href="../{{ item.url_name }}/" class="{% if item.exists %}historylink{% else %}addlink{% endif %}" title="{{ item.description }}">{% if item.exists %}{{ item.name.capitalize }}{% else %}Add {{ item.name }}{% endif %}</a></li>
|
|
||||||
{% endfor %}
|
|
||||||
<li>
|
|
||||||
{% url opts|admin_urlname:'history' user.pk|admin_urlquote as history_url %}
|
|
||||||
<a href="{% add_preserved_filters history_url %}" class="historylink">{% trans "History" %}</a>
|
|
||||||
</li>
|
|
||||||
{% if has_absolute_url %}<li><a href="../../../r/{{ content_type_id }}/{{ object_id }}/" class="viewsitelink">{% trans "View on site" %}</a></li>{% endif%}
|
|
||||||
{% endblock %}
|
|
||||||
</ul>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
<form action="" method="post">{% csrf_token %}
|
|
||||||
<fieldset class="module aligned wide">
|
|
||||||
{% for field in form %}
|
|
||||||
<div class="form-row{% if line.fields|length_is:'1' and line.errors %} errors{% endif %}{% if not line.has_visible_field %} hidden{% endif %}{% for field in line %}{% if field.name %} field-{{ field.field.name }}{% endif %}{% endfor %}">
|
|
||||||
<div{% if not line.fields|length_is:'1' %} class="field-box{% if field.field.name %} field-{{ field.field.name }}{% endif %}{% if not field.is_readonly and field.errors %} errors{% endif %}{% if field.field.is_hidden %} hidden{% endif %}"{% elif field.is_checkbox %} class="checkbox-row"{% endif %}>
|
|
||||||
{% if not line.fields|length_is:'1' and not field.is_readonly %}{{ field.errors }}{% endif %}
|
|
||||||
{% if field|is_checkbox %}
|
|
||||||
{{ field }} <label for="{{ field.id_for_label }}" class="vCheckboxLabel">{{ field.label }}</label>
|
|
||||||
{% else %}
|
|
||||||
{{ field.label_tag }} {{ field }}
|
|
||||||
{% endif %}
|
|
||||||
{% if field.help_text %}
|
|
||||||
<p class="help">{{ field.help_text|safe }}</p>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<div class="submit-row">
|
|
||||||
<input type="submit" value="{{ action }}" class="default" name="_save" />
|
|
||||||
{% if role.exists %}<p class="deletelink-box"><a href="delete/" class="deletelink">Delete</a></p>{% endif %}
|
|
||||||
<input type="submit" value="{{ action }} and continue editing" name="_continue" />
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
{% endblock %}
|
|
Loading…
Reference in a new issue