Mailbox tests passing
This commit is contained in:
parent
6240fa3139
commit
9082770642
3
TODO.md
3
TODO.md
|
@ -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=[])
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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'))
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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'
|
||||||
|
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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',)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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',),
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -36,7 +36,10 @@ class Mailbox(models.Model):
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def active(self):
|
def active(self):
|
||||||
|
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:
|
||||||
|
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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
@ -208,6 +171,47 @@ class MailboxMixin(object):
|
||||||
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):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
@ -244,6 +248,20 @@ class RESTMailboxMixin(MailboxMixin):
|
||||||
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):
|
||||||
|
@ -321,6 +328,18 @@ class AdminMailboxMixin(MailboxMixin):
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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
|
|
||||||
inlines = []
|
|
||||||
for existing in getattr(modeladmin, 'inlines', []):
|
|
||||||
if type(inline) == type(existing):
|
|
||||||
existing = inline
|
|
||||||
inserted = True
|
|
||||||
inlines.append(existing)
|
|
||||||
if inserted:
|
|
||||||
modeladmin.inlines = inlines
|
|
||||||
else:
|
|
||||||
insertattr(model, 'inlines', inline)
|
insertattr(model, 'inlines', inline)
|
||||||
|
modeladmin.inlines = type(modeladmin).inlines
|
||||||
|
|
||||||
|
if database_ready():
|
||||||
|
insert_resource_inlines()
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
@ -78,6 +79,7 @@ class SystemUserMixin(object):
|
||||||
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
|
||||||
|
|
|
@ -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 \
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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('',
|
||||||
|
|
|
@ -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 """
|
||||||
|
|
|
@ -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
|
||||||
|
@ -116,6 +117,42 @@ class BaseLiveServerTestCase(AppDependencyMixin, LiveServerTestCase):
|
||||||
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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue