Commit 6b1d46f5 by Kálmán Viktor

Merge branch 'master' into feature-helptexts

parents 09eda579 9e28c78c
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding model 'Level'
db.create_table(u'acl_level', (
(u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('name', self.gf('django.db.models.fields.CharField')(max_length=50)),
('content_type', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['contenttypes.ContentType'])),
('codename', self.gf('django.db.models.fields.CharField')(max_length=100)),
('weight', self.gf('django.db.models.fields.IntegerField')(null=True)),
))
db.send_create_signal(u'acl', ['Level'])
# Adding unique constraint on 'Level', fields ['content_type', 'codename']
db.create_unique(u'acl_level', ['content_type_id', 'codename'])
# Adding model 'ObjectLevel'
db.create_table(u'acl_objectlevel', (
(u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('level', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['acl.Level'])),
('content_type', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['contenttypes.ContentType'])),
('object_id', self.gf('django.db.models.fields.CharField')(max_length=255)),
))
db.send_create_signal(u'acl', ['ObjectLevel'])
# Adding unique constraint on 'ObjectLevel', fields ['content_type', 'object_id', 'level']
db.create_unique(u'acl_objectlevel', ['content_type_id', 'object_id', 'level_id'])
# Adding M2M table for field users on 'ObjectLevel'
m2m_table_name = db.shorten_name(u'acl_objectlevel_users')
db.create_table(m2m_table_name, (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('objectlevel', models.ForeignKey(orm[u'acl.objectlevel'], null=False)),
('user', models.ForeignKey(orm[u'auth.user'], null=False))
))
db.create_unique(m2m_table_name, ['objectlevel_id', 'user_id'])
# Adding M2M table for field groups on 'ObjectLevel'
m2m_table_name = db.shorten_name(u'acl_objectlevel_groups')
db.create_table(m2m_table_name, (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('objectlevel', models.ForeignKey(orm[u'acl.objectlevel'], null=False)),
('group', models.ForeignKey(orm[u'auth.group'], null=False))
))
db.create_unique(m2m_table_name, ['objectlevel_id', 'group_id'])
def backwards(self, orm):
# Removing unique constraint on 'ObjectLevel', fields ['content_type', 'object_id', 'level']
db.delete_unique(u'acl_objectlevel', ['content_type_id', 'object_id', 'level_id'])
# Removing unique constraint on 'Level', fields ['content_type', 'codename']
db.delete_unique(u'acl_level', ['content_type_id', 'codename'])
# Deleting model 'Level'
db.delete_table(u'acl_level')
# Deleting model 'ObjectLevel'
db.delete_table(u'acl_objectlevel')
# Removing M2M table for field users on 'ObjectLevel'
db.delete_table(db.shorten_name(u'acl_objectlevel_users'))
# Removing M2M table for field groups on 'ObjectLevel'
db.delete_table(db.shorten_name(u'acl_objectlevel_groups'))
models = {
u'acl.level': {
'Meta': {'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Level'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
'weight': ('django.db.models.fields.IntegerField', [], {'null': 'True'})
},
u'acl.objectlevel': {
'Meta': {'unique_together': "(('content_type', 'object_id', 'level'),)", 'object_name': 'ObjectLevel'},
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'level': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['acl.Level']"}),
'object_id': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'users': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.User']", 'symmetrical': 'False'})
},
u'auth.group': {
'Meta': {'object_name': 'Group'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
u'auth.permission': {
'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
u'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
u'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
}
}
complete_apps = ['acl']
\ No newline at end of file
...@@ -15,29 +15,4 @@ ...@@ -15,29 +15,4 @@
# You should have received a copy of the GNU General Public License along # You should have received a copy of the GNU General Public License along
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>. # with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
from django.db.models import TextField, ForeignKey from .test_acl import TestModel, Test2Model # noqa
from django.contrib.auth.models import User
from ..models import AclBase
class TestModel(AclBase):
normal_field = TextField()
ACL_LEVELS = (
('alfa', 'Alfa'),
('bravo', 'Bravo'),
('charlie', 'Charlie'),
)
class Test2Model(AclBase):
normal2_field = TextField()
owner = ForeignKey(User, null=True)
ACL_LEVELS = (
('one', 'One'),
('two', 'Two'),
('three', 'Three'),
('owner', 'owner'),
)
...@@ -17,9 +17,31 @@ ...@@ -17,9 +17,31 @@
from django.test import TestCase from django.test import TestCase
from django.contrib.auth.models import User, Group, AnonymousUser from django.contrib.auth.models import User, Group, AnonymousUser
from django.db.models import TextField, ForeignKey
from ..models import ObjectLevel from ..models import ObjectLevel, AclBase
from .models import TestModel, Test2Model
class TestModel(AclBase):
normal_field = TextField()
ACL_LEVELS = (
('alfa', 'Alfa'),
('bravo', 'Bravo'),
('charlie', 'Charlie'),
)
class Test2Model(AclBase):
normal2_field = TextField()
owner = ForeignKey(User, null=True)
ACL_LEVELS = (
('one', 'One'),
('two', 'Two'),
('three', 'Three'),
('owner', 'owner'),
)
class AclUserTest(TestCase): class AclUserTest(TestCase):
......
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
"jquery-simple-slider": "https://github.com/BME-IK/jquery-simple-slider.git", "jquery-simple-slider": "https://github.com/BME-IK/jquery-simple-slider.git",
"bootbox": "~4.3.0", "bootbox": "~4.3.0",
"intro.js": "0.9.0", "intro.js": "0.9.0",
"favico.js": "~0.3.5" "favico.js": "~0.3.5",
"datatables": "~1.10.4"
} }
} }
...@@ -50,20 +50,20 @@ def get_env_variable(var_name, default=None): ...@@ -50,20 +50,20 @@ def get_env_variable(var_name, default=None):
########## PATH CONFIGURATION ########## PATH CONFIGURATION
# Absolute filesystem path to the Django project directory: # Absolute filesystem path to the Django project directory:
DJANGO_ROOT = dirname(dirname(abspath(__file__))) BASE_DIR = dirname(dirname(abspath(__file__)))
# Absolute filesystem path to the top-level project folder: # Absolute filesystem path to the top-level project folder:
SITE_ROOT = dirname(DJANGO_ROOT) SITE_ROOT = dirname(BASE_DIR)
# Site name: # Site name:
SITE_NAME = basename(DJANGO_ROOT) SITE_NAME = basename(BASE_DIR)
# Url to site: (e.g. http://localhost:8080/) # Url to site: (e.g. http://localhost:8080/)
DJANGO_URL = get_env_variable('DJANGO_URL', '/') DJANGO_URL = get_env_variable('DJANGO_URL', '/')
# Add our project to our pythonpath, this way we don't need to type our project # Add our project to our pythonpath, this way we don't need to type our project
# name in our dotted import paths: # name in our dotted import paths:
path.append(DJANGO_ROOT) path.append(BASE_DIR)
########## END PATH CONFIGURATION ########## END PATH CONFIGURATION
...@@ -78,14 +78,9 @@ TEMPLATE_DEBUG = DEBUG ...@@ -78,14 +78,9 @@ TEMPLATE_DEBUG = DEBUG
########## MANAGER CONFIGURATION ########## MANAGER CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#admins # See: https://docs.djangoproject.com/en/dev/ref/settings/#admins
ADMINS = (
('Root', 'root@localhost'),
)
EMAIL_SUBJECT_PREFIX = get_env_variable('DJANGO_SUBJECT_PREFIX', '[CIRCLE] ') EMAIL_SUBJECT_PREFIX = get_env_variable('DJANGO_SUBJECT_PREFIX', '[CIRCLE] ')
# See: https://docs.djangoproject.com/en/dev/ref/settings/#managers
MANAGERS = ADMINS
########## END MANAGER CONFIGURATION ########## END MANAGER CONFIGURATION
...@@ -198,6 +193,7 @@ PIPELINE_JS = { ...@@ -198,6 +193,7 @@ PIPELINE_JS = {
"jquery-knob/dist/jquery.knob.min.js", "jquery-knob/dist/jquery.knob.min.js",
"jquery-simple-slider/js/simple-slider.js", "jquery-simple-slider/js/simple-slider.js",
"favico.js/favico.js", "favico.js/favico.js",
"datatables/media/js/jquery.dataTables.js",
"dashboard/dashboard.js", "dashboard/dashboard.js",
"dashboard/activity.js", "dashboard/activity.js",
"dashboard/group-details.js", "dashboard/group-details.js",
...@@ -215,6 +211,7 @@ PIPELINE_JS = { ...@@ -215,6 +211,7 @@ PIPELINE_JS = {
"js/host.js", "js/host.js",
"js/network.js", "js/network.js",
"js/switch-port.js", "js/switch-port.js",
"js/host-list.js",
"autocomplete_light/autocomplete.js", "autocomplete_light/autocomplete.js",
"autocomplete_light/widget.js", "autocomplete_light/widget.js",
"autocomplete_light/addanother.js", "autocomplete_light/addanother.js",
...@@ -283,12 +280,6 @@ TEMPLATE_CONTEXT_PROCESSORS = ( ...@@ -283,12 +280,6 @@ TEMPLATE_CONTEXT_PROCESSORS = (
'dashboard.context_processors.extract_settings', 'dashboard.context_processors.extract_settings',
) )
# See: https://docs.djangoproject.com/en/dev/ref/settings/#template-loaders
TEMPLATE_LOADERS = (
'django.template.loaders.filesystem.Loader',
'django.template.loaders.app_directories.Loader',
)
# See: https://docs.djangoproject.com/en/dev/ref/settings/#template-dirs # See: https://docs.djangoproject.com/en/dev/ref/settings/#template-dirs
TEMPLATE_DIRS = ( TEMPLATE_DIRS = (
normpath(join(SITE_ROOT, '../../site-circle/templates')), normpath(join(SITE_ROOT, '../../site-circle/templates')),
...@@ -337,7 +328,6 @@ DJANGO_APPS = ( ...@@ -337,7 +328,6 @@ DJANGO_APPS = (
) )
THIRD_PARTY_APPS = ( THIRD_PARTY_APPS = (
'south',
'django_tables2', 'django_tables2',
'crispy_forms', 'crispy_forms',
'djcelery', 'djcelery',
...@@ -349,6 +339,11 @@ THIRD_PARTY_APPS = ( ...@@ -349,6 +339,11 @@ THIRD_PARTY_APPS = (
'pipeline', 'pipeline',
) )
import django
if django.get_version() < '1.7':
THIRD_PARTY_APPS += 'south',
# Apps specific for this project go here. # Apps specific for this project go here.
LOCAL_APPS = ( LOCAL_APPS = (
'common', 'common',
...@@ -533,8 +528,14 @@ LOCALE_PATHS = (join(SITE_ROOT, 'locale'), ) ...@@ -533,8 +528,14 @@ LOCALE_PATHS = (join(SITE_ROOT, 'locale'), )
COMPANY_NAME = "BME IK 2014" COMPANY_NAME = "BME IK 2014"
SOUTH_MIGRATION_MODULES = { SOUTH_MIGRATION_MODULES = {
'taggit': 'taggit.south_migrations', 'taggit': 'taggit.south_migrations',
'vm': 'vm.south_migrations',
'firewall': 'firewall.south_migrations',
'acl': 'acl.south_migrations',
'dashboard': 'dashboard.south_migrations',
'storage': 'storage.south_migrations',
} }
graphite_host = environ.get("GRAPHITE_HOST", None) graphite_host = environ.get("GRAPHITE_HOST", None)
graphite_port = environ.get("GRAPHITE_PORT", None) graphite_port = environ.get("GRAPHITE_PORT", None)
if graphite_host and graphite_port: if graphite_host and graphite_port:
......
...@@ -43,7 +43,7 @@ EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' ...@@ -43,7 +43,7 @@ EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
# DATABASES = { # DATABASES = {
# 'default': { # 'default': {
# 'ENGINE': 'django.db.backends.sqlite3', # 'ENGINE': 'django.db.backends.sqlite3',
# 'NAME': normpath(join(DJANGO_ROOT, 'default.db')), # 'NAME': normpath(join(BASE_DIR, 'default.db')),
# 'USER': '', # 'USER': '',
# 'PASSWORD': '', # 'PASSWORD': '',
# 'HOST': '', # 'HOST': '',
......
...@@ -370,6 +370,12 @@ class HumanSortField(CharField): ...@@ -370,6 +370,12 @@ class HumanSortField(CharField):
def get_monitored_value(self, instance): def get_monitored_value(self, instance):
return getattr(instance, self.monitor) return getattr(instance, self.monitor)
def deconstruct(self):
name, path, args, kwargs = super(HumanSortField, self).deconstruct()
if self.monitor is not None:
kwargs['monitor'] = self.monitor
return name, path, args, kwargs
@staticmethod @staticmethod
def _partition(s, pred): def _partition(s, pred):
"""Partition a deque of chars to a tuple of a """Partition a deque of chars to a tuple of a
......
...@@ -41,6 +41,7 @@ from django.forms.widgets import TextInput, HiddenInput ...@@ -41,6 +41,7 @@ from django.forms.widgets import TextInput, HiddenInput
from django.template import Context from django.template import Context
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.utils.html import escape, format_html from django.utils.html import escape, format_html
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from sizefield.widgets import FileSizeWidget from sizefield.widgets import FileSizeWidget
from django.core.urlresolvers import reverse_lazy from django.core.urlresolvers import reverse_lazy
...@@ -95,10 +96,10 @@ class VmSaveForm(OperationForm): ...@@ -95,10 +96,10 @@ class VmSaveForm(OperationForm):
if default: if default:
self.fields['name'].initial = default self.fields['name'].initial = default
if clone: if clone:
self.fields.insert(2, "clone", forms.BooleanField( self.fields["clone"] = forms.BooleanField(
required=False, label=_("Clone template permissions"), required=False, label=_("Clone template permissions"),
help_text=_("Clone the access list of parent template. Useful " help_text=_("Clone the access list of parent template. Useful "
"for updating a template."))) "for updating a template."))
class VmCustomizeForm(forms.Form): class VmCustomizeForm(forms.Form):
...@@ -749,9 +750,9 @@ class VmRenewForm(OperationForm): ...@@ -749,9 +750,9 @@ class VmRenewForm(OperationForm):
default = kwargs.pop('default') default = kwargs.pop('default')
super(VmRenewForm, self).__init__(*args, **kwargs) super(VmRenewForm, self).__init__(*args, **kwargs)
self.fields.insert(0, 'lease', forms.ModelChoiceField( self.fields['lease'] = forms.ModelChoiceField(
queryset=choices, initial=default, required=False, queryset=choices, initial=default, required=False,
empty_label=None, label=_('Length'))) empty_label=None, label=_('Length'))
if len(choices) < 2: if len(choices) < 2:
self.fields['lease'].widget = HiddenInput() self.fields['lease'].widget = HiddenInput()
self.fields['save'].widget = HiddenInput() self.fields['save'].widget = HiddenInput()
...@@ -771,9 +772,9 @@ class VmMigrateForm(forms.Form): ...@@ -771,9 +772,9 @@ class VmMigrateForm(forms.Form):
default = kwargs.pop('default') default = kwargs.pop('default')
super(VmMigrateForm, self).__init__(*args, **kwargs) super(VmMigrateForm, self).__init__(*args, **kwargs)
self.fields.insert(0, 'to_node', forms.ModelChoiceField( self.fields['to_node'] = forms.ModelChoiceField(
queryset=choices, initial=default, required=False, queryset=choices, initial=default, required=False,
widget=forms.RadioSelect(), label=_("Node"))) widget=forms.RadioSelect(), label=_("Node"))
class VmStateChangeForm(OperationForm): class VmStateChangeForm(OperationForm):
...@@ -834,9 +835,9 @@ class VmDiskResizeForm(OperationForm): ...@@ -834,9 +835,9 @@ class VmDiskResizeForm(OperationForm):
super(VmDiskResizeForm, self).__init__(*args, **kwargs) super(VmDiskResizeForm, self).__init__(*args, **kwargs)
self.fields.insert(0, 'disk', forms.ModelChoiceField( self.fields['disk'] = forms.ModelChoiceField(
queryset=choices, initial=self.disk, required=True, queryset=choices, initial=self.disk, required=True,
empty_label=None, label=_('Disk'))) empty_label=None, label=_('Disk'))
if self.disk: if self.disk:
self.fields['disk'].widget = HiddenInput() self.fields['disk'].widget = HiddenInput()
self.fields['size'].initial += self.disk.size self.fields['size'].initial += self.disk.size
...@@ -870,9 +871,9 @@ class VmDiskRemoveForm(OperationForm): ...@@ -870,9 +871,9 @@ class VmDiskRemoveForm(OperationForm):
super(VmDiskRemoveForm, self).__init__(*args, **kwargs) super(VmDiskRemoveForm, self).__init__(*args, **kwargs)
self.fields.insert(0, 'disk', forms.ModelChoiceField( self.fields['disk'] = forms.ModelChoiceField(
queryset=choices, initial=self.disk, required=True, queryset=choices, initial=self.disk, required=True,
empty_label=None, label=_('Disk'))) empty_label=None, label=_('Disk'))
if self.disk: if self.disk:
self.fields['disk'].widget = HiddenInput() self.fields['disk'].widget = HiddenInput()
...@@ -915,9 +916,9 @@ class VmRemoveInterfaceForm(OperationForm): ...@@ -915,9 +916,9 @@ class VmRemoveInterfaceForm(OperationForm):
super(VmRemoveInterfaceForm, self).__init__(*args, **kwargs) super(VmRemoveInterfaceForm, self).__init__(*args, **kwargs)
self.fields.insert(0, 'interface', forms.ModelChoiceField( self.fields['interface'] = forms.ModelChoiceField(
queryset=choices, initial=self.interface, required=True, queryset=choices, initial=self.interface, required=True,
empty_label=None, label=_('Interface'))) empty_label=None, label=_('Interface'))
if self.interface: if self.interface:
self.fields['interface'].widget = HiddenInput() self.fields['interface'].widget = HiddenInput()
...@@ -951,18 +952,45 @@ class VmAddInterfaceForm(OperationForm): ...@@ -951,18 +952,45 @@ class VmAddInterfaceForm(OperationForm):
self.fields['vlan'] = field self.fields['vlan'] = field
class DeployChoiceField(forms.ModelChoiceField):
def __init__(self, *args, **kwargs):
self.instance = kwargs.pop("instance")
super(DeployChoiceField, self).__init__(*args, **kwargs)
def label_from_instance(self, obj):
traits = set(obj.traits.all())
req_traits = set(self.instance.req_traits.all())
# if the subset is empty the node satisfies the required traits
subset = req_traits - traits
label = "%s %s" % (
"&#xf071" if subset else "&#xf00c;", escape(obj.name),
)
if subset:
missing_traits = ", ".join(map(lambda x: escape(x.name), subset))
label += _(" (missing_traits: %s)") % missing_traits
return mark_safe(label)
class VmDeployForm(OperationForm): class VmDeployForm(OperationForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
choices = kwargs.pop('choices', None) choices = kwargs.pop('choices', None)
instance = kwargs.pop("instance")
super(VmDeployForm, self).__init__(*args, **kwargs) super(VmDeployForm, self).__init__(*args, **kwargs)
if choices is not None: if choices is not None:
self.fields.insert(0, 'node', forms.ModelChoiceField( self.fields['node'] = DeployChoiceField(
queryset=choices, required=False, label=_('Node'), help_text=_( queryset=choices, required=False, label=_('Node'), help_text=_(
"Deploy virtual machine to this node " "Deploy virtual machine to this node "
"(blank allows scheduling automatically)."))) "(blank allows scheduling automatically)."),
widget=forms.Select(attrs={
'class': "font-awesome-font",
}), instance=instance
)
class VmPortRemoveForm(OperationForm): class VmPortRemoveForm(OperationForm):
...@@ -972,9 +1000,9 @@ class VmPortRemoveForm(OperationForm): ...@@ -972,9 +1000,9 @@ class VmPortRemoveForm(OperationForm):
super(VmPortRemoveForm, self).__init__(*args, **kwargs) super(VmPortRemoveForm, self).__init__(*args, **kwargs)
self.fields.insert(0, 'rule', forms.ModelChoiceField( self.fields['rule'] = forms.ModelChoiceField(
queryset=choices, initial=self.rule, required=True, queryset=choices, initial=self.rule, required=True,
empty_label=None, label=_('Port'))) empty_label=None, label=_('Port'))
if self.rule: if self.rule:
self.fields['rule'].widget = HiddenInput() self.fields['rule'].widget = HiddenInput()
...@@ -991,9 +1019,9 @@ class VmPortAddForm(OperationForm): ...@@ -991,9 +1019,9 @@ class VmPortAddForm(OperationForm):
super(VmPortAddForm, self).__init__(*args, **kwargs) super(VmPortAddForm, self).__init__(*args, **kwargs)
self.fields.insert(0, 'host', forms.ModelChoiceField( self.fields['host'] = forms.ModelChoiceField(
queryset=choices, initial=self.host, required=True, queryset=choices, initial=self.host, required=True,
empty_label=None, label=_('Host'))) empty_label=None, label=_('Host'))
if self.host: if self.host:
self.fields['host'].widget = HiddenInput() self.fields['host'].widget = HiddenInput()
...@@ -1183,7 +1211,10 @@ class TraitForm(forms.ModelForm): ...@@ -1183,7 +1211,10 @@ class TraitForm(forms.ModelForm):
class MyProfileForm(forms.ModelForm): class MyProfileForm(forms.ModelForm):
preferred_language = forms.ChoiceField(LANGUAGES_WITH_CODE) preferred_language = forms.ChoiceField(
LANGUAGES_WITH_CODE,
label=_("Preferred language"),
)
class Meta: class Meta:
fields = ('preferred_language', 'email_notifications', fields = ('preferred_language', 'email_notifications',
......
...@@ -54,7 +54,9 @@ from .validators import connect_command_template_validator ...@@ -54,7 +54,9 @@ from .validators import connect_command_template_validator
logger = getLogger(__name__) logger = getLogger(__name__)
pwgen = User.objects.make_random_password
def pwgen():
return User.objects.make_random_password()
class Favourite(Model): class Favourite(Model):
......
...@@ -137,16 +137,16 @@ $(function() { ...@@ -137,16 +137,16 @@ $(function() {
} }
var vm_state = $("#vm-details-state"); var vm_state = $("#vm-details-state");
if (vm_state.length) { if (vm_state.length) {
vm_state.data("status", data['status']); vm_state.data("status", data['status']); // jshint ignore:line
$("#vm-details-state span").html(data['human_readable_status'].toUpperCase()); $("#vm-details-state span").html(data.human_readable_status.toUpperCase());
} }
if(data['status'] == "RUNNING") { if(data['status'] == "RUNNING") { // jshint ignore:line
if(data['connect_uri']) { if(data.connect_uri) {
$("#dashboard-vm-details-connect-button").removeClass('disabled'); $("#dashboard-vm-details-connect-button").removeClass('disabled');
} }
$("[data-target=#_console]").attr("data-toggle", "pill").attr("href", "#console").parent("li").removeClass("disabled"); $("[data-target=#_console]").attr("data-toggle", "pill").attr("href", "#console").parent("li").removeClass("disabled");
} else { } else {
if(data['connect_uri']) { if(data.connect_uri) {
$("#dashboard-vm-details-connect-button").addClass('disabled'); $("#dashboard-vm-details-connect-button").addClass('disabled');
} }
$("[data-target=#_console]").attr("data-toggle", "_pill").attr("href", "#").parent("li").addClass("disabled"); $("[data-target=#_console]").attr("data-toggle", "_pill").attr("href", "#").parent("li").addClass("disabled");
...@@ -181,7 +181,7 @@ $(function() { ...@@ -181,7 +181,7 @@ $(function() {
String.prototype.hashCode = function() { String.prototype.hashCode = function() {
var hash = 0, i, chr, len; var hash = 0, i, chr, len;
if (this.length == 0) return hash; if (this.length === 0) return hash;
for (i = 0, len = this.length; i < len; i++) { for (i = 0, len = this.length; i < len; i++) {
chr = this.charCodeAt(i); chr = this.charCodeAt(i);
hash = ((hash << 5) - hash) + chr; hash = ((hash << 5) - hash) + chr;
......
...@@ -44,7 +44,7 @@ $(function () { ...@@ -44,7 +44,7 @@ $(function () {
if(xhr.status === 403) { if(xhr.status === 403) {
addMessage(gettext("Only the owners can delete the selected object."), "warning"); addMessage(gettext("Only the owners can delete the selected object."), "warning");
} else { } else {
addMessage(gettext("An error occurred. (") + xhr.status + ")", 'danger') addMessage(gettext("An error occurred. (") + xhr.status + ")", 'danger');
} }
} }
}); });
...@@ -189,7 +189,7 @@ $(function () { ...@@ -189,7 +189,7 @@ $(function () {
} }
} }
search_result.sort(compareVmByFav); search_result.sort(compareVmByFav);
for(var i=0; i<5 && i<search_result.length; i++) for(i=0; i<5 && i<search_result.length; i++)
html += generateVmHTML(search_result[i].pk, search_result[i].name, html += generateVmHTML(search_result[i].pk, search_result[i].name,
search_result[i].owner ? search_result[i].owner : search_result[i].host, search_result[i].icon, search_result[i].owner ? search_result[i].owner : search_result[i].host, search_result[i].icon,
search_result[i].status, search_result[i].fav, search_result[i].status, search_result[i].fav,
...@@ -520,12 +520,12 @@ function clientInstalledAction(location) { ...@@ -520,12 +520,12 @@ function clientInstalledAction(location) {
} }
function setCookie(name, value, seconds, path) { function setCookie(name, value, seconds, path) {
if (seconds!=null) { if (seconds !== null) {
var today = new Date(); var today = new Date();
var expire = new Date(); var expire = new Date();
expire.setTime(today.getTime() + seconds); expire.setTime(today.getTime() + seconds);
}
document.cookie = name+"="+escape(value)+"; expires="+expire.toUTCString()+"; path="+path; document.cookie = name+"="+escape(value)+"; expires="+expire.toUTCString()+"; path="+path;
}
} }
/* no js compatibility */ /* no js compatibility */
...@@ -539,7 +539,7 @@ function getParameterByName(name) { ...@@ -539,7 +539,7 @@ function getParameterByName(name) {
name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]"); name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"), var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
results = regex.exec(location.search); results = regex.exec(location.search);
return results == null ? "" : decodeURIComponent(results[1].replace(/\+/g, " ")); return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
} }
// for AJAX calls // for AJAX calls
...@@ -581,7 +581,7 @@ $.ajaxSetup({ ...@@ -581,7 +581,7 @@ $.ajaxSetup({
$(function() { $(function() {
yourlabs.TextWidget.prototype.getValue = function(choice) { yourlabs.TextWidget.prototype.getValue = function(choice) {
return choice.children().html(); return choice.children().html();
} };
}); });
var tagsToReplace = { var tagsToReplace = {
......
...@@ -188,11 +188,15 @@ html { ...@@ -188,11 +188,15 @@ html {
display: none; display: none;
} }
.vm-details-home-name { #group-details-rename-form {
display: inline-block;
}
.vm-details-home-name, #group-details-rename-form .input-group {
max-width: 401px; max-width: 401px;
} }
#node-details-rename-name, #group-details-rename-name { #node-details-rename-name {
max-width: 160px; max-width: 160px;
} }
......
$(function() {
/* rename */ /* rename */
$("#group-details-h1-name, .group-details-rename-button").click(function() { $("#group-details-h1-name, .group-details-rename-button").click(function() {
$("#group-details-h1-name").hide(); $("#group-details-h1-name span").hide();
$("#group-details-rename").css('display', 'inline'); $("#group-details-rename-form").show().css('display', 'inline-block');
$("#group-details-rename-name").focus(); $("#group-details-rename-name").select();
}); });
/* rename ajax */ /* rename ajax */
$('#group-details-rename-submit').click(function() { $('#group-details-rename-submit').click(function() {
if(!$("#group-details-rename-name")[0].checkValidity()) {
return true;
}
var name = $('#group-details-rename-name').val(); var name = $('#group-details-rename-name').val();
$.ajax({ $.ajax({
method: 'POST', method: 'POST',
url: location.href, url: location.href,
data: {'new_name': name}, data: {'new_name': name},
headers: {"X-CSRFToken": getCookie('csrftoken')}, headers: {"X-CSRFToken": getCookie('csrftoken')},
success: function(data, textStatus, xhr) { success: function(data, textStatus, xhr) {
$("#group-details-h1-name").text(data['new_name']).show(); $("#group-details-h1-name span").text(data.new_name).show();
$('#group-details-rename').hide(); $('#group-details-rename-form').hide();
// addMessage(data['message'], "success");
}, },
error: function(xhr, textStatus, error) { error: function(xhr, textStatus, error) {
addMessage("Error during renaming!", "danger"); addMessage("Error during renaming.", "danger");
} }
}); });
return false; return false;
}); });
});
$(".group-details-help-button").click(function() {
$(".group-details-help").stop().slideToggle();
});
...@@ -15,7 +15,7 @@ $(function() { ...@@ -15,7 +15,7 @@ $(function() {
data: {'new_name': name}, data: {'new_name': name},
headers: {"X-CSRFToken": getCookie('csrftoken')}, headers: {"X-CSRFToken": getCookie('csrftoken')},
success: function(data, textStatus, xhr) { success: function(data, textStatus, xhr) {
$("#node-details-h1-name").text(data['new_name']).show(); $("#node-details-h1-name").text(data.new_name).show();
$('#node-details-rename').hide(); $('#node-details-rename').hide();
// addMessage(data.message, "success"); // addMessage(data.message, "success");
}, },
......
$(function() { $(function() {
$(document).ready( function() { $(document).ready( function() {
colortable();
});
// find disabled nodes, set danger (red) on the rows // find disabled nodes, set danger (red) on the rows
function colortable() $('.node-disabled').closest("tr").addClass('danger');
{ });
$('.false').closest("tr").addClass('danger');
$('.true').closest("tr").removeClass('danger');
}
}); });
...@@ -71,7 +71,7 @@ $(function() { ...@@ -71,7 +71,7 @@ $(function() {
var eye = $(this).children("#vm-details-pw-eye"); var eye = $(this).children("#vm-details-pw-eye");
var span = $(this); var span = $(this);
span.tooltip("destroy") span.tooltip("destroy");
if(eye.hasClass("fa-eye")) { if(eye.hasClass("fa-eye")) {
eye.removeClass("fa-eye").addClass("fa-eye-slash"); eye.removeClass("fa-eye").addClass("fa-eye-slash");
input.prop("type", "text"); input.prop("type", "text");
...@@ -132,7 +132,7 @@ $(function() { ...@@ -132,7 +132,7 @@ $(function() {
var tmp = ta.val(); var tmp = ta.val();
ta.val(""); ta.val("");
ta.focus(); ta.focus();
ta.val(tmp) ta.val(tmp);
e.preventDefault(); e.preventDefault();
}); });
...@@ -207,7 +207,7 @@ $(function() { ...@@ -207,7 +207,7 @@ $(function() {
$("#dashboard-tutorial-toggle").click(function() { $("#dashboard-tutorial-toggle").click(function() {
var box = $("#alert-new-template"); var box = $("#alert-new-template");
var list = box.find("ol") var list = box.find("ol");
list.stop().slideToggle(function() { list.stop().slideToggle(function() {
var url = box.find("form").prop("action"); var url = box.find("form").prop("action");
var hidden = list.css("display") === "none"; var hidden = list.css("display") === "none";
......
...@@ -82,7 +82,7 @@ $(function() { ...@@ -82,7 +82,7 @@ $(function() {
/* mass operations */ /* mass operations */
$("#vm-mass-ops").on('click', '.mass-operation', function(e) { $("#vm-mass-ops").on('click', '.mass-operation', function(e) {
var icon = $(this).children("i").addClass('fa-spinner fa-spin'); var icon = $(this).children("i").addClass('fa-spinner fa-spin');
params = "?" + selected.map(function(a){return "vm=" + a.vm}).join("&"); params = "?" + selected.map(function(a){return "vm=" + a.vm;}).join("&");
$.ajax({ $.ajax({
type: 'GET', type: 'GET',
...@@ -212,7 +212,7 @@ function updateStatuses(runs) { ...@@ -212,7 +212,7 @@ function updateStatuses(runs) {
if(checkStatusUpdate()) { if(checkStatusUpdate()) {
setTimeout( setTimeout(
function() {updateStatuses(runs + 1)}, function() {updateStatuses(runs + 1);},
1000 + Math.exp(runs * 0.05) 1000 + Math.exp(runs * 0.05)
); );
} }
......
...@@ -23,11 +23,35 @@ Choose a compute node to migrate {{obj}} to. ...@@ -23,11 +23,35 @@ Choose a compute node to migrate {{obj}} to.
<i class="fa {{n.get_status_icon}}"></i> {{n.get_status_display}}</div> <i class="fa {{n.get_status_icon}}"></i> {{n.get_status_display}}</div>
{% if current == n.pk %}<div class="label label-info">{% trans "current" %}</div>{% endif %} {% if current == n.pk %}<div class="label label-info">{% trans "current" %}</div>{% endif %}
{% if recommended == n.pk %}<div class="label label-success">{% trans "recommended" %}</div>{% endif %} {% if recommended == n.pk %}<div class="label label-success">{% trans "recommended" %}</div>{% endif %}
{% if n.pk not in nodes_w_traits %}
<div class="label label-warning">
<i class="fa fa-warning"></i>
{% trans "missing traits" %}</div>
{% endif %}
</label> </label>
<input id="migrate-to-{{n.pk}}" type="radio" name="to_node" value="{{ n.pk }}" style="float: right;" <input id="migrate-to-{{n.pk}}" type="radio" name="to_node" value="{{ n.pk }}" style="float: right;"
{% if current == n.pk %}disabled="disabled"{% endif %} {% if current == n.pk %}disabled="disabled"{% endif %}
{% if recommended == n.pk %}checked="checked"{% endif %} {% if recommended == n.pk and n.pk != current %}checked="checked"{% endif %}
/> />
{% if n.pk not in nodes_w_traits %}
<span class="vm-migrate-node-property">
{% trans "Node traits" %}:
{% if n.traits.all %}
{{ n.traits.all|join:", " }}
{% else %}
-
{% endif %}
</span>
<span class="vm-migrate-node-property">
{% trans "Required traits" %}:
{% if object.req_traits.all %}
{{ object.req_traits.all|join:", " }}
{% else %}
-
{% endif %}
</span>
<hr />
{% endif %}
<span class="vm-migrate-node-property">{% trans "CPU load" %}: {{ n.cpu_usage }}</span> <span class="vm-migrate-node-property">{% trans "CPU load" %}: {{ n.cpu_usage }}</span>
<span class="vm-migrate-node-property"> <span class="vm-migrate-node-property">
{% trans "RAM usage" %}: {{ n.byte_ram_usage|filesize }}/{{ n.ram_size|filesize }}</span> {% trans "RAM usage" %}: {{ n.byte_ram_usage|filesize }}/{{ n.ram_size|filesize }}</span>
......
...@@ -9,43 +9,33 @@ ...@@ -9,43 +9,33 @@
<div class="body-content"> <div class="body-content">
<div class="page-header"> <div class="page-header">
<div class="pull-right" style="padding-top: 15px;"> <div class="pull-right" style="padding-top: 15px;">
<a title="{% trans "Rename" %}" href="#" class="btn btn-default btn-xs group-details-rename-button"> <a title="{% trans "Rename" %}" class="btn btn-default btn-xs group-details-rename-button">
<i class="fa fa-pencil"></i> <i class="fa fa-pencil"></i>
</a> </a>
<a title="{% trans "Delete" %}" data-group-pk="{{ group.pk }}" class="btn btn-default btn-xs real-link group-delete" href="{% url "dashboard.views.delete-group" pk=group.pk %}"> <a title="{% trans "Delete" %}" data-group-pk="{{ group.pk }}" class="btn btn-default btn-xs real-link group-delete" href="{% url "dashboard.views.delete-group" pk=group.pk %}">
<i class="fa fa-trash-o"></i> <i class="fa fa-trash-o"></i>
</a> </a>
<a title="{% trans "Help" %}" href="#" class="btn btn-default btn-xs group-details-help-button">
<i class="fa fa-question"></i>
</a>
</div> </div>
<h1> <h1>
<div id="group-details-rename"> <form action="" method="POST" id="group-details-rename-form" class="js-hidden">
<form action="" method="POST" id="group-details-rename-form">
{% csrf_token %} {% csrf_token %}
<input id="group-details-rename-name" class="form-control" name="new_name" type="text" value="{{ group.name }}"/> <div class="input-group">
<button type="submit" id="group-details-rename-submit" class="btn">{% trans "Rename" %}</button> <input id="group-details-rename-name" class="form-control" name="new_name"
</form> type="text" value="{{ group.name }}" required />
<span class="input-group-btn">
<button type="submit" id="group-details-rename-submit" class="btn">
{% trans "Rename" %}
</button>
</span>
</div> </div>
</form>
<div id="group-details-h1-name"> <div id="group-details-h1-name">
{{ group.name }} <span class="no-js-hidden">{{ group.name }}</span>
{% if group.groupprofile.org_id %} {% if group.groupprofile.org_id %}
<small>{{group.groupprofile.org_id}}</small> <small>{{group.groupprofile.org_id}}</small>
{% endif %} {% endif %}
</div> </div>
</h1> </h1>
<div class="group-details-help js-hidden">
<ul style="list-style: none;">
<li>
<strong>{% trans "Rename" %}:</strong>
{% trans "Change the name of the group." %}
</li>
<li>
<strong>{% trans "Delete" %}:</strong>
{% trans "Delete group." %}
</li>
</ul>
</div>
</div><!-- .page-header --> </div><!-- .page-header -->
<div class="row"> <div class="row">
<div class="col-md-12" id="group-detail-pane"> <div class="col-md-12" id="group-detail-pane">
......
...@@ -73,7 +73,7 @@ ...@@ -73,7 +73,7 @@
</a> </a>
</li> </li>
<li> <li>
<a href="{% url "dashboard.views.vm-list" %}?s=node:{{ node.name }}" <a href="{% url "dashboard.views.vm-list" %}?s=node_exact:{{ node.name }}"
target="blank" class="text-center"> target="blank" class="text-center">
<i class="fa fa-desktop fa-2x"></i><br> <i class="fa fa-desktop fa-2x"></i><br>
{% trans "Virtual Machines" %} {% trans "Virtual Machines" %}
......
{% load sizefieldtags %} {% load sizefieldtags %}
{% load i18n %} {% load i18n %}
<span class="{% if not record.enabled %}node-disabled{% endif %}"></span>
<i class="fa fa-gears"></i> {% trans "CPU" %} <i class="fa fa-gears"></i> {% trans "CPU" %}
<div class="progress pull-right"> <div class="progress pull-right">
<div class="progress-bar progress-bar-success" role="progressbar" <div class="progress-bar progress-bar-success" role="progressbar"
......
...@@ -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
<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" type="submit" id="op-form-send">
{% if opview.icon %}<i class="fa fa-fw fa-{{opview.icon}}"></i> {% endif %}{{ op|capfirst }} {% if opview.icon %}<i class="fa fa-fw fa-{{opview.icon}}"></i> {% endif %}{{ op.name|capfirst }}
</button> </button>
</div> </div>
</form> </form>
...@@ -389,6 +389,7 @@ class RenewViewTest(unittest.TestCase): ...@@ -389,6 +389,7 @@ class RenewViewTest(unittest.TestCase):
inst = MagicMock(spec=Instance) inst = MagicMock(spec=Instance)
inst._meta.object_name = "Instance" inst._meta.object_name = "Instance"
inst.name = 'foo' inst.name = 'foo'
inst.lease = MagicMock(pk=99)
inst.renew = Instance._ops['renew'](inst) inst.renew = Instance._ops['renew'](inst)
inst.has_level.return_value = True inst.has_level.return_value = True
go.return_value = inst go.return_value = inst
...@@ -403,6 +404,7 @@ class RenewViewTest(unittest.TestCase): ...@@ -403,6 +404,7 @@ class RenewViewTest(unittest.TestCase):
patch('dashboard.views.util.messages') as msg: patch('dashboard.views.util.messages') as msg:
inst = MagicMock(spec=Instance) inst = MagicMock(spec=Instance)
inst._meta.object_name = "Instance" inst._meta.object_name = "Instance"
inst.lease = MagicMock(pk=99)
inst.renew = Instance._ops['renew'](inst) inst.renew = Instance._ops['renew'](inst)
inst.renew.async = MagicMock() inst.renew.async = MagicMock()
inst.has_level.return_value = True inst.has_level.return_value = True
...@@ -421,6 +423,7 @@ class RenewViewTest(unittest.TestCase): ...@@ -421,6 +423,7 @@ class RenewViewTest(unittest.TestCase):
patch('dashboard.views.util.messages') as msg: patch('dashboard.views.util.messages') as msg:
inst = MagicMock(spec=Instance) inst = MagicMock(spec=Instance)
inst._meta.object_name = "Instance" inst._meta.object_name = "Instance"
inst.lease = MagicMock(pk=99)
inst.renew = Instance._ops['renew'](inst) inst.renew = Instance._ops['renew'](inst)
inst.renew.async = MagicMock() inst.renew.async = MagicMock()
inst.has_level.return_value = True inst.has_level.return_value = True
...@@ -463,6 +466,7 @@ class RenewViewTest(unittest.TestCase): ...@@ -463,6 +466,7 @@ class RenewViewTest(unittest.TestCase):
with patch.object(view, 'get_object') as go: with patch.object(view, 'get_object') as go:
inst = MagicMock(spec=Instance, pk=11) inst = MagicMock(spec=Instance, pk=11)
inst._meta.object_name = "Instance" inst._meta.object_name = "Instance"
inst.lease = MagicMock(pk=99)
inst.renew = Instance._ops['renew'](inst) inst.renew = Instance._ops['renew'](inst)
inst.renew.async = MagicMock() inst.renew.async = MagicMock()
inst.has_level.return_value = False inst.has_level.return_value = False
......
...@@ -62,7 +62,7 @@ class GraphViewBase(LoginRequiredMixin, View): ...@@ -62,7 +62,7 @@ class GraphViewBase(LoginRequiredMixin, View):
metric = self.create_class(metric)(instance) metric = self.create_class(metric)(instance)
return HttpResponse(metric.get_graph(graphite_url, time), return HttpResponse(metric.get_graph(graphite_url, time),
mimetype="image/png") content_type="image/png")
def get_object(self, request, pk): def get_object(self, request, pk):
instance = self.model.objects.get(id=pk) instance = self.model.objects.get(id=pk)
......
...@@ -62,7 +62,7 @@ class NodeOperationView(AjaxOperationMixin, OperationView): ...@@ -62,7 +62,7 @@ class NodeOperationView(AjaxOperationMixin, OperationView):
model = Node model = Node
context_object_name = 'node' # much simpler to mock object context_object_name = 'node' # much simpler to mock object
with_reload = True with_reload = True
wait_for_result = 1 wait_for_result = None
node_ops = OrderedDict([ node_ops = OrderedDict([
...@@ -143,8 +143,13 @@ class NodeDetailView(LoginRequiredMixin, ...@@ -143,8 +143,13 @@ class NodeDetailView(LoginRequiredMixin,
def __remove_trait(self, request): def __remove_trait(self, request):
try: try:
to_remove = request.POST.get('to_remove') to_remove = request.POST.get('to_remove')
self.object = self.get_object() trait = Trait.objects.get(pk=to_remove)
self.object.traits.remove(to_remove) node = self.get_object()
node.traits.remove(to_remove)
if not trait.in_use:
trait.delete()
message = u"Success" message = u"Success"
except: # note this won't really happen except: # note this won't really happen
message = u"Not success" message = u"Not success"
...@@ -155,7 +160,7 @@ class NodeDetailView(LoginRequiredMixin, ...@@ -155,7 +160,7 @@ class NodeDetailView(LoginRequiredMixin,
content_type="application/json" content_type="application/json"
) )
else: else:
return redirect(self.object.get_absolute_url()) return redirect(node.get_absolute_url())
class NodeList(LoginRequiredMixin, GraphMixin, SingleTableView): class NodeList(LoginRequiredMixin, GraphMixin, SingleTableView):
......
...@@ -267,6 +267,9 @@ class UserCreationView(LoginRequiredMixin, PermissionRequiredMixin, ...@@ -267,6 +267,9 @@ class UserCreationView(LoginRequiredMixin, PermissionRequiredMixin,
template_name = 'dashboard/user-create.html' template_name = 'dashboard/user-create.html'
permission_required = "auth.add_user" permission_required = "auth.add_user"
def get_success_url(self):
reverse('dashboard.views.group-detail', args=[self.group.pk])
def get_group(self, group_pk): def get_group(self, group_pk):
self.group = get_object_or_404(Group, pk=group_pk) self.group = get_object_or_404(Group, pk=group_pk)
if not self.group.profile.has_level(self.request.user, 'owner'): if not self.group.profile.has_level(self.request.user, 'owner'):
......
...@@ -67,6 +67,7 @@ from ..forms import ( ...@@ -67,6 +67,7 @@ from ..forms import (
VmRemoveInterfaceForm, VmRemoveInterfaceForm,
) )
from ..models import Favourite from ..models import Favourite
from manager.scheduler import has_traits
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
...@@ -444,6 +445,20 @@ class VmMigrateView(FormOperationMixin, VmOperationView): ...@@ -444,6 +445,20 @@ class VmMigrateView(FormOperationMixin, VmOperationView):
val.update({'choices': choices, 'default': default}) val.update({'choices': choices, 'default': default})
return val return val
def get_context_data(self, *args, **kwargs):
ctx = super(VmMigrateView, self).get_context_data(*args, **kwargs)
inst = self.get_object()
if isinstance(inst, Instance):
nodes_w_traits = [
n.pk for n in Node.objects.filter(enabled=True)
if n.online and
has_traits(inst.req_traits.all(), n)
]
ctx['nodes_w_traits'] = nodes_w_traits
return ctx
class VmPortRemoveView(FormOperationMixin, VmOperationView): class VmPortRemoveView(FormOperationMixin, VmOperationView):
...@@ -698,6 +713,7 @@ class VmDeployView(FormOperationMixin, VmOperationView): ...@@ -698,6 +713,7 @@ class VmDeployView(FormOperationMixin, VmOperationView):
online = (n.pk for n in online = (n.pk for n in
Node.objects.filter(enabled=True) if n.online) Node.objects.filter(enabled=True) if n.online)
kwargs['choices'] = Node.objects.filter(pk__in=online) kwargs['choices'] = Node.objects.filter(pk__in=online)
kwargs['instance'] = self.get_object()
return kwargs return kwargs
...@@ -1135,7 +1151,7 @@ def get_vm_screenshot(request, pk): ...@@ -1135,7 +1151,7 @@ def get_vm_screenshot(request, pk):
# TODO handle this better # TODO handle this better
raise Http404() raise Http404()
return HttpResponse(image, mimetype="image/png") return HttpResponse(image, content_type="image/png")
class InstanceActivityDetail(CheckedDetailView): class InstanceActivityDetail(CheckedDetailView):
......
...@@ -189,7 +189,7 @@ def cleanup(): ...@@ -189,7 +189,7 @@ def cleanup():
def _cleanup(dir="~/circle/circle"): def _cleanup(dir="~/circle/circle"):
"Clean pyc files" "Clean pyc files"
with cd("~/circle/circle"): with cd(dir):
run("find -name '*.py[co]' -exec rm -f {} +") run("find -name '*.py[co]' -exec rm -f {} +")
......
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import firewall.fields
class Migration(migrations.Migration):
dependencies = [
('firewall', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='vlan',
name='ipv6_template',
field=models.TextField(help_text='Template for translating IPv4 addresses to IPv6. Automatically generated hosts in dual-stack networks will get this address. The template can contain four tokens: "%(a)d", "%(b)d", "%(c)d", and "%(d)d", representing the four bytes of the IPv4 address, respectively, in decimal notation. Moreover you can use any standard printf format specification like %(a)02x to get the first byte as two hexadecimal digits. Usual choices for mapping 198.51.100.0/24 to 2001:0DB8:1:1::/64 would be "2001:db8:1:1:%(d)d::" and "2001:db8:1:1:%(d)02x00::".', blank=True, verbose_name='ipv6 template', validators=[firewall.fields.val_ipv6_template]),
preserve_default=True,
),
]
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment