Random improvements
This commit is contained in:
parent
5619141514
commit
9534e6e571
20
TODO.md
20
TODO.md
|
@ -12,7 +12,6 @@ TODO
|
||||||
* enforce an emergency email contact and account to contact contacts about problems when mailserver is down
|
* enforce an emergency email contact and account to contact contacts about problems when mailserver is down
|
||||||
|
|
||||||
* add `BackendLog` retry action
|
* add `BackendLog` retry action
|
||||||
* move invoice contact to invoices app?
|
|
||||||
* PHPbBckendMiixin with get_php_ini
|
* PHPbBckendMiixin with get_php_ini
|
||||||
* Apache: `IncludeOptional /etc/apache2/extra-vhos[t]/account-site-custom.con[f]`
|
* Apache: `IncludeOptional /etc/apache2/extra-vhos[t]/account-site-custom.con[f]`
|
||||||
* webmail identities and addresses
|
* webmail identities and addresses
|
||||||
|
@ -143,7 +142,7 @@ Remember that, as always with QuerySets, any subsequent chained methods which im
|
||||||
* based on a merge set of save(update_fields)
|
* based on a merge set of save(update_fields)
|
||||||
|
|
||||||
|
|
||||||
textwrap.dedent( \\)
|
* textwrap.dedent( \\)
|
||||||
|
|
||||||
* accounts
|
* accounts
|
||||||
* short name / long name, account name really needed? address? only minimal info..
|
* short name / long name, account name really needed? address? only minimal info..
|
||||||
|
@ -159,7 +158,22 @@ textwrap.dedent( \\)
|
||||||
* better modeling of the interdependency between webapps and websites (settings)
|
* better modeling of the interdependency between webapps and websites (settings)
|
||||||
* webapp options cfig agnostic
|
* webapp options cfig agnostic
|
||||||
|
|
||||||
* Disable menu on tests, fucking overlapping
|
|
||||||
* service.name / verbose_name instead of .description ?
|
* service.name / verbose_name instead of .description ?
|
||||||
|
* miscellaneous.name / verbose_name
|
||||||
|
* service.invoice_name
|
||||||
|
|
||||||
* Bills can have sublines?
|
* Bills can have sublines?
|
||||||
|
|
||||||
|
* proforma without billing contact?
|
||||||
|
|
||||||
|
* remove contact addresss, and use invoice contact for it (maybe move to contacts app again)
|
||||||
|
|
||||||
|
* env ORCHESTRA_MASTER_SERVER='test1.orchestra.lan' ORCHESTRA_SECOND_SERVER='test2.orchestra.lan' ORCHESTRA_SLAVE_SERVER='test3.orchestra.lan' python manage.py test orchestra.apps.domains.tests.functional_tests.tests:AdminBind9BackendDomainTest
|
||||||
|
|
||||||
|
* Pangea modifications: domain registered/non-registered list_display and field with register link: inconsistent, what happen to related objects with a domain that is converted to register-only?
|
||||||
|
|
||||||
|
* ForeignKey.swappable
|
||||||
|
* Field.editable
|
||||||
|
* ManyToManyField.symmetrical = False (user group)
|
||||||
|
|
||||||
|
* REST PERMISSIONS
|
||||||
|
|
|
@ -63,6 +63,7 @@ class AccountAdmin(ChangePasswordAdminMixin, auth.UserAdmin, ExtendedModelAdmin)
|
||||||
change_form_template = 'admin/accounts/account/change_form.html'
|
change_form_template = 'admin/accounts/account/change_form.html'
|
||||||
actions = [disable]
|
actions = [disable]
|
||||||
change_view_actions = actions
|
change_view_actions = actions
|
||||||
|
list_select_related = ('billcontact',)
|
||||||
|
|
||||||
def formfield_for_dbfield(self, db_field, **kwargs):
|
def formfield_for_dbfield(self, db_field, **kwargs):
|
||||||
""" Make value input widget bigger """
|
""" Make value input widget bigger """
|
||||||
|
@ -99,12 +100,6 @@ class AccountAdmin(ChangePasswordAdminMixin, auth.UserAdmin, ExtendedModelAdmin)
|
||||||
fieldsets.insert(1, (_("Related services"), {'fields': fields}))
|
fieldsets.insert(1, (_("Related services"), {'fields': fields}))
|
||||||
return fieldsets
|
return fieldsets
|
||||||
|
|
||||||
def get_queryset(self, request):
|
|
||||||
""" Select related for performance """
|
|
||||||
qs = super(AccountAdmin, self).get_queryset(request)
|
|
||||||
related = ('invoicecontact',)
|
|
||||||
return qs.select_related(*related)
|
|
||||||
|
|
||||||
def save_model(self, request, obj, form, change):
|
def save_model(self, request, obj, form, change):
|
||||||
super(AccountAdmin, self).save_model(request, obj, form, change)
|
super(AccountAdmin, self).save_model(request, obj, form, change)
|
||||||
if not change:
|
if not change:
|
||||||
|
@ -152,6 +147,7 @@ class AccountAdminMixin(object):
|
||||||
change_list_template = 'admin/accounts/account/change_list.html'
|
change_list_template = 'admin/accounts/account/change_list.html'
|
||||||
change_form_template = 'admin/accounts/account/change_form.html'
|
change_form_template = 'admin/accounts/account/change_form.html'
|
||||||
account = None
|
account = None
|
||||||
|
list_select_related = ('account',)
|
||||||
|
|
||||||
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
|
||||||
|
@ -167,11 +163,6 @@ class AccountAdminMixin(object):
|
||||||
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=obj)
|
||||||
|
|
||||||
def get_queryset(self, request):
|
|
||||||
""" Select related for performance """
|
|
||||||
qs = super(AccountAdminMixin, self).get_queryset(request)
|
|
||||||
return qs.select_related('account')
|
|
||||||
|
|
||||||
def formfield_for_dbfield(self, db_field, **kwargs):
|
def formfield_for_dbfield(self, db_field, **kwargs):
|
||||||
""" Filter by account """
|
""" Filter by account """
|
||||||
formfield = super(AccountAdminMixin, self).formfield_for_dbfield(db_field, **kwargs)
|
formfield = super(AccountAdminMixin, self).formfield_for_dbfield(db_field, **kwargs)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from rest_framework import viewsets
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from rest_framework import viewsets, exceptions
|
||||||
|
|
||||||
from orchestra.api import router, SetPasswordApiMixin
|
from orchestra.api import router, SetPasswordApiMixin
|
||||||
|
|
||||||
|
@ -20,6 +21,12 @@ class AccountViewSet(SetPasswordApiMixin, viewsets.ModelViewSet):
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
qs = super(AccountViewSet, self).get_queryset()
|
qs = super(AccountViewSet, self).get_queryset()
|
||||||
return qs.filter(id=self.request.user.pk)
|
return qs.filter(id=self.request.user.pk)
|
||||||
|
|
||||||
|
def destroy(self, request, pk=None):
|
||||||
|
# TODO reimplement in permissions
|
||||||
|
if not request.user.is_superuser:
|
||||||
|
raise exceptions.PermissionDenied(_("Accounts can not be deleted."))
|
||||||
|
super(AccountViewSet, self).destroy(request, pk=pk)
|
||||||
|
|
||||||
|
|
||||||
router.register(r'accounts', AccountViewSet)
|
router.register(r'accounts', AccountViewSet)
|
||||||
|
|
|
@ -9,7 +9,12 @@ from .models import Account
|
||||||
|
|
||||||
|
|
||||||
def create_account_creation_form():
|
def create_account_creation_form():
|
||||||
fields = {}
|
fields = {
|
||||||
|
'create_systemuser': forms.BooleanField(initial=True, required=False,
|
||||||
|
label=_("Create systemuser"), widget=forms.CheckboxInput(attrs={'disabled': True}),
|
||||||
|
help_text=_("Designates whether to creates a related system user with the same "
|
||||||
|
"username and password or not."))
|
||||||
|
}
|
||||||
for model, key, kwargs, help_text in settings.ACCOUNTS_CREATE_RELATED:
|
for model, key, kwargs, help_text in settings.ACCOUNTS_CREATE_RELATED:
|
||||||
model = get_model(model)
|
model = get_model(model)
|
||||||
field_name = 'create_%s' % model._meta.model_name
|
field_name = 'create_%s' % model._meta.model_name
|
||||||
|
@ -28,6 +33,9 @@ def create_account_creation_form():
|
||||||
except KeyError:
|
except KeyError:
|
||||||
# Previous validation error
|
# Previous validation error
|
||||||
return
|
return
|
||||||
|
systemuser_model = Account.main_systemuser.field.rel.to
|
||||||
|
if systemuser_model.objects.filter(username=account.username).exists():
|
||||||
|
raise forms.ValidationError(_("A system user with this name already exists"))
|
||||||
for model, key, related_kwargs, __ in settings.ACCOUNTS_CREATE_RELATED:
|
for model, key, related_kwargs, __ in settings.ACCOUNTS_CREATE_RELATED:
|
||||||
model = get_model(model)
|
model = get_model(model)
|
||||||
kwargs = {
|
kwargs = {
|
||||||
|
@ -44,9 +52,10 @@ def create_account_creation_form():
|
||||||
model = get_model(model)
|
model = get_model(model)
|
||||||
field_name = 'create_%s' % model._meta.model_name
|
field_name = 'create_%s' % model._meta.model_name
|
||||||
if self.cleaned_data[field_name]:
|
if self.cleaned_data[field_name]:
|
||||||
for key, value in related_kwargs.iteritems():
|
kwargs = {
|
||||||
related_kwargs[key] = eval(value, {'account': account})
|
key: eval(value, {'account': account}) for key, value in related_kwargs.iteritems()
|
||||||
model.objects.create(account=account, **related_kwargs)
|
}
|
||||||
|
model.objects.create(account=account, **kwargs)
|
||||||
|
|
||||||
fields.update({
|
fields.update({
|
||||||
'create_related_fields': fields.keys(),
|
'create_related_fields': fields.keys(),
|
||||||
|
|
|
@ -17,6 +17,8 @@ class Account(auth.AbstractBaseUser):
|
||||||
help_text=_("Required. 30 characters or fewer. Letters, digits and ./-/_ only."),
|
help_text=_("Required. 30 characters or fewer. Letters, digits and ./-/_ only."),
|
||||||
validators=[validators.RegexValidator(r'^[\w.-]+$',
|
validators=[validators.RegexValidator(r'^[\w.-]+$',
|
||||||
_("Enter a valid username."), 'invalid')])
|
_("Enter a valid username."), 'invalid')])
|
||||||
|
main_systemuser = models.ForeignKey(settings.ACCOUNTS_SYSTEMUSER_MODEL, null=True,
|
||||||
|
related_name='accounts_main')
|
||||||
first_name = models.CharField(_("first name"), max_length=30, blank=True)
|
first_name = models.CharField(_("first name"), max_length=30, blank=True)
|
||||||
last_name = models.CharField(_("last name"), max_length=30, blank=True)
|
last_name = models.CharField(_("last name"), max_length=30, blank=True)
|
||||||
email = models.EmailField(_('email address'), help_text=_("Used for password recovery"))
|
email = models.EmailField(_('email address'), help_text=_("Used for password recovery"))
|
||||||
|
@ -50,14 +52,22 @@ class Account(auth.AbstractBaseUser):
|
||||||
def is_staff(self):
|
def is_staff(self):
|
||||||
return self.is_superuser
|
return self.is_superuser
|
||||||
|
|
||||||
@property
|
# @property
|
||||||
def main_systemuser(self):
|
# def main_systemuser(self):
|
||||||
return self.systemusers.get(is_main=True)
|
# return self.systemusers.get(is_main=True)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_main(cls):
|
def get_main(cls):
|
||||||
return cls.objects.get(pk=settings.ACCOUNTS_MAIN_PK)
|
return cls.objects.get(pk=settings.ACCOUNTS_MAIN_PK)
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
created = not self.pk
|
||||||
|
super(Account, self).save(*args, **kwargs)
|
||||||
|
if created:
|
||||||
|
self.main_systemuser = self.systemusers.create(account=self, username=self.username,
|
||||||
|
password=self.password)
|
||||||
|
self.save(update_fields=['main_systemuser'])
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
self.first_name = self.first_name.strip()
|
self.first_name = self.first_name.strip()
|
||||||
self.last_name = self.last_name.strip()
|
self.last_name = self.last_name.strip()
|
||||||
|
@ -124,15 +134,17 @@ class Account(auth.AbstractBaseUser):
|
||||||
if self.is_active and self.is_superuser:
|
if self.is_active and self.is_superuser:
|
||||||
return True
|
return True
|
||||||
return auth._user_has_module_perms(self, app_label)
|
return auth._user_has_module_perms(self, app_label)
|
||||||
|
|
||||||
def get_related_passwords(self):
|
def get_related_passwords(self):
|
||||||
related = []
|
related = [
|
||||||
for model, key, kwargs, __ in settings.ACCOUNTS_CREATE_RELATED:
|
self.main_systemuser,
|
||||||
if 'password' not in kwargs:
|
]
|
||||||
|
for model, key, related_kwargs, __ in settings.ACCOUNTS_CREATE_RELATED:
|
||||||
|
if 'password' not in related_kwargs:
|
||||||
continue
|
continue
|
||||||
model = get_model(model)
|
model = get_model(model)
|
||||||
kwargs = {
|
kwargs = {
|
||||||
key: eval(kwargs[key], {'account': self})
|
key: eval(related_kwargs[key], {'account': self})
|
||||||
}
|
}
|
||||||
try:
|
try:
|
||||||
rel = model.objects.get(account=self, **kwargs)
|
rel = model.objects.get(account=self, **kwargs)
|
||||||
|
|
|
@ -18,6 +18,10 @@ ACCOUNTS_LANGUAGES = getattr(settings, 'ACCOUNTS_LANGUAGES', (
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
||||||
|
ACCOUNTS_SYSTEMUSER_MODEL = getattr(settings, 'ACCOUNTS_SYSTEMUSER_MODEL',
|
||||||
|
'systemusers.SystemUser')
|
||||||
|
|
||||||
|
|
||||||
ACCOUNTS_DEFAULT_LANGUAGE = getattr(settings, 'ACCOUNTS_DEFAULT_LANGUAGE', 'en')
|
ACCOUNTS_DEFAULT_LANGUAGE = getattr(settings, 'ACCOUNTS_DEFAULT_LANGUAGE', 'en')
|
||||||
|
|
||||||
|
|
||||||
|
@ -26,15 +30,6 @@ ACCOUNTS_MAIN_PK = getattr(settings, 'ACCOUNTS_MAIN_PK', 1)
|
||||||
|
|
||||||
ACCOUNTS_CREATE_RELATED = getattr(settings, 'ACCOUNTS_CREATE_RELATED', (
|
ACCOUNTS_CREATE_RELATED = getattr(settings, 'ACCOUNTS_CREATE_RELATED', (
|
||||||
# <model>, <key field>, <kwargs>, <help_text>
|
# <model>, <key field>, <kwargs>, <help_text>
|
||||||
('systemusers.SystemUser',
|
|
||||||
'username',
|
|
||||||
{
|
|
||||||
'username': 'account.username',
|
|
||||||
'password': 'account.password',
|
|
||||||
'is_main': 'True',
|
|
||||||
},
|
|
||||||
_("Designates whether to creates a related system user with the same username and password or not."),
|
|
||||||
),
|
|
||||||
('mailboxes.Mailbox',
|
('mailboxes.Mailbox',
|
||||||
'name',
|
'name',
|
||||||
{
|
{
|
||||||
|
|
|
@ -36,7 +36,7 @@ class BillLineInline(admin.TabularInline):
|
||||||
if sublines:
|
if sublines:
|
||||||
content = '\n'.join(['%s: %s' % (sub.description, sub.total) for sub in sublines])
|
content = '\n'.join(['%s: %s' % (sub.description, sub.total) for sub in sublines])
|
||||||
img = static('admin/img/icon_alert.gif')
|
img = static('admin/img/icon_alert.gif')
|
||||||
return '<span title="%s">%s<img src="%s"></img></span>' % (content, str(total), img)
|
return '<span title="%s">%s <img src="%s"></img></span>' % (content, str(total), img)
|
||||||
return total
|
return total
|
||||||
display_total.short_description = _("Total")
|
display_total.short_description = _("Total")
|
||||||
display_total.allow_tags = True
|
display_total.allow_tags = True
|
||||||
|
|
|
@ -3,7 +3,7 @@ from django.contrib import admin
|
||||||
from django.utils.translation import ugettext, ugettext_lazy as _
|
from django.utils.translation import ugettext, ugettext_lazy as _
|
||||||
|
|
||||||
from orchestra.admin import AtLeastOneRequiredInlineFormSet
|
from orchestra.admin import AtLeastOneRequiredInlineFormSet
|
||||||
from orchestra.admin.utils import insertattr
|
from orchestra.admin.utils import insertattr, admin_link, change_url
|
||||||
from orchestra.apps.accounts.admin import AccountAdmin, AccountAdminMixin
|
from orchestra.apps.accounts.admin import AccountAdmin, AccountAdminMixin
|
||||||
from orchestra.forms.widgets import paddingCheckboxSelectMultiple
|
from orchestra.forms.widgets import paddingCheckboxSelectMultiple
|
||||||
|
|
||||||
|
@ -74,15 +74,20 @@ class ContactInline(admin.StackedInline):
|
||||||
formset = AtLeastOneRequiredInlineFormSet
|
formset = AtLeastOneRequiredInlineFormSet
|
||||||
extra = 0
|
extra = 0
|
||||||
fields = (
|
fields = (
|
||||||
'short_name', 'full_name', 'email', 'email_usage', ('phone', 'phone2'),
|
('short_name', 'full_name'), 'email', 'email_usage', ('phone', 'phone2'),
|
||||||
'address', ('city', 'zipcode'), 'country',
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_extra(self, request, obj=None, **kwargs):
|
def get_extra(self, request, obj=None, **kwargs):
|
||||||
return 0 if obj and obj.contacts.exists() else 1
|
return 0 if obj and obj.contacts.exists() else 1
|
||||||
|
|
||||||
|
def get_view_on_site_url(self, obj=None):
|
||||||
|
if obj:
|
||||||
|
return change_url(obj)
|
||||||
|
|
||||||
def formfield_for_dbfield(self, db_field, **kwargs):
|
def formfield_for_dbfield(self, db_field, **kwargs):
|
||||||
""" Make value input widget bigger """
|
""" Make value input widget bigger """
|
||||||
|
if db_field.name == 'short_name':
|
||||||
|
kwargs['widget'] = forms.TextInput(attrs={'size':'15'})
|
||||||
if db_field.name == 'address':
|
if db_field.name == 'address':
|
||||||
kwargs['widget'] = forms.Textarea(attrs={'cols': 70, 'rows': 2})
|
kwargs['widget'] = forms.Textarea(attrs={'cols': 70, 'rows': 2})
|
||||||
if db_field.name == 'email_usage':
|
if db_field.name == 'email_usage':
|
||||||
|
|
|
@ -296,10 +296,13 @@ class AdminDatabaseMixin(DatabaseTestMixin):
|
||||||
self.selenium.get(url)
|
self.selenium.get(url)
|
||||||
|
|
||||||
user = DatabaseUser.objects.get(username=username, type=self.db_type)
|
user = DatabaseUser.objects.get(username=username, type=self.db_type)
|
||||||
users_input = self.selenium.find_element_by_id('id_users')
|
users_from = self.selenium.find_element_by_id('id_users_from')
|
||||||
users_select = Select(users_input)
|
users_select = Select(users_from)
|
||||||
users_select.select_by_value(str(user.pk))
|
users_select.select_by_value(str(user.pk))
|
||||||
|
|
||||||
|
add_user = self.selenium.find_element_by_id('id_users_add_link')
|
||||||
|
add_user.click()
|
||||||
|
|
||||||
save = self.selenium.find_element_by_name('_save')
|
save = self.selenium.find_element_by_name('_save')
|
||||||
save.submit()
|
save.submit()
|
||||||
self.assertNotEqual(url, self.selenium.current_url)
|
self.assertNotEqual(url, self.selenium.current_url)
|
||||||
|
@ -310,13 +313,23 @@ class AdminDatabaseMixin(DatabaseTestMixin):
|
||||||
url = self.live_server_url + change_url(database)
|
url = self.live_server_url + change_url(database)
|
||||||
self.selenium.get(url)
|
self.selenium.get(url)
|
||||||
|
|
||||||
|
# remove user "username"
|
||||||
user = DatabaseUser.objects.get(username=username, type=self.db_type)
|
user = DatabaseUser.objects.get(username=username, type=self.db_type)
|
||||||
users_input = self.selenium.find_element_by_id('id_users')
|
users_to = self.selenium.find_element_by_id('id_users_to')
|
||||||
users_select = Select(users_input)
|
users_select = Select(users_to)
|
||||||
users_select.deselect_by_value(str(user.pk))
|
|
||||||
|
|
||||||
user = DatabaseUser.objects.get(username=username2, type=self.db_type)
|
|
||||||
users_select.select_by_value(str(user.pk))
|
users_select.select_by_value(str(user.pk))
|
||||||
|
remove_user = self.selenium.find_element_by_id('id_users_remove_link')
|
||||||
|
remove_user.click()
|
||||||
|
time.sleep(0.2)
|
||||||
|
|
||||||
|
# add user "username2"
|
||||||
|
user = DatabaseUser.objects.get(username=username2, type=self.db_type)
|
||||||
|
users_from = self.selenium.find_element_by_id('id_users_from')
|
||||||
|
users_select = Select(users_from)
|
||||||
|
users_select.select_by_value(str(user.pk))
|
||||||
|
add_user = self.selenium.find_element_by_id('id_users_add_link')
|
||||||
|
add_user.click()
|
||||||
|
time.sleep(0.2)
|
||||||
|
|
||||||
save = self.selenium.find_element_by_name('_save')
|
save = self.selenium.find_element_by_name('_save')
|
||||||
save.submit()
|
save.submit()
|
||||||
|
|
|
@ -20,19 +20,22 @@ class RecordInline(admin.TabularInline):
|
||||||
formset = RecordInlineFormSet
|
formset = RecordInlineFormSet
|
||||||
verbose_name_plural = _("Extra records")
|
verbose_name_plural = _("Extra records")
|
||||||
|
|
||||||
class Media:
|
# class Media:
|
||||||
css = {
|
# css = {
|
||||||
'all': ('orchestra/css/hide-inline-id.css',)
|
# 'all': ('orchestra/css/hide-inline-id.css',)
|
||||||
}
|
# }
|
||||||
|
#
|
||||||
def formfield_for_dbfield(self, db_field, **kwargs):
|
def formfield_for_dbfield(self, db_field, **kwargs):
|
||||||
""" Make value input widget bigger """
|
""" Make value input widget bigger """
|
||||||
if db_field.name == 'value':
|
if db_field.name == 'value':
|
||||||
kwargs['widget'] = forms.TextInput(attrs={'size':'100'})
|
kwargs['widget'] = forms.TextInput(attrs={'size':'100'})
|
||||||
|
if db_field.name == 'ttl':
|
||||||
|
kwargs['widget'] = forms.TextInput(attrs={'size':'10'})
|
||||||
return super(RecordInline, self).formfield_for_dbfield(db_field, **kwargs)
|
return super(RecordInline, self).formfield_for_dbfield(db_field, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class DomainInline(admin.TabularInline):
|
class DomainInline(admin.TabularInline):
|
||||||
|
# TODO account, and record sumary fields
|
||||||
model = Domain
|
model = Domain
|
||||||
fields = ('domain_link',)
|
fields = ('domain_link',)
|
||||||
readonly_fields = ('domain_link',)
|
readonly_fields = ('domain_link',)
|
||||||
|
@ -47,6 +50,7 @@ class DomainInline(admin.TabularInline):
|
||||||
|
|
||||||
|
|
||||||
class DomainAdmin(ChangeListDefaultFilter, AccountAdminMixin, ExtendedModelAdmin):
|
class DomainAdmin(ChangeListDefaultFilter, AccountAdminMixin, ExtendedModelAdmin):
|
||||||
|
# TODO name link
|
||||||
fields = ('name', 'account')
|
fields = ('name', 'account')
|
||||||
list_display = (
|
list_display = (
|
||||||
'structured_name', 'display_is_top', 'websites', 'account_link'
|
'structured_name', 'display_is_top', 'websites', 'account_link'
|
||||||
|
|
|
@ -91,7 +91,7 @@ class Bind9MasterDomainBackend(ServiceController):
|
||||||
'zone_path': settings.DOMAINS_ZONE_PATH % {'name': domain.name},
|
'zone_path': settings.DOMAINS_ZONE_PATH % {'name': domain.name},
|
||||||
'subdomains': domain.subdomains.all(),
|
'subdomains': domain.subdomains.all(),
|
||||||
'banner': self.get_banner(),
|
'banner': self.get_banner(),
|
||||||
'slaves': '; '.join(self.get_slaves(domain)) or '"none"',
|
'slaves': '; '.join(self.get_slaves(domain)) or '',
|
||||||
}
|
}
|
||||||
context.update({
|
context.update({
|
||||||
'conf_path': settings.DOMAINS_MASTERS_PATH,
|
'conf_path': settings.DOMAINS_MASTERS_PATH,
|
||||||
|
@ -133,7 +133,7 @@ class Bind9SlaveDomainBackend(Bind9MasterDomainBackend):
|
||||||
'name': domain.name,
|
'name': domain.name,
|
||||||
'banner': self.get_banner(),
|
'banner': self.get_banner(),
|
||||||
'subdomains': domain.subdomains.all(),
|
'subdomains': domain.subdomains.all(),
|
||||||
'masters': '; '.join(self.get_masters(domain)) or '"none"',
|
'masters': '; '.join(self.get_masters(domain)) or '',
|
||||||
}
|
}
|
||||||
context.update({
|
context.update({
|
||||||
'conf_path': settings.DOMAINS_SLAVES_PATH,
|
'conf_path': settings.DOMAINS_SLAVES_PATH,
|
||||||
|
|
|
@ -42,6 +42,9 @@ class Domain(models.Model):
|
||||||
# don't cache, don't replace by top_id
|
# don't cache, don't replace by top_id
|
||||||
return not bool(self.top)
|
return not bool(self.top)
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
return 'http://%s' % self.name
|
||||||
|
|
||||||
def get_records(self):
|
def get_records(self):
|
||||||
""" proxy method, needed for input validation, see helpers.domain_for_validation """
|
""" proxy method, needed for input validation, see helpers.domain_for_validation """
|
||||||
return self.records.all()
|
return self.records.all()
|
||||||
|
|
|
@ -180,6 +180,7 @@ class TicketAdmin(ChangeListDefaultFilter, ExtendedModelAdmin):
|
||||||
'description')
|
'description')
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
list_select_related = ('queue', 'owner', 'creator')
|
||||||
|
|
||||||
class Media:
|
class Media:
|
||||||
css = {
|
css = {
|
||||||
|
@ -285,11 +286,6 @@ class TicketAdmin(ChangeListDefaultFilter, ExtendedModelAdmin):
|
||||||
data = request.POST.get("data")
|
data = request.POST.get("data")
|
||||||
data_formated = markdown(strip_tags(data))
|
data_formated = markdown(strip_tags(data))
|
||||||
return HttpResponse(data_formated)
|
return HttpResponse(data_formated)
|
||||||
|
|
||||||
def get_queryset(self, request):
|
|
||||||
""" Order by structured name and imporve performance """
|
|
||||||
qs = super(TicketAdmin, self).get_queryset(request)
|
|
||||||
return qs.select_related('queue', 'owner', 'creator')
|
|
||||||
|
|
||||||
|
|
||||||
class QueueAdmin(admin.ModelAdmin):
|
class QueueAdmin(admin.ModelAdmin):
|
||||||
|
|
|
@ -43,6 +43,12 @@ class List(models.Model):
|
||||||
|
|
||||||
def set_password(self, password):
|
def set_password(self, password):
|
||||||
self.password = password
|
self.password = password
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
context = {
|
||||||
|
'name': self.name
|
||||||
|
}
|
||||||
|
return settings.LISTS_LIST_URL % context
|
||||||
|
|
||||||
|
|
||||||
services.register(List)
|
services.register(List)
|
||||||
|
|
|
@ -7,10 +7,11 @@ LISTS_DOMAIN_MODEL = getattr(settings, 'LISTS_DOMAIN_MODEL', 'domains.Domain')
|
||||||
LISTS_DEFAULT_DOMAIN = getattr(settings, 'LIST_DEFAULT_DOMAIN', 'lists.orchestra.lan')
|
LISTS_DEFAULT_DOMAIN = getattr(settings, 'LIST_DEFAULT_DOMAIN', 'lists.orchestra.lan')
|
||||||
|
|
||||||
|
|
||||||
|
LISTS_LIST_URL = getattr(settings, 'LISTS_LIST_URL', 'https://lists.orchestra.lan/mailman/listinfo/%(name)s')
|
||||||
|
|
||||||
LISTS_MAILMAN_POST_LOG_PATH = getattr(settings, 'LISTS_MAILMAN_POST_LOG_PATH',
|
LISTS_MAILMAN_POST_LOG_PATH = getattr(settings, 'LISTS_MAILMAN_POST_LOG_PATH',
|
||||||
'/var/log/mailman/post')
|
'/var/log/mailman/post')
|
||||||
|
|
||||||
|
|
||||||
LISTS_MAILMAN_ROOT_PATH = getattr(settings, 'LISTS_MAILMAN_ROOT_PATH',
|
LISTS_MAILMAN_ROOT_PATH = getattr(settings, 'LISTS_MAILMAN_ROOT_PATH',
|
||||||
'/var/lib/mailman/')
|
'/var/lib/mailman/')
|
||||||
|
|
||||||
|
|
|
@ -70,11 +70,15 @@ class MailboxAdmin(ChangePasswordAdminMixin, SelectAccountAdminMixin, ExtendedMo
|
||||||
display_addresses.allow_tags = True
|
display_addresses.allow_tags = True
|
||||||
|
|
||||||
def get_fieldsets(self, request, obj=None):
|
def get_fieldsets(self, request, obj=None):
|
||||||
""" not collapsed filtering when exists """
|
|
||||||
fieldsets = super(MailboxAdmin, self).get_fieldsets(request, obj=obj)
|
fieldsets = super(MailboxAdmin, self).get_fieldsets(request, obj=obj)
|
||||||
if obj and obj.filtering == obj.CUSTOM:
|
if obj and obj.filtering == obj.CUSTOM:
|
||||||
|
# not collapsed filtering when exists
|
||||||
fieldsets = copy.deepcopy(fieldsets)
|
fieldsets = copy.deepcopy(fieldsets)
|
||||||
fieldsets[1][1]['classes'] = fieldsets[0][1]['fields'] + ('open',)
|
fieldsets[1][1]['classes'] = fieldsets[0][1]['fields'] + ('open',)
|
||||||
|
elif '_to_field' in parse_qs(request.META['QUERY_STRING']):
|
||||||
|
# remove address from popup
|
||||||
|
fieldsets = list(copy.deepcopy(fieldsets))
|
||||||
|
fieldsets.pop(-1)
|
||||||
return fieldsets
|
return fieldsets
|
||||||
|
|
||||||
def get_form(self, *args, **kwargs):
|
def get_form(self, *args, **kwargs):
|
||||||
|
|
|
@ -14,7 +14,6 @@ from django.core.management.base import CommandError
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from selenium.webdriver.support.select import Select
|
from selenium.webdriver.support.select import Select
|
||||||
|
|
||||||
from orchestra.apps.accounts.models import Account
|
|
||||||
from orchestra.apps.orchestration.models import Server, Route
|
from orchestra.apps.orchestration.models import Server, Route
|
||||||
from orchestra.apps.resources.models import Resource
|
from orchestra.apps.resources.models import Resource
|
||||||
from orchestra.utils.system import run, sshrun
|
from orchestra.utils.system import run, sshrun
|
||||||
|
@ -303,9 +302,9 @@ class AdminMailboxMixin(MailboxMixin):
|
||||||
url = self.live_server_url + reverse('admin:mailboxes_mailbox_add')
|
url = self.live_server_url + reverse('admin:mailboxes_mailbox_add')
|
||||||
self.selenium.get(url)
|
self.selenium.get(url)
|
||||||
|
|
||||||
account_input = self.selenium.find_element_by_id('id_account')
|
# account_input = self.selenium.find_element_by_id('id_account')
|
||||||
account_select = Select(account_input)
|
# account_select = Select(account_input)
|
||||||
account_select.select_by_value(str(self.account.pk))
|
# account_select.select_by_value(str(self.account.pk))
|
||||||
|
|
||||||
name_field = self.selenium.find_element_by_id('id_name')
|
name_field = self.selenium.find_element_by_id('id_name')
|
||||||
name_field.send_keys(username)
|
name_field.send_keys(username)
|
||||||
|
|
|
@ -10,7 +10,7 @@ from .models import MiscService, Miscellaneous
|
||||||
|
|
||||||
|
|
||||||
class MiscServiceAdmin(admin.ModelAdmin):
|
class MiscServiceAdmin(admin.ModelAdmin):
|
||||||
list_display = ('name', 'num_instances')
|
list_display = ('name', 'verbose_name', 'num_instances')
|
||||||
|
|
||||||
def num_instances(self, misc):
|
def num_instances(self, misc):
|
||||||
""" return num slivers as a link to slivers changelist view """
|
""" return num slivers as a link to slivers changelist view """
|
||||||
|
|
|
@ -3,11 +3,16 @@ from django.utils.functional import cached_property
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from orchestra.core import services
|
from orchestra.core import services
|
||||||
|
from orchestra.core.validators import validate_name
|
||||||
|
|
||||||
|
|
||||||
class MiscService(models.Model):
|
class MiscService(models.Model):
|
||||||
name = models.CharField(_("name"), max_length=256)
|
name = models.CharField(_("name"), max_length=32, unique=True, validators=[validate_name],
|
||||||
description = models.TextField(_("description"), blank=True)
|
help_text=_("Raw name used for internal referenciation, i.e. service match definition"))
|
||||||
|
verbose_name = models.CharField(_("verbose name"), max_length=256, blank=True,
|
||||||
|
help_text=_("Human readable name"))
|
||||||
|
description = models.TextField(_("description"), blank=True,
|
||||||
|
help_text=_("Optional description"))
|
||||||
has_amount = models.BooleanField(_("has amount"), default=False,
|
has_amount = models.BooleanField(_("has amount"), default=False,
|
||||||
help_text=_("Designates whether this service has <tt>amount</tt> "
|
help_text=_("Designates whether this service has <tt>amount</tt> "
|
||||||
"property or not."))
|
"property or not."))
|
||||||
|
@ -17,6 +22,12 @@ class MiscService(models.Model):
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
self.verbose_name = self.verbose_name.strip()
|
||||||
|
|
||||||
|
def get_verbose_name(self):
|
||||||
|
return self.verbose_name or self.name
|
||||||
|
|
||||||
|
|
||||||
class Miscellaneous(models.Model):
|
class Miscellaneous(models.Model):
|
||||||
|
|
|
@ -181,7 +181,7 @@ class Order(models.Model):
|
||||||
if metric is not None:
|
if metric is not None:
|
||||||
MetricStorage.store(self, metric)
|
MetricStorage.store(self, metric)
|
||||||
metric = ', metric:{}'.format(metric)
|
metric = ', metric:{}'.format(metric)
|
||||||
description = "{}: {}".format(handler.description, str(instance))
|
description = handler.get_order_description(instance)
|
||||||
logger.info("UPDATED order id:{id}, description:{description}{metric}".format(
|
logger.info("UPDATED order id:{id}, description:{description}{metric}".format(
|
||||||
id=self.id, description=description, metric=metric))
|
id=self.id, description=description, metric=metric))
|
||||||
if self.description != description:
|
if self.description != description:
|
||||||
|
|
|
@ -31,8 +31,18 @@ def process_transactions(modeladmin, request, queryset):
|
||||||
if not processes:
|
if not processes:
|
||||||
return
|
return
|
||||||
opts = modeladmin.model._meta
|
opts = modeladmin.model._meta
|
||||||
|
num = len(queryset)
|
||||||
context = {
|
context = {
|
||||||
'title': _("Huston, be advised"),
|
'title': ungettext(
|
||||||
|
_("Selected transaction has been processed."),
|
||||||
|
_("%s Selected transactions have been processed.") % num,
|
||||||
|
num),
|
||||||
|
'content_message': ungettext(
|
||||||
|
_("The following transaction process has been generated, "
|
||||||
|
"you may want to save it on your computer now."),
|
||||||
|
_("The following %s transaction processes have been generated, "
|
||||||
|
"you may want to save it on your computer now.") % len(processes),
|
||||||
|
len(processes)),
|
||||||
'action_name': _("Process"),
|
'action_name': _("Process"),
|
||||||
'processes': processes,
|
'processes': processes,
|
||||||
'opts': opts,
|
'opts': opts,
|
||||||
|
|
|
@ -92,6 +92,7 @@ class TransactionAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
|
||||||
filter_by_account_fields = ('bill', 'source')
|
filter_by_account_fields = ('bill', 'source')
|
||||||
change_readonly_fields = ('amount', 'currency')
|
change_readonly_fields = ('amount', 'currency')
|
||||||
readonly_fields = ('bill_link', 'display_state', 'process_link', 'account_link', 'source_link')
|
readonly_fields = ('bill_link', 'display_state', 'process_link', 'account_link', 'source_link')
|
||||||
|
list_select_related = ('account', 'source', 'bill__account')
|
||||||
|
|
||||||
bill_link = admin_link('bill')
|
bill_link = admin_link('bill')
|
||||||
source_link = admin_link('source')
|
source_link = admin_link('source')
|
||||||
|
@ -99,10 +100,6 @@ class TransactionAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
|
||||||
account_link = admin_link('bill__account')
|
account_link = admin_link('bill__account')
|
||||||
display_state = admin_colored('state', colors=STATE_COLORS)
|
display_state = admin_colored('state', colors=STATE_COLORS)
|
||||||
|
|
||||||
def get_queryset(self, request):
|
|
||||||
qs = super(TransactionAdmin, self).get_queryset(request)
|
|
||||||
return qs.select_related('source', 'bill__account')
|
|
||||||
|
|
||||||
def get_change_view_actions(self, obj=None):
|
def get_change_view_actions(self, obj=None):
|
||||||
actions = super(TransactionAdmin, self).get_change_view_actions()
|
actions = super(TransactionAdmin, self).get_change_view_actions()
|
||||||
exclude = []
|
exclude = []
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
{% extends "admin/orchestra/generic_confirmation.html" %}
|
{% extends "admin/orchestra/generic_confirmation.html" %}
|
||||||
{% load i18n admin_urls utils %}
|
{% load i18n admin_urls utils %}
|
||||||
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<p>The following transaction processes have been generated, you may want to save them on your computer now.</p>
|
<p>{{ content_message }}</p>
|
||||||
<ul>
|
<ul>
|
||||||
{% for proc in processes %}
|
{% for proc in processes %}
|
||||||
<li> <a href="{{ proc.id }}">Process #{{ proc.id }}</a>
|
<li> <a href="{{ proc.id }}">Process #{{ proc.id }}</a>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
from django.contrib import admin, messages
|
from django.contrib import admin, messages
|
||||||
from django.contrib.contenttypes import generic
|
from django.contrib.contenttypes import generic
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
|
from django.utils.safestring import mark_safe
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from orchestra.admin import ExtendedModelAdmin
|
from orchestra.admin import ExtendedModelAdmin
|
||||||
|
@ -14,6 +15,8 @@ from .models import Resource, ResourceData, MonitorData
|
||||||
|
|
||||||
|
|
||||||
class ResourceAdmin(ExtendedModelAdmin):
|
class ResourceAdmin(ExtendedModelAdmin):
|
||||||
|
# TODO error after saving: u"Key 'name' not found in 'ResourceForm'"
|
||||||
|
# prepopulated_fields = {'name': ('verbose_name',)}
|
||||||
list_display = (
|
list_display = (
|
||||||
'id', 'verbose_name', 'content_type', 'period', 'on_demand',
|
'id', 'verbose_name', 'content_type', 'period', 'on_demand',
|
||||||
'default_allocation', 'unit', 'disable_trigger', 'crontab',
|
'default_allocation', 'unit', 'disable_trigger', 'crontab',
|
||||||
|
@ -21,11 +24,11 @@ class ResourceAdmin(ExtendedModelAdmin):
|
||||||
list_filter = (UsedContentTypeFilter, 'period', 'on_demand', 'disable_trigger')
|
list_filter = (UsedContentTypeFilter, 'period', 'on_demand', 'disable_trigger')
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, {
|
(None, {
|
||||||
'fields': ('name', 'content_type', 'period'),
|
'fields': ('verbose_name', 'name', 'content_type', 'period'),
|
||||||
}),
|
}),
|
||||||
(_("Configuration"), {
|
(_("Configuration"), {
|
||||||
'fields': ('verbose_name', 'unit', 'scale', 'on_demand',
|
'fields': ('unit', 'scale', 'on_demand', 'default_allocation', 'disable_trigger',
|
||||||
'default_allocation', 'disable_trigger', 'is_active'),
|
'is_active'),
|
||||||
}),
|
}),
|
||||||
(_("Monitoring"), {
|
(_("Monitoring"), {
|
||||||
'fields': ('monitors', 'crontab'),
|
'fields': ('monitors', 'crontab'),
|
||||||
|
@ -36,10 +39,10 @@ class ResourceAdmin(ExtendedModelAdmin):
|
||||||
def add_view(self, request, **kwargs):
|
def add_view(self, request, **kwargs):
|
||||||
""" Warning user if the node is not fully configured """
|
""" Warning user if the node is not fully configured """
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
messages.warning(request, _(
|
messages.warning(request, mark_safe(_(
|
||||||
"Restarting orchestra and celerybeat is required to fully apply changes. "
|
"Restarting orchestra and celerybeat is required to fully apply changes.<br> "
|
||||||
"Remember that new allocated values will be applied when objects are saved."
|
"Remember that new allocated values will be applied when objects are saved."
|
||||||
))
|
)))
|
||||||
return super(ResourceAdmin, self).add_view(request, **kwargs)
|
return super(ResourceAdmin, self).add_view(request, **kwargs)
|
||||||
|
|
||||||
def save_model(self, request, obj, form, change):
|
def save_model(self, request, obj, form, change):
|
||||||
|
|
|
@ -8,9 +8,12 @@ from djcelery.models import PeriodicTask, CrontabSchedule
|
||||||
|
|
||||||
from orchestra.core import validators
|
from orchestra.core import validators
|
||||||
from orchestra.models import queryset, fields
|
from orchestra.models import queryset, fields
|
||||||
|
from orchestra.utils.paths import get_project_root
|
||||||
|
from orchestra.utils.system import run
|
||||||
|
|
||||||
from . import helpers
|
from . import helpers
|
||||||
from .backends import ServiceMonitor
|
from .backends import ServiceMonitor
|
||||||
|
from .validators import validate_scale
|
||||||
|
|
||||||
|
|
||||||
class ResourceQuerySet(models.QuerySet):
|
class ResourceQuerySet(models.QuerySet):
|
||||||
|
@ -34,16 +37,15 @@ class Resource(models.Model):
|
||||||
_related = set() # keeps track of related models for resource cleanup
|
_related = set() # keeps track of related models for resource cleanup
|
||||||
|
|
||||||
name = models.CharField(_("name"), max_length=32,
|
name = models.CharField(_("name"), max_length=32,
|
||||||
help_text=_('Required. 32 characters or fewer. Lowercase letters, '
|
help_text=_("Required. 32 characters or fewer. Lowercase letters, "
|
||||||
'digits and hyphen only.'),
|
"digits and hyphen only."),
|
||||||
validators=[validators.validate_name])
|
validators=[validators.validate_name])
|
||||||
verbose_name = models.CharField(_("verbose name"), max_length=256)
|
verbose_name = models.CharField(_("verbose name"), max_length=256)
|
||||||
content_type = models.ForeignKey(ContentType,
|
content_type = models.ForeignKey(ContentType,
|
||||||
help_text=_("Model where this resource will be hooked."))
|
help_text=_("Model where this resource will be hooked."))
|
||||||
period = models.CharField(_("period"), max_length=16, choices=PERIODS,
|
period = models.CharField(_("period"), max_length=16, choices=PERIODS,
|
||||||
default=LAST,
|
default=LAST,
|
||||||
help_text=_("Operation used for aggregating this resource monitored"
|
help_text=_("Operation used for aggregating this resource monitored data."))
|
||||||
"data."))
|
|
||||||
on_demand = models.BooleanField(_("on demand"), default=False,
|
on_demand = models.BooleanField(_("on demand"), default=False,
|
||||||
help_text=_("If enabled the resource will not be pre-allocated, "
|
help_text=_("If enabled the resource will not be pre-allocated, "
|
||||||
"but allocated under the application demand"))
|
"but allocated under the application demand"))
|
||||||
|
@ -53,8 +55,8 @@ class Resource(models.Model):
|
||||||
"on demand resource"))
|
"on demand resource"))
|
||||||
unit = models.CharField(_("unit"), max_length=16,
|
unit = models.CharField(_("unit"), max_length=16,
|
||||||
help_text=_("The unit in which this resource is measured. "
|
help_text=_("The unit in which this resource is measured. "
|
||||||
"For example GB, KB or subscribers"))
|
"For example GB, KB or subscribers"))
|
||||||
scale = models.PositiveIntegerField(_("scale"),
|
scale = models.CharField(_("scale"), max_length=32, validators=[validate_scale],
|
||||||
help_text=_("Scale in which this resource monitoring resoults should "
|
help_text=_("Scale in which this resource monitoring resoults should "
|
||||||
"be prorcessed to match with unit. e.g. <tt>10**9</tt>"))
|
"be prorcessed to match with unit. e.g. <tt>10**9</tt>"))
|
||||||
disable_trigger = models.BooleanField(_("disable trigger"), default=False,
|
disable_trigger = models.BooleanField(_("disable trigger"), default=False,
|
||||||
|
@ -79,6 +81,9 @@ class Resource(models.Model):
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return "{}-{}".format(str(self.content_type), self.name)
|
return "{}-{}".format(str(self.content_type), self.name)
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
self.verbose_name = self.verbose_name.strip()
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
created = not self.pk
|
created = not self.pk
|
||||||
super(Resource, self).save(*args, **kwargs)
|
super(Resource, self).save(*args, **kwargs)
|
||||||
|
@ -102,7 +107,7 @@ class Resource(models.Model):
|
||||||
task.save(update_fields=['crontab'])
|
task.save(update_fields=['crontab'])
|
||||||
# This only work on tests (multiprocessing used on real deployments)
|
# This only work on tests (multiprocessing used on real deployments)
|
||||||
apps.get_app_config('resources').reload_relations()
|
apps.get_app_config('resources').reload_relations()
|
||||||
# TODO touch wsgi.py for code reloading?
|
run('touch %s/wsgi.py' % get_project_root())
|
||||||
|
|
||||||
def delete(self, *args, **kwargs):
|
def delete(self, *args, **kwargs):
|
||||||
super(Resource, self).delete(*args, **kwargs)
|
super(Resource, self).delete(*args, **kwargs)
|
||||||
|
|
8
orchestra/apps/resources/validators.py
Normal file
8
orchestra/apps/resources/validators.py
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
from django.core.validators import ValidationError
|
||||||
|
|
||||||
|
|
||||||
|
def validate_scale(value):
|
||||||
|
try:
|
||||||
|
int(eval(value))
|
||||||
|
except ValueError:
|
||||||
|
raise ValidationError(_("%s value is not a valid scale expression"))
|
|
@ -1,7 +1,7 @@
|
||||||
from django.contrib.admin import helpers
|
from django.contrib.admin import helpers
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render, redirect
|
||||||
from django.template.response import TemplateResponse
|
from django.template.response import TemplateResponse
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
@ -62,3 +62,21 @@ def view_help(modeladmin, request, queryset):
|
||||||
return TemplateResponse(request, 'admin/services/service/help.html', context)
|
return TemplateResponse(request, 'admin/services/service/help.html', context)
|
||||||
view_help.url_name = 'help'
|
view_help.url_name = 'help'
|
||||||
view_help.verbose_name = _("Help")
|
view_help.verbose_name = _("Help")
|
||||||
|
|
||||||
|
|
||||||
|
def clone(modeladmin, request, queryset):
|
||||||
|
service = queryset.get()
|
||||||
|
fields = (
|
||||||
|
'content_type_id', 'match', 'handler_type', 'is_active', 'ignore_superusers', 'billing_period',
|
||||||
|
'billing_point', 'is_fee', 'metric', 'nominal_price', 'tax', 'pricing_period',
|
||||||
|
'rate_algorithm', 'on_cancel', 'payment_style',
|
||||||
|
)
|
||||||
|
query = []
|
||||||
|
for field in fields:
|
||||||
|
value = getattr(service, field)
|
||||||
|
field = field.replace('_id', '')
|
||||||
|
query.append('%s=%s' % (field, value))
|
||||||
|
opts = service._meta
|
||||||
|
url = reverse('admin:%s_%s_add' % (opts.app_label, opts.model_name))
|
||||||
|
url += '?%s' % '&'.join(query)
|
||||||
|
return redirect(url)
|
||||||
|
|
|
@ -9,7 +9,7 @@ from orchestra.admin.filters import UsedContentTypeFilter
|
||||||
from orchestra.apps.accounts.admin import AccountAdminMixin
|
from orchestra.apps.accounts.admin import AccountAdminMixin
|
||||||
from orchestra.core import services
|
from orchestra.core import services
|
||||||
|
|
||||||
from .actions import update_orders, view_help
|
from .actions import update_orders, view_help, clone
|
||||||
from .models import Plan, ContractedPlan, Rate, Service
|
from .models import Plan, ContractedPlan, Rate, Service
|
||||||
|
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ class ServiceAdmin(ChangeViewActionsMixin, admin.ModelAdmin):
|
||||||
}),
|
}),
|
||||||
(_("Billing options"), {
|
(_("Billing options"), {
|
||||||
'classes': ('wide',),
|
'classes': ('wide',),
|
||||||
'fields': ('billing_period', 'billing_point', 'is_fee')
|
'fields': ('billing_period', 'billing_point', 'is_fee', 'order_description')
|
||||||
}),
|
}),
|
||||||
(_("Pricing options"), {
|
(_("Pricing options"), {
|
||||||
'classes': ('wide',),
|
'classes': ('wide',),
|
||||||
|
@ -51,7 +51,7 @@ class ServiceAdmin(ChangeViewActionsMixin, admin.ModelAdmin):
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
inlines = [RateInline]
|
inlines = [RateInline]
|
||||||
actions = [update_orders]
|
actions = [update_orders, clone]
|
||||||
change_view_actions = actions + [view_help]
|
change_view_actions = actions + [view_help]
|
||||||
|
|
||||||
def formfield_for_dbfield(self, db_field, **kwargs):
|
def formfield_for_dbfield(self, db_field, **kwargs):
|
||||||
|
@ -60,7 +60,7 @@ class ServiceAdmin(ChangeViewActionsMixin, admin.ModelAdmin):
|
||||||
models = [model._meta.model_name for model in services.get()]
|
models = [model._meta.model_name for model in services.get()]
|
||||||
queryset = db_field.rel.to.objects
|
queryset = db_field.rel.to.objects
|
||||||
kwargs['queryset'] = queryset.filter(model__in=models)
|
kwargs['queryset'] = queryset.filter(model__in=models)
|
||||||
if db_field.name in ['match', 'metric']:
|
if db_field.name in ['match', 'metric', 'order_description']:
|
||||||
kwargs['widget'] = forms.TextInput(attrs={'size':'160'})
|
kwargs['widget'] = forms.TextInput(attrs={'size':'160'})
|
||||||
return super(ServiceAdmin, self).formfield_for_dbfield(db_field, **kwargs)
|
return super(ServiceAdmin, self).formfield_for_dbfield(db_field, **kwargs)
|
||||||
|
|
||||||
|
|
|
@ -62,6 +62,16 @@ class ServiceHandler(plugins.Plugin):
|
||||||
}
|
}
|
||||||
return eval(self.metric, safe_locals)
|
return eval(self.metric, safe_locals)
|
||||||
|
|
||||||
|
def get_order_description(self, instance):
|
||||||
|
safe_locals = {
|
||||||
|
'instance': instance,
|
||||||
|
'obj': instance,
|
||||||
|
instance._meta.model_name: instance,
|
||||||
|
}
|
||||||
|
if not self.order_description:
|
||||||
|
return '%s: %s' % (self.description, instance)
|
||||||
|
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):
|
||||||
not_cachable = self.billing_point == self.FIXED_DATE and options.get('fixed_point')
|
not_cachable = self.billing_point == self.FIXED_DATE and options.get('fixed_point')
|
||||||
if not_cachable or bp is None:
|
if not_cachable or bp is None:
|
||||||
|
|
|
@ -10,6 +10,7 @@ from django.utils.module_loading import autodiscover_modules
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from orchestra.core import caches, services, accounts
|
from orchestra.core import caches, services, accounts
|
||||||
|
from orchestra.core.validators import validate_name
|
||||||
from orchestra.models import queryset
|
from orchestra.models import queryset
|
||||||
|
|
||||||
from . import settings, rating
|
from . import settings, rating
|
||||||
|
@ -17,7 +18,8 @@ from .handlers import ServiceHandler
|
||||||
|
|
||||||
|
|
||||||
class Plan(models.Model):
|
class Plan(models.Model):
|
||||||
name = models.CharField(_("plan"), max_length=128)
|
name = models.CharField(_("name"), max_length=32, unique=True, validators=[validate_name])
|
||||||
|
verbose_name = models.CharField(_("verbose_name"), max_length=128, blank=True)
|
||||||
is_default = models.BooleanField(_("default"), default=False,
|
is_default = models.BooleanField(_("default"), default=False,
|
||||||
help_text=_("Designates whether this plan is used by default or not."))
|
help_text=_("Designates whether this plan is used by default or not."))
|
||||||
is_combinable = models.BooleanField(_("combinable"), default=True,
|
is_combinable = models.BooleanField(_("combinable"), default=True,
|
||||||
|
@ -29,7 +31,10 @@ class Plan(models.Model):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
self.name = self.name.strip()
|
self.verbose_name = self.verbose_name.strip()
|
||||||
|
|
||||||
|
def get_verbose_name(self):
|
||||||
|
return self.verbose_name or self.name
|
||||||
|
|
||||||
|
|
||||||
class ContractedPlan(models.Model):
|
class ContractedPlan(models.Model):
|
||||||
|
@ -147,6 +152,12 @@ class Service(models.Model):
|
||||||
is_fee = models.BooleanField(_("fee"), default=False,
|
is_fee = models.BooleanField(_("fee"), default=False,
|
||||||
help_text=_("Designates whether this service should be billed as "
|
help_text=_("Designates whether this service should be billed as "
|
||||||
" membership fee or not"))
|
" membership fee or not"))
|
||||||
|
order_description = models.CharField(_("Order description"), max_length=128, blank=True,
|
||||||
|
help_text=_(
|
||||||
|
"Python <a href='https://docs.python.org/2/library/functions.html#eval'>expression</a> "
|
||||||
|
"used for generating the description for the bill lines of this services.<br>"
|
||||||
|
"Defaults to <tt>'%s: %s' % (handler.description, instance)</tt>"
|
||||||
|
))
|
||||||
# Pricing
|
# Pricing
|
||||||
metric = models.CharField(_("metric"), max_length=256, blank=True,
|
metric = models.CharField(_("metric"), max_length=256, blank=True,
|
||||||
help_text=_(
|
help_text=_(
|
||||||
|
|
|
@ -34,8 +34,7 @@ class JobBillingTest(BaseBillingTest):
|
||||||
if not account:
|
if not account:
|
||||||
account = self.create_account()
|
account = self.create_account()
|
||||||
description = 'Random Job %s' % random_ascii(10)
|
description = 'Random Job %s' % random_ascii(10)
|
||||||
service, __ = MiscService.objects.get_or_create(name='job', description=description,
|
service, __ = MiscService.objects.get_or_create(name='job', has_amount=True)
|
||||||
has_amount=True)
|
|
||||||
return account.miscellaneous.create(service=service, description=description, amount=amount)
|
return account.miscellaneous.create(service=service, description=description, amount=amount)
|
||||||
|
|
||||||
def test_job(self):
|
def test_job(self):
|
||||||
|
|
|
@ -43,7 +43,7 @@ class BaseTrafficBillingTest(BaseBillingTest):
|
||||||
period=Resource.MONTHLY_SUM,
|
period=Resource.MONTHLY_SUM,
|
||||||
verbose_name='Account Traffic',
|
verbose_name='Account Traffic',
|
||||||
unit='GB',
|
unit='GB',
|
||||||
scale=10**9,
|
scale='10**9',
|
||||||
on_demand=True,
|
on_demand=True,
|
||||||
monitors='FTPTraffic',
|
monitors='FTPTraffic',
|
||||||
)
|
)
|
||||||
|
|
|
@ -4,18 +4,20 @@ from django.contrib import admin
|
||||||
from django.contrib.admin.util import unquote
|
from django.contrib.admin.util import unquote
|
||||||
from django.contrib.auth.admin import UserAdmin
|
from django.contrib.auth.admin import UserAdmin
|
||||||
from django.utils.translation import ugettext, ugettext_lazy as _
|
from django.utils.translation import ugettext, ugettext_lazy as _
|
||||||
|
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.forms import UserCreationForm, UserChangeForm
|
from orchestra.forms import UserCreationForm, UserChangeForm
|
||||||
|
|
||||||
|
from .filters import IsMainListFilter
|
||||||
from .models import SystemUser
|
from .models import SystemUser
|
||||||
|
|
||||||
|
|
||||||
class SystemUserAdmin(ChangePasswordAdminMixin, SelectAccountAdminMixin, ExtendedModelAdmin):
|
class SystemUserAdmin(ChangePasswordAdminMixin, SelectAccountAdminMixin, ExtendedModelAdmin):
|
||||||
list_display = ('username', 'account_link', 'shell', 'home', 'is_active', 'is_main')
|
list_display = ('username', 'account_link', 'shell', 'home', 'display_active', 'display_main')
|
||||||
list_filter = ('is_active', 'is_main', 'shell')
|
list_filter = ('is_active', 'shell', IsMainListFilter)
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, {
|
(None, {
|
||||||
'fields': ('username', 'password', 'account_link', 'is_active')
|
'fields': ('username', 'password', 'account_link', 'is_active')
|
||||||
|
@ -26,7 +28,7 @@ class SystemUserAdmin(ChangePasswordAdminMixin, SelectAccountAdminMixin, Extende
|
||||||
)
|
)
|
||||||
add_fieldsets = (
|
add_fieldsets = (
|
||||||
(None, {
|
(None, {
|
||||||
'fields': ('username', 'password1', 'password2', 'account')
|
'fields': ('account_link', 'username', 'password1', 'password2')
|
||||||
}),
|
}),
|
||||||
(_("System"), {
|
(_("System"), {
|
||||||
'fields': ('home', 'shell', 'groups'),
|
'fields': ('home', 'shell', 'groups'),
|
||||||
|
@ -41,6 +43,17 @@ class SystemUserAdmin(ChangePasswordAdminMixin, SelectAccountAdminMixin, Extende
|
||||||
form = UserChangeForm
|
form = UserChangeForm
|
||||||
ordering = ('-id',)
|
ordering = ('-id',)
|
||||||
|
|
||||||
|
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")
|
||||||
|
display_main.boolean = True
|
||||||
|
|
||||||
def get_form(self, request, obj=None, **kwargs):
|
def get_form(self, request, obj=None, **kwargs):
|
||||||
""" exclude self reference on groups """
|
""" exclude self reference on groups """
|
||||||
form = super(SystemUserAdmin, self).get_form(request, obj=obj, **kwargs)
|
form = super(SystemUserAdmin, self).get_form(request, obj=obj, **kwargs)
|
||||||
|
@ -51,6 +64,10 @@ class SystemUserAdmin(ChangePasswordAdminMixin, SelectAccountAdminMixin, Extende
|
||||||
formfield = form.base_fields['groups']
|
formfield = form.base_fields['groups']
|
||||||
formfield.queryset = formfield.queryset.exclude(id=obj.id)
|
formfield.queryset = formfield.queryset.exclude(id=obj.id)
|
||||||
return form
|
return form
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
admin.site.register(SystemUser, SystemUserAdmin)
|
admin.site.register(SystemUser, SystemUserAdmin)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from rest_framework import viewsets
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from rest_framework import viewsets, exceptions
|
||||||
|
|
||||||
from orchestra.api import router, SetPasswordApiMixin
|
from orchestra.api import router, SetPasswordApiMixin
|
||||||
from orchestra.apps.accounts.api import AccountApiMixin
|
from orchestra.apps.accounts.api import AccountApiMixin
|
||||||
|
@ -11,6 +12,12 @@ class SystemUserViewSet(AccountApiMixin, SetPasswordApiMixin, viewsets.ModelView
|
||||||
model = SystemUser
|
model = SystemUser
|
||||||
serializer_class = SystemUserSerializer
|
serializer_class = SystemUserSerializer
|
||||||
filter_fields = ('username',)
|
filter_fields = ('username',)
|
||||||
|
|
||||||
|
def destroy(self, request, pk=None):
|
||||||
|
user = self.get_object()
|
||||||
|
if user.is_main:
|
||||||
|
raise exceptions.PermissionDenied(_("Main system user can not be deleted."))
|
||||||
|
super(SystemUserViewSet, self).destroy(request, pk=pk)
|
||||||
|
|
||||||
|
|
||||||
router.register(r'systemusers', SystemUserViewSet)
|
router.register(r'systemusers', SystemUserViewSet)
|
||||||
|
|
23
orchestra/apps/systemusers/filters.py
Normal file
23
orchestra/apps/systemusers/filters.py
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
from django.contrib.admin import SimpleListFilter
|
||||||
|
from django.db.models import F
|
||||||
|
from django.utils.encoding import force_text
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
class IsMainListFilter(SimpleListFilter):
|
||||||
|
""" Filter Nodes by group according to request.user """
|
||||||
|
title = _("main")
|
||||||
|
parameter_name = 'is_main'
|
||||||
|
|
||||||
|
def lookups(self, request, model_admin):
|
||||||
|
return (
|
||||||
|
('True', _("Yes")),
|
||||||
|
('False', _("No")),
|
||||||
|
)
|
||||||
|
|
||||||
|
def queryset(self, request, queryset):
|
||||||
|
if self.value() == 'True':
|
||||||
|
return queryset.filter(account__main_systemuser_id=F('id'))
|
||||||
|
if self.value() == 'False':
|
||||||
|
return queryset.exclude(account__main_systemuser_id=F('id'))
|
||||||
|
|
|
@ -36,7 +36,7 @@ class SystemUser(models.Model):
|
||||||
groups = models.ManyToManyField('self', blank=True,
|
groups = models.ManyToManyField('self', blank=True,
|
||||||
help_text=_("A new group will be created for the user. "
|
help_text=_("A new group will be created for the user. "
|
||||||
"Which additional groups would you like them to be a member of?"))
|
"Which additional groups would you like them to be a member of?"))
|
||||||
is_main = models.BooleanField(_("is main"), default=False)
|
# is_main = models.BooleanField(_("is main"), default=False)
|
||||||
is_active = models.BooleanField(_("active"), default=True,
|
is_active = models.BooleanField(_("active"), default=True,
|
||||||
help_text=_("Designates whether this account should be treated as active. "
|
help_text=_("Designates whether this account should be treated as active. "
|
||||||
"Unselect this instead of deleting accounts."))
|
"Unselect this instead of deleting accounts."))
|
||||||
|
@ -53,6 +53,13 @@ class SystemUser(models.Model):
|
||||||
except type(self).account.field.rel.to.DoesNotExist:
|
except type(self).account.field.rel.to.DoesNotExist:
|
||||||
return self.is_active
|
return self.is_active
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_main(self):
|
||||||
|
# On account creation main_systemuser_id is still None
|
||||||
|
if self.account.main_systemuser_id:
|
||||||
|
return self.account.main_systemuser_id == self.pk
|
||||||
|
return self.account.username == self.username
|
||||||
|
|
||||||
def set_password(self, raw_password):
|
def set_password(self, raw_password):
|
||||||
self.password = make_password(raw_password)
|
self.password = make_password(raw_password)
|
||||||
|
|
||||||
|
@ -63,7 +70,7 @@ class SystemUser(models.Model):
|
||||||
}
|
}
|
||||||
basehome = settings.SYSTEMUSERS_HOME % context
|
basehome = settings.SYSTEMUSERS_HOME % context
|
||||||
else:
|
else:
|
||||||
basehome = self.account.systemusers.get(is_main=True).get_home()
|
basehome = self.account.main_systemuser.get_home()
|
||||||
basehome = basehome.replace('/./', '/')
|
basehome = basehome.replace('/./', '/')
|
||||||
home = os.path.join(basehome, self.home)
|
home = os.path.join(basehome, self.home)
|
||||||
# Chrooting
|
# Chrooting
|
||||||
|
|
|
@ -143,6 +143,7 @@ class SystemUserMixin(object):
|
||||||
self.validate_user(username)
|
self.validate_user(username)
|
||||||
self.delete(username)
|
self.delete(username)
|
||||||
self.validate_delete(username)
|
self.validate_delete(username)
|
||||||
|
self.assertRaises(Exception, self.delete, self.account.username)
|
||||||
|
|
||||||
def test_add_group(self):
|
def test_add_group(self):
|
||||||
username = '%s_systemuser' % random_ascii(10)
|
username = '%s_systemuser' % random_ascii(10)
|
||||||
|
@ -190,7 +191,7 @@ class RESTSystemUserMixin(SystemUserMixin):
|
||||||
self.rest_login()
|
self.rest_login()
|
||||||
# create main user
|
# create main user
|
||||||
self.save(self.account.username)
|
self.save(self.account.username)
|
||||||
self.addCleanup(self.delete, self.account.username)
|
self.addCleanup(self.delete_account, self.account.username)
|
||||||
|
|
||||||
@save_response_on_error
|
@save_response_on_error
|
||||||
def add(self, username, password, shell='/dev/null'):
|
def add(self, username, password, shell='/dev/null'):
|
||||||
|
@ -230,7 +231,7 @@ class AdminSystemUserMixin(SystemUserMixin):
|
||||||
self.admin_login()
|
self.admin_login()
|
||||||
# create main user
|
# create main user
|
||||||
self.save(self.account.username)
|
self.save(self.account.username)
|
||||||
self.addCleanup(self.delete, self.account.username)
|
self.addCleanup(self.delete_account, self.account.username)
|
||||||
|
|
||||||
@snapshot_on_error
|
@snapshot_on_error
|
||||||
def add(self, username, password, shell='/dev/null'):
|
def add(self, username, password, shell='/dev/null'):
|
||||||
|
@ -245,10 +246,6 @@ class AdminSystemUserMixin(SystemUserMixin):
|
||||||
password_field = self.selenium.find_element_by_id('id_password2')
|
password_field = self.selenium.find_element_by_id('id_password2')
|
||||||
password_field.send_keys(password)
|
password_field.send_keys(password)
|
||||||
|
|
||||||
account_input = self.selenium.find_element_by_id('id_account')
|
|
||||||
account_select = Select(account_input)
|
|
||||||
account_select.select_by_value(str(self.account.pk))
|
|
||||||
|
|
||||||
shell_input = self.selenium.find_element_by_id('id_shell')
|
shell_input = self.selenium.find_element_by_id('id_shell')
|
||||||
shell_select = Select(shell_input)
|
shell_select = Select(shell_input)
|
||||||
shell_select.select_by_value(shell)
|
shell_select.select_by_value(shell)
|
||||||
|
@ -261,6 +258,10 @@ class AdminSystemUserMixin(SystemUserMixin):
|
||||||
user = SystemUser.objects.get(username=username)
|
user = SystemUser.objects.get(username=username)
|
||||||
self.admin_delete(user)
|
self.admin_delete(user)
|
||||||
|
|
||||||
|
@snapshot_on_error
|
||||||
|
def delete_account(self, username):
|
||||||
|
self.admin_delete(self.account)
|
||||||
|
|
||||||
@snapshot_on_error
|
@snapshot_on_error
|
||||||
def disable(self, username):
|
def disable(self, username):
|
||||||
user = SystemUser.objects.get(username=username)
|
user = SystemUser.objects.get(username=username)
|
||||||
|
@ -332,7 +333,7 @@ class AdminSystemUserTest(AdminSystemUserMixin, BaseLiveServerTestCase):
|
||||||
|
|
||||||
@snapshot_on_error
|
@snapshot_on_error
|
||||||
def test_delete_account(self):
|
def test_delete_account(self):
|
||||||
home = self.account.systemusers.get(is_main=True).get_home()
|
home = self.account.main_systemuser.get_home()
|
||||||
|
|
||||||
delete = reverse('admin:accounts_account_delete', args=(self.account.pk,))
|
delete = reverse('admin:accounts_account_delete', args=(self.account.pk,))
|
||||||
url = self.live_server_url + delete
|
url = self.live_server_url + delete
|
||||||
|
|
|
@ -46,8 +46,8 @@ class WebAppServiceMixin(object):
|
||||||
|
|
||||||
def get_context(self, webapp):
|
def get_context(self, webapp):
|
||||||
return {
|
return {
|
||||||
'user': webapp.account.username,
|
'user': webapp.get_username(),
|
||||||
'group': webapp.account.username,
|
'group': webapp.get_groupname(),
|
||||||
'app_name': webapp.name,
|
'app_name': webapp.name,
|
||||||
'type': webapp.type,
|
'type': webapp.type,
|
||||||
'app_path': webapp.get_path().rstrip('/'),
|
'app_path': webapp.get_path().rstrip('/'),
|
||||||
|
|
|
@ -52,6 +52,12 @@ class WebApp(models.Model):
|
||||||
'app_name': self.name,
|
'app_name': self.name,
|
||||||
}
|
}
|
||||||
return settings.WEBAPPS_BASE_ROOT % context
|
return settings.WEBAPPS_BASE_ROOT % context
|
||||||
|
|
||||||
|
def get_username(self):
|
||||||
|
return self.account.username
|
||||||
|
|
||||||
|
def get_groupname(self):
|
||||||
|
return self.get_username()
|
||||||
|
|
||||||
|
|
||||||
class WebAppOption(models.Model):
|
class WebAppOption(models.Model):
|
||||||
|
|
|
@ -13,10 +13,10 @@ class WebsiteOptionInline(admin.TabularInline):
|
||||||
model = WebsiteOption
|
model = WebsiteOption
|
||||||
extra = 1
|
extra = 1
|
||||||
|
|
||||||
class Media:
|
# class Media:
|
||||||
css = {
|
# css = {
|
||||||
'all': ('orchestra/css/hide-inline-id.css',)
|
# 'all': ('orchestra/css/hide-inline-id.css',)
|
||||||
}
|
# }
|
||||||
|
|
||||||
def formfield_for_dbfield(self, db_field, **kwargs):
|
def formfield_for_dbfield(self, db_field, **kwargs):
|
||||||
""" Make value input widget bigger """
|
""" Make value input widget bigger """
|
||||||
|
|
|
@ -164,8 +164,8 @@ class Apache2Backend(ServiceController):
|
||||||
'site_name': site.name,
|
'site_name': site.name,
|
||||||
'ip': settings.WEBSITES_DEFAULT_IP,
|
'ip': settings.WEBSITES_DEFAULT_IP,
|
||||||
'site_unique_name': site.unique_name,
|
'site_unique_name': site.unique_name,
|
||||||
'user': site.account.username,
|
'user': site.get_username(),
|
||||||
'group': site.account.username,
|
'group': site.get_groupname(),
|
||||||
'sites_enabled': sites_enabled,
|
'sites_enabled': sites_enabled,
|
||||||
'sites_available': "%s.conf" % os.path.join(sites_available, site.unique_name),
|
'sites_available': "%s.conf" % os.path.join(sites_available, site.unique_name),
|
||||||
'logs': os.path.join(settings.WEBSITES_BASE_APACHE_LOGS, site.unique_name),
|
'logs': os.path.join(settings.WEBSITES_BASE_APACHE_LOGS, site.unique_name),
|
||||||
|
|
|
@ -50,6 +50,17 @@ class Website(models.Model):
|
||||||
if self.port == 443:
|
if self.port == 443:
|
||||||
return 'https'
|
return 'https'
|
||||||
raise TypeError('No protocol for port "%s"' % self.port)
|
raise TypeError('No protocol for port "%s"' % self.port)
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
domain = self.domains.first()
|
||||||
|
if domain:
|
||||||
|
return '%s://%s' % (self.protocol, domain)
|
||||||
|
|
||||||
|
def get_username(self):
|
||||||
|
return self.account.username
|
||||||
|
|
||||||
|
def get_groupname(self):
|
||||||
|
return self.get_username()
|
||||||
|
|
||||||
|
|
||||||
class WebsiteOption(models.Model):
|
class WebsiteOption(models.Model):
|
||||||
|
@ -93,6 +104,11 @@ class Content(models.Model):
|
||||||
def clean(self):
|
def clean(self):
|
||||||
if not self.path.startswith('/'):
|
if not self.path.startswith('/'):
|
||||||
self.path = '/' + self.path
|
self.path = '/' + self.path
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
domain = self.website.domains.first()
|
||||||
|
if domain:
|
||||||
|
return '%s://%s%s' % (self.website.protocol, domain, self.path)
|
||||||
|
|
||||||
|
|
||||||
services.register(Website)
|
services.register(Website)
|
||||||
|
|
|
@ -46,7 +46,7 @@ def read_async(fd):
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
|
|
||||||
def run(command, display=True, error_codes=[0], silent=False, stdin=''):
|
def run(command, display=False, error_codes=[0], silent=False, stdin=''):
|
||||||
""" Subprocess wrapper for running commands """
|
""" Subprocess wrapper for running commands """
|
||||||
if display:
|
if display:
|
||||||
sys.stderr.write("\n\033[1m $ %s\033[0m\n" % command)
|
sys.stderr.write("\n\033[1m $ %s\033[0m\n" % command)
|
||||||
|
|
|
@ -69,6 +69,8 @@ class BaseTestCase(TestCase, AppDependencyMixin):
|
||||||
class BaseLiveServerTestCase(AppDependencyMixin, LiveServerTestCase):
|
class BaseLiveServerTestCase(AppDependencyMixin, LiveServerTestCase):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
|
# Avoid problems with the overlaping menu when clicking
|
||||||
|
settings.ADMIN_TOOLS_MENU = 'admin_tools.menu.Menu'
|
||||||
cls.vdisplay = Xvfb()
|
cls.vdisplay = Xvfb()
|
||||||
cls.vdisplay.start()
|
cls.vdisplay.start()
|
||||||
cls.selenium = WebDriver()
|
cls.selenium = WebDriver()
|
||||||
|
@ -180,4 +182,3 @@ def save_response_on_error(test):
|
||||||
dumpfile.write(self.rest.last_response.content)
|
dumpfile.write(self.rest.last_response.content)
|
||||||
raise
|
raise
|
||||||
return inner
|
return inner
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue