Added seafile incon andn fixed random bugs
This commit is contained in:
parent
c55cff9a37
commit
a6734ea1d1
61
TODO.md
61
TODO.md
|
@ -19,8 +19,6 @@
|
||||||
|
|
||||||
|
|
||||||
* backend logs with hal logo
|
* backend logs with hal logo
|
||||||
* set_password orchestration method?
|
|
||||||
|
|
||||||
|
|
||||||
* LAST version of this shit http://wkhtmltopdf.org/downloads.html
|
* LAST version of this shit http://wkhtmltopdf.org/downloads.html
|
||||||
|
|
||||||
|
@ -39,8 +37,6 @@
|
||||||
|
|
||||||
* mail backend related_models = ('resources__content_type') ??
|
* mail backend related_models = ('resources__content_type') ??
|
||||||
|
|
||||||
* Domain backend PowerDNS Bind validation support?
|
|
||||||
|
|
||||||
* Maildir billing tests/ webdisk billing tests (avg metric)
|
* Maildir billing tests/ webdisk billing tests (avg metric)
|
||||||
|
|
||||||
|
|
||||||
|
@ -50,10 +46,6 @@
|
||||||
|
|
||||||
* rename accounts register to "account", and reated api and admin references
|
* rename accounts register to "account", and reated api and admin references
|
||||||
|
|
||||||
|
|
||||||
* Disable services is_active should be computed on the fly in order to distinguish account.is_active from service.is_active when reactivation.
|
|
||||||
* Perhaps it is time to create a ServiceModel ?
|
|
||||||
|
|
||||||
* prevent deletion of main user by the user itself
|
* prevent deletion of main user by the user itself
|
||||||
|
|
||||||
* AccountAdminMixin auto adds 'account__name' on searchfields
|
* AccountAdminMixin auto adds 'account__name' on searchfields
|
||||||
|
@ -119,8 +111,6 @@
|
||||||
* Make main systemuser able to write/read everything on its home, including stuff created by the CGI user and secondary users
|
* Make main systemuser able to write/read everything on its home, including stuff created by the CGI user and secondary users
|
||||||
* Prevent users from accessing other users home while at the same time allow access Apache/fcgid/fpm and secondary users (x)
|
* Prevent users from accessing other users home while at the same time allow access Apache/fcgid/fpm and secondary users (x)
|
||||||
|
|
||||||
* public_html/webapps directory with root owner and permissions
|
|
||||||
|
|
||||||
* 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)
|
||||||
|
@ -144,28 +134,11 @@
|
||||||
|
|
||||||
* Create an admin service_view with icons (like SaaS app)
|
* Create an admin service_view with icons (like SaaS app)
|
||||||
|
|
||||||
|
|
||||||
* Resource graph for each related object
|
* Resource graph for each related object
|
||||||
|
|
||||||
|
|
||||||
* multitenant webapps modeled on WepApp -> name unique for all accounts
|
|
||||||
|
|
||||||
* webapp compat webapp-options
|
|
||||||
* webapps modeled on classes instead of settings?
|
|
||||||
|
|
||||||
* Service.account change and orders consistency
|
* Service.account change and orders consistency
|
||||||
|
|
||||||
* Mix webapps type with backends (two for the price of one)
|
* SaaS model splitted into SaaSUser and SaaSSite? inherit from SaaS
|
||||||
|
|
||||||
* Webapp options and type compatibility
|
|
||||||
|
|
||||||
* SaaS model splitted into SaaSUser and SaaSSite?
|
|
||||||
|
|
||||||
Multi-tenant WebApps
|
|
||||||
--------------------
|
|
||||||
* SaaS - Those apps that can't use custom domain
|
|
||||||
* WebApp - Those apps that can use custom domain
|
|
||||||
|
|
||||||
|
|
||||||
* prevent @pangea.org email addresses on contacts, enforce at least one email without @pangea.org
|
* prevent @pangea.org email addresses on contacts, enforce at least one email without @pangea.org
|
||||||
|
|
||||||
|
@ -192,19 +165,17 @@ Php binaries should have this format: /usr/bin/php5.2-cgi
|
||||||
* <IfModule security2_module> and other IfModule on backend SecRule
|
* <IfModule security2_module> and other IfModule on backend SecRule
|
||||||
|
|
||||||
|
|
||||||
* Orchestra global search box on the header, based https://github.com/django/django/blob/master/django/contrib/admin/options.py#L866 and iterating over all registered services and inspectin its admin.search_fields
|
* Orchestra global search box on the page head, based https://github.com/django/django/blob/master/django/contrib/admin/options.py#L866 and iterating over all registered services and inspectin its admin.search_fields
|
||||||
|
|
||||||
|
|
||||||
* contain error on plugin missing key (plugin dissabled): NOP, fail hard is better than silently, perhaps fail at starttime? apploading machinary
|
* contain error on plugin missing key (plugin dissabled): NOP, fail hard is better than silently, perhaps fail at starttime? apploading machinary
|
||||||
|
|
||||||
* contact.alternative_phone on a phone.tooltip, email:to
|
* contact.alternative_phone on a phone.tooltip, email:to
|
||||||
|
|
||||||
|
|
||||||
* better validate options and directives (url locations, filesystem paths, etc..)
|
* better validate options and directives (url locations, filesystem paths, etc..)
|
||||||
|
|
||||||
* make sure that you understand the risks
|
* make sure that you understand the risks
|
||||||
|
|
||||||
|
|
||||||
* full support for deactivation of services/accounts
|
* full support for deactivation of services/accounts
|
||||||
* Display admin.is_active (disabled account special icon and order by support)
|
* Display admin.is_active (disabled account special icon and order by support)
|
||||||
|
|
||||||
|
@ -232,17 +203,39 @@ require_once(‘/etc/moodles/’.$moodle_host.‘config.php’);``` moodle/drupl
|
||||||
|
|
||||||
* normurlpath '' return '/'
|
* normurlpath '' return '/'
|
||||||
|
|
||||||
* initial configuration of multisite sas apps with password stored in DATA
|
* initial configuration of multisite sas apps with password stored in DATA ?? Dsign decission: initial pwds vs eventual consistency vs externa service vs backend raise exception?
|
||||||
|
|
||||||
* webapps installation complete, passowrd protected
|
* webapps installation complete, passowrd protected
|
||||||
* saas.initial_password autogenerated (ok because its random and not user provided) vs saas.password /change_Form provided + send email with initial_password
|
* saas.initial_password autogenerated (ok because its random and not user provided) vs saas.password /change_Form provided + send email with initial_password
|
||||||
|
|
||||||
* more robust backend error handling, continue executing but exit code > 0 if failure, replace exit_code=0; do_sometging || exit_code=1
|
* more robust backend error handling, continue executing but exit code > 0 if failure, replace exit_code=0; do_sometging || exit_code=1
|
||||||
|
|
||||||
* saas require unique emails? connect to backend server to find out because they change
|
|
||||||
|
|
||||||
* automaitcally set passwords and email users?
|
* automaitcally set passwords and email users?
|
||||||
|
|
||||||
* website directives uniquenes validation on serializers
|
* website directives uniquenes validation on serializers
|
||||||
|
|
||||||
|
+ is_Active custom filter with support for instance.account.is_Active
|
||||||
|
|
||||||
|
* django virtual field for saas and webapps related objects (db) to show on delete confirmation
|
||||||
|
if only extra related objects are databases and user databases why not make them first class relations?????
|
||||||
|
* >>> Account._meta.virtual_fields[0].bulk_related_objects([Account.objects.all()[0]])
|
||||||
|
[<ResourceData: account-disk: entrep>, <ResourceData: account-traffic: entrep>]
|
||||||
|
https://github.com/django/django/blob/master/django/db/models/deletion.py#L232
|
||||||
|
https://github.com/django/django/blob/master/django/contrib/contenttypes/fields.py#L282
|
||||||
|
|
||||||
|
from django.contrib.contenttypes.fields import GenericRelation
|
||||||
|
from django.db import DEFAULT_DB_ALIAS
|
||||||
|
from orchestra.apps.databases.models import Database
|
||||||
|
class VirtualRelation(GenericRelation):
|
||||||
|
def bulk_related_objects(self, objs, using=DEFAULT_DB_ALIAS):
|
||||||
|
return []
|
||||||
|
# return Database.objects.filter(name__in=
|
||||||
|
# obj.service_instance.get_related() for obj in objs
|
||||||
|
## return self.remote_field.model._base_manager.db_manager(using).all()
|
||||||
|
relation = VirtualRelation('databases.Database')
|
||||||
|
SaaS.add_to_class('databases', relation)
|
||||||
|
|
||||||
|
* one to one relation deleteion on both sides??
|
||||||
|
|
||||||
|
|
||||||
|
* webapps/saas delete related db by id not name !! type!=Mysql
|
||||||
|
|
|
@ -124,7 +124,7 @@ class ChangeAddFieldsMixin(object):
|
||||||
def get_readonly_fields(self, request, obj=None):
|
def get_readonly_fields(self, request, obj=None):
|
||||||
fields = super(ChangeAddFieldsMixin, self).get_readonly_fields(request, obj)
|
fields = super(ChangeAddFieldsMixin, self).get_readonly_fields(request, obj)
|
||||||
if obj:
|
if obj:
|
||||||
return fields + self.get_change_readonly_fields(request, obj=obj)
|
return fields + self.get_change_readonly_fields(request, obj)
|
||||||
return fields
|
return fields
|
||||||
|
|
||||||
def get_fieldsets(self, request, obj=None):
|
def get_fieldsets(self, request, obj=None):
|
||||||
|
|
|
@ -31,7 +31,7 @@ disable.verbose_name = _("Disable")
|
||||||
def list_contacts(modeladmin, request, queryset):
|
def list_contacts(modeladmin, request, queryset):
|
||||||
ids = queryset.values_list('id', flat=True)
|
ids = queryset.values_list('id', flat=True)
|
||||||
if not ids:
|
if not ids:
|
||||||
message.warning(request, "Select at least one account.")
|
messages.warning(request, "Select at least one account.")
|
||||||
return
|
return
|
||||||
url = reverse('admin:contacts_contact_changelist')
|
url = reverse('admin:contacts_contact_changelist')
|
||||||
url += '?account__in=%s' % ','.join(map(str, ids))
|
url += '?account__in=%s' % ','.join(map(str, ids))
|
||||||
|
|
|
@ -95,7 +95,7 @@ class AccountAdmin(ChangePasswordAdminMixin, auth.UserAdmin, ExtendedModelAdmin)
|
||||||
form_url=form_url, extra_context=context)
|
form_url=form_url, extra_context=context)
|
||||||
|
|
||||||
def get_fieldsets(self, request, obj=None):
|
def get_fieldsets(self, request, obj=None):
|
||||||
fieldsets = super(AccountAdmin, self).get_fieldsets(request, obj=obj)
|
fieldsets = super(AccountAdmin, self).get_fieldsets(request, obj)
|
||||||
if not obj:
|
if not obj:
|
||||||
fields = AccountCreationForm.create_related_fields
|
fields = AccountCreationForm.create_related_fields
|
||||||
if fields:
|
if fields:
|
||||||
|
@ -153,6 +153,17 @@ class AccountAdminMixin(object):
|
||||||
account = None
|
account = None
|
||||||
list_select_related = ('account',)
|
list_select_related = ('account',)
|
||||||
|
|
||||||
|
def display_active(self, instance):
|
||||||
|
if not instance.is_active:
|
||||||
|
return '<img src="/static/admin/img/icon-no.gif" alt="False">'
|
||||||
|
elif not instance.account.is_active:
|
||||||
|
msg = _("Account disabled")
|
||||||
|
return '<img src="/static/admin/img/icon-unknown.gif" alt="False" title="%s">' % msg
|
||||||
|
return '<img src="/static/admin/img/icon-yes.gif" alt="True">'
|
||||||
|
display_active.short_description = _("active")
|
||||||
|
display_active.allow_tags = True
|
||||||
|
display_active.admin_order_field = 'is_active'
|
||||||
|
|
||||||
def account_link(self, instance):
|
def account_link(self, instance):
|
||||||
account = instance.account if instance.pk else self.account
|
account = instance.account if instance.pk else self.account
|
||||||
url = change_url(account)
|
url = change_url(account)
|
||||||
|
@ -161,6 +172,23 @@ class AccountAdminMixin(object):
|
||||||
account_link.allow_tags = True
|
account_link.allow_tags = True
|
||||||
account_link.admin_order_field = 'account__username'
|
account_link.admin_order_field = 'account__username'
|
||||||
|
|
||||||
|
def render_change_form(self, request, context, *args, **kwargs):
|
||||||
|
""" Warns user when object's account is disabled """
|
||||||
|
try:
|
||||||
|
field = context['adminform'].form.fields['is_active']
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
help_text = (
|
||||||
|
"Designates whether this account should be treated as active. "
|
||||||
|
"Unselect this instead of deleting accounts."
|
||||||
|
)
|
||||||
|
obj = kwargs.get('obj')
|
||||||
|
if obj and not obj.account.is_active:
|
||||||
|
help_text += "<br><b style='color:red;'>This user's account is dissabled</b>"
|
||||||
|
field.help_text = _(help_text)
|
||||||
|
return super(AccountAdminMixin, self).render_change_form(request, context, *args, **kwargs)
|
||||||
|
|
||||||
def get_fields(self, request, obj=None):
|
def get_fields(self, request, obj=None):
|
||||||
""" remove account or account_link depending on the case """
|
""" remove account or account_link depending on the case """
|
||||||
fields = super(AccountAdminMixin, self).get_fields(request, obj)
|
fields = super(AccountAdminMixin, self).get_fields(request, obj)
|
||||||
|
@ -181,7 +209,7 @@ class AccountAdminMixin(object):
|
||||||
""" provide account for filter_by_account_fields """
|
""" provide account for filter_by_account_fields """
|
||||||
if obj:
|
if obj:
|
||||||
self.account = obj.account
|
self.account = obj.account
|
||||||
return super(AccountAdminMixin, self).get_readonly_fields(request, obj=obj)
|
return super(AccountAdminMixin, self).get_readonly_fields(request, obj)
|
||||||
|
|
||||||
def formfield_for_dbfield(self, db_field, **kwargs):
|
def formfield_for_dbfield(self, db_field, **kwargs):
|
||||||
""" Filter by account """
|
""" Filter by account """
|
||||||
|
@ -207,7 +235,7 @@ class AccountAdminMixin(object):
|
||||||
|
|
||||||
def get_formset(self, request, obj=None, **kwargs):
|
def get_formset(self, request, obj=None, **kwargs):
|
||||||
""" provides form.account for convinience """
|
""" provides form.account for convinience """
|
||||||
formset = super(AccountAdminMixin, self).get_formset(request, obj=obj, **kwargs)
|
formset = super(AccountAdminMixin, self).get_formset(request, obj, **kwargs)
|
||||||
formset.form.account = self.account
|
formset.form.account = self.account
|
||||||
formset.account = self.account
|
formset.account = self.account
|
||||||
return formset
|
return formset
|
||||||
|
@ -263,7 +291,7 @@ class AccountAdminMixin(object):
|
||||||
class SelectAccountAdminMixin(AccountAdminMixin):
|
class SelectAccountAdminMixin(AccountAdminMixin):
|
||||||
""" Provides support for accounts on ModelAdmin """
|
""" Provides support for accounts on ModelAdmin """
|
||||||
def get_inline_instances(self, request, obj=None):
|
def get_inline_instances(self, request, obj=None):
|
||||||
inlines = super(AccountAdminMixin, self).get_inline_instances(request, obj=obj)
|
inlines = super(AccountAdminMixin, self).get_inline_instances(request, obj)
|
||||||
if self.account:
|
if self.account:
|
||||||
account = self.account
|
account = self.account
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from django.contrib.admin import SimpleListFilter
|
from django.contrib.admin import SimpleListFilter
|
||||||
|
from django.db.models import Q
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
@ -18,3 +19,23 @@ class HasMainUserListFilter(SimpleListFilter):
|
||||||
return queryset.filter(users__isnull=False).distinct()
|
return queryset.filter(users__isnull=False).distinct()
|
||||||
if self.value() == 'False':
|
if self.value() == 'False':
|
||||||
return queryset.filter(users__isnull=True).distinct()
|
return queryset.filter(users__isnull=True).distinct()
|
||||||
|
|
||||||
|
|
||||||
|
class IsActiveListFilter(SimpleListFilter):
|
||||||
|
title = _("Is active")
|
||||||
|
parameter_name = 'active'
|
||||||
|
|
||||||
|
def lookups(self, request, model_admin):
|
||||||
|
return (
|
||||||
|
('True', _("True")),
|
||||||
|
('False', _("False")),
|
||||||
|
)
|
||||||
|
|
||||||
|
def queryset(self, request, queryset):
|
||||||
|
if self.value() == 'True':
|
||||||
|
return queryset.filter(is_active=True, account__is_active=True)
|
||||||
|
elif self.value() == 'False':
|
||||||
|
return queryset.filter(Q(is_active=False) | Q(account__is_active=False))
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,8 @@ from django.db.models.loading import get_model
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from orchestra.apps.orchestration.middlewares import OperationsMiddleware
|
||||||
|
from orchestra.apps.orchestration.models import BackendOperation as Operation
|
||||||
from orchestra.core import services, accounts
|
from orchestra.core import services, accounts
|
||||||
from orchestra.utils import send_email_template
|
from orchestra.utils import send_email_template
|
||||||
|
|
||||||
|
@ -63,11 +65,15 @@ class Account(auth.AbstractBaseUser):
|
||||||
|
|
||||||
def save(self, active_systemuser=False, *args, **kwargs):
|
def save(self, active_systemuser=False, *args, **kwargs):
|
||||||
created = not self.pk
|
created = not self.pk
|
||||||
|
if not created:
|
||||||
|
was_active = Account.objects.filter(pk=self.pk).values_list('is_active', flat=True)[0]
|
||||||
super(Account, self).save(*args, **kwargs)
|
super(Account, self).save(*args, **kwargs)
|
||||||
if created:
|
if created:
|
||||||
self.main_systemuser = self.systemusers.create(account=self, username=self.username,
|
self.main_systemuser = self.systemusers.create(account=self, username=self.username,
|
||||||
password=self.password, is_active=active_systemuser)
|
password=self.password, is_active=active_systemuser)
|
||||||
self.save(update_fields=['main_systemuser'])
|
self.save(update_fields=['main_systemuser'])
|
||||||
|
elif was_active != self.is_active:
|
||||||
|
self.notify_related()
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
self.short_name = self.short_name.strip()
|
self.short_name = self.short_name.strip()
|
||||||
|
@ -76,12 +82,15 @@ class Account(auth.AbstractBaseUser):
|
||||||
def disable(self):
|
def disable(self):
|
||||||
self.is_active = False
|
self.is_active = False
|
||||||
self.save(update_fields=['is_active'])
|
self.save(update_fields=['is_active'])
|
||||||
|
self.notify_related()
|
||||||
|
|
||||||
|
def notify_related(self):
|
||||||
# Trigger save() on related objects that depend on this account
|
# Trigger save() on related objects that depend on this account
|
||||||
for rel in self._meta.get_all_related_objects():
|
for rel in self._meta.get_all_related_objects():
|
||||||
source = getattr(rel, 'related_model', rel.model)
|
source = getattr(rel, 'related_model', rel.model)
|
||||||
if source in services and hasattr(source, 'active'):
|
if source in services and hasattr(source, 'active'):
|
||||||
for obj in getattr(self, rel.get_accessor_name()).all():
|
for obj in getattr(self, rel.get_accessor_name()).all():
|
||||||
obj.save(update_fields=[])
|
OperationsMiddleware.collect(Operation.SAVE, instance=obj, update_fields=[])
|
||||||
|
|
||||||
def send_email(self, template, context, contacts=[], attachments=[], html=None):
|
def send_email(self, template, context, contacts=[], attachments=[], html=None):
|
||||||
contacts = self.contacts.filter(email_usages=contacts)
|
contacts = self.contacts.filter(email_usages=contacts)
|
||||||
|
|
|
@ -145,19 +145,19 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
|
||||||
display_payment_state.short_description = _("Payment")
|
display_payment_state.short_description = _("Payment")
|
||||||
|
|
||||||
def get_readonly_fields(self, request, obj=None):
|
def get_readonly_fields(self, request, obj=None):
|
||||||
fields = super(BillAdmin, self).get_readonly_fields(request, obj=obj)
|
fields = super(BillAdmin, self).get_readonly_fields(request, obj)
|
||||||
if obj and not obj.is_open:
|
if obj and not obj.is_open:
|
||||||
fields += self.add_fields
|
fields += self.add_fields
|
||||||
return fields
|
return fields
|
||||||
|
|
||||||
def get_fieldsets(self, request, obj=None):
|
def get_fieldsets(self, request, obj=None):
|
||||||
fieldsets = super(BillAdmin, self).get_fieldsets(request, obj=obj)
|
fieldsets = super(BillAdmin, self).get_fieldsets(request, obj)
|
||||||
if obj and obj.is_open:
|
if obj and obj.is_open:
|
||||||
fieldsets = (fieldsets[0],)
|
fieldsets = (fieldsets[0],)
|
||||||
return fieldsets
|
return fieldsets
|
||||||
|
|
||||||
def get_change_view_actions(self, obj=None):
|
def get_change_view_actions(self, obj=None):
|
||||||
actions = super(BillAdmin, self).get_change_view_actions(obj=obj)
|
actions = super(BillAdmin, self).get_change_view_actions(obj)
|
||||||
exclude = []
|
exclude = []
|
||||||
if obj:
|
if obj:
|
||||||
if not obj.is_open:
|
if not obj.is_open:
|
||||||
|
@ -165,7 +165,7 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
|
||||||
return [action for action in actions if action.__name__ not in exclude]
|
return [action for action in actions if action.__name__ not in exclude]
|
||||||
|
|
||||||
def get_inline_instances(self, request, obj=None):
|
def get_inline_instances(self, request, obj=None):
|
||||||
inlines = super(BillAdmin, self).get_inline_instances(request, obj=obj)
|
inlines = super(BillAdmin, self).get_inline_instances(request, obj)
|
||||||
if obj and not obj.is_open:
|
if obj and not obj.is_open:
|
||||||
return [inline for inline in inlines if not type(inline) == BillLineInline]
|
return [inline for inline in inlines if not type(inline) == BillLineInline]
|
||||||
return [inline for inline in inlines if not type(inline) == ClosedBillLineInline]
|
return [inline for inline in inlines if not type(inline) == ClosedBillLineInline]
|
||||||
|
|
|
@ -110,7 +110,6 @@ def validate_zone(zone):
|
||||||
zone_name = zone.split()[0][:-1]
|
zone_name = zone.split()[0][:-1]
|
||||||
checkzone = settings.DOMAINS_CHECKZONE_BIN_PATH
|
checkzone = settings.DOMAINS_CHECKZONE_BIN_PATH
|
||||||
cmd = ' '.join(["echo -e '%s'" % zone, '|', checkzone, zone_name, '/dev/stdin'])
|
cmd = ' '.join(["echo -e '%s'" % zone, '|', checkzone, zone_name, '/dev/stdin'])
|
||||||
print cmd
|
|
||||||
check = run(cmd, error_codes=[0, 1], display=False)
|
check = run(cmd, error_codes=[0, 1], display=False)
|
||||||
if check.return_code == 1:
|
if check.return_code == 1:
|
||||||
errors = re.compile(r'zone.*: (.*)').findall(check.stdout)[:-1]
|
errors = re.compile(r'zone.*: (.*)').findall(check.stdout)[:-1]
|
||||||
|
|
|
@ -10,6 +10,7 @@ from django.utils.translation import ugettext_lazy as _
|
||||||
from orchestra.admin import ExtendedModelAdmin, ChangePasswordAdminMixin
|
from orchestra.admin import ExtendedModelAdmin, ChangePasswordAdminMixin
|
||||||
from orchestra.admin.utils import admin_link, change_url
|
from orchestra.admin.utils import admin_link, change_url
|
||||||
from orchestra.apps.accounts.admin import SelectAccountAdminMixin, AccountAdminMixin
|
from orchestra.apps.accounts.admin import SelectAccountAdminMixin, AccountAdminMixin
|
||||||
|
from orchestra.apps.accounts.filters import IsActiveListFilter
|
||||||
|
|
||||||
from . import settings
|
from . import settings
|
||||||
from .actions import SendMailboxEmail
|
from .actions import SendMailboxEmail
|
||||||
|
@ -30,9 +31,9 @@ class AutoresponseInline(admin.StackedInline):
|
||||||
|
|
||||||
class MailboxAdmin(ChangePasswordAdminMixin, SelectAccountAdminMixin, ExtendedModelAdmin):
|
class MailboxAdmin(ChangePasswordAdminMixin, SelectAccountAdminMixin, ExtendedModelAdmin):
|
||||||
list_display = (
|
list_display = (
|
||||||
'name', 'account_link', 'filtering', 'display_addresses'
|
'name', 'account_link', 'filtering', 'display_addresses', 'display_active',
|
||||||
)
|
)
|
||||||
list_filter = (HasAddressListFilter, 'filtering')
|
list_filter = (IsActiveListFilter, HasAddressListFilter, 'filtering')
|
||||||
search_fields = ('account__username', 'account__short_name', 'account__full_name', 'name')
|
search_fields = ('account__username', 'account__short_name', 'account__full_name', 'name')
|
||||||
add_fieldsets = (
|
add_fieldsets = (
|
||||||
(None, {
|
(None, {
|
||||||
|
@ -81,7 +82,7 @@ class MailboxAdmin(ChangePasswordAdminMixin, SelectAccountAdminMixin, ExtendedMo
|
||||||
return super(MailboxAdmin, self).get_actions(request)
|
return super(MailboxAdmin, self).get_actions(request)
|
||||||
|
|
||||||
def get_fieldsets(self, request, obj=None):
|
def get_fieldsets(self, request, obj=None):
|
||||||
fieldsets = super(MailboxAdmin, self).get_fieldsets(request, obj=obj)
|
fieldsets = super(MailboxAdmin, self).get_fieldsets(request, obj)
|
||||||
if obj and obj.filtering == obj.CUSTOM:
|
if obj and obj.filtering == obj.CUSTOM:
|
||||||
# not collapsed filtering when exists
|
# not collapsed filtering when exists
|
||||||
fieldsets = copy.deepcopy(fieldsets)
|
fieldsets = copy.deepcopy(fieldsets)
|
||||||
|
@ -147,7 +148,7 @@ class AddressAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
|
||||||
|
|
||||||
def get_fields(self, request, obj=None):
|
def get_fields(self, request, obj=None):
|
||||||
""" Remove mailboxes field when creating address from a popup i.e. from mailbox add form """
|
""" Remove mailboxes field when creating address from a popup i.e. from mailbox add form """
|
||||||
fields = super(AddressAdmin, self).get_fields(request, obj=obj)
|
fields = super(AddressAdmin, self).get_fields(request, obj)
|
||||||
if '_to_field' in parse_qs(request.META['QUERY_STRING']):
|
if '_to_field' in parse_qs(request.META['QUERY_STRING']):
|
||||||
# Add address popup
|
# Add address popup
|
||||||
fields = list(fields)
|
fields = list(fields)
|
||||||
|
|
|
@ -88,7 +88,7 @@ class MiscellaneousAdmin(AccountAdminMixin, SelectPluginAdminMixin, admin.ModelA
|
||||||
return fields
|
return fields
|
||||||
|
|
||||||
def get_form(self, request, obj=None, **kwargs):
|
def get_form(self, request, obj=None, **kwargs):
|
||||||
form = super(SelectPluginAdminMixin, self).get_form(request, obj=obj, **kwargs)
|
form = super(SelectPluginAdminMixin, self).get_form(request, obj, **kwargs)
|
||||||
service = self.get_service(obj)
|
service = self.get_service(obj)
|
||||||
def clean_identifier(self, service=service):
|
def clean_identifier(self, service=service):
|
||||||
identifier = self.cleaned_data['identifier']
|
identifier = self.cleaned_data['identifier']
|
||||||
|
|
|
@ -62,7 +62,7 @@ class RouteAdmin(admin.ModelAdmin):
|
||||||
|
|
||||||
def get_form(self, request, obj=None, **kwargs):
|
def get_form(self, request, obj=None, **kwargs):
|
||||||
""" Include dynamic help text for existing objects """
|
""" Include dynamic help text for existing objects """
|
||||||
form = super(RouteAdmin, self).get_form(request, obj=obj, **kwargs)
|
form = super(RouteAdmin, self).get_form(request, obj, **kwargs)
|
||||||
if obj:
|
if obj:
|
||||||
form.base_fields['backend'].help_text = self.BACKEND_HELP_TEXT.get(obj.backend, '')
|
form.base_fields['backend'].help_text = self.BACKEND_HELP_TEXT.get(obj.backend, '')
|
||||||
return form
|
return form
|
||||||
|
|
|
@ -128,7 +128,7 @@ class Order(models.Model):
|
||||||
get_latest_by = 'id'
|
get_latest_by = 'id'
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return str(self.service)
|
return unicode(self.service)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def update_orders(cls, instance, service=None, commit=True):
|
def update_orders(cls, instance, service=None, commit=True):
|
||||||
|
@ -178,8 +178,9 @@ class Order(models.Model):
|
||||||
MetricStorage.store(self, metric)
|
MetricStorage.store(self, metric)
|
||||||
metric = ', metric:{}'.format(metric)
|
metric = ', metric:{}'.format(metric)
|
||||||
description = handler.get_order_description(instance)
|
description = handler.get_order_description(instance)
|
||||||
logger.info("UPDATED order id:{id}, description:{description}{metric}".format(
|
logger.info(u"UPDATED order id:{id}, description:{description}{metric}".format(
|
||||||
id=self.id, description=description, metric=metric))
|
id=self.id, description=description, metric=metric).encode('ascii', 'ignore')
|
||||||
|
)
|
||||||
if self.description != description:
|
if self.description != description:
|
||||||
self.description = description
|
self.description = description
|
||||||
self.save(update_fields=['description'])
|
self.save(update_fields=['description'])
|
||||||
|
|
|
@ -34,7 +34,7 @@ class PaymentSource(models.Model):
|
||||||
return PaymentMethod.get_plugin(self.method)
|
return PaymentMethod.get_plugin(self.method)
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def service_instance(self):
|
def method_instance(self):
|
||||||
""" Per request lived method_instance """
|
""" Per request lived method_instance """
|
||||||
return self.method_class(self)
|
return self.method_class(self)
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,5 @@ class BSCWService(SoftwareService):
|
||||||
form = BSCWForm
|
form = BSCWForm
|
||||||
serializer = BSCWDataSerializer
|
serializer = BSCWDataSerializer
|
||||||
icon = 'orchestra/icons/apps/BSCW.png'
|
icon = 'orchestra/icons/apps/BSCW.png'
|
||||||
# TODO override from settings
|
|
||||||
site_domain = settings.SAAS_BSCW_DOMAIN
|
site_domain = settings.SAAS_BSCW_DOMAIN
|
||||||
change_readonly_fileds = ('email',)
|
change_readonly_fileds = ('email',)
|
||||||
|
|
|
@ -93,3 +93,6 @@ class SoftwareService(plugins.Plugin):
|
||||||
|
|
||||||
def delete(self):
|
def delete(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def get_related(self):
|
||||||
|
return []
|
||||||
|
|
|
@ -9,6 +9,7 @@ SAAS_ENABLED_SERVICES = getattr(settings, 'SAAS_ENABLED_SERVICES', (
|
||||||
'orchestra.apps.saas.services.wordpress.WordPressService',
|
'orchestra.apps.saas.services.wordpress.WordPressService',
|
||||||
'orchestra.apps.saas.services.dokuwiki.DokuWikiService',
|
'orchestra.apps.saas.services.dokuwiki.DokuWikiService',
|
||||||
'orchestra.apps.saas.services.drupal.DrupalService',
|
'orchestra.apps.saas.services.drupal.DrupalService',
|
||||||
|
'orchestra.apps.saas.services.seafile.SeaFileService',
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
||||||
|
@ -42,16 +43,24 @@ SAAS_PHPLIST_BASE_DOMAIN = getattr(settings, 'SAAS_PHPLIST_BASE_DOMAIN',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
SAAS_SEAFILE_DOMAIN = getattr(settings, 'SAAS_SEAFILE_DOMAIN',
|
||||||
|
'seafile.orchestra.lan'
|
||||||
|
)
|
||||||
|
|
||||||
|
SAAS_SEAFILE_DEFAULT_QUOTA = getattr(settings, 'SAAS_SEAFILE_DEFAULT_QUOTA',
|
||||||
|
50
|
||||||
|
)
|
||||||
|
|
||||||
SAAS_BSCW_DOMAIN = getattr(settings, 'SAAS_BSCW_DOMAIN',
|
SAAS_BSCW_DOMAIN = getattr(settings, 'SAAS_BSCW_DOMAIN',
|
||||||
'bscw.orchestra.lan'
|
'bscw.orchestra.lan'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
SAAS_BSCW_DEFAULT_QUOTA = getattr(settings, 'SAAS_BSCW_DEFAULT_QUOTA',
|
SAAS_BSCW_DEFAULT_QUOTA = getattr(settings, 'SAAS_BSCW_DEFAULT_QUOTA',
|
||||||
50
|
50
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
SAAS_GITLAB_ROOT_PASSWORD = getattr(settings, 'SAAS_GITLAB_ROOT_PASSWORD',
|
SAAS_GITLAB_ROOT_PASSWORD = getattr(settings, 'SAAS_GITLAB_ROOT_PASSWORD',
|
||||||
'secret'
|
'secret'
|
||||||
)
|
)
|
||||||
|
|
|
@ -67,11 +67,13 @@ view_help.verbose_name = _("Help")
|
||||||
def clone(modeladmin, request, queryset):
|
def clone(modeladmin, request, queryset):
|
||||||
service = queryset.get()
|
service = queryset.get()
|
||||||
fields = modeladmin.get_fields(request)
|
fields = modeladmin.get_fields(request)
|
||||||
fk_fields = ('content_type',)
|
|
||||||
query = []
|
query = []
|
||||||
for field in fields:
|
for field in fields:
|
||||||
if field in fk_fields:
|
model_field = type(service)._meta.get_field_by_name(field)[0]
|
||||||
|
if model_field.rel:
|
||||||
value = getattr(service, field + '_id')
|
value = getattr(service, field + '_id')
|
||||||
|
elif 'Boolean' in model_field.__class__.__name__:
|
||||||
|
value = 'True' if getattr(service, field) else ''
|
||||||
else:
|
else:
|
||||||
value = getattr(service, field)
|
value = getattr(service, field)
|
||||||
query.append('%s=%s' % (field, value))
|
query.append('%s=%s' % (field, value))
|
||||||
|
|
|
@ -4,6 +4,7 @@ import decimal
|
||||||
|
|
||||||
from dateutil import relativedelta
|
from dateutil import relativedelta
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
@ -44,7 +45,7 @@ class ServiceHandler(plugins.Plugin):
|
||||||
|
|
||||||
def validate_match(self, service):
|
def validate_match(self, service):
|
||||||
if not service.match:
|
if not service.match:
|
||||||
raise ValidationError(_("Match should be provided."))
|
service.match = 'True'
|
||||||
try:
|
try:
|
||||||
obj = service.content_type.model_class().objects.all()[0]
|
obj = service.content_type.model_class().objects.all()[0]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
|
@ -125,7 +126,7 @@ class ServiceHandler(plugins.Plugin):
|
||||||
instance._meta.model_name: instance,
|
instance._meta.model_name: instance,
|
||||||
}
|
}
|
||||||
if not self.order_description:
|
if not self.order_description:
|
||||||
return '%s: %s' % (self.description, instance)
|
return u'%s: %s' % (self.description, instance)
|
||||||
return eval(self.order_description, safe_locals)
|
return eval(self.order_description, safe_locals)
|
||||||
|
|
||||||
def get_billing_point(self, order, bp=None, **options):
|
def get_billing_point(self, order, bp=None, **options):
|
||||||
|
|
|
@ -53,7 +53,7 @@ class Service(models.Model):
|
||||||
"Related instance can be instantiated with <tt>instance</tt> keyword or "
|
"Related instance can be instantiated with <tt>instance</tt> keyword or "
|
||||||
"<tt>content_type.model_name</tt>.</br>"
|
"<tt>content_type.model_name</tt>.</br>"
|
||||||
"<tt> databaseuser.type == 'MYSQL'</tt><br>"
|
"<tt> databaseuser.type == 'MYSQL'</tt><br>"
|
||||||
"<tt> miscellaneous.active and miscellaneous.identifier.endswith(('.org', '.net', '.com'))'</tt><br>"
|
"<tt> miscellaneous.active and miscellaneous.identifier.endswith(('.org', '.net', '.com'))</tt><br>"
|
||||||
"<tt> contractedplan.plan.name == 'association_fee''</tt><br>"
|
"<tt> contractedplan.plan.name == 'association_fee''</tt><br>"
|
||||||
"<tt> instance.active</tt>"))
|
"<tt> instance.active</tt>"))
|
||||||
handler_type = models.CharField(_("handler"), max_length=256, blank=True,
|
handler_type = models.CharField(_("handler"), max_length=256, blank=True,
|
||||||
|
@ -94,7 +94,7 @@ class Service(models.Model):
|
||||||
help_text=_("Period in which orders will be ignored if cancelled. "
|
help_text=_("Period in which orders will be ignored if cancelled. "
|
||||||
"Useful for designating <i>trial periods</i>"),
|
"Useful for designating <i>trial periods</i>"),
|
||||||
choices=(
|
choices=(
|
||||||
(NEVER, _("No ignore")),
|
(NEVER, _("Never")),
|
||||||
(ONE_DAY, _("One day")),
|
(ONE_DAY, _("One day")),
|
||||||
(TWO_DAYS, _("Two days")),
|
(TWO_DAYS, _("Two days")),
|
||||||
(TEN_DAYS, _("Ten days")),
|
(TEN_DAYS, _("Ten days")),
|
||||||
|
@ -112,7 +112,7 @@ class Service(models.Model):
|
||||||
"<tt> miscellaneous.amount</tt><br>"
|
"<tt> miscellaneous.amount</tt><br>"
|
||||||
"<tt> max((account.resources.traffic.used or 0) -"
|
"<tt> max((account.resources.traffic.used or 0) -"
|
||||||
" getattr(account.miscellaneous.filter(is_active=True,"
|
" getattr(account.miscellaneous.filter(is_active=True,"
|
||||||
" service__name='traffic prepay').last(), 'amount', 0), 0)</tt>"))
|
" service__name='traffic-prepay').last(), 'amount', 0), 0)</tt>"))
|
||||||
nominal_price = models.DecimalField(_("nominal price"), max_digits=12,
|
nominal_price = models.DecimalField(_("nominal price"), max_digits=12,
|
||||||
decimal_places=2)
|
decimal_places=2)
|
||||||
tax = models.PositiveIntegerField(_("tax"), choices=settings.SERVICES_SERVICE_TAXES,
|
tax = models.PositiveIntegerField(_("tax"), choices=settings.SERVICES_SERVICE_TAXES,
|
||||||
|
@ -174,10 +174,11 @@ class Service(models.Model):
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
self.description = self.description.strip()
|
self.description = self.description.strip()
|
||||||
|
if hasattr(self, 'content_type'):
|
||||||
validators.all_valid({
|
validators.all_valid({
|
||||||
'content_type': (self.handler.validate_content_type, self),
|
'content_type': (self.handler.validate_content_type, self),
|
||||||
'match': (self.handlers.validate_match, self),
|
'match': (self.handler.validate_match, self),
|
||||||
'metric': (self.handlers.validate_metric, self),
|
'metric': (self.handler.validate_metric, self),
|
||||||
})
|
})
|
||||||
|
|
||||||
def get_pricing_period(self):
|
def get_pricing_period(self):
|
||||||
|
|
|
@ -18,4 +18,4 @@ SERVICES_SERVICE_ANUAL_BILLING_MONTH = getattr(settings, 'SERVICES_SERVICE_ANUAL
|
||||||
SERVICES_ORDER_MODEL = getattr(settings, 'SERVICES_ORDER_MODEL', 'orders.Order')
|
SERVICES_ORDER_MODEL = getattr(settings, 'SERVICES_ORDER_MODEL', 'orders.Order')
|
||||||
|
|
||||||
|
|
||||||
SERVICES_DEFAULT_IGNORE_PERIOD = getattr(settings, 'SERVICES_DEFAULT_IGNORE_PERIOD', 'TWO_DAYS')
|
SERVICES_DEFAULT_IGNORE_PERIOD = getattr(settings, 'SERVICES_DEFAULT_IGNORE_PERIOD', 'TEN_DAYS')
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.contrib import messages
|
from django.contrib import messages, admin
|
||||||
|
from django.core.exceptions import PermissionDenied
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
|
@ -28,3 +29,30 @@ def grant_permission(modeladmin, request, queryset):
|
||||||
# TODO
|
# TODO
|
||||||
grant_permission.url_name = 'grant-permission'
|
grant_permission.url_name = 'grant-permission'
|
||||||
grant_permission.verbose_name = _("Grant permission")
|
grant_permission.verbose_name = _("Grant permission")
|
||||||
|
|
||||||
|
|
||||||
|
def delete_selected(modeladmin, request, queryset):
|
||||||
|
""" wrapper arround admin.actions.delete_selected to prevent main system users deletion """
|
||||||
|
opts = modeladmin.model._meta
|
||||||
|
app_label = opts.app_label
|
||||||
|
# Check that the user has delete permission for the actual model
|
||||||
|
if not modeladmin.has_delete_permission(request):
|
||||||
|
raise PermissionDenied
|
||||||
|
else:
|
||||||
|
accounts = []
|
||||||
|
for user in queryset:
|
||||||
|
if user.is_main:
|
||||||
|
accounts.append(user.username)
|
||||||
|
if accounts:
|
||||||
|
n = len(accounts)
|
||||||
|
messages.error(request, ungettext(
|
||||||
|
"You have selected one main system user (%(accounts)s), which can not be deleted.",
|
||||||
|
"You have selected some main system users which can not be deleted (%(accounts)s).",
|
||||||
|
n) % {
|
||||||
|
'accounts': ', '.join(accounts[:10]+['...'] if n > 10 else accounts)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return
|
||||||
|
return admin.actions.delete_selected(modeladmin, request, queryset)
|
||||||
|
delete_selected.short_description = _("Delete selected %(verbose_name_plural)s")
|
||||||
|
|
||||||
|
|
|
@ -12,10 +12,11 @@ from django.utils.safestring import mark_safe
|
||||||
from orchestra.admin import ExtendedModelAdmin, ChangePasswordAdminMixin
|
from orchestra.admin import ExtendedModelAdmin, ChangePasswordAdminMixin
|
||||||
from orchestra.admin.utils import wrap_admin_view
|
from orchestra.admin.utils import wrap_admin_view
|
||||||
from orchestra.apps.accounts.admin import SelectAccountAdminMixin
|
from orchestra.apps.accounts.admin import SelectAccountAdminMixin
|
||||||
|
from orchestra.apps.accounts.filters import IsActiveListFilter
|
||||||
from orchestra.forms import UserCreationForm, UserChangeForm
|
from orchestra.forms import UserCreationForm, UserChangeForm
|
||||||
|
|
||||||
from . import settings
|
from . import settings
|
||||||
from .actions import grant_permission
|
from .actions import grant_permission, delete_selected
|
||||||
from .filters import IsMainListFilter
|
from .filters import IsMainListFilter
|
||||||
from .forms import SystemUserCreationForm, SystemUserChangeForm
|
from .forms import SystemUserCreationForm, SystemUserChangeForm
|
||||||
from .models import SystemUser
|
from .models import SystemUser
|
||||||
|
@ -25,7 +26,7 @@ class SystemUserAdmin(ChangePasswordAdminMixin, SelectAccountAdminMixin, Extende
|
||||||
list_display = (
|
list_display = (
|
||||||
'username', 'account_link', 'shell', 'display_home', 'display_active', 'display_main'
|
'username', 'account_link', 'shell', 'display_home', 'display_active', 'display_main'
|
||||||
)
|
)
|
||||||
list_filter = ('is_active', 'shell', IsMainListFilter)
|
list_filter = (IsActiveListFilter, 'shell', IsMainListFilter)
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, {
|
(None, {
|
||||||
'fields': ('username', 'password', 'account_link', 'is_active')
|
'fields': ('username', 'password', 'account_link', 'is_active')
|
||||||
|
@ -50,15 +51,9 @@ class SystemUserAdmin(ChangePasswordAdminMixin, SelectAccountAdminMixin, Extende
|
||||||
add_form = SystemUserCreationForm
|
add_form = SystemUserCreationForm
|
||||||
form = SystemUserChangeForm
|
form = SystemUserChangeForm
|
||||||
ordering = ('-id',)
|
ordering = ('-id',)
|
||||||
actions = (grant_permission,)
|
actions = (delete_selected, grant_permission,)
|
||||||
change_view_actions = actions
|
change_view_actions = actions
|
||||||
|
|
||||||
def display_active(self, user):
|
|
||||||
return user.active
|
|
||||||
display_active.short_description = _("Active")
|
|
||||||
display_active.admin_order_field = 'is_active'
|
|
||||||
display_active.boolean = True
|
|
||||||
|
|
||||||
def display_main(self, user):
|
def display_main(self, user):
|
||||||
return user.is_main
|
return user.is_main
|
||||||
display_main.short_description = _("Main")
|
display_main.short_description = _("Main")
|
||||||
|
@ -70,7 +65,7 @@ class SystemUserAdmin(ChangePasswordAdminMixin, SelectAccountAdminMixin, Extende
|
||||||
display_home.admin_order_field = 'home'
|
display_home.admin_order_field = 'home'
|
||||||
|
|
||||||
def get_form(self, request, obj=None, **kwargs):
|
def get_form(self, request, obj=None, **kwargs):
|
||||||
form = super(SystemUserAdmin, self).get_form(request, obj=obj, **kwargs)
|
form = super(SystemUserAdmin, self).get_form(request, obj, **kwargs)
|
||||||
form.account = self.account
|
form.account = self.account
|
||||||
if obj:
|
if obj:
|
||||||
# Has to be done here and not in the form because of strange phenomenon
|
# Has to be done here and not in the form because of strange phenomenon
|
||||||
|
@ -83,7 +78,7 @@ class SystemUserAdmin(ChangePasswordAdminMixin, SelectAccountAdminMixin, Extende
|
||||||
def has_delete_permission(self, request, obj=None):
|
def has_delete_permission(self, request, obj=None):
|
||||||
if obj and obj.is_main:
|
if obj and obj.is_main:
|
||||||
return False
|
return False
|
||||||
return super(SystemUserAdmin, self).has_delete_permission(request, obj=obj)
|
return super(SystemUserAdmin, self).has_delete_permission(request, obj)
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(SystemUser, SystemUserAdmin)
|
admin.site.register(SystemUser, SystemUserAdmin)
|
||||||
|
|
|
@ -18,6 +18,7 @@ class SystemUserBackend(ServiceController):
|
||||||
context = self.get_context(user)
|
context = self.get_context(user)
|
||||||
groups = ','.join(self.get_groups(user))
|
groups = ','.join(self.get_groups(user))
|
||||||
context['groups_arg'] = '--groups %s' % groups if groups else ''
|
context['groups_arg'] = '--groups %s' % groups if groups else ''
|
||||||
|
# TODO userd add will fail if %(user)s group already exists
|
||||||
self.append(textwrap.dedent("""
|
self.append(textwrap.dedent("""
|
||||||
if [[ $( id %(user)s ) ]]; then
|
if [[ $( id %(user)s ) ]]; then
|
||||||
usermod %(user)s --password '%(password)s' --shell %(shell)s %(groups_arg)s
|
usermod %(user)s --password '%(password)s' --shell %(shell)s %(groups_arg)s
|
||||||
|
|
|
@ -19,11 +19,11 @@ class SelectPluginAdminMixin(object):
|
||||||
else:
|
else:
|
||||||
plugin = self.plugin.get_plugin(self.plugin_value)()
|
plugin = self.plugin.get_plugin(self.plugin_value)()
|
||||||
self.form = plugin.get_form()
|
self.form = plugin.get_form()
|
||||||
return super(SelectPluginAdminMixin, self).get_form(request, obj=obj, **kwargs)
|
return super(SelectPluginAdminMixin, self).get_form(request, obj, **kwargs)
|
||||||
|
|
||||||
def get_fields(self, request, obj=None):
|
def get_fields(self, request, obj=None):
|
||||||
""" Try to maintain original field ordering """
|
""" Try to maintain original field ordering """
|
||||||
fields = super(SelectPluginAdminMixin, self).get_fields(request, obj=obj)
|
fields = super(SelectPluginAdminMixin, self).get_fields(request, obj)
|
||||||
head_fields = list(self.get_readonly_fields(request, obj))
|
head_fields = list(self.get_readonly_fields(request, obj))
|
||||||
head, tail = [], []
|
head, tail = [], []
|
||||||
for field in fields:
|
for field in fields:
|
||||||
|
|
Loading…
Reference in New Issue