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
|
||||
* set_password orchestration method?
|
||||
|
||||
|
||||
* LAST version of this shit http://wkhtmltopdf.org/downloads.html
|
||||
|
||||
|
@ -39,8 +37,6 @@
|
|||
|
||||
* mail backend related_models = ('resources__content_type') ??
|
||||
|
||||
* Domain backend PowerDNS Bind validation support?
|
||||
|
||||
* Maildir billing tests/ webdisk billing tests (avg metric)
|
||||
|
||||
|
||||
|
@ -50,10 +46,6 @@
|
|||
|
||||
* 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
|
||||
|
||||
* 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
|
||||
* 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
|
||||
|
||||
* 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)
|
||||
|
||||
|
||||
* 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
|
||||
|
||||
* Mix webapps type with backends (two for the price of one)
|
||||
|
||||
* 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
|
||||
|
||||
* SaaS model splitted into SaaSUser and SaaSSite? inherit from SaaS
|
||||
|
||||
* 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
|
||||
|
||||
|
||||
* 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
|
||||
|
||||
* contact.alternative_phone on a phone.tooltip, email:to
|
||||
|
||||
|
||||
* better validate options and directives (url locations, filesystem paths, etc..)
|
||||
|
||||
* make sure that you understand the risks
|
||||
|
||||
|
||||
* full support for deactivation of services/accounts
|
||||
* 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 '/'
|
||||
|
||||
* 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
|
||||
* 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
|
||||
|
||||
* saas require unique emails? connect to backend server to find out because they change
|
||||
|
||||
* automaitcally set passwords and email users?
|
||||
|
||||
* 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):
|
||||
fields = super(ChangeAddFieldsMixin, self).get_readonly_fields(request, obj)
|
||||
if obj:
|
||||
return fields + self.get_change_readonly_fields(request, obj=obj)
|
||||
return fields + self.get_change_readonly_fields(request, obj)
|
||||
return fields
|
||||
|
||||
def get_fieldsets(self, request, obj=None):
|
||||
|
|
|
@ -31,7 +31,7 @@ disable.verbose_name = _("Disable")
|
|||
def list_contacts(modeladmin, request, queryset):
|
||||
ids = queryset.values_list('id', flat=True)
|
||||
if not ids:
|
||||
message.warning(request, "Select at least one account.")
|
||||
messages.warning(request, "Select at least one account.")
|
||||
return
|
||||
url = reverse('admin:contacts_contact_changelist')
|
||||
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)
|
||||
|
||||
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:
|
||||
fields = AccountCreationForm.create_related_fields
|
||||
if fields:
|
||||
|
@ -153,6 +153,17 @@ class AccountAdminMixin(object):
|
|||
account = None
|
||||
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):
|
||||
account = instance.account if instance.pk else self.account
|
||||
url = change_url(account)
|
||||
|
@ -161,6 +172,23 @@ class AccountAdminMixin(object):
|
|||
account_link.allow_tags = True
|
||||
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):
|
||||
""" remove account or account_link depending on the case """
|
||||
fields = super(AccountAdminMixin, self).get_fields(request, obj)
|
||||
|
@ -181,7 +209,7 @@ class AccountAdminMixin(object):
|
|||
""" provide account for filter_by_account_fields """
|
||||
if obj:
|
||||
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):
|
||||
""" Filter by account """
|
||||
|
@ -207,7 +235,7 @@ class AccountAdminMixin(object):
|
|||
|
||||
def get_formset(self, request, obj=None, **kwargs):
|
||||
""" 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.account = self.account
|
||||
return formset
|
||||
|
@ -263,7 +291,7 @@ class AccountAdminMixin(object):
|
|||
class SelectAccountAdminMixin(AccountAdminMixin):
|
||||
""" Provides support for accounts on ModelAdmin """
|
||||
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:
|
||||
account = self.account
|
||||
else:
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from django.contrib.admin import SimpleListFilter
|
||||
from django.db.models import Q
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
|
@ -18,3 +19,23 @@ class HasMainUserListFilter(SimpleListFilter):
|
|||
return queryset.filter(users__isnull=False).distinct()
|
||||
if self.value() == 'False':
|
||||
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.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.utils import send_email_template
|
||||
|
||||
|
@ -63,11 +65,15 @@ class Account(auth.AbstractBaseUser):
|
|||
|
||||
def save(self, active_systemuser=False, *args, **kwargs):
|
||||
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)
|
||||
if created:
|
||||
self.main_systemuser = self.systemusers.create(account=self, username=self.username,
|
||||
password=self.password, is_active=active_systemuser)
|
||||
self.save(update_fields=['main_systemuser'])
|
||||
elif was_active != self.is_active:
|
||||
self.notify_related()
|
||||
|
||||
def clean(self):
|
||||
self.short_name = self.short_name.strip()
|
||||
|
@ -76,12 +82,15 @@ class Account(auth.AbstractBaseUser):
|
|||
def disable(self):
|
||||
self.is_active = False
|
||||
self.save(update_fields=['is_active'])
|
||||
self.notify_related()
|
||||
|
||||
def notify_related(self):
|
||||
# Trigger save() on related objects that depend on this account
|
||||
for rel in self._meta.get_all_related_objects():
|
||||
source = getattr(rel, 'related_model', rel.model)
|
||||
if source in services and hasattr(source, 'active'):
|
||||
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):
|
||||
contacts = self.contacts.filter(email_usages=contacts)
|
||||
|
|
|
@ -145,19 +145,19 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
|
|||
display_payment_state.short_description = _("Payment")
|
||||
|
||||
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:
|
||||
fields += self.add_fields
|
||||
return fields
|
||||
|
||||
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:
|
||||
fieldsets = (fieldsets[0],)
|
||||
return fieldsets
|
||||
|
||||
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 = []
|
||||
if obj:
|
||||
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]
|
||||
|
||||
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:
|
||||
return [inline for inline in inlines if not type(inline) == BillLineInline]
|
||||
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]
|
||||
checkzone = settings.DOMAINS_CHECKZONE_BIN_PATH
|
||||
cmd = ' '.join(["echo -e '%s'" % zone, '|', checkzone, zone_name, '/dev/stdin'])
|
||||
print cmd
|
||||
check = run(cmd, error_codes=[0, 1], display=False)
|
||||
if check.return_code == 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.utils import admin_link, change_url
|
||||
from orchestra.apps.accounts.admin import SelectAccountAdminMixin, AccountAdminMixin
|
||||
from orchestra.apps.accounts.filters import IsActiveListFilter
|
||||
|
||||
from . import settings
|
||||
from .actions import SendMailboxEmail
|
||||
|
@ -30,9 +31,9 @@ class AutoresponseInline(admin.StackedInline):
|
|||
|
||||
class MailboxAdmin(ChangePasswordAdminMixin, SelectAccountAdminMixin, ExtendedModelAdmin):
|
||||
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')
|
||||
add_fieldsets = (
|
||||
(None, {
|
||||
|
@ -81,7 +82,7 @@ class MailboxAdmin(ChangePasswordAdminMixin, SelectAccountAdminMixin, ExtendedMo
|
|||
return super(MailboxAdmin, self).get_actions(request)
|
||||
|
||||
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:
|
||||
# not collapsed filtering when exists
|
||||
fieldsets = copy.deepcopy(fieldsets)
|
||||
|
@ -147,7 +148,7 @@ class AddressAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
|
|||
|
||||
def get_fields(self, request, obj=None):
|
||||
""" 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']):
|
||||
# Add address popup
|
||||
fields = list(fields)
|
||||
|
|
|
@ -88,7 +88,7 @@ class MiscellaneousAdmin(AccountAdminMixin, SelectPluginAdminMixin, admin.ModelA
|
|||
return fields
|
||||
|
||||
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)
|
||||
def clean_identifier(self, service=service):
|
||||
identifier = self.cleaned_data['identifier']
|
||||
|
|
|
@ -62,7 +62,7 @@ class RouteAdmin(admin.ModelAdmin):
|
|||
|
||||
def get_form(self, request, obj=None, **kwargs):
|
||||
""" 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:
|
||||
form.base_fields['backend'].help_text = self.BACKEND_HELP_TEXT.get(obj.backend, '')
|
||||
return form
|
||||
|
|
|
@ -128,7 +128,7 @@ class Order(models.Model):
|
|||
get_latest_by = 'id'
|
||||
|
||||
def __unicode__(self):
|
||||
return str(self.service)
|
||||
return unicode(self.service)
|
||||
|
||||
@classmethod
|
||||
def update_orders(cls, instance, service=None, commit=True):
|
||||
|
@ -178,8 +178,9 @@ class Order(models.Model):
|
|||
MetricStorage.store(self, metric)
|
||||
metric = ', metric:{}'.format(metric)
|
||||
description = handler.get_order_description(instance)
|
||||
logger.info("UPDATED order id:{id}, description:{description}{metric}".format(
|
||||
id=self.id, description=description, metric=metric))
|
||||
logger.info(u"UPDATED order id:{id}, description:{description}{metric}".format(
|
||||
id=self.id, description=description, metric=metric).encode('ascii', 'ignore')
|
||||
)
|
||||
if self.description != description:
|
||||
self.description = description
|
||||
self.save(update_fields=['description'])
|
||||
|
|
|
@ -34,7 +34,7 @@ class PaymentSource(models.Model):
|
|||
return PaymentMethod.get_plugin(self.method)
|
||||
|
||||
@cached_property
|
||||
def service_instance(self):
|
||||
def method_instance(self):
|
||||
""" Per request lived method_instance """
|
||||
return self.method_class(self)
|
||||
|
||||
|
|
|
@ -27,6 +27,5 @@ class BSCWService(SoftwareService):
|
|||
form = BSCWForm
|
||||
serializer = BSCWDataSerializer
|
||||
icon = 'orchestra/icons/apps/BSCW.png'
|
||||
# TODO override from settings
|
||||
site_domain = settings.SAAS_BSCW_DOMAIN
|
||||
change_readonly_fileds = ('email',)
|
||||
|
|
|
@ -93,3 +93,6 @@ class SoftwareService(plugins.Plugin):
|
|||
|
||||
def delete(self):
|
||||
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.dokuwiki.DokuWikiService',
|
||||
'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',
|
||||
'bscw.orchestra.lan'
|
||||
)
|
||||
|
||||
|
||||
SAAS_BSCW_DEFAULT_QUOTA = getattr(settings, 'SAAS_BSCW_DEFAULT_QUOTA',
|
||||
50
|
||||
)
|
||||
|
||||
|
||||
|
||||
SAAS_GITLAB_ROOT_PASSWORD = getattr(settings, 'SAAS_GITLAB_ROOT_PASSWORD',
|
||||
'secret'
|
||||
)
|
||||
|
|
|
@ -67,11 +67,13 @@ view_help.verbose_name = _("Help")
|
|||
def clone(modeladmin, request, queryset):
|
||||
service = queryset.get()
|
||||
fields = modeladmin.get_fields(request)
|
||||
fk_fields = ('content_type',)
|
||||
query = []
|
||||
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')
|
||||
elif 'Boolean' in model_field.__class__.__name__:
|
||||
value = 'True' if getattr(service, field) else ''
|
||||
else:
|
||||
value = getattr(service, field)
|
||||
query.append('%s=%s' % (field, value))
|
||||
|
|
|
@ -4,6 +4,7 @@ import decimal
|
|||
|
||||
from dateutil import relativedelta
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
@ -44,7 +45,7 @@ class ServiceHandler(plugins.Plugin):
|
|||
|
||||
def validate_match(self, service):
|
||||
if not service.match:
|
||||
raise ValidationError(_("Match should be provided."))
|
||||
service.match = 'True'
|
||||
try:
|
||||
obj = service.content_type.model_class().objects.all()[0]
|
||||
except IndexError:
|
||||
|
@ -125,7 +126,7 @@ class ServiceHandler(plugins.Plugin):
|
|||
instance._meta.model_name: instance,
|
||||
}
|
||||
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)
|
||||
|
||||
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 "
|
||||
"<tt>content_type.model_name</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> instance.active</tt>"))
|
||||
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. "
|
||||
"Useful for designating <i>trial periods</i>"),
|
||||
choices=(
|
||||
(NEVER, _("No ignore")),
|
||||
(NEVER, _("Never")),
|
||||
(ONE_DAY, _("One day")),
|
||||
(TWO_DAYS, _("Two days")),
|
||||
(TEN_DAYS, _("Ten days")),
|
||||
|
@ -112,7 +112,7 @@ class Service(models.Model):
|
|||
"<tt> miscellaneous.amount</tt><br>"
|
||||
"<tt> max((account.resources.traffic.used or 0) -"
|
||||
" 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,
|
||||
decimal_places=2)
|
||||
tax = models.PositiveIntegerField(_("tax"), choices=settings.SERVICES_SERVICE_TAXES,
|
||||
|
@ -174,11 +174,12 @@ class Service(models.Model):
|
|||
|
||||
def clean(self):
|
||||
self.description = self.description.strip()
|
||||
validators.all_valid({
|
||||
'content_type': (self.handler.validate_content_type, self),
|
||||
'match': (self.handlers.validate_match, self),
|
||||
'metric': (self.handlers.validate_metric, self),
|
||||
})
|
||||
if hasattr(self, 'content_type'):
|
||||
validators.all_valid({
|
||||
'content_type': (self.handler.validate_content_type, self),
|
||||
'match': (self.handler.validate_match, self),
|
||||
'metric': (self.handler.validate_metric, self),
|
||||
})
|
||||
|
||||
def get_pricing_period(self):
|
||||
if self.pricing_period == self.BILLING_PERIOD:
|
||||
|
|
|
@ -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_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 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.shortcuts import render
|
||||
from django.utils.safestring import mark_safe
|
||||
|
@ -28,3 +29,30 @@ def grant_permission(modeladmin, request, queryset):
|
|||
# TODO
|
||||
grant_permission.url_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.utils import wrap_admin_view
|
||||
from orchestra.apps.accounts.admin import SelectAccountAdminMixin
|
||||
from orchestra.apps.accounts.filters import IsActiveListFilter
|
||||
from orchestra.forms import UserCreationForm, UserChangeForm
|
||||
|
||||
from . import settings
|
||||
from .actions import grant_permission
|
||||
from .actions import grant_permission, delete_selected
|
||||
from .filters import IsMainListFilter
|
||||
from .forms import SystemUserCreationForm, SystemUserChangeForm
|
||||
from .models import SystemUser
|
||||
|
@ -25,7 +26,7 @@ class SystemUserAdmin(ChangePasswordAdminMixin, SelectAccountAdminMixin, Extende
|
|||
list_display = (
|
||||
'username', 'account_link', 'shell', 'display_home', 'display_active', 'display_main'
|
||||
)
|
||||
list_filter = ('is_active', 'shell', IsMainListFilter)
|
||||
list_filter = (IsActiveListFilter, 'shell', IsMainListFilter)
|
||||
fieldsets = (
|
||||
(None, {
|
||||
'fields': ('username', 'password', 'account_link', 'is_active')
|
||||
|
@ -50,15 +51,9 @@ class SystemUserAdmin(ChangePasswordAdminMixin, SelectAccountAdminMixin, Extende
|
|||
add_form = SystemUserCreationForm
|
||||
form = SystemUserChangeForm
|
||||
ordering = ('-id',)
|
||||
actions = (grant_permission,)
|
||||
actions = (delete_selected, grant_permission,)
|
||||
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):
|
||||
return user.is_main
|
||||
display_main.short_description = _("Main")
|
||||
|
@ -70,7 +65,7 @@ class SystemUserAdmin(ChangePasswordAdminMixin, SelectAccountAdminMixin, Extende
|
|||
display_home.admin_order_field = 'home'
|
||||
|
||||
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
|
||||
if obj:
|
||||
# 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):
|
||||
if obj and obj.is_main:
|
||||
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)
|
||||
|
|
|
@ -18,6 +18,7 @@ class SystemUserBackend(ServiceController):
|
|||
context = self.get_context(user)
|
||||
groups = ','.join(self.get_groups(user))
|
||||
context['groups_arg'] = '--groups %s' % groups if groups else ''
|
||||
# TODO userd add will fail if %(user)s group already exists
|
||||
self.append(textwrap.dedent("""
|
||||
if [[ $( id %(user)s ) ]]; then
|
||||
usermod %(user)s --password '%(password)s' --shell %(shell)s %(groups_arg)s
|
||||
|
|
|
@ -19,11 +19,11 @@ class SelectPluginAdminMixin(object):
|
|||
else:
|
||||
plugin = self.plugin.get_plugin(self.plugin_value)()
|
||||
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):
|
||||
""" 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, tail = [], []
|
||||
for field in fields:
|
||||
|
|
Loading…
Reference in New Issue