Mailbox tests passing

This commit is contained in:
Marc 2014-10-07 13:08:59 +00:00
parent 6240fa3139
commit 9082770642
34 changed files with 365 additions and 230 deletions

View File

@ -163,3 +163,6 @@ APPS app?
* pip upgrade or install * pip upgrade or install
* disable account triggers save on cascade to execute backends save(update_field=[])

View File

@ -111,14 +111,15 @@ class AdminPasswordChangeForm(forms.Form):
if password: if password:
self.user.set_password(password) self.user.set_password(password)
if commit: if commit:
self.user.save() self.user.save(update_fields=['password'])
for ix, rel in enumerate(self.related): for ix, rel in enumerate(self.related):
password = self.cleaned_data['password1_%s' % ix] password = self.cleaned_data['password1_%s' % ix]
if password: if password:
print password
set_password = getattr(rel, 'set_password') set_password = getattr(rel, 'set_password')
set_password(password) set_password(password)
if commit: if commit:
rel.save() rel.save(update_fields=['password'])
return self.user return self.user
def _get_changed_data(self): def _get_changed_data(self):

View File

@ -33,28 +33,28 @@ def get_modeladmin(model, import_module=True):
def insertattr(model, name, value, weight=0): def insertattr(model, name, value, weight=0):
""" Inserts attribute to a modeladmin """ """ Inserts attribute to a modeladmin """
modeladmin = model modeladmin_class = model
if models.Model in model.__mro__: if models.Model in model.__mro__:
modeladmin = type(get_modeladmin(model)) modeladmin_class = type(get_modeladmin(model))
# Avoid inlines defined on parent class be shared between subclasses # Avoid inlines defined on parent class be shared between subclasses
# Seems that if we use tuples they are lost in some conditions like changing # Seems that if we use tuples they are lost in some conditions like changing
# the tuple in modeladmin.__init__ # the tuple in modeladmin.__init__
if not getattr(modeladmin, name): if not getattr(modeladmin_class, name):
setattr(type(modeladmin), name, []) setattr(modeladmin_class, name, [])
inserted_attrs = getattr(modeladmin, '__inserted_attrs__', {}) inserted_attrs = getattr(modeladmin_class, '__inserted_attrs__', {})
if not name in inserted_attrs: if not name in inserted_attrs:
weights = {} weights = {}
if hasattr(modeladmin, 'weights') and name in modeladmin.weights: if hasattr(modeladmin_class, 'weights') and name in modeladmin_class.weights:
weights = modeladmin.weights.get(name) weights = modeladmin_class.weights.get(name)
inserted_attrs[name] = [ inserted_attrs[name] = [
(attr, weights.get(attr, 0)) for attr in getattr(modeladmin, name) (attr, weights.get(attr, 0)) for attr in getattr(modeladmin_class, name)
] ]
inserted_attrs[name].append((value, weight)) inserted_attrs[name].append((value, weight))
inserted_attrs[name].sort(key=lambda a: a[1]) inserted_attrs[name].sort(key=lambda a: a[1])
setattr(modeladmin, name, [ attr[0] for attr in inserted_attrs[name] ]) setattr(modeladmin_class, name, [ attr[0] for attr in inserted_attrs[name] ])
setattr(modeladmin, '__inserted_attrs__', inserted_attrs) setattr(modeladmin_class, '__inserted_attrs__', inserted_attrs)
def wrap_admin_view(modeladmin, view): def wrap_admin_view(modeladmin, view):

View File

@ -9,10 +9,13 @@ class SetPasswordApiMixin(object):
@action(serializer_class=SetPasswordSerializer) @action(serializer_class=SetPasswordSerializer)
def set_password(self, request, pk): def set_password(self, request, pk):
obj = self.get_object() obj = self.get_object()
serializer = SetPasswordSerializer(data=request.DATA) data = request.DATA
if isinstance(data, basestring):
data = {'password': data}
serializer = SetPasswordSerializer(data=data)
if serializer.is_valid(): if serializer.is_valid():
obj.set_password(serializer.data['password']) obj.set_password(serializer.data['password'])
obj.save() obj.save(update_fields=['password'])
return Response({'status': 'password changed'}) return Response({'status': 'password changed'})
else: else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

View File

@ -1,8 +1,9 @@
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.utils.module_loading import autodiscover_modules
from rest_framework.routers import DefaultRouter, Route, flatten, replace_methodname from rest_framework.routers import DefaultRouter, Route, flatten, replace_methodname
from orchestra import settings from orchestra import settings
from orchestra.utils.apps import autodiscover as module_autodiscover #from orchestra.utils.apps import autodiscover as module_autodiscover
from orchestra.utils.python import import_class from orchestra.utils.python import import_class
from .helpers import insert_links, replace_collectionmethodname from .helpers import insert_links, replace_collectionmethodname
@ -99,16 +100,16 @@ class LinkHeaderRouter(DefaultRouter):
def insert(self, prefix_or_model, name, field, **kwargs): def insert(self, prefix_or_model, name, field, **kwargs):
""" Dynamically add new fields to an existing serializer """ """ Dynamically add new fields to an existing serializer """
viewset = self.get_viewset(prefix_or_model) viewset = self.get_viewset(prefix_or_model)
setattr(viewset, 'inserted', getattr(viewset, 'inserted', [])) # setattr(viewset, 'inserted', getattr(viewset, 'inserted', []))
if viewset.serializer_class is None: if viewset.serializer_class is None:
viewset.serializer_class = viewset().get_serializer_class() viewset.serializer_class = viewset().get_serializer_class()
viewset.serializer_class.base_fields.update({name: field(**kwargs)}) viewset.serializer_class.base_fields.update({name: field(**kwargs)})
if not name in viewset.inserted: # if not name in viewset.inserted:
viewset.serializer_class.Meta.fields += (name,) viewset.serializer_class.Meta.fields += (name,)
viewset.inserted.append(name) # viewset.inserted.append(name)
# Create a router and register our viewsets with it. # Create a router and register our viewsets with it.
router = LinkHeaderRouter() router = LinkHeaderRouter()
autodiscover = lambda: (module_autodiscover('api'), module_autodiscover('serializers')) autodiscover = lambda: (autodiscover_modules('api'), autodiscover_modules('serializers'))

View File

@ -1,6 +1,6 @@
from rest_framework import viewsets from rest_framework import viewsets
from orchestra.api import router from orchestra.api import router, SetPasswordApiMixin
from .models import Account from .models import Account
from .serializers import AccountSerializer from .serializers import AccountSerializer
@ -12,7 +12,7 @@ class AccountApiMixin(object):
return qs.filter(account=self.request.user.pk) return qs.filter(account=self.request.user.pk)
class AccountViewSet(viewsets.ModelViewSet): class AccountViewSet(SetPasswordApiMixin, viewsets.ModelViewSet):
model = Account model = Account
serializer_class = AccountSerializer serializer_class = AccountSerializer
singleton_pk = lambda _,request: request.user.pk singleton_pk = lambda _,request: request.user.pk

View File

@ -42,7 +42,7 @@ class PermissionInline(AccountAdminMixin, admin.TabularInline):
""" Make value input widget bigger """ """ Make value input widget bigger """
formfield = super(PermissionInline, self).formfield_for_dbfield(db_field, **kwargs) formfield = super(PermissionInline, self).formfield_for_dbfield(db_field, **kwargs)
if db_field.name == 'database': if db_field.name == 'database':
# Hack widget render in order to append ?account=id to the add url # Hack widget render in order to append ?type='db_type' to the add url
db_type = self.parent_object.type db_type = self.parent_object.type
old_render = formfield.widget.render old_render = formfield.widget.render
def render(*args, **kwargs): def render(*args, **kwargs):

