Commit 8a3dbb79 by Kálmán Viktor

Merge branch 'master' into feature-request

parents f157eb56 3159f5f9
# Copyright 2014 Budapest University of Technology and Economics (BME IK)
#
# This file is part of CIRCLE Cloud.
#
# CIRCLE is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# CIRCLE is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
import os# noqa
from .base import * # noqa
# flake8: noqa
os.environ['REUSE_DB'] = "1"
os.environ['DJANGO_TEST_DB_NAME'] = "circle"
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.' +
get_env_variable('DJANG_DB_TYPE', 'postgresql_psycopg2'),
'NAME': get_env_variable('DJANGO_DB_NAME', 'circle'),
'TEST_NAME': get_env_variable('DJANGO_TEST_DB_NAME', 'circle'),
'USER': get_env_variable('DJANGO_DB_USER', 'circle'),
'PASSWORD': get_env_variable('DJANGO_DB_PASSWORD'),
'HOST': get_env_variable('DJANGO_DB_HOST', ''),
'PORT': get_env_variable('DJANGO_DB_PORT', ''),
}
}
SOUTH_TESTS_MIGRATE = False
INSTALLED_APPS += (
'acl.tests',
'django_nose',
'django_jenkins',
)
TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'
path_to_selenium_test = os.path.expanduser('~/circle/circle/dashboard/tests/selenium')
NOSE_ARGS = ['--stop', '--with-doctest', '--with-selenium-driver', '--selenium-driver=firefox', '-w%s' % path_to_selenium_test]
PASSWORD_HASHERS = ['django.contrib.auth.hashers.MD5PasswordHasher']
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.dummy.DummyCache'
}
}
LOGGING['loggers']['djangosaml2'] = {'handlers': ['console'],
'level': 'CRITICAL'}
level = environ.get('LOGLEVEL', 'CRITICAL')
LOGGING['handlers']['console'] = {'level': level,
'class': 'logging.StreamHandler',
'formatter': 'simple'}
for i in LOCAL_APPS:
LOGGING['loggers'][i] = {'handlers': ['console'], 'level': level}
...@@ -38,7 +38,7 @@ INSTALLED_APPS += ( ...@@ -38,7 +38,7 @@ INSTALLED_APPS += (
'django_nose', 'django_nose',
) )
TEST_RUNNER = 'django_nose.NoseTestSuiteRunner' TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'
NOSE_ARGS = ['--with-doctest'] NOSE_ARGS = ['--with-doctest', '--exclude-dir=dashboard/tests/selenium']
PASSWORD_HASHERS = ['django.contrib.auth.hashers.MD5PasswordHasher'] PASSWORD_HASHERS = ['django.contrib.auth.hashers.MD5PasswordHasher']
CACHES = { CACHES = {
......
...@@ -69,7 +69,8 @@ def activitycontextimpl(act, on_abort=None, on_commit=None): ...@@ -69,7 +69,8 @@ def activitycontextimpl(act, on_abort=None, on_commit=None):
# system-exiting exceptions, e.g. KeyboardInterrupt # system-exiting exceptions, e.g. KeyboardInterrupt
result = create_readable( result = create_readable(
ugettext_noop("Failure."), ugettext_noop("Failure."),
ugettext_noop("Unhandled exception: %(error)s"), ugettext_noop("Unhandled exception: %(e)s: %(error)s"),
e=str(e.__class__.__name__),
error=get_error_msg(e)) error=get_error_msg(e))
raise raise
except: except:
......
# Copyright 2014 Budapest University of Technology and Economics (BME IK)
#
# This file is part of CIRCLE Cloud.
#
# CIRCLE is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# CIRCLE is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
from mock import patch
class MockCeleryMixin(object):
def _pre_setup(self):
self.reloadtask_patcher = patch(
'firewall.tasks.local_tasks.reloadtask.apply_async', spec=True)
self.reloadtask_patcher.start()
self.kombu_patcher = patch('kombu.connection.Connection.ensure',
side_effect=RuntimeError())
self.kombu_patcher.start()
self.check_queue_patcher = patch('vm.tasks.vm_tasks.check_queue',
return_value=True)
self.check_queue_patcher.start()
super(MockCeleryMixin, self)._pre_setup()
def _post_teardown(self):
self.reloadtask_patcher.stop()
self.kombu_patcher.stop()
super(MockCeleryMixin, self)._post_teardown()
...@@ -20,12 +20,13 @@ from collections import deque ...@@ -20,12 +20,13 @@ from collections import deque
from django.test import TestCase from django.test import TestCase
from mock import MagicMock from mock import MagicMock
from .celery_mock import MockCeleryMixin
from .models import TestClass from .models import TestClass
from ..models import HumanSortField from ..models import HumanSortField
from ..models import activitycontextimpl from ..models import activitycontextimpl
class MethodCacheTestCase(TestCase): class MethodCacheTestCase(MockCeleryMixin, TestCase):
def test_cache(self): def test_cache(self):
t1 = TestClass(1) t1 = TestClass(1)
t2 = TestClass(2) t2 = TestClass(2)
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
from __future__ import unicode_literals, absolute_import from __future__ import unicode_literals, absolute_import
import logging
from optparse import make_option from optparse import make_option
from django.contrib.auth.models import User from django.contrib.auth.models import User
...@@ -28,6 +29,9 @@ from storage.models import DataStore ...@@ -28,6 +29,9 @@ from storage.models import DataStore
from vm.models import Lease from vm.models import Lease
logger = logging.getLogger(__name__)
class Command(BaseCommand): class Command(BaseCommand):
option_list = BaseCommand.option_list + ( option_list = BaseCommand.option_list + (
make_option('--force', action="store_true"), make_option('--force', action="store_true"),
...@@ -49,18 +53,18 @@ class Command(BaseCommand): ...@@ -49,18 +53,18 @@ class Command(BaseCommand):
qs = model.objects.filter(**{field: value})[:1] qs = model.objects.filter(**{field: value})[:1]
if not qs.exists(): if not qs.exists():
obj = model.objects.create(**kwargs) obj = model.objects.create(**kwargs)
self.changed.append('New %s: %s' % (model, obj)) logger.info('New %s: %s', model, obj)
self.changed = True
return obj return obj
else: else:
return qs[0] return qs[0]
# http://docs.saltstack.com/en/latest/ref/states/all/salt.states.cmd.html # http://docs.saltstack.com/en/latest/ref/states/all/salt.states.cmd.html
def print_state(self): def print_state(self):
changed = "yes" if len(self.changed) else "no" print "\nchanged=%s" % ("yes" if self.changed else "no")
print "\nchanged=%s comment='%s'" % (changed, ", ".join(self.changed))
def handle(self, *args, **options): def handle(self, *args, **options):
self.changed = [] self.changed = False
if (DataStore.objects.exists() and Vlan.objects.exists() if (DataStore.objects.exists() and Vlan.objects.exists()
and not options['force']): and not options['force']):
......
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('dashboard', '0001_initial'),
]
operations = [
migrations.AlterModelOptions(
name='connectcommand',
options={'ordering': ('id',)},
),
migrations.AlterModelOptions(
name='futuremember',
options={'ordering': ('id',)},
),
migrations.AlterModelOptions(
name='groupprofile',
options={'ordering': ('id',)},
),
migrations.AlterModelOptions(
name='profile',
options={'ordering': ('id',), 'permissions': (('use_autocomplete', 'Can use autocomplete.'),)},
),
]
...@@ -128,6 +128,9 @@ class ConnectCommand(Model): ...@@ -128,6 +128,9 @@ class ConnectCommand(Model):
'host, port.'), 'host, port.'),
validators=[connect_command_template_validator]) validators=[connect_command_template_validator])
class Meta:
ordering = ('id', )
def __unicode__(self): def __unicode__(self):
return self.template return self.template
...@@ -218,6 +221,7 @@ class Profile(Model): ...@@ -218,6 +221,7 @@ class Profile(Model):
super(Profile, self).save(*args, **kwargs) super(Profile, self).save(*args, **kwargs)
class Meta: class Meta:
ordering = ('id', )
permissions = ( permissions = (
('use_autocomplete', _('Can use autocomplete.')), ('use_autocomplete', _('Can use autocomplete.')),
) )
...@@ -229,6 +233,7 @@ class FutureMember(Model): ...@@ -229,6 +233,7 @@ class FutureMember(Model):
group = ForeignKey(Group) group = ForeignKey(Group)
class Meta: class Meta:
ordering = ('id', )
unique_together = ('org_id', 'group') unique_together = ('org_id', 'group')
def __unicode__(self): def __unicode__(self):
...@@ -247,6 +252,9 @@ class GroupProfile(AclBase): ...@@ -247,6 +252,9 @@ class GroupProfile(AclBase):
help_text=_('Unique identifier of the group at the organization.')) help_text=_('Unique identifier of the group at the organization.'))
description = TextField(blank=True) description = TextField(blank=True)
class Meta:
ordering = ('id', )
def __unicode__(self): def __unicode__(self):
return self.group.name return self.group.name
......
...@@ -149,6 +149,7 @@ class GroupListTable(Table): ...@@ -149,6 +149,7 @@ class GroupListTable(Table):
attrs = {'class': ('table table-bordered table-striped table-hover ' attrs = {'class': ('table table-bordered table-striped table-hover '
'group-list-table')} 'group-list-table')}
fields = ('pk', 'name', ) fields = ('pk', 'name', )
order_by = ('pk', )
class UserListTable(Table): class UserListTable(Table):
...@@ -176,6 +177,7 @@ class UserListTable(Table): ...@@ -176,6 +177,7 @@ class UserListTable(Table):
attrs = {'class': ('table table-bordered table-striped table-hover')} attrs = {'class': ('table table-bordered table-striped table-hover')}
fields = ('username', 'last_name', 'first_name', 'profile__org_id', fields = ('username', 'last_name', 'first_name', 'profile__org_id',
'email', 'is_active', 'is_superuser') 'email', 'is_active', 'is_superuser')
order_by = ('username', )
class TemplateListTable(Table): class TemplateListTable(Table):
......
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
{% csrf_token %} {% csrf_token %}
<a class="btn btn-default" href="{% url "dashboard.views.detail" pk=instance.pk %}" data-dismiss="modal">{% trans "Cancel" %}</a> <a class="btn btn-default" href="{% url "dashboard.views.detail" pk=instance.pk %}" data-dismiss="modal">{% trans "Cancel" %}</a>
<a class="btn btn-info" href="{{ client_download_url }}" traget="_blank">{% trans "Download the Client" %}</a> <a class="btn btn-info" href="{{ client_download_url }}" traget="_blank">{% trans "Download the Client" %}</a>
<button data-dismiss="modal" id="client-check-button" type="submit" class="btn btn-success" title="{% trans "I downloaded and installed the client and I want to connect using it. This choice will be saved to your compuer" %}"> <button data-dismiss="modal" id="client-check-button" type="submit" class="btn btn-success modal-accept" title="{% trans "I downloaded and installed the client and I want to connect using it. This choice will be saved to your compuer" %}">
<i class="fa fa-external-link"></i> {% trans "I have the Client installed" %} <i class="fa fa-external-link"></i> {% trans "I have the Client installed" %}
</button> </button>
<input id="connect-uri" name="connect-uri" type="hidden" value="{% if instance.get_connect_uri %}{{ instance.get_connect_uri}}{% endif %}" /> <input id="connect-uri" name="connect-uri" type="hidden" value="{% if instance.get_connect_uri %}{{ instance.get_connect_uri}}{% endif %}" />
......
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
{% csrf_token %} {% csrf_token %}
<button type="button" class="btn btn-default" data-dismiss="modal">{% trans "Cancel" %}</button> <button type="button" class="btn btn-default" data-dismiss="modal">{% trans "Cancel" %}</button>
<input type="hidden" name="next" value="{{ request.GET.next }}"/> <input type="hidden" name="next" value="{{ request.GET.next }}"/>
<button class="btn btn-danger" <button class="btn btn-danger modal-accept"
{% if disable_submit %}disabled{% endif %} {% if disable_submit %}disabled{% endif %}
>{% trans "Delete" %}</button> >{% trans "Delete" %}</button>
</form> </form>
......
...@@ -31,7 +31,7 @@ Do you want to perform the <strong>{{op}}</strong> operation on the following {{ ...@@ -31,7 +31,7 @@ Do you want to perform the <strong>{{op}}</strong> operation on the following {{
<div class="pull-right"> <div class="pull-right">
<a class="btn btn-default" href="{% url "dashboard.views.vm-list" %}" <a class="btn btn-default" href="{% url "dashboard.views.vm-list" %}"
data-dismiss="modal">{% trans "Cancel" %}</a> data-dismiss="modal">{% trans "Cancel" %}</a>
<button class="btn btn-{{ opview.effect }}" type="submit" id="mass-op-form-send"> <button class="btn btn-{{ opview.effect }} modal-accept" type="submit" id="mass-op-form-send">
{% if opview.icon %}<i class="fa fa-fw fa-{{opview.icon}}"></i> {% endif %}{{ opview.name|capfirst }} {% if opview.icon %}<i class="fa fa-fw fa-{{opview.icon}}"></i> {% endif %}{{ opview.name|capfirst }}
</button> </button>
</div> </div>
......
...@@ -39,3 +39,15 @@ ...@@ -39,3 +39,15 @@
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
{% if show_show_all %}
<div id="show-all-activities-container">
<a id="show-all-activities" href="#">
{% if activities|length > 10 %}
{% trans "Show less activities" %} <i class="fa fa-angle-double-up"></i>
{% else %}
{% trans "Show all activities" %} <i class="fa fa-angle-double-down"></i>
{% endif %}
</a>
</div>
{% endif %}
...@@ -20,7 +20,7 @@ Do you want to perform the following operation on ...@@ -20,7 +20,7 @@ Do you want to perform the following operation on
<div class="pull-right"> <div class="pull-right">
<a class="btn btn-default" href="{{object.get_absolute_url}}" <a class="btn btn-default" href="{{object.get_absolute_url}}"
data-dismiss="modal">{% trans "Cancel" %}</a> data-dismiss="modal">{% trans "Cancel" %}</a>
<button class="btn btn-{{ opview.effect }} btn-op-form-send" type="submit" id="op-form-send"> <button class="btn btn-{{ opview.effect }} btn-op-form-send modal-accept" type="submit" id="op-form-send">
{% if opview.icon %}<i class="fa fa-fw fa-{{opview.icon}}"></i> {% endif %}{{ op.name|capfirst }} {% if opview.icon %}<i class="fa fa-fw fa-{{opview.icon}}"></i> {% endif %}{{ op.name|capfirst }}
</button> </button>
</div> </div>
......
...@@ -89,10 +89,10 @@ ...@@ -89,10 +89,10 @@
<div class="input-group"> <div class="input-group">
<input type="text" name="s" class="form-control" <input type="text" name="s" class="form-control"
value="{{ request.GET.s }}" placeholder="{% trans "Search..." %}"/> value="{{ request.GET.s }}" placeholder="{% trans "Search..." %}"/>
<input type="hidden" name="filter" value="{{ request.GET.filter }}"/>
<span class="input-group-btn"> <span class="input-group-btn">
<button class="btn btn-primary"><i class="fa fa-search"></i></button> <button class="btn btn-primary"><i class="fa fa-search"></i></button>
</span> </span>
<input type="hidden" name="filter" value="{{ request.GET.filter }}"/>
</div> </div>
</form> </form>
</div> </div>
......
...@@ -24,10 +24,12 @@ from django.contrib.auth.models import User, Group ...@@ -24,10 +24,12 @@ from django.contrib.auth.models import User, Group
from django.contrib.auth.models import Permission from django.contrib.auth.models import Permission
from django.contrib.auth import authenticate from django.contrib.auth import authenticate
from common.tests.celery_mock import MockCeleryMixin
from dashboard.views import VmAddInterfaceView from dashboard.views import VmAddInterfaceView
from vm.models import Instance, InstanceTemplate, Lease, Node, Trait from vm.models import Instance, InstanceTemplate, Lease, Node, Trait
from vm.operations import (WakeUpOperation, AddInterfaceOperation, from vm.operations import (WakeUpOperation, AddInterfaceOperation,
AddPortOperation, RemoveInterfaceOperation) AddPortOperation, RemoveInterfaceOperation,
DeployOperation)
from ..models import Profile from ..models import Profile
from firewall.models import Vlan, Host, VlanGroup from firewall.models import Vlan, Host, VlanGroup
from mock import Mock, patch from mock import Mock, patch
...@@ -44,7 +46,7 @@ class LoginMixin(object): ...@@ -44,7 +46,7 @@ class LoginMixin(object):
self.assertNotEqual(response.status_code, 403) self.assertNotEqual(response.status_code, 403)
class VmDetailTest(LoginMixin, TestCase): class VmDetailTest(LoginMixin, MockCeleryMixin, TestCase):
fixtures = ['test-vm-fixture.json', 'node.json'] fixtures = ['test-vm-fixture.json', 'node.json']
def setUp(self): def setUp(self):
...@@ -217,21 +219,25 @@ class VmDetailTest(LoginMixin, TestCase): ...@@ -217,21 +219,25 @@ class VmDetailTest(LoginMixin, TestCase):
self.login(c, 'user1') self.login(c, 'user1')
InstanceTemplate.objects.get(id=1).set_level(self.u1, 'user') InstanceTemplate.objects.get(id=1).set_level(self.u1, 'user')
Vlan.objects.get(id=1).set_level(self.u1, 'user') Vlan.objects.get(id=1).set_level(self.u1, 'user')
response = c.post('/dashboard/vm/create/', with patch.object(DeployOperation, 'async') as async:
{'template': 1, response = c.post('/dashboard/vm/create/',
'system': "bubi", {'template': 1,
'cpu_priority': 1, 'cpu_count': 1, 'system': "bubi",
'ram_size': 1000}) 'cpu_priority': 1, 'cpu_count': 1,
'ram_size': 1000})
assert async.called
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)
def test_use_permitted_template_superuser(self): def test_use_permitted_template_superuser(self):
c = Client() c = Client()
self.login(c, 'superuser') self.login(c, 'superuser')
response = c.post('/dashboard/vm/create/', with patch.object(DeployOperation, 'async') as async:
{'template': 1, response = c.post('/dashboard/vm/create/',
'system': "bubi", {'template': 1,
'cpu_priority': 1, 'cpu_count': 1, 'system': "bubi",
'ram_size': 1000}) 'cpu_priority': 1, 'cpu_count': 1,
'ram_size': 1000})
assert async.called
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)
def test_edit_unpermitted_template(self): def test_edit_unpermitted_template(self):
...@@ -537,15 +543,17 @@ class VmDetailTest(LoginMixin, TestCase): ...@@ -537,15 +543,17 @@ class VmDetailTest(LoginMixin, TestCase):
self.login(c, "superuser") self.login(c, "superuser")
instance_count = Instance.objects.all().count() instance_count = Instance.objects.all().count()
response = c.post("/dashboard/vm/create/", { with patch.object(DeployOperation, 'async') as async:
'name': 'vm', response = c.post("/dashboard/vm/create/", {
'amount': 2, 'name': 'vm',
'customized': 1, 'amount': 2,
'template': 1, 'customized': 1,
'cpu_priority': 10, 'cpu_count': 1, 'ram_size': 128, 'template': 1,
'network': [], 'cpu_priority': 10, 'cpu_count': 1, 'ram_size': 128,
}) 'network': [],
})
assert async.called
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)
self.assertEqual(instance_count + 2, Instance.objects.all().count()) self.assertEqual(instance_count + 2, Instance.objects.all().count())
...@@ -585,7 +593,7 @@ class VmDetailTest(LoginMixin, TestCase): ...@@ -585,7 +593,7 @@ class VmDetailTest(LoginMixin, TestCase):
self.assertEqual(Instance.objects.get(pk=1).description, "naonyo") self.assertEqual(Instance.objects.get(pk=1).description, "naonyo")
class NodeDetailTest(LoginMixin, TestCase): class NodeDetailTest(LoginMixin, MockCeleryMixin, TestCase):
fixtures = ['test-vm-fixture.json', 'node.json'] fixtures = ['test-vm-fixture.json', 'node.json']
def setUp(self): def setUp(self):
...@@ -756,7 +764,7 @@ class NodeDetailTest(LoginMixin, TestCase): ...@@ -756,7 +764,7 @@ class NodeDetailTest(LoginMixin, TestCase):
self.assertEqual(len(Node.objects.get(pk=1).traits.all()), trait_count) self.assertEqual(len(Node.objects.get(pk=1).traits.all()), trait_count)
class GroupCreateTest(LoginMixin, TestCase): class GroupCreateTest(LoginMixin, MockCeleryMixin, TestCase):
fixtures = ['test-vm-fixture.json', 'node.json'] fixtures = ['test-vm-fixture.json', 'node.json']
def setUp(self): def setUp(self):
...@@ -858,7 +866,7 @@ class GroupCreateTest(LoginMixin, TestCase): ...@@ -858,7 +866,7 @@ class GroupCreateTest(LoginMixin, TestCase):
self.assertTrue(newgroup.profile.has_level(self.u0, 'owner')) self.assertTrue(newgroup.profile.has_level(self.u0, 'owner'))
class GroupDeleteTest(LoginMixin, TestCase): class GroupDeleteTest(LoginMixin, MockCeleryMixin, TestCase):
fixtures = ['test-vm-fixture.json', 'node.json'] fixtures = ['test-vm-fixture.json', 'node.json']
def setUp(self): def setUp(self):
...@@ -948,7 +956,7 @@ class GroupDeleteTest(LoginMixin, TestCase): ...@@ -948,7 +956,7 @@ class GroupDeleteTest(LoginMixin, TestCase):
self.assertEqual(Group.objects.count(), groupnum - 1) self.assertEqual(Group.objects.count(), groupnum - 1)
class GroupDetailTest(LoginMixin, TestCase): class GroupDetailTest(LoginMixin, MockCeleryMixin, TestCase):
fixtures = ['test-vm-fixture.json', 'node.json'] fixtures = ['test-vm-fixture.json', 'node.json']
def setUp(self): def setUp(self):
...@@ -1332,7 +1340,7 @@ class GroupDetailTest(LoginMixin, TestCase): ...@@ -1332,7 +1340,7 @@ class GroupDetailTest(LoginMixin, TestCase):
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)
class GroupListTest(LoginMixin, TestCase): class GroupListTest(LoginMixin, MockCeleryMixin, TestCase):
fixtures = ['test-vm-fixture.json', 'node.json'] fixtures = ['test-vm-fixture.json', 'node.json']
def setUp(self): def setUp(self):
...@@ -1375,7 +1383,7 @@ class GroupListTest(LoginMixin, TestCase): ...@@ -1375,7 +1383,7 @@ class GroupListTest(LoginMixin, TestCase):
self.g2.delete() self.g2.delete()
class VmDetailVncTest(LoginMixin, TestCase): class VmDetailVncTest(LoginMixin, MockCeleryMixin, TestCase):
fixtures = ['test-vm-fixture.json', 'node.json'] fixtures = ['test-vm-fixture.json', 'node.json']
def setUp(self): def setUp(self):
...@@ -1408,7 +1416,7 @@ class VmDetailVncTest(LoginMixin, TestCase): ...@@ -1408,7 +1416,7 @@ class VmDetailVncTest(LoginMixin, TestCase):
self.assertEqual(response.status_code, 403) self.assertEqual(response.status_code, 403)
class TransferOwnershipViewTest(LoginMixin, TestCase): class TransferOwnershipViewTest(LoginMixin, MockCeleryMixin, TestCase):
fixtures = ['test-vm-fixture.json'] fixtures = ['test-vm-fixture.json']
def setUp(self): def setUp(self):
...@@ -1446,7 +1454,7 @@ class TransferOwnershipViewTest(LoginMixin, TestCase): ...@@ -1446,7 +1454,7 @@ class TransferOwnershipViewTest(LoginMixin, TestCase):
self.assertEqual(self.u2.notification_set.count(), c2 + 1) self.assertEqual(self.u2.notification_set.count(), c2 + 1)
class IndexViewTest(LoginMixin, TestCase): class IndexViewTest(LoginMixin, MockCeleryMixin, TestCase):
fixtures = ['test-vm-fixture.json', 'node.json'] fixtures = ['test-vm-fixture.json', 'node.json']
def setUp(self): def setUp(self):
...@@ -1548,7 +1556,7 @@ class ProfileViewTest(LoginMixin, TestCase): ...@@ -1548,7 +1556,7 @@ class ProfileViewTest(LoginMixin, TestCase):
self.assertIsNone(authenticate(username="user1", password="asd")) self.assertIsNone(authenticate(username="user1", password="asd"))
class AclViewTest(LoginMixin, TestCase): class AclViewTest(LoginMixin, MockCeleryMixin, TestCase):
fixtures = ['test-vm-fixture.json', 'node.json'] fixtures = ['test-vm-fixture.json', 'node.json']
def setUp(self): def setUp(self):
...@@ -1666,7 +1674,7 @@ class AclViewTest(LoginMixin, TestCase): ...@@ -1666,7 +1674,7 @@ class AclViewTest(LoginMixin, TestCase):
self.assertEqual(resp.status_code, 302) self.assertEqual(resp.status_code, 302)
class VmListTest(LoginMixin, TestCase): class VmListTest(LoginMixin, MockCeleryMixin, TestCase):
fixtures = ['test-vm-fixture.json', 'node.json'] fixtures = ['test-vm-fixture.json', 'node.json']
def setUp(self): def setUp(self):
......
...@@ -102,7 +102,8 @@ class NodeDetailView(LoginRequiredMixin, ...@@ -102,7 +102,8 @@ class NodeDetailView(LoginRequiredMixin,
).order_by('-started').select_related() ).order_by('-started').select_related()
context['ops'] = get_operations(self.object, self.request.user) context['ops'] = get_operations(self.object, self.request.user)
context['op'] = {i.op: i for i in context['ops']} context['op'] = {i.op: i for i in context['ops']}
context['activities'] = na context['show_show_all'] = len(na) > 10
context['activities'] = na[:10]
context['trait_form'] = form context['trait_form'] = form
context['graphite_enabled'] = ( context['graphite_enabled'] = (
settings.GRAPHITE_URL is not None) settings.GRAPHITE_URL is not None)
...@@ -294,15 +295,21 @@ class NodeAddTraitView(SuperuserRequiredMixin, DetailView): ...@@ -294,15 +295,21 @@ class NodeAddTraitView(SuperuserRequiredMixin, DetailView):
class NodeActivityView(LoginRequiredMixin, SuperuserRequiredMixin, View): class NodeActivityView(LoginRequiredMixin, SuperuserRequiredMixin, View):
def get(self, request, pk): def get(self, request, pk):
show_all = request.GET.get("show_all", "false") == "true"
node = Node.objects.get(pk=pk) node = Node.objects.get(pk=pk)
activities = NodeActivity.objects.filter( activities = NodeActivity.objects.filter(
node=node, parent=None).order_by('-started').select_related() node=node, parent=None).order_by('-started').select_related()
show_show_all = len(activities) > 10
if not show_all:
activities = activities[:10]
response = { response = {
'activities': render_to_string( 'activities': render_to_string(
"dashboard/node-detail/_activity-timeline.html", "dashboard/node-detail/_activity-timeline.html",
RequestContext(request, {'activities': activities})) RequestContext(request, {'activities': activities,
'show_show_all': show_show_all}))
} }
return HttpResponse( return HttpResponse(
......
...@@ -135,6 +135,18 @@ def test(test=""): ...@@ -135,6 +135,18 @@ def test(test=""):
run("./manage.py test --settings=circle.settings.test %s" % test) run("./manage.py test --settings=circle.settings.test %s" % test)
@roles('portal')
def selenium(test=""):
"Run selenium tests"
with _workon("circle"), cd("~/circle/circle"):
if test == "f":
test = "--failed"
else:
test += " --with-id"
run("xvfb-run ./manage.py test "
"--settings=circle.settings.selenium_test %s" % test)
def pull(dir="~/circle/circle"): def pull(dir="~/circle/circle"):
"Pull from upstream branch (stash any changes)" "Pull from upstream branch (stash any changes)"
now = unicode(datetime.datetime.now()) now = unicode(datetime.datetime.now())
......