View File

@ -6,7 +6,7 @@ from orchestra.apps.resources import ServiceMonitor
from . import settings from . import settings
class MySQLDBBackend(ServiceController): class MySQLBackend(ServiceController):
verbose_name = "MySQL database" verbose_name = "MySQL database"
model = 'databases.Database' model = 'databases.Database'

View File

@ -32,7 +32,7 @@ class Database(models.Model):
@property @property
def owner(self): def owner(self):
self.users.get(is_owner=True) return self.roles.get(is_owner=True).user
class Role(models.Model): class Role(models.Model):
@ -52,6 +52,11 @@ class Role(models.Model):
if self.user.type != self.database.type: if self.user.type != self.database.type:
msg = _("Database and user type doesn't match") msg = _("Database and user type doesn't match")
raise validators.ValidationError(msg) raise validators.ValidationError(msg)
roles = self.database.roles.values('id')
print roles
if not roles or (len(roles) == 1 and roles[0].id == self.id):
print 'seld'
self.is_owner = True
class DatabaseUser(models.Model): class DatabaseUser(models.Model):

View File

@ -14,7 +14,7 @@ class UserSerializer(serializers.HyperlinkedModelSerializer):
fields = ('user', 'is_owner',) fields = ('user', 'is_owner',)
class PermissionSerializer(serializers.HyperlinkedModelSerializer): class RoleSerializer(serializers.HyperlinkedModelSerializer):
class Meta: class Meta:
model = Role model = Role
fields = ('database', 'is_owner',) fields = ('database', 'is_owner',)
@ -32,9 +32,9 @@ class DatabaseUserSerializer(AccountSerializerMixin, serializers.HyperlinkedMode
password = serializers.CharField(max_length=128, label=_('Password'), password = serializers.CharField(max_length=128, label=_('Password'),
validators=[validate_password], write_only=True, validators=[validate_password], write_only=True,
widget=widgets.PasswordInput) widget=widgets.PasswordInput)
permission = PermissionSerializer(source='roles', many=True) roles = RoleSerializer(many=True, read_only=True)
class Meta: class Meta:
model = DatabaseUser model = DatabaseUser
fields = ('url', 'username', 'password', 'type', 'permission') fields = ('url', 'username', 'password', 'type', 'roles')
write_only_fields = ('username',) write_only_fields = ('username',)

View File

@ -1,4 +1,5 @@
#import MySQLdb import MySQLdb
import os
from functools import partial from functools import partial
from django.conf import settings as djsettings from django.conf import settings as djsettings
@ -9,21 +10,22 @@ from selenium.webdriver.support.select import Select
from orchestra.apps.accounts.models import Account 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.utils.system import run from orchestra.utils.system import run
from orchestra.utils.tests import BaseLiveServerTestCase, random_ascii from orchestra.utils.tests import (BaseLiveServerTestCase, random_ascii, save_response_on_error,
snapshot_on_error)
from ... import backends, settings from ... import backends, settings
from ...models import Database from ...models import Database
class DatabaseTestMixin(object): class DatabaseTestMixin(object):
MASTER_ADDR = 'localhost' MASTER_SERVER = os.environ.get('ORCHESTRA_SECOND_SERVER', 'localhost')
DEPENDENCIES = ( DEPENDENCIES = (
'orchestra.apps.orchestration', 'orchestra.apps.orchestration',
'orcgestra.apps.databases', 'orcgestra.apps.databases',
) )
def setUp(self): def setUp(self):
super(SystemUserMixin, self).setUp() super(DatabaseTestMixin, self).setUp()
self.add_route() self.add_route()
djsettings.DEBUG = True djsettings.DEBUG = True
@ -49,28 +51,79 @@ class DatabaseTestMixin(object):
raise NotImplementedError raise NotImplementedError
def test_add(self): def test_add(self):
self.add() dbname = '%s_database' % random_ascii(5)
username = '%s_dbuser' % random_ascii(10)
password = '@!?%spppP001' % random_ascii(5)
self.add(dbname, username, password)
self.validate_create_table(dbname, username, password)
class MySQLBackendMixin(object):
db_type = 'mysql'
class MysqlBackendMixin(object):
def add_route(self): def add_route(self):
server = Server.objects.create(name=self.MASTER_ADDR) server = Server.objects.create(name=self.MASTER_SERVER)
backend = backends.MysqlBackend.get_name() backend = backends.MySQLBackend.get_name()
Route.objects.create(backend=backend, match="database.type == 'mysql'", host=server) match = "database.type == '%s'" % self.db_type
Route.objects.create(backend=backend, match=match, host=server)
match = "databaseuser.type == '%s'" % self.db_type
backend = backends.MySQLUserBackend.get_name()
Route.objects.create(backend=backend, match=match, host=server)
def validate_create_table(self, name, username, password): def validate_create_table(self, name, username, password):
db = MySQLdb.connect(host=self.MASTER_ADDR, user=username, passwd=password, db=name) db = MySQLdb.connect(host=self.MASTER_SERVER, port=3306, user=username, passwd=password, db=name)
cur = db.cursor() cur = db.cursor()
cur.execute('CREATE TABLE test;') cur.execute('CREATE TABLE test;')
def validate_delete(self, name, username, password): def validate_delete(self, name, username, password):
self.asseRaises(MySQLdb.ConnectionError, self.asseRaises(MySQLdb.ConnectionError,
MySQLdb.connect(host=self.MASTER_ADDR, user=username, passwd=password, db=name)) self.validate_create_table, name, username, password)
class RESTDatabaseTest(DatabaseTestMixin): class RESTDatabaseMixin(DatabaseTestMixin):
def add(self, dbname): def setUp(self):
self.api.databases.create(name=dbname) super(RESTDatabaseMixin, self).setUp()
self.rest_login()
@save_response_on_error
def add(self, dbname, username, password):
user = self.rest.databaseusers.create(username=username, password=password)
self.rest.databases.create(name=dbname, user=user, type=self.db_type)
class AdminDatabaseMixin(DatabaseTestMixin):
def setUp(self):
super(AdminDatabaseMixin, self).setUp()
self.admin_login()
@snapshot_on_error
def add(self, dbname, username, password):
url = self.live_server_url + reverse('admin:databases_database_add')
self.selenium.get(url)
type_input = self.selenium.find_element_by_id('id_type')
type_select = Select(type_input)
type_select.select_by_value(self.db_type)
name_field = self.selenium.find_element_by_id('id_name')
name_field.send_keys(dbname)
username_field = self.selenium.find_element_by_id('id_username')
username_field.send_keys(username)
password_field = self.selenium.find_element_by_id('id_password1')
password_field.send_keys(password)
password_field = self.selenium.find_element_by_id('id_password2')
password_field.send_keys(password)
name_field.submit()
self.assertNotEqual(url, self.selenium.current_url)
class RESTMysqlDatabaseTest(MySQLBackendMixin, RESTDatabaseMixin, BaseLiveServerTestCase):
pass
class AdminMysqlDatabaseTest(MySQLBackendMixin, AdminDatabaseMixin, BaseLiveServerTestCase):
pass

View File

@ -257,12 +257,7 @@ class AdminDomainMixin(DomainTestMixin):
@snapshot_on_error @snapshot_on_error
def delete(self, domain_name): def delete(self, domain_name):
domain = Domain.objects.get(name=domain_name) domain = Domain.objects.get(name=domain_name)
delete = reverse('admin:domains_domain_delete', args=(domain.pk,)) self.admin_delete(domain)
url = self.live_server_url + delete
self.selenium.get(url)
form = self.selenium.find_element_by_name('post')
form.submit()
self.assertNotEqual(url, self.selenium.current_url)
@snapshot_on_error @snapshot_on_error
def update(self, domain_name, records): def update(self, domain_name, records):

View File

@ -43,7 +43,7 @@ class MailboxAdmin(ChangePasswordAdminMixin, AccountAdminMixin, ExtendedModelAdm
fieldsets = ( fieldsets = (
(None, { (None, {
'classes': ('wide',), 'classes': ('wide',),
'fields': ('account_link', 'name', 'password'), 'fields': ('name', 'password', 'is_active', 'account_link'),
}), }),
(_("Filtering"), { (_("Filtering"), {
'classes': ('collapse',), 'classes': ('collapse',),

View File

@ -1,6 +1,6 @@
from rest_framework import viewsets from rest_framework import viewsets
from orchestra.api import router from orchestra.api import router, SetPasswordApiMixin
from orchestra.apps.accounts.api import AccountApiMixin from orchestra.apps.accounts.api import AccountApiMixin
from .models import Address, Mailbox from .models import Address, Mailbox
@ -13,7 +13,7 @@ class AddressViewSet(AccountApiMixin, viewsets.ModelViewSet):
class MailboxViewSet(AccountApiMixin, viewsets.ModelViewSet): class MailboxViewSet(SetPasswordApiMixin, AccountApiMixin, viewsets.ModelViewSet):
model = Mailbox model = Mailbox
serializer_class = MailboxSerializer serializer_class = MailboxSerializer

View File

@ -28,7 +28,7 @@ class PasswdVirtualUserBackend(ServiceController):
def set_user(self, context): def set_user(self, context):
self.append(textwrap.dedent(""" self.append(textwrap.dedent("""
if [[ $( grep "^%(username)s:" %(passwd_path)s ) ]]; then if [[ $( grep "^%(username)s:" %(passwd_path)s ) ]]; then
sed -i "s/^%(username)s:.*/%(passwd)s/" %(passwd_path)s sed -i 's#^%(username)s:.*#%(passwd)s#' %(passwd_path)s
else else
echo '%(passwd)s' >> %(passwd_path)s echo '%(passwd)s' >> %(passwd_path)s
fi""" % context fi""" % context
@ -49,22 +49,6 @@ class PasswdVirtualUserBackend(ServiceController):
context['filter_path'] = os.path.join(context['home'], '.orchestra.sieve') context['filter_path'] = os.path.join(context['home'], '.orchestra.sieve')
self.append("echo '%(filtering)s' > %(filter_path)s" % context) self.append("echo '%(filtering)s' > %(filter_path)s" % context)
def set_quota(self, mailbox, context):
if not hasattr(mailbox, 'resources'):
return
context.update({
'maildir_path': '~%(username)s/Maildir' % context,
'maildirsize_path': '~%(username)s/Maildir/maildirsize' % context,
'quota': mailbox.resources.disk.allocated*1000*1000,
})
self.append("mkdir -p %(maildir_path)s" % context)
self.append(textwrap.dedent("""
sed -i '1s/.*/%(quota)s,S/' %(maildirsize_path)s || {
echo '%(quota)s,S' > %(maildirsize_path)s &&
chown %(username)s %(maildirsize_path)s;
}""" % context
))
def save(self, mailbox): def save(self, mailbox):
context = self.get_context(mailbox) context = self.get_context(mailbox)
self.set_user(context) self.set_user(context)
@ -73,9 +57,11 @@ class PasswdVirtualUserBackend(ServiceController):
def delete(self, mailbox): def delete(self, mailbox):
context = self.get_context(mailbox) context = self.get_context(mailbox)
self.append("{ sleep 2 && killall -u %(uid)s -s KILL; } &" % context) self.append("{ sleep 2 && killall -u %(uid)s -s KILL; } &" % context)
self.append("killall -u %(uid)s" % context) self.append("killall -u %(uid)s || true" % context)
self.append("sed -i '/^%(username)s:.*/d' %(passwd_path)s" % context) self.append("sed -i '/^%(username)s:.*/d' %(passwd_path)s" % context)
self.append("rm -fr %(home)s" % context) # TODO delete
context['deleted'] = context['home'].rstrip('/') + '.deleted'
self.append("mv %(home)s %(deleted)s" % context)
def get_extra_fields(self, mailbox, context): def get_extra_fields(self, mailbox, context):
context['quota'] = self.get_quota(mailbox) context['quota'] = self.get_quota(mailbox)

View File

@ -36,7 +36,10 @@ class Mailbox(models.Model):
@cached_property @cached_property
def active(self): def active(self):
return self.is_active and self.account.is_active try:
return self.is_active and self.account.is_active
except type(self).account.field.rel.to.DoesNotExist:
return self.is_active
def set_password(self, raw_password): def set_password(self, raw_password):
self.password = make_password(raw_password) self.password = make_password(raw_password)

View File

@ -9,7 +9,7 @@ class MailboxSerializer(AccountSerializerMixin, serializers.HyperlinkedModelSeri
class Meta: class Meta:
model = Mailbox model = Mailbox
# TODO 'use_custom_filtering', # TODO 'use_custom_filtering',
fields = ('url', 'name', 'password', 'custom_filtering', 'addresses') fields = ('url', 'name', 'password', 'custom_filtering', 'addresses', 'is_active')
def validate_password(self, attrs, source): def validate_password(self, attrs, source):
""" POST only password """ """ POST only password """
@ -44,6 +44,6 @@ class AddressSerializer(AccountSerializerMixin, serializers.HyperlinkedModelSeri
def validate(self, attrs): def validate(self, attrs):
if not attrs['mailboxes'] and not attrs['forward']: if not attrs['mailboxes'] and not attrs['forward']:
raise serializers.ValidationError("mailboxes or forward should be provided") raise serializers.ValidationError("mailboxes or forward addresses should be provided")
return attrs return attrs

View File

@ -20,31 +20,8 @@ from orchestra.utils.tests import BaseLiveServerTestCase, random_ascii, snapshot
from ... import backends, settings from ... import backends, settings
from ...models import Mailbox from ...models import Mailbox
#>>> mail.list()
#('OK', ['(\\HasNoChildren) "." INBOX'])
#>>> mail.select('INBOX')
#('OK', ['18'])
#>>> mail.getquota('INBOX')
#imaplib.error: GETQUOTA command error: BAD ['Error in IMAP command GETQUOTA: Unknown command.']
#mail.fetch(10, '(RFC822)')
#('OK', [('10 (FLAGS (\\Seen) RFC822 {550}', 'Return-Path: <root@test3.orchestra.lan>\r\nDelivered-To: <rata@orchestra.lan>\r\nReceived: from test3.orchestra.lan\r\n\tby test3.orchestra.lan (Dovecot) with LMTP id hvDUEAIKL1QlOQAAL4hJug\r\n\tfor <rata@orchestra.lan>; Fri, 03 Oct 2014 16:41:38 -0400\r\nReceived: by test3.orchestra.lan (Postfix, from userid 0)\r\n\tid 43BB1F94633; Fri, 3 Oct 2014 16:41:38 -0400 (EDT)\r\nTo: rata@orchestra.lan\r\nSubject: hola\r\nMessage-Id: <20141003204138.43BB1F94633@test3.orchestra.lan>\r\nDate: Fri, 3 Oct 2014 16:41:38 -0400 (EDT)\r\nFrom: root@test3.orchestra.lan (root)\r\n\r\n\r\n\r\n'), ')'])
#>>> mail.close()
#('OK', ['Close completed.'])
#pop = poplib.POP3('localhost')
#pop.user('rata')
#pop.pass_('3')
#>>> pop.list()
#('+OK 18 messages:', ['1 552', '2 550', '3 550', '4 548', '5 546', '6 546', '7 554', '8 548', '9 550', '10 550', '11 546', '12 546', '13 546', '14 544', '15 548', '16 577', '17 546', '18 546'], 135)
#>>> pop.quit()
#'+OK Logging out.'
# FIXME django load production database at the begining of tests
class MailboxMixin(object): class MailboxMixin(object):
MASTER_SERVER = os.environ.get('ORCHESTRA_SLAVE_SERVER', 'localhost') MASTER_SERVER = os.environ.get('ORCHESTRA_SLAVE_SERVER', 'localhost')
DEPENDENCIES = ( DEPENDENCIES = (
@ -56,7 +33,10 @@ class MailboxMixin(object):
def setUp(self): def setUp(self):
super(MailboxMixin, self).setUp() super(MailboxMixin, self).setUp()
self.add_route() self.add_route()
# apps.get_app_config('resources').reload_relations() doesn't work # TODO fix this
from django.apps import apps
# clean resource relation from other tests
apps.get_app_config('resources').reload_relations()
djsettings.DEBUG = True djsettings.DEBUG = True
def add_route(self): def add_route(self):
@ -96,27 +76,6 @@ class MailboxMixin(object):
def add_group(self, username, groupname): def add_group(self, username, groupname):
raise NotImplementedError raise NotImplementedError
def validate_user(self, username):
idcmd = sshr(self.MASTER_SERVER, "id %s" % username)
self.assertEqual(0, idcmd.return_code)
user = SystemUser.objects.get(username=username)
groups = list(user.groups.values_list('username', flat=True))
groups.append(user.username)
idgroups = idcmd.stdout.strip().split(' ')[2]
idgroups = re.findall(r'\d+\((\w+)\)', idgroups)
self.assertEqual(set(groups), set(idgroups))
def validate_delete(self, username):
self.assertRaises(SystemUser.DoesNotExist, SystemUser.objects.get, username=username)
self.assertRaises(CommandError,
sshrun, self.MASTER_SERVER,'id %s' % username, display=False)
self.assertRaises(CommandError,
sshrun, self.MASTER_SERVER, 'grep "^%s:" /etc/groups' % username, display=False)
self.assertRaises(CommandError,
sshrun, self.MASTER_SERVER, 'grep "^%s:" /etc/passwd' % username, display=False)
self.assertRaises(CommandError,
sshrun, self.MASTER_SERVER, 'grep "^%s:" /etc/shadow' % username, display=False)
def login_imap(self, username, password): def login_imap(self, username, password):
mail = imaplib.IMAP4_SSL(self.MASTER_SERVER) mail = imaplib.IMAP4_SSL(self.MASTER_SERVER)
status, msg = mail.login(username, password) status, msg = mail.login(username, password)
@ -144,6 +103,9 @@ class MailboxMixin(object):
finally: finally:
server.quit() server.quit()
def validate_mailbox(self, username):
sshrun(self.MASTER_SERVER, "doveadm search -u %s ALL" % username, display=False)
def validate_email(self, username, token): def validate_email(self, username, token):
home = Mailbox.objects.get(name=username).get_home() home = Mailbox.objects.get(name=username).get_home()
sshrun(self.MASTER_SERVER, "grep '%s' %s/Maildir/new/*" % (token, home), display=False) sshrun(self.MASTER_SERVER, "grep '%s' %s/Maildir/new/*" % (token, home), display=False)
@ -152,14 +114,15 @@ class MailboxMixin(object):
username = '%s_mailbox' % random_ascii(10) username = '%s_mailbox' % random_ascii(10)
password = '@!?%spppP001' % random_ascii(5) password = '@!?%spppP001' % random_ascii(5)
self.add(username, password) self.add(username, password)
self.addCleanup(partial(self.delete, username)) self.addCleanup(self.delete, username)
imap = self.login_imap(username, password) imap = self.login_imap(username, password)
self.validate_mailbox(username)
def test_change_password(self): def test_change_password(self):
username = '%s_systemuser' % random_ascii(10) username = '%s_systemuser' % random_ascii(10)
password = '@!?%spppP001' % random_ascii(5) password = '@!?%spppP001' % random_ascii(5)
self.add(username, password) self.add(username, password)
self.addCleanup(partial(self.delete, username)) self.addCleanup(self.delete, username)
imap = self.login_imap(username, password) imap = self.login_imap(username, password)
new_password = '@!?%spppP001' % random_ascii(5) new_password = '@!?%spppP001' % random_ascii(5)
self.change_password(username, new_password) self.change_password(username, new_password)
@ -171,7 +134,7 @@ class MailboxMixin(object):
self.add_quota_resource() self.add_quota_resource()
quota = 100 quota = 100
self.add(username, password, quota=quota) self.add(username, password, quota=quota)
self.addCleanup(partial(self.delete, username)) self.addCleanup(self.delete, username)
get_quota = "doveadm quota get -u %s 2>&1|grep STORAGE|awk {'print $5'}" % username get_quota = "doveadm quota get -u %s 2>&1|grep STORAGE|awk {'print $5'}" % username
stdout = sshrun(self.MASTER_SERVER, get_quota, display=False).stdout stdout = sshrun(self.MASTER_SERVER, get_quota, display=False).stdout
self.assertEqual(quota*1024, int(stdout)) self.assertEqual(quota*1024, int(stdout))
@ -183,7 +146,7 @@ class MailboxMixin(object):
username = '%s_mailbox' % random_ascii(10) username = '%s_mailbox' % random_ascii(10)
password = '@!?%spppP001' % random_ascii(5) password = '@!?%spppP001' % random_ascii(5)
self.add(username, password) self.add(username, password)
self.addCleanup(partial(self.delete, username)) self.addCleanup(self.delete, username)
msg = MIMEText("Hola bishuns") msg = MIMEText("Hola bishuns")
msg['To'] = 'noexists@example.com' msg['To'] = 'noexists@example.com'
msg['From'] = '%s@%s' % (username, self.MASTER_SERVER) msg['From'] = '%s@%s' % (username, self.MASTER_SERVER)
@ -199,7 +162,7 @@ class MailboxMixin(object):
username = '%s_mailbox' % random_ascii(10) username = '%s_mailbox' % random_ascii(10)
password = '@!?%spppP001' % random_ascii(5) password = '@!?%spppP001' % random_ascii(5)
self.add(username, password) self.add(username, password)
self.addCleanup(partial(self.delete, username)) self.addCleanup(self.delete, username)
domain = '%s_domain.lan' % random_ascii(5) domain = '%s_domain.lan' % random_ascii(5)
name = '%s_name' % random_ascii(5) name = '%s_name' % random_ascii(5)
domain = self.account.domains.create(name=domain) domain = self.account.domains.create(name=domain)
@ -207,6 +170,47 @@ class MailboxMixin(object):
token = random_ascii(100) token = random_ascii(100)
self.send_email("%s@%s" % (name, domain), token) self.send_email("%s@%s" % (name, domain), token)
self.validate_email(username, token) self.validate_email(username, token)
def test_disable(self):
username = '%s_systemuser' % random_ascii(10)
password = '@!?%spppP001' % random_ascii(5)
self.add(username, password)
self.validate_mailbox(username)
self.addCleanup(self.delete, username)
imap = self.login_imap(username, password)
self.disable(username)
self.assertRaises(imap.error, self.login_imap, username, password)
def test_delete(self):
username = '%s_systemuser' % random_ascii(10)
password = '@!?%sppppP001' % random_ascii(5)
self.add(username, password)
imap = self.login_imap(username, password)
self.validate_mailbox(username)
mailbox = Mailbox.objects.get(name=username)
home = mailbox.get_home()
self.delete(username)
self.assertRaises(Mailbox.DoesNotExist, Mailbox.objects.get, name=username)
self.assertRaises(CommandError, self.validate_mailbox, username)
self.assertRaises(imap.error, self.login_imap, username, password)
self.assertRaises(CommandError,
sshrun, self.MASTER_SERVER, 'ls %s' % home, display=False)
def test_delete_address(self):
username = '%s_mailbox' % random_ascii(10)
password = '@!?%spppP001' % random_ascii(5)
self.add(username, password)
self.addCleanup(self.delete, username)
domain = '%s_domain.lan' % random_ascii(5)
name = '%s_name' % random_ascii(5)
domain = self.account.domains.create(name=domain)
self.add_address(username, name, domain)
token = random_ascii(100)
self.send_email("%s@%s" % (name, domain), token)
self.validate_email(username, token)
self.delete_address(username)
self.send_email("%s@%s" % (name, domain), token)
self.validate_email(username, token)
class RESTMailboxMixin(MailboxMixin): class RESTMailboxMixin(MailboxMixin):
@ -243,7 +247,21 @@ class RESTMailboxMixin(MailboxMixin):
mailbox = self.rest.mailboxes.retrieve(name=username).get() mailbox = self.rest.mailboxes.retrieve(name=username).get()
domain = self.rest.domains.retrieve(name=domain.name).get() domain = self.rest.domains.retrieve(name=domain.name).get()
self.rest.addresses.create(name=name, domain=domain, mailboxes=[mailbox]) self.rest.addresses.create(name=name, domain=domain, mailboxes=[mailbox])
@save_response_on_error
def delete_address(self, username):
mailbox = self.rest.mailboxes.retrieve(name=username).get()
self.rest.addresses.delete()
@save_response_on_error
def change_password(self, username, password):
mailbox = self.rest.mailboxes.retrieve(name=username).get()
mailbox.set_password(password=password)
@save_response_on_error
def disable(self, username):
mailbox = self.rest.mailboxes.retrieve(name=username).get()
mailbox.update(is_active=False)
class AdminMailboxMixin(MailboxMixin): class AdminMailboxMixin(MailboxMixin):
@ -267,8 +285,12 @@ class AdminMailboxMixin(MailboxMixin):
password_field.send_keys(password) password_field.send_keys(password)
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)
if quota is not None: if quota is not None:
from orchestra.admin.utils import get_modeladmin
m = get_modeladmin(Mailbox)
print 't', type(m).inlines
print 'm', m.inlines
self.take_screenshot()
quota_field = self.selenium.find_element_by_id( quota_field = self.selenium.find_element_by_id(
'id_resources-resourcedata-content_type-object_id-0-allocated') 'id_resources-resourcedata-content_type-object_id-0-allocated')
quota_field.clear() quota_field.clear()
@ -280,27 +302,12 @@ class AdminMailboxMixin(MailboxMixin):
@snapshot_on_error @snapshot_on_error
def delete(self, username): def delete(self, username):
mailbox = Mailbox.objects.get(name=username) mailbox = Mailbox.objects.get(name=username)
delete = reverse('admin:mails_mailbox_delete', args=(mailbox.pk,)) self.admin_delete(mailbox)
url = self.live_server_url + delete
self.selenium.get(url)
confirmation = self.selenium.find_element_by_name('post')
confirmation.submit()
self.assertNotEqual(url, self.selenium.current_url)
@snapshot_on_error @snapshot_on_error
def change_password(self, username, password): def change_password(self, username, password):
mailbox = Mailbox.objects.get(name=username) mailbox = Mailbox.objects.get(name=username)
change_password = reverse('admin:mails_mailbox_change_password', args=(mailbox.pk,)) self.admin_change_password(mailbox, password)
url = self.live_server_url + change_password
self.selenium.get(url)
password_field = self.selenium.find_element_by_id('id_password1')
password_field.send_keys(password)
password_field = self.selenium.find_element_by_id('id_password2')
password_field.send_keys(password)
password_field.submit()
self.assertNotEqual(url, self.selenium.current_url)
@snapshot_on_error @snapshot_on_error
def add_address(self, username, name, domain): def add_address(self, username, name, domain):
@ -320,6 +327,18 @@ class AdminMailboxMixin(MailboxMixin):
name_field.submit() name_field.submit()
self.assertNotEqual(url, self.selenium.current_url) self.assertNotEqual(url, self.selenium.current_url)
@snapshot_on_error
def delete_address(self, username):
mailbox = Mailbox.objects.get(name=username)
address = mailbox.addresses.get()
self.admin_delete(address)
@snapshot_on_error
def disable(self, username):
mailbox = Mailbox.objects.get(name=username)
self.admin_disable(mailbox)
class RESTMailboxTest(RESTMailboxMixin, BaseLiveServerTestCase): class RESTMailboxTest(RESTMailboxMixin, BaseLiveServerTestCase):
pass pass

View File

@ -3,11 +3,12 @@ import socket
from django.contrib.contenttypes import generic from django.contrib.contenttypes import generic
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.db import models from django.db import models
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.validators import validate_ip_address, ValidationError from orchestra.core.validators import validate_ip_address, ValidationError
from orchestra.models.fields import NullableCharField from orchestra.models.fields import NullableCharField
from orchestra.utils.apps import autodiscover #from orchestra.utils.apps import autodiscover
from . import settings, manager from . import settings, manager
from .backends import ServiceBackend from .backends import ServiceBackend
@ -133,7 +134,7 @@ class BackendOperation(models.Model):
return ServiceBackend.get_backend(self.backend) return ServiceBackend.get_backend(self.backend)
autodiscover('backends') autodiscover_modules('backends')
class Route(models.Model): class Route(models.Model):

View File

@ -132,23 +132,26 @@ def resource_inline_factory(resources):
def has_add_permission(self, *args, **kwargs): def has_add_permission(self, *args, **kwargs):
""" Hidde add another """ """ Hidde add another """
return False return False
return ResourceInline return ResourceInline
from orchestra.utils import database_ready
def insert_resource_inlines(): def insert_resource_inlines():
# Clean previous state
for related in Resource._related:
modeladmin = get_modeladmin(related)
modeladmin_class = type(modeladmin)
for inline in getattr(modeladmin_class, 'inlines', []):
if inline.__name__ == 'ResourceInline':
modeladmin_class.inlines.remove(inline)
modeladmin.inlines = modeladmin_class.inlines
for ct, resources in Resource.objects.group_by('content_type').iteritems(): for ct, resources in Resource.objects.group_by('content_type').iteritems():
inline = resource_inline_factory(resources) inline = resource_inline_factory(resources)
model = ct.model_class() model = ct.model_class()
modeladmin = get_modeladmin(model) modeladmin = get_modeladmin(model)
inserted = False insertattr(model, 'inlines', inline)
inlines = [] modeladmin.inlines = type(modeladmin).inlines
for existing in getattr(modeladmin, 'inlines', []):
if type(inline) == type(existing): if database_ready():
existing = inline insert_resource_inlines()
inserted = True
inlines.append(existing)
if inserted:
modeladmin.inlines = inlines
else:
insertattr(model, 'inlines', inline)

View File

@ -9,15 +9,13 @@ class ResourcesConfig(AppConfig):
def ready(self): def ready(self):
if database_ready(): if database_ready():
from .admin import insert_resource_inlines
from .models import create_resource_relation from .models import create_resource_relation
create_resource_relation() create_resource_relation()
insert_resource_inlines()
def reload_relations(self): def reload_relations(self):
from .admin import insert_resource_inlines from .admin import insert_resource_inlines
from .models import create_resource_relation from .models import create_resource_relation
from .serializers import insert_resource_serializers from .serializers import insert_resource_serializers
create_resource_relation()
insert_resource_inlines() insert_resource_inlines()
insert_resource_serializers() insert_resource_serializers()
create_resource_relation()

View File

@ -31,6 +31,7 @@ class Resource(models.Model):
(MONTHLY_SUM, _("Monthly Sum")), (MONTHLY_SUM, _("Monthly Sum")),
(MONTHLY_AVG, _("Monthly Average")), (MONTHLY_AVG, _("Monthly Average")),
) )
_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, '
@ -102,6 +103,7 @@ class Resource(models.Model):
task.save(update_fields=['crontab']) task.save(update_fields=['crontab'])
if created: if created:
# This only work on tests because of multiprocessing used on real deployments # This only work on tests because of multiprocessing used on real deployments
print 'saved'
apps.get_app_config('resources').reload_relations() apps.get_app_config('resources').reload_relations()
def delete(self, *args, **kwargs): def delete(self, *args, **kwargs):
@ -192,8 +194,21 @@ def create_resource_relation():
self.obj = obj self.obj = obj
return self return self
# Clean previous state
for related in Resource._related:
try:
delattr(related, 'resource_set')
delattr(related, 'resources')
except AttributeError:
pass
else:
related._meta.virtual_fields = [
field for field in related._meta.virtual_fields if field.rel.to != ResourceData
]
relation = GenericRelation('resources.ResourceData') relation = GenericRelation('resources.ResourceData')
for ct, resources in Resource.objects.group_by('content_type').iteritems(): for ct, resources in Resource.objects.group_by('content_type').iteritems():
model = ct.model_class() model = ct.model_class()
model.add_to_class('resource_set', relation) model.add_to_class('resource_set', relation)
model.resources = ResourceHandler() model.resources = ResourceHandler()
Resource._related.add(model)

View File

@ -30,6 +30,15 @@ class ResourceSerializer(serializers.ModelSerializer):
# Monkey-patching section # Monkey-patching section
def insert_resource_serializers(): def insert_resource_serializers():
# clean previous state
for related in Resource._related:
viewset = router.get_viewset(related)
fields = list(viewset.serializer_class.Meta.fields)
try:
fields.remove('resources')
except ValueError:
pass
viewset.serializer_class.Meta.fields = fields
# Create nested serializers on target models # Create nested serializers on target models
for ct, resources in Resource.objects.group_by('content_type').iteritems(): for ct, resources in Resource.objects.group_by('content_type').iteritems():
model = ct.model_class() model = ct.model_class()

View File

@ -6,11 +6,12 @@ from django.db.models.loading import get_model
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.core.validators import ValidationError from django.core.validators import ValidationError
from django.utils.functional import cached_property from django.utils.functional import cached_property
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.models import queryset from orchestra.models import queryset
from orchestra.utils.apps import autodiscover #from orchestra.utils.apps import autodiscover
from . import settings, rating from . import settings, rating
from .handlers import ServiceHandler from .handlers import ServiceHandler
@ -70,7 +71,7 @@ class Rate(models.Model):
return "{}-{}".format(str(self.price), self.quantity) return "{}-{}".format(str(self.price), self.quantity)
autodiscover('handlers') autodiscover_modules('handlers')
class Service(models.Model): class Service(models.Model):

View File

@ -37,7 +37,7 @@ class SystemUserBackend(ServiceController):
self.append("groupdel %(username)s || true" % context) self.append("groupdel %(username)s || true" % context)
if user.is_main: if user.is_main:
# TODO delete instead of this shit # TODO delete instead of this shit
context['deleted'] = context['home'][:-1]+'.deleted' context['deleted'] = context['home'].rstrip('/') + '.deleted'
self.append("mv %(home)s %(deleted)s" % context) self.append("mv %(home)s %(deleted)s" % context)
def get_groups(self, user): def get_groups(self, user):

View File

@ -48,7 +48,6 @@ class SystemUser(models.Model):
@cached_property @cached_property
def active(self): def active(self):
a = type(self).account.field.model
try: try:
return self.is_active and self.account.is_active return self.is_active and self.account.is_active
except type(self).account.field.rel.to.DoesNotExist: except type(self).account.field.rel.to.DoesNotExist:

View File

@ -9,11 +9,20 @@ from orchestra.core.validators import validate_password
from .models import SystemUser from .models import SystemUser
class GroupSerializer(serializers.ModelSerializer):
class Meta:
model = SystemUser
fields = ('username',)
def from_native(self, data, files=None):
return SystemUser.objects.get(username=data['username'])
class SystemUserSerializer(AccountSerializerMixin, serializers.HyperlinkedModelSerializer): class SystemUserSerializer(AccountSerializerMixin, serializers.HyperlinkedModelSerializer):
password = serializers.CharField(max_length=128, label=_('Password'), password = serializers.CharField(max_length=128, label=_('Password'),
validators=[validate_password], write_only=True, required=False, validators=[validate_password], write_only=True, required=False,
widget=widgets.PasswordInput) widget=widgets.PasswordInput)
groups = serializers.RelatedField(many=True) groups = GroupSerializer(many=True, allow_add_remove=True, required=False)
class Meta: class Meta:
model = SystemUser model = SystemUser
@ -30,6 +39,8 @@ class SystemUserSerializer(AccountSerializerMixin, serializers.HyperlinkedModelS
raise serializers.ValidationError(_("Password required")) raise serializers.ValidationError(_("Password required"))
return attrs return attrs
# TODO validate gruops != self
def save_object(self, obj, **kwargs): def save_object(self, obj, **kwargs):
# FIXME this method will be called when saving nested serializers :( # FIXME this method will be called when saving nested serializers :(
if not obj.pk: if not obj.pk:

View File

@ -13,7 +13,8 @@ from selenium.webdriver.support.select import Select
from orchestra.apps.accounts.models import Account 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.utils.system import run, sshrun from orchestra.utils.system import run, sshrun
from orchestra.utils.tests import BaseLiveServerTestCase, random_ascii, snapshot_on_error from orchestra.utils.tests import (BaseLiveServerTestCase, random_ascii, snapshot_on_error,
save_response_on_error)
from ... import backends, settings from ... import backends, settings
from ...models import SystemUser from ...models import SystemUser
@ -71,13 +72,14 @@ class SystemUserMixin(object):
def validate_delete(self, username): def validate_delete(self, username):
self.assertRaises(SystemUser.DoesNotExist, SystemUser.objects.get, username=username) self.assertRaises(SystemUser.DoesNotExist, SystemUser.objects.get, username=username)
self.assertRaises(CommandError, self.assertRaises(CommandError,
sshrun, self.MASTER_SERVER,'id %s' % username, display=False) sshrun, self.MASTER_SERVER, 'id %s' % username, display=False)
self.assertRaises(CommandError, self.assertRaises(CommandError,
sshrun, self.MASTER_SERVER, 'grep "^%s:" /etc/groups' % username, display=False) sshrun, self.MASTER_SERVER, 'grep "^%s:" /etc/groups' % username, display=False)
self.assertRaises(CommandError, self.assertRaises(CommandError,
sshrun, self.MASTER_SERVER, 'grep "^%s:" /etc/passwd' % username, display=False) sshrun, self.MASTER_SERVER, 'grep "^%s:" /etc/passwd' % username, display=False)
self.assertRaises(CommandError, self.assertRaises(CommandError,
sshrun, self.MASTER_SERVER, 'grep "^%s:" /etc/shadow' % username, display=False) sshrun, self.MASTER_SERVER, 'grep "^%s:" /etc/shadow' % username, display=False)
# Home will be deleted on account delete, see test_delete_account
def validate_ftp(self, username, password): def validate_ftp(self, username, password):
connection = ftplib.FTP(self.MASTER_SERVER) connection = ftplib.FTP(self.MASTER_SERVER)
@ -105,22 +107,24 @@ class SystemUserMixin(object):
username = '%s_systemuser' % random_ascii(10) username = '%s_systemuser' % random_ascii(10)
password = '@!?%spppP001' % random_ascii(5) password = '@!?%spppP001' % random_ascii(5)
self.add(username, password) self.add(username, password)
self.addCleanup(partial(self.delete, username)) self.addCleanup(self.delete, username)
self.validate_user(username) self.validate_user(username)
def test_ftp(self): def test_ftp(self):
username = '%s_systemuser' % random_ascii(10) username = '%s_systemuser' % random_ascii(10)
password = '@!?%spppP001' % random_ascii(5) password = '@!?%spppP001' % random_ascii(5)
self.add(username, password, shell='/dev/null') self.add(username, password, shell='/dev/null')
self.addCleanup(partial(self.delete, username)) self.addCleanup(self.delete, username)
self.assertRaises(paramiko.AuthenticationException, self.validate_sftp, username, password) self.assertRaises(paramiko.AuthenticationException,
self.assertRaises(paramiko.AuthenticationException, self.validate_ssh, username, password) self.validate_sftp, username, password)
self.assertRaises(paramiko.AuthenticationException,
self.validate_ssh, username, password)
def test_sftp(self): def test_sftp(self):
username = '%s_systemuser' % random_ascii(10) username = '%s_systemuser' % random_ascii(10)
password = '@!?%spppP001' % random_ascii(5) password = '@!?%spppP001' % random_ascii(5)
self.add(username, password, shell='/bin/rssh') self.add(username, password, shell='/bin/rssh')
self.addCleanup(partial(self.delete, username)) self.addCleanup(self.delete, username)
self.validate_sftp(username, password) self.validate_sftp(username, password)
self.assertRaises(AssertionError, self.validate_ssh, username, password) self.assertRaises(AssertionError, self.validate_ssh, username, password)
@ -128,7 +132,7 @@ class SystemUserMixin(object):
username = '%s_systemuser' % random_ascii(10) username = '%s_systemuser' % random_ascii(10)
password = '@!?%spppP001' % random_ascii(5) password = '@!?%spppP001' % random_ascii(5)
self.add(username, password, shell='/bin/bash') self.add(username, password, shell='/bin/bash')
self.addCleanup(partial(self.delete, username)) self.addCleanup(self.delete, username)
self.validate_ssh(username, password) self.validate_ssh(username, password)
def test_delete(self): def test_delete(self):
@ -143,12 +147,12 @@ class SystemUserMixin(object):
username = '%s_systemuser' % random_ascii(10) username = '%s_systemuser' % random_ascii(10)
password = '@!?%spppP001' % random_ascii(5) password = '@!?%spppP001' % random_ascii(5)
self.add(username, password) self.add(username, password)
self.addCleanup(partial(self.delete, username)) self.addCleanup(self.delete, username)
self.validate_user(username) self.validate_user(username)
username2 = '%s_systemuser' % random_ascii(10) username2 = '%s_systemuser' % random_ascii(10)
password2 = '@!?%spppP001' % random_ascii(5) password2 = '@!?%spppP001' % random_ascii(5)
self.add(username2, password2) self.add(username2, password2)
self.addCleanup(partial(self.delete, username2)) self.addCleanup(self.delete, username2)
self.validate_user(username2) self.validate_user(username2)
self.add_group(username, username2) self.add_group(username, username2)
user = SystemUser.objects.get(username=username) user = SystemUser.objects.get(username=username)
@ -160,7 +164,7 @@ class SystemUserMixin(object):
username = '%s_systemuser' % random_ascii(10) username = '%s_systemuser' % random_ascii(10)
password = '@!?%spppP001' % random_ascii(5) password = '@!?%spppP001' % random_ascii(5)
self.add(username, password, shell='/dev/null') self.add(username, password, shell='/dev/null')
self.addCleanup(partial(self.delete, username)) self.addCleanup(self.delete, username)
self.validate_ftp(username, password) self.validate_ftp(username, password)
self.disable(username) self.disable(username)
self.validate_user(username) self.validate_user(username)
@ -170,7 +174,7 @@ class SystemUserMixin(object):
username = '%s_systemuser' % random_ascii(10) username = '%s_systemuser' % random_ascii(10)
password = '@!?%spppP001' % random_ascii(5) password = '@!?%spppP001' % random_ascii(5)
self.add(username, password) self.add(username, password)
self.addCleanup(partial(self.delete, username)) self.addCleanup(self.delete, username)
self.validate_ftp(username, password) self.validate_ftp(username, password)
new_password = '@!?%spppP001' % random_ascii(5) new_password = '@!?%spppP001' % random_ascii(5)
self.change_password(username, new_password) self.change_password(username, new_password)
@ -185,33 +189,38 @@ 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(partial(self.delete, self.account.username)) self.addCleanup(self.delete, self.account.username)
@save_response_on_error
def add(self, username, password, shell='/dev/null'): def add(self, username, password, shell='/dev/null'):
self.rest.systemusers.create(username=username, password=password, shell=shell) self.rest.systemusers.create(username=username, password=password, shell=shell)
@save_response_on_error
def delete(self, username): def delete(self, username):
user = self.rest.systemusers.retrieve(username=username).get() user = self.rest.systemusers.retrieve(username=username).get()
user.delete() user.delete()
@save_response_on_error
def add_group(self, username, groupname): def add_group(self, username, groupname):
user = self.rest.systemusers.retrieve(username=username).get() user = self.rest.systemusers.retrieve(username=username).get()
group = self.rest.systemusers.retrieve(username=groupname).get() user.groups.append({'username': groupname})
user.groups.append(group) # TODO
user.save() user.save()
@save_response_on_error
def disable(self, username): def disable(self, username):
user = self.rest.systemusers.retrieve(username=username).get() user = self.rest.systemusers.retrieve(username=username).get()
user.is_active = False user.is_active = False
user.save() user.save()
@save_response_on_error
def save(self, username): def save(self, username):
user = self.rest.systemusers.retrieve(username=username).get() user = self.rest.systemusers.retrieve(username=username).get()
user.save() user.save()
@save_response_on_error
def change_password(self, username, password): def change_password(self, username, password):
user = self.rest.systemusers.retrieve(username=username).get() user = self.rest.systemusers.retrieve(username=username).get()
user.change_password(password) user.set_password(password)
class AdminSystemUserMixin(SystemUserMixin): class AdminSystemUserMixin(SystemUserMixin):
@ -220,7 +229,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(partial(self.delete, self.account.username)) self.addCleanup(self.delete, 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'):
@ -249,24 +258,12 @@ class AdminSystemUserMixin(SystemUserMixin):
@snapshot_on_error @snapshot_on_error
def delete(self, username): def delete(self, username):
user = SystemUser.objects.get(username=username) user = SystemUser.objects.get(username=username)
delete = reverse('admin:systemusers_systemuser_delete', args=(user.pk,)) self.admin_delete(user)
url = self.live_server_url + delete
self.selenium.get(url)
confirmation = self.selenium.find_element_by_name('post')
confirmation.submit()
self.assertNotEqual(url, self.selenium.current_url)
@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)
change = reverse('admin:systemusers_systemuser_change', args=(user.pk,)) self.admin_disable(user)
url = self.live_server_url + change
self.selenium.get(url)
is_active = self.selenium.find_element_by_id('id_is_active')
is_active.click()
save = self.selenium.find_element_by_name('_save')
save.submit()
self.assertNotEqual(url, self.selenium.current_url)
@snapshot_on_error @snapshot_on_error
def add_group(self, username, groupname): def add_group(self, username, groupname):
@ -294,17 +291,8 @@ class AdminSystemUserMixin(SystemUserMixin):
@snapshot_on_error @snapshot_on_error
def change_password(self, username, password): def change_password(self, username, password):
user = SystemUser.objects.get(username=username) user = SystemUser.objects.get(username=username)
change_password = reverse('admin:systemusers_systemuser_change_password', args=(user.pk,)) self.admin_change_password(user, password)
url = self.live_server_url + change_password
self.selenium.get(url)
password_field = self.selenium.find_element_by_id('id_password1')
password_field.send_keys(password)
password_field = self.selenium.find_element_by_id('id_password2')
password_field.send_keys(password)
password_field.submit()
self.assertNotEqual(url, self.selenium.current_url)
class RESTSystemUserTest(RESTSystemUserMixin, BaseLiveServerTestCase): class RESTSystemUserTest(RESTSystemUserMixin, BaseLiveServerTestCase):
pass pass
@ -337,10 +325,10 @@ class AdminSystemUserTest(AdminSystemUserMixin, BaseLiveServerTestCase):
email = self.selenium.find_element_by_id('id_contacts-0-email') email = self.selenium.find_element_by_id('id_contacts-0-email')
email.send_keys(account_email) email.send_keys(account_email)
email.submit() email.submit()
self.assertNotEqual(url, self.selenium.current_url)
account = Account.objects.get(username=account_username) account = Account.objects.get(username=account_username)
self.addCleanup(account.delete) self.addCleanup(self.delete, account_username)
self.assertNotEqual(url, self.selenium.current_url)
self.assertEqual(0, sshr(self.MASTER_SERVER, "id %s" % account.username).return_code) self.assertEqual(0, sshr(self.MASTER_SERVER, "id %s" % account.username).return_code)
@snapshot_on_error @snapshot_on_error

View File

@ -130,7 +130,8 @@ function install_requirements () {
libxml2-dev \ libxml2-dev \
libxslt1-dev \ libxslt1-dev \
wkhtmltopdf \ wkhtmltopdf \
xvfb" xvfb \
python-mysqldb"
PIP="django==1.7 \ PIP="django==1.7 \
django-celery-email==1.0.4 \ django-celery-email==1.0.4 \

View File

@ -107,7 +107,7 @@ INSTALLED_APPS = (
'django.contrib.sessions', 'django.contrib.sessions',
'django.contrib.messages', 'django.contrib.messages',
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'django.contrib.admin', 'django.contrib.admin.apps.SimpleAdminConfig',
'orchestra.apps.accounts', 'orchestra.apps.accounts',
'orchestra.apps.contacts', 'orchestra.apps.contacts',

View File

@ -25,9 +25,9 @@ urlpatterns = patterns('',
url(r'^media/(?P<path>.*)$', 'django.views.static.serve', url(r'^media/(?P<path>.*)$', 'django.views.static.serve',
{'document_root': settings.MEDIA_ROOT, 'show_indexes': True} {'document_root': settings.MEDIA_ROOT, 'show_indexes': True}
) )
) )
if settings.DEBUG: if settings.DEBUG:
import debug_toolbar import debug_toolbar
urlpatterns += patterns('', urlpatterns += patterns('',

View File

@ -1,20 +1,21 @@
from django.utils.importlib import import_module #from django.utils.importlib import import_module
from django.utils.module_loading import module_has_submodule #from django.utils.module_loading import module_has_submodule
def autodiscover(module):
""" Auto-discover INSTALLED_APPS module.py """ #def autodiscover(module):
from django.conf import settings # """ Auto-discover INSTALLED_APPS module.py """
for app in settings.INSTALLED_APPS: # from django.conf import settings
mod = import_module(app) # for app in settings.INSTALLED_APPS:
try: # mod = import_module(app)
import_module('%s.%s' % (app, module)) # try:
except ImportError: # import_module('%s.%s' % (app, module))
# Decide whether to bubble up this error. If the app just # except ImportError:
# doesn't have the module, we can ignore the error # # Decide whether to bubble up this error. If the app just
# attempting to import it, otherwise we want it to bubble up. # # doesn't have the module, we can ignore the error
if module_has_submodule(mod, module): # # attempting to import it, otherwise we want it to bubble up.
print '%s module caused this error:' % module # if module_has_submodule(mod, module):
raise # print '%s module caused this error:' % module
# raise
def isinstalled(app): def isinstalled(app):
""" returns True if app is installed """ """ returns True if app is installed """

View File

@ -7,6 +7,7 @@ from functools import wraps
from django.conf import settings from django.conf import settings
from django.contrib.auth import BACKEND_SESSION_KEY, SESSION_KEY, get_user_model from django.contrib.auth import BACKEND_SESSION_KEY, SESSION_KEY, get_user_model
from django.contrib.sessions.backends.db import SessionStore from django.contrib.sessions.backends.db import SessionStore
from django.core.urlresolvers import reverse
from django.test import LiveServerTestCase, TestCase from django.test import LiveServerTestCase, TestCase
from orm.api import Api from orm.api import Api
from selenium.webdriver.firefox.webdriver import WebDriver from selenium.webdriver.firefox.webdriver import WebDriver
@ -115,7 +116,43 @@ class BaseLiveServerTestCase(AppDependencyMixin, LiveServerTestCase):
filename = 'screenshot_%s_%s.png' % (self.id(), timestamp) filename = 'screenshot_%s_%s.png' % (self.id(), timestamp)
path = '/home/orchestra/snapshots' path = '/home/orchestra/snapshots'
self.selenium.save_screenshot(os.path.join(path, filename)) self.selenium.save_screenshot(os.path.join(path, filename))
def admin_delete(self, obj):
opts = obj._meta
app_label, model_name = opts.app_label, opts.model_name
delete = reverse('admin:%s_%s_delete' % (app_label, model_name), args=(obj.pk,))
url = self.live_server_url + delete
self.selenium.get(url)
confirmation = self.selenium.find_element_by_name('post')
confirmation.submit()
self.assertNotEqual(url, self.selenium.current_url)
def admin_disable(self, obj):
opts = obj._meta
app_label, model_name = opts.app_label, opts.model_name
change = reverse('admin:%s_%s_change' % (app_label, model_name), args=(obj.pk,))
url = self.live_server_url + change
self.selenium.get(url)
is_active = self.selenium.find_element_by_id('id_is_active')
is_active.click()
save = self.selenium.find_element_by_name('_save')
save.submit()
self.assertNotEqual(url, self.selenium.current_url)
def admin_change_password(self, obj, password):
opts = obj._meta
app_label, model_name = opts.app_label, opts.model_name
change_password = reverse('admin:%s_%s_change_password' % (app_label, model_name), args=(obj.pk,))
url = self.live_server_url + change_password
self.selenium.get(url)
password_field = self.selenium.find_element_by_id('id_password1')
password_field.send_keys(password)
password_field = self.selenium.find_element_by_id('id_password2')
password_field.send_keys(password)
password_field.submit()
self.assertNotEqual(url, self.selenium.current_url)
def snapshot_on_error(test): def snapshot_on_error(test):
@wraps(test) @wraps(test)

View File

@ -2,3 +2,5 @@ MySQL
===== =====
apt-get install mysql-server apt-get install mysql-server
sed -i "s/bind-address = 127.0.0.1/bind-address = 0.0.0.0/" /etc/mysql/my.cnf