diff --git a/circle/acl/management/__init__.py b/circle/acl/management/__init__.py index 0ee1caf..801c9c3 100644 --- a/circle/acl/management/__init__.py +++ b/circle/acl/management/__init__.py @@ -1,24 +1,26 @@ """ Creates Levels for all installed apps that have levels. """ -from django.db.models import get_models, signals +from django.db.models import signals +from django.apps import apps from django.db import DEFAULT_DB_ALIAS from django.core.exceptions import ImproperlyConfigured from ..models import Level, AclBase -def create_levels(app, created_models, verbosity, db=DEFAULT_DB_ALIAS, +def create_levels(app_config, verbosity=False, using=DEFAULT_DB_ALIAS, **kwargs): """Create and set the weights of the configured Levels. Based on django.contrib.auth.management.__init__.create_permissions""" - # if not router.allow_migrate(db, auth_app.Permission): + # if not router.allow_migrate(using, auth_app.Permission): # return from django.contrib.contenttypes.models import ContentType - app_models = [k for k in get_models(app) if AclBase in k.__bases__] + app_models = [k for k in apps.get_models(app_config) + if AclBase in k.__bases__] print "Creating levels for models: %s." % ", ".join( [m.__name__ for m in app_models]) @@ -31,7 +33,7 @@ def create_levels(app, created_models, verbosity, db=DEFAULT_DB_ALIAS, for klass in app_models: # Force looking up the content types in the current database # before creating foreign keys to them. - ctype1 = ContentType.objects.db_manager(db).get_for_model(klass) + ctype1 = ContentType.objects.db_manager(using).get_for_model(klass) ctypes.add(ctype1) weight = 0 try: @@ -46,7 +48,7 @@ def create_levels(app, created_models, verbosity, db=DEFAULT_DB_ALIAS, # Find all the Levels that have a content_type for a model we're # looking for. We don't need to check for codenames since we already have # a list of the ones we're going to create. - all_levels = set(Level.objects.using(db).filter( + all_levels = set(Level.objects.using(using).filter( content_type__in=ctypes, ).values_list( "content_type", "codename" @@ -57,7 +59,7 @@ def create_levels(app, created_models, verbosity, db=DEFAULT_DB_ALIAS, for ctype, (codename, name) in searched_levels if (ctype.pk, codename) not in all_levels ] - Level.objects.using(db).bulk_create(levels) + Level.objects.using(using).bulk_create(levels) if verbosity >= 2: print("Adding levels [%s]." % ", ".join(unicode(l) for l in levels)) print("Searched: [%s]." % ", ".join( @@ -70,5 +72,5 @@ def create_levels(app, created_models, verbosity, db=DEFAULT_DB_ALIAS, content_type=ctype).update(weight=weight) -signals.post_syncdb.connect( +signals.post_migrate.connect( create_levels, dispatch_uid="circle.acl.management.create_levels") diff --git a/circle/acl/models.py b/circle/acl/models.py index 590857a..109a03a 100644 --- a/circle/acl/models.py +++ b/circle/acl/models.py @@ -18,7 +18,7 @@ import logging from django.contrib.auth.models import User, Group -from django.contrib.contenttypes.generic import ( +from django.contrib.contenttypes.fields import ( GenericForeignKey, GenericRelation ) from django.contrib.contenttypes.models import ContentType diff --git a/circle/circle/__init__.py b/circle/circle/__init__.py index b2279b4..d7f6581 100644 --- a/circle/circle/__init__.py +++ b/circle/circle/__init__.py @@ -12,9 +12,10 @@ def update_permissions_after_migration(sender, **kwargs): """ from django.conf import settings - from django.db.models import get_models + from django.apps import apps from django.contrib.auth.management import create_permissions - create_permissions(sender, get_models(), 2 if settings.DEBUG else 0) + create_permissions(sender, apps.get_models(), 2 if settings.DEBUG else 0) + post_migrate.connect(update_permissions_after_migration) diff --git a/circle/circle/settings/base.py b/circle/circle/settings/base.py index 8600b77..65b1c04 100644 --- a/circle/circle/settings/base.py +++ b/circle/circle/settings/base.py @@ -166,96 +166,95 @@ if exists(p): STATICFILES_DIRS.append(p) STATICFILES_STORAGE = 'pipeline.storage.PipelineCachedStorage' -PIPELINE_COMPILERS = ( - 'pipeline.compilers.less.LessCompiler', -) -PIPELINE_CSS_COMPRESSOR = 'pipeline.compressors.yuglify.YuglifyCompressor' -# PIPELINE_JS_COMPRESSOR = 'pipeline.compressors.slimit.SlimItCompressor' -PIPELINE_JS_COMPRESSOR = None -PIPELINE_DISABLE_WRAPPER = True -PIPELINE_LESS_ARGUMENTS = u'--include-path={}'.format(':'.join(STATICFILES_DIRS)) -PIPELINE_CSS = { - "all": {"source_filenames": ( - "compile_bootstrap.less", - "bootstrap/dist/css/bootstrap-theme.css", - "fontawesome/css/font-awesome.css", - "jquery-simple-slider/css/simple-slider.css", - "intro.js/introjs.css", - "template.less", - "dashboard/dashboard.less", - "network/network.less", - "autocomplete_light/style.css", - ), - "output_filename": "all.css", - } -} -PIPELINE_JS = { - "all": {"source_filenames": ( - # "jquery/dist/jquery.js", # included separately - "bootbox/bootbox.js", - "bootstrap/dist/js/bootstrap.js", - "intro.js/intro.js", - "jquery-knob/dist/jquery.knob.min.js", - "jquery-simple-slider/js/simple-slider.js", - "favico.js/favico.js", - "datatables/media/js/jquery.dataTables.js", - "dashboard/dashboard.js", - "dashboard/activity.js", - "dashboard/group-details.js", - "dashboard/group-list.js", - "dashboard/js/stupidtable.min.js", # no bower file - "dashboard/node-create.js", - "dashboard/node-details.js", - "dashboard/node-list.js", - "dashboard/profile.js", - "dashboard/store.js", - "dashboard/template-list.js", - "dashboard/vm-common.js", - "dashboard/vm-create.js", - "dashboard/vm-list.js", - "dashboard/help.js", - "js/host.js", - "js/network.js", - "js/switch-port.js", - "js/host-list.js", - "autocomplete_light/autocomplete.js", - "autocomplete_light/widget.js", - "autocomplete_light/addanother.js", - "autocomplete_light/text_widget.js", - "autocomplete_light/remote.js", - ), - "output_filename": "all.js", - }, - "vm-detail": {"source_filenames": ( - "clipboard/dist/clipboard.min.js", - "dashboard/vm-details.js", - "no-vnc/include/util.js", - "no-vnc/include/webutil.js", - "no-vnc/include/base64.js", - "no-vnc/include/websock.js", - "no-vnc/include/des.js", - "no-vnc/include/keysym.js", - "no-vnc/include/keysymdef.js", - "no-vnc/include/keyboard.js", - "no-vnc/include/input.js", - "no-vnc/include/display.js", - "no-vnc/include/jsunzip.js", - "no-vnc/include/rfb.js", - "dashboard/vm-console.js", - "dashboard/vm-tour.js", - ), - "output_filename": "vm-detail.js", + +PIPELINE = { + 'COMPILERS' : ('pipeline.compilers.less.LessCompiler',), + 'LESS_ARGUMENTS': u'--include-path={}'.format(':'.join(STATICFILES_DIRS)), + 'CSS_COMPRESSOR': 'pipeline.compressors.yuglify.YuglifyCompressor', + 'JS_COMPRESSOR': None, + 'DISABLE_WRAPPER': True, + 'STYLESHEETS': { + "all": {"source_filenames": ( + "compile_bootstrap.less", + "bootstrap/dist/css/bootstrap-theme.css", + "fontawesome/css/font-awesome.css", + "jquery-simple-slider/css/simple-slider.css", + "intro.js/introjs.css", + "template.less", + "dashboard/dashboard.less", + "network/network.less", + "autocomplete_light/vendor/select2/dist/css/select2.css", + "autocomplete_light/select2.css", + ), + "output_filename": "all.css", + } }, - "datastore": {"source_filenames": ( - "chart.js/dist/Chart.min.js", - "dashboard/datastore-details.js" - ), - "output_filename": "datastore.js", + 'JAVASCRIPT': { + "all": {"source_filenames": ( + # "jquery/dist/jquery.js", # included separately + "bootbox/bootbox.js", + "bootstrap/dist/js/bootstrap.js", + "intro.js/intro.js", + "jquery-knob/dist/jquery.knob.min.js", + "jquery-simple-slider/js/simple-slider.js", + "favico.js/favico.js", + "datatables/media/js/jquery.dataTables.js", + "autocomplete_light/jquery.init.js", + "autocomplete_light/autocomplete.init.js", + "autocomplete_light/vendor/select2/dist/js/select2.js", + "autocomplete_light/select2.js", + "dashboard/dashboard.js", + "dashboard/activity.js", + "dashboard/group-details.js", + "dashboard/group-list.js", + "dashboard/js/stupidtable.min.js", # no bower file + "dashboard/node-create.js", + "dashboard/node-details.js", + "dashboard/node-list.js", + "dashboard/profile.js", + "dashboard/store.js", + "dashboard/template-list.js", + "dashboard/vm-common.js", + "dashboard/vm-create.js", + "dashboard/vm-list.js", + "dashboard/help.js", + "js/host.js", + "js/network.js", + "js/switch-port.js", + "js/host-list.js", + ), + "output_filename": "all.js", + }, + "vm-detail": {"source_filenames": ( + "clipboard/dist/clipboard.min.js", + "dashboard/vm-details.js", + "no-vnc/include/util.js", + "no-vnc/include/webutil.js", + "no-vnc/include/base64.js", + "no-vnc/include/websock.js", + "no-vnc/include/des.js", + "no-vnc/include/keysym.js", + "no-vnc/include/keysymdef.js", + "no-vnc/include/keyboard.js", + "no-vnc/include/input.js", + "no-vnc/include/display.js", + "no-vnc/include/jsunzip.js", + "no-vnc/include/rfb.js", + "dashboard/vm-console.js", + "dashboard/vm-tour.js", + ), + "output_filename": "vm-detail.js", + }, + "datastore": {"source_filenames": ( + "chart.js/dist/Chart.min.js", + "dashboard/datastore-details.js" + ), + "output_filename": "datastore.js", + }, }, } - ########## SECRET CONFIGURATION # See: https://docs.djangoproject.com/en/dev/ref/settings/#secret-key # Note: This key should only be used for development and testing. @@ -279,26 +278,31 @@ FIXTURE_DIRS = ( ########## TEMPLATE CONFIGURATION -# See: https://docs.djangoproject.com/en/dev/ref/settings/#template-context-processors -TEMPLATE_CONTEXT_PROCESSORS = ( - 'django.contrib.auth.context_processors.auth', - 'django.core.context_processors.debug', - 'django.core.context_processors.i18n', - 'django.core.context_processors.media', - 'django.core.context_processors.static', - 'django.core.context_processors.tz', - 'django.contrib.messages.context_processors.messages', - 'django.core.context_processors.request', - 'dashboard.context_processors.notifications', - 'dashboard.context_processors.extract_settings', - 'dashboard.context_processors.broadcast_messages', -) -# See: https://docs.djangoproject.com/en/dev/ref/settings/#template-dirs -TEMPLATE_DIRS = ( - normpath(join(SITE_ROOT, '../../site-circle/templates')), - normpath(join(SITE_ROOT, 'templates')), -) +# See: https://docs.djangoproject.com/en/dev/ref/settings/#TEMPLATES +TEMPLATES = [{ + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS' : ( + normpath(join(SITE_ROOT, '../../site-circle/templates')), + normpath(join(SITE_ROOT, 'templates')), + ), + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': ( + 'django.contrib.auth.context_processors.auth', + 'django.template.context_processors.debug', + 'django.template.context_processors.i18n', + 'django.template.context_processors.media', + 'django.template.context_processors.static', + 'django.template.context_processors.tz', + 'django.contrib.messages.context_processors.messages', + 'django.template.context_processors.request', + 'dashboard.context_processors.notifications', + 'dashboard.context_processors.extract_settings', + 'dashboard.context_processors.broadcast_messages', + ), + }, +}] ########## END TEMPLATE CONFIGURATION @@ -336,6 +340,10 @@ DJANGO_APPS = ( # Useful template tags: # 'django.contrib.humanize', + # Django autocomplete light + # it needs registering before django admin + 'dal', + 'dal_select2', # Admin panel and documentation: 'django.contrib.admin', # 'django.contrib.admindocs', @@ -348,7 +356,6 @@ THIRD_PARTY_APPS = ( 'taggit', 'statici18n', 'django_sshkey', - 'autocomplete_light', 'pipeline', ) diff --git a/circle/circle/settings/local.py b/circle/circle/settings/local.py index 96f2322..cc5b2ae 100644 --- a/circle/circle/settings/local.py +++ b/circle/circle/settings/local.py @@ -27,7 +27,7 @@ from base import * # noqa DEBUG = True # See: https://docs.djangoproject.com/en/dev/ref/settings/#template-debug -TEMPLATE_DEBUG = DEBUG +TEMPLATES[0]['OPTIONS']['debug'] = DEBUG ########## END DEBUG CONFIGURATION SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTOCOL', 'https') @@ -110,8 +110,10 @@ if DEBUG: from django.dispatch import Signal Signal.send_robust = Signal.send -PIPELINE_COMPILERS = ( +PIPELINE["COMPILERS"] = ( 'dashboard.compilers.DummyLessCompiler', ) ADMIN_ENABLED = True + +ALLOWED_HOSTS = ['*'] diff --git a/circle/circle/settings/selenium_test.py b/circle/circle/settings/selenium_test.py index aaaeb54..5966f36 100644 --- a/circle/circle/settings/selenium_test.py +++ b/circle/circle/settings/selenium_test.py @@ -14,9 +14,10 @@ # # 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 -from .base import * # noqa +from .base import * # flake8:noqa # fix https://github.com/django-nose/django-nose/issues/197 diff --git a/circle/circle/settings/test.py b/circle/circle/settings/test.py index a4dd668..a44b068 100644 --- a/circle/circle/settings/test.py +++ b/circle/circle/settings/test.py @@ -38,7 +38,11 @@ INSTALLED_APPS += ( 'django_nose', ) TEST_RUNNER = 'django_nose.NoseTestSuiteRunner' -NOSE_ARGS = ['--with-doctest', '--exclude-dir=dashboard/tests/selenium'] +NOSE_ARGS = [ + '--with-doctest', + '--exclude-dir=dashboard/tests/selenium', + '--exclude=circle' +] PASSWORD_HASHERS = ['django.contrib.auth.hashers.MD5PasswordHasher'] CACHES = { @@ -59,7 +63,7 @@ for i in LOCAL_APPS: # don't print SQL queries LOGGING['handlers']['null'] = {'level': "DEBUG", - 'class': "django.utils.log.NullHandler"} + 'class': "logging.NullHandler"} LOGGING['loggers']['django.db.backends'] = { 'handlers': ['null'], 'propagate': False, diff --git a/circle/circle/urls.py b/circle/circle/urls.py index 0130af6..a9a81bc 100644 --- a/circle/circle/urls.py +++ b/circle/circle/urls.py @@ -15,13 +15,16 @@ # You should have received a copy of the GNU General Public License along # with CIRCLE. If not, see <http://www.gnu.org/licenses/>. -from django.conf.urls import patterns, include, url +from django.conf.urls import include, url from django.views.generic import TemplateView from django.conf import settings from django.contrib import admin from django.core.urlresolvers import reverse from django.shortcuts import redirect +from django.contrib.auth.views import ( + password_reset_confirm, password_reset +) from circle.settings.base import get_env_variable @@ -33,9 +36,7 @@ from firewall.views import add_blacklist_item admin.autodiscover() -urlpatterns = patterns( - '', - +urlpatterns = [ url(r'^$', lambda x: redirect(reverse("dashboard.index"))), url(r'^network/', include('network.urls')), url(r'^blacklist-add/', add_blacklist_item), @@ -45,12 +46,11 @@ urlpatterns = patterns( # django/contrib/auth/urls.py (care when new version) url((r'^accounts/reset/(?P<uidb64>[0-9A-Za-z_\-]+)/' r'(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$'), - 'django.contrib.auth.views.password_reset_confirm', + password_reset_confirm, {'set_password_form': CircleSetPasswordForm}, name='accounts.password_reset_confirm' ), - url(r'^accounts/password/reset/$', ("django.contrib.auth.views." - "password_reset"), + url(r'^accounts/password/reset/$', password_reset, {'password_reset_form': CirclePasswordResetForm}, name="accounts.password-reset", ), @@ -73,27 +73,24 @@ urlpatterns = patterns( name="info.support"), url(r'^info/resize-how-to/$', ResizeHelpView.as_view(), name="info.resize"), -) +] if 'rosetta' in settings.INSTALLED_APPS: - urlpatterns += patterns( - '', + urlpatterns += [ url(r'^rosetta/', include('rosetta.urls')), - ) + ] if settings.ADMIN_ENABLED: - urlpatterns += patterns( - '', + urlpatterns += [ url(r'^admin/', include(admin.site.urls)), - ) + ] if get_env_variable('DJANGO_SAML', 'FALSE') == 'TRUE': - urlpatterns += patterns( - '', + urlpatterns += [ (r'^saml2/', include('djangosaml2.urls')), - ) + ] handler500 = 'common.views.handler500' handler403 = 'common.views.handler403' diff --git a/circle/common/views.py b/circle/common/views.py index 5a6a3a2..9613d94 100644 --- a/circle/common/views.py +++ b/circle/common/views.py @@ -45,7 +45,8 @@ def handler500(request): logger.exception("unhandled exception") ctx = get_context(request, exception) try: - resp = render_to_response("500.html", ctx, RequestContext(request)) + resp = render_to_response("500.html", ctx, + RequestContext(request).flatten()) except: resp = render_to_response("500.html", ctx) resp.status_code = 500 diff --git a/circle/dashboard/forms.py b/circle/dashboard/forms.py index d1a0330..23cca74 100644 --- a/circle/dashboard/forms.py +++ b/circle/dashboard/forms.py @@ -31,7 +31,7 @@ from django.contrib.auth.models import User, Group from django.core.validators import URLValidator from django.core.exceptions import PermissionDenied, ValidationError -import autocomplete_light +from dal import autocomplete from crispy_forms.helper import FormHelper from crispy_forms.layout import ( Layout, Div, BaseInput, Field, HTML, Submit, TEMPLATE_PACK, Fieldset @@ -43,7 +43,6 @@ from crispy_forms.bootstrap import FormActions from django import forms from django.contrib.auth.forms import UserCreationForm as OrgUserCreationForm from django.forms.widgets import TextInput, HiddenInput -from django.template import Context from django.template.loader import render_to_string from django.utils.html import escape, format_html from django.utils.safestring import mark_safe @@ -67,6 +66,7 @@ from .validators import domain_validator from dashboard.models import ConnectCommand, create_profile + LANGUAGES_WITH_CODE = ((l[0], string_concat(l[1], " (", l[0], ")")) for l in LANGUAGES) @@ -1180,8 +1180,7 @@ class AnyTag(Div): fields += render_field(field, form, form_style, context, template_pack=template_pack) - return render_to_string(self.template, Context({'tag': self, - 'fields': fields})) + return render_to_string(self.template, {'tag': self, 'fields': fields}) class WorkingBaseInput(BaseInput): @@ -1334,27 +1333,31 @@ class UserEditForm(forms.ModelForm): class AclUserOrGroupAddForm(forms.Form): - name = forms.CharField(widget=autocomplete_light.TextWidget( - 'AclUserGroupAutocomplete', - attrs={'class': 'form-control', - 'placeholder': _("Name of group or user")})) + name = forms.CharField( + widget=autocomplete.ListSelect2( + url='autocomplete.acl.user-group', + attrs={'class': 'form-control', + 'data-html': 'true', + 'data-placeholder': _("Name of group or user")})) class TransferOwnershipForm(forms.Form): name = forms.CharField( - widget=autocomplete_light.TextWidget( - 'AclUserAutocomplete', + widget=autocomplete.ListSelect2( + url='autocomplete.acl.user', attrs={'class': 'form-control', - 'placeholder': _("Name of user")}), + 'data-html': 'true', + 'data-placeholder': _("Name of user")}), label=_("E-mail address or identifier of user")) class AddGroupMemberForm(forms.Form): new_member = forms.CharField( - widget=autocomplete_light.TextWidget( - 'AclUserAutocomplete', + widget=autocomplete.ListSelect2( + url='autocomplete.acl.user', attrs={'class': 'form-control', - 'placeholder': _("Name of user")}), + 'data-html': 'true', + 'data-placeholder': _("Name of user")}), label=_("E-mail address or identifier of user")) diff --git a/circle/dashboard/management/commands/init.py b/circle/dashboard/management/commands/init.py index 7efd9e5..34d4dcc 100644 --- a/circle/dashboard/management/commands/init.py +++ b/circle/dashboard/management/commands/init.py @@ -18,7 +18,6 @@ from __future__ import unicode_literals, absolute_import import logging -from optparse import make_option from django.contrib.auth.models import User from django.core.management.base import BaseCommand @@ -32,19 +31,18 @@ logger = logging.getLogger(__name__) class Command(BaseCommand): - option_list = BaseCommand.option_list + ( - make_option('--force', action="store_true"), - make_option('--external-net'), - make_option('--management-net'), - make_option('--vm-net'), - make_option('--external-if'), - make_option('--management-if'), - make_option('--vm-if'), - make_option('--datastore-queue'), - make_option('--firewall-queue'), - make_option('--admin-user'), - make_option('--admin-pass'), - ) + def add_arguments(self, parser): + parser.add_argument('--force', action="store_true") + parser.add_argument('--external-net') + parser.add_argument('--management-net') + parser.add_argument('--vm-net') + parser.add_argument('--external-if') + parser.add_argument('--management-if') + parser.add_argument('--vm-if') + parser.add_argument('--datastore-queue') + parser.add_argument('--firewall-queue') + parser.add_argument('--admin-user') + parser.add_argument('--admin-pass') def create(self, model, field, **kwargs): value = kwargs[field] @@ -59,14 +57,15 @@ class Command(BaseCommand): # http://docs.saltstack.com/en/latest/ref/states/all/salt.states.cmd.html def print_state(self): - print "\nchanged=%s" % ("yes" if self.changed else "no") + self.stdout.write("\nchanged=%s" % ("yes" if self.changed else "no")) def handle(self, *args, **options): self.changed = False if (DataStore.objects.exists() and Vlan.objects.exists() and not options['force']): - return self.print_state() + self.print_state() + return admin = self.create(User, 'username', username=options['admin_user'], is_superuser=True, is_staff=True) @@ -153,4 +152,4 @@ class Command(BaseCommand): direction='out', action='accept', foreign_network=vg_net, vlan=man) - return self.print_state() + self.print_state() diff --git a/circle/dashboard/migrations/0006_auto_20170707_1909.py b/circle/dashboard/migrations/0006_auto_20170707_1909.py new file mode 100644 index 0000000..cbc4e20 --- /dev/null +++ b/circle/dashboard/migrations/0006_auto_20170707_1909.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.3 on 2017-07-07 19:09 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('dashboard', '0005_profile_two_factor_secret'), + ] + + operations = [ + migrations.AlterField( + model_name='connectcommand', + name='name', + field=models.CharField(help_text='Name of your custom command.', max_length=128, verbose_name='name'), + ), + ] diff --git a/circle/dashboard/models.py b/circle/dashboard/models.py index a906763..2050590 100644 --- a/circle/dashboard/models.py +++ b/circle/dashboard/models.py @@ -151,7 +151,7 @@ class ConnectCommand(Model): access_method = CharField(max_length=10, choices=ACCESS_METHODS, verbose_name=_('access method'), help_text=_('Type of the remote access method.')) - name = CharField(max_length="128", verbose_name=_('name'), blank=False, + name = CharField(max_length=128, verbose_name=_('name'), blank=False, help_text=_("Name of your custom command.")) template = CharField(blank=True, null=True, max_length=256, verbose_name=_('command template'), diff --git a/circle/dashboard/static/dashboard/dashboard.js b/circle/dashboard/static/dashboard/dashboard.js index f2c65df..4db99a6 100644 --- a/circle/dashboard/static/dashboard/dashboard.js +++ b/circle/dashboard/static/dashboard/dashboard.js @@ -508,13 +508,6 @@ $.ajaxSetup({ } }); -/* for autocomplete */ -$(function() { - yourlabs.TextWidget.prototype.getValue = function(choice) { - return choice.children().html(); - }; -}); - var tagsToReplace = { '&': '&', '<': '<', diff --git a/circle/dashboard/static/dashboard/dashboard.less b/circle/dashboard/static/dashboard/dashboard.less index 548ad83..3c0447f 100644 --- a/circle/dashboard/static/dashboard/dashboard.less +++ b/circle/dashboard/static/dashboard/dashboard.less @@ -1031,7 +1031,7 @@ textarea[name="new_members"] { font-weight: bold; } -.hilight .autocomplete-hl { +.select2-results__option--highlighted .autocomplete-hl { color: orange; } diff --git a/circle/dashboard/templates/dashboard/_manage_access.html b/circle/dashboard/templates/dashboard/_manage_access.html index 99190ed..fca5542 100644 --- a/circle/dashboard/templates/dashboard/_manage_access.html +++ b/circle/dashboard/templates/dashboard/_manage_access.html @@ -24,7 +24,7 @@ <td> <select class="form-control" name="perm-u-{{i.user.id}}"{% if i.level not in acl.allowed_levels %} disabled{% endif %}> {% for id, name in acl.levels %} - <option{%if id = i.level%} selected="selected"{%endif%} + <option{%if id == i.level%} selected="selected"{%endif%} {% if id not in acl.allowed_levels %} disabled{% endif %} value="{{id}}">{{name}}</option> {% endfor %} @@ -46,7 +46,7 @@ <td> <select class="form-control" name="perm-g-{{i.group.id}}{% if i.level not in acl.allowed_levels %} disabled{% endif %}"> {% for id, name in acl.levels %} - <option{%if id = i.level%} selected="selected"{%endif%} + <option{%if id == i.level%} selected="selected"{%endif%} {% if id not in acl.allowed_levels %} disabled{% endif %} value="{{id}}">{{name}}</option> {% endfor %} diff --git a/circle/dashboard/templates/dashboard/group-detail.html b/circle/dashboard/templates/dashboard/group-detail.html index 19119d9..54b194b 100644 --- a/circle/dashboard/templates/dashboard/group-detail.html +++ b/circle/dashboard/templates/dashboard/group-detail.html @@ -137,8 +137,10 @@ {% if user.is_superuser %} <hr /> - <script type="text/javascript" src="/static/admin/js/jquery.min.js"></script> - <script type="text/javascript" src="/static/admin/js/jquery.init.js"></script> + <script type="text/javascript" src="/static/admin/js/vendor/jquery/jquery.min.js"></script> + <script type="text/javascript" src="/static/admin/js/jquery.init.js"></script> + <script type="text/javascript" src="/static/autocomplete_light/jquery.init.js"></script> + <script type="text/javascript" src="/static/autocomplete_light/vendor/select2/dist/js/select2.js"></script> {{ group_perm_form.media }} <h3>{% trans "Group permissions" %}</h3> diff --git a/circle/dashboard/templates/dashboard/lease-edit.html b/circle/dashboard/templates/dashboard/lease-edit.html index 03c282e..bda8c8b 100644 --- a/circle/dashboard/templates/dashboard/lease-edit.html +++ b/circle/dashboard/templates/dashboard/lease-edit.html @@ -52,7 +52,7 @@ <td> <select class="form-control" name="perm-u-{{i.user.id}}"> {% for id, name in acl.levels %} - <option{%if id = i.level%} selected="selected"{%endif%} value="{{id}}">{{name}}</option> + <option{%if id == i.level%} selected="selected"{%endif%} value="{{id}}">{{name}}</option> {% endfor %} </select> </td> @@ -72,7 +72,7 @@ <td> <select class="form-control" name="perm-g-{{i.group.id}}"> {% for id, name in acl.levels %} - <option{%if id = i.level%} selected="selected"{%endif%} value="{{id}}">{{name}}</option> + <option{%if id == i.level%} selected="selected"{%endif%} value="{{id}}">{{name}}</option> {% endfor %} </select> </td> diff --git a/circle/dashboard/templates/django_tables2/table_no_page.html b/circle/dashboard/templates/django_tables2/table_no_page.html index 866a81a..c6281bf 100644 --- a/circle/dashboard/templates/django_tables2/table_no_page.html +++ b/circle/dashboard/templates/django_tables2/table_no_page.html @@ -4,9 +4,9 @@ {% if table.page %} <div class="table-container"> {% endif %} +{% endspaceless %} {% block table %} <table{% if table.attrs %} {{ table.attrs.as_html }}{% endif %}> - {% nospaceless %} {% block table.thead %} <thead> <tr> @@ -42,10 +42,9 @@ {% block table.tfoot %} <tfoot></tfoot> {% endblock table.tfoot %} - {% endnospaceless %} </table> {% endblock table %} - +{% spaceless %} {% if table.page %} </div> {% endif %} diff --git a/circle/dashboard/tests/test_mockedviews.py b/circle/dashboard/tests/test_mockedviews.py index 511edbd..adb38fd 100644 --- a/circle/dashboard/tests/test_mockedviews.py +++ b/circle/dashboard/tests/test_mockedviews.py @@ -21,7 +21,7 @@ import warnings from factory import Factory, Sequence from mock import patch, MagicMock -from django.contrib.auth.models import User +from django.contrib.auth.models import User, AnonymousUser from django.core.exceptions import PermissionDenied from django.core.signing import TimestampSigner, JSONSerializer, b64_encode from django.http import HttpRequest, Http404, QueryDict @@ -629,13 +629,13 @@ def FakeRequestFactory(user=None, **kwargs): ''' if user is None: - user = UserFactory() auth = kwargs.pop('authenticated', True) - user.is_authenticated = lambda: auth + user = UserFactory() if auth else AnonymousUser() user.is_superuser = kwargs.pop('superuser', False) if kwargs.pop('has_perms_mock', False): user.has_perms = MagicMock(return_value=True) - user.save() + if auth: + user.save() request = HttpRequest() request.user = user diff --git a/circle/dashboard/tests/test_templates.py b/circle/dashboard/tests/test_templates.py index c6a356e..370aa36 100644 --- a/circle/dashboard/tests/test_templates.py +++ b/circle/dashboard/tests/test_templates.py @@ -29,22 +29,22 @@ class TemplateSyntaxTestCase(unittest.TestCase): def test_templates(self): """Test all templates for syntax errors.""" for loader in Engine.get_default().template_loaders: - print loader + print(loader) self._test_dir(loader.get_template_sources('')) def _test_dir(self, dir, path="/"): for i in dir: - i = join(path, i) + i = join(path, str(i)) if isfile(i): self._test_template(join(path, i)) elif isdir(i): - print "%s:" % i + print("%s:" % i) self._test_dir(listdir(i), i) def _test_template(self, path): - print path + print(path) try: Template(open(path).read()).render(Context({})) except (NoReverseMatch, VariableDoesNotExist, KeyError, AttributeError, ValueError, ) as e: - print e + print(e) diff --git a/circle/dashboard/tests/test_views.py b/circle/dashboard/tests/test_views.py index e0389b6..f77157e 100644 --- a/circle/dashboard/tests/test_views.py +++ b/circle/dashboard/tests/test_views.py @@ -1902,9 +1902,9 @@ class TwoFactorTest(LoginMixin, TestCase): response = c.get("/two-factor-login/", follow=True) self.assertItemsEqual( response.redirect_chain, - [('http://testserver/', 302), - ('http://testserver/dashboard/', 302), - ('http://testserver/accounts/login/?next=/dashboard/', 302)] + [('/', 302), + ('/dashboard/', 302), + ('/accounts/login/?next=/dashboard/', 302)] ) def test_straight_to_2fa_as_user(self): @@ -1913,6 +1913,6 @@ class TwoFactorTest(LoginMixin, TestCase): response = c.get("/two-factor-login/", follow=True) self.assertItemsEqual( response.redirect_chain, - [('http://testserver/', 302), - ('http://testserver/dashboard/', 302)] + [('/', 302), + ('/dashboard/', 302)] ) diff --git a/circle/dashboard/urls.py b/circle/dashboard/urls.py index ab0fa2e..d465676 100644 --- a/circle/dashboard/urls.py +++ b/circle/dashboard/urls.py @@ -16,9 +16,8 @@ # with CIRCLE. If not, see <http://www.gnu.org/licenses/>. from __future__ import absolute_import -from django.conf.urls import patterns, url, include +from django.conf.urls import url -import autocomplete_light from vm.models import Instance from .views import ( AclUpdateView, FavouriteView, GroupAclUpdateView, GroupDelete, @@ -56,14 +55,13 @@ from .views import ( StorageDetail, DiskDetail, MessageList, MessageDetail, MessageCreate, MessageDelete, EnableTwoFactorView, DisableTwoFactorView, + AclUserGroupAutocomplete, AclUserAutocomplete, ) from .views.vm import vm_ops, vm_mass_ops from .views.node import node_ops -autocomplete_light.autodiscover() -urlpatterns = patterns( - '', +urlpatterns = [ url(r'^$', IndexView.as_view(), name="dashboard.index"), url(r"^profile/list/$", UserList.as_view(), name="dashboard.views.user-list"), @@ -217,8 +215,6 @@ urlpatterns = patterns( ConnectCommandCreate.as_view(), name="dashboard.views.connect-command-create"), - url(r'^autocomplete/', include('autocomplete_light.urls')), - url(r"^store/list/$", StoreList.as_view(), name="dashboard.views.store-list"), url(r"^store/download/$", store_download, @@ -253,22 +249,26 @@ urlpatterns = patterns( name="dashboard.views.message-create"), url(r'^message/delete/(?P<pk>\d+)/$', MessageDelete.as_view(), name="dashboard.views.message-delete"), -) -urlpatterns += patterns( - '', - *(url(r'^vm/(?P<pk>\d+)/op/%s/$' % op, v.as_view(), name=v.get_urlname()) - for op, v in vm_ops.iteritems()) -) + url(r'^autocomplete/acl/user-group/$', + AclUserGroupAutocomplete.as_view(), + name='autocomplete.acl.user-group'), + url(r'^autocomplete/acl/user/$', + AclUserAutocomplete.as_view(), + name='autocomplete.acl.user'), +] -urlpatterns += patterns( - '', - *(url(r'^vm/mass_op/%s/$' % op, v.as_view(), name=v.get_urlname()) - for op, v in vm_mass_ops.iteritems()) -) +urlpatterns += [ + url(r'^vm/(?P<pk>\d+)/op/%s/$' % op, v.as_view(), name=v.get_urlname()) + for op, v in vm_ops.iteritems() +] -urlpatterns += patterns( - '', - *(url(r'^node/(?P<pk>\d+)/op/%s/$' % op, v.as_view(), name=v.get_urlname()) - for op, v in node_ops.iteritems()) -) +urlpatterns += [ + url(r'^vm/mass_op/%s/$' % op, v.as_view(), name=v.get_urlname()) + for op, v in vm_mass_ops.iteritems() +] + +urlpatterns += [ + url(r'^node/(?P<pk>\d+)/op/%s/$' % op, v.as_view(), name=v.get_urlname()) + for op, v in node_ops.iteritems() +] diff --git a/circle/dashboard/views/__init__.py b/circle/dashboard/views/__init__.py index f799d8d..13af4ac 100644 --- a/circle/dashboard/views/__init__.py +++ b/circle/dashboard/views/__init__.py @@ -15,3 +15,4 @@ from graph import * from storage import * from request import * from message import * +from autocomplete import * diff --git a/circle/dashboard/autocomplete_light_registry.py b/circle/dashboard/views/autocomplete.py similarity index 63% rename from circle/dashboard/autocomplete_light_registry.py rename to circle/dashboard/views/autocomplete.py index 47e5d8e..be6984b 100644 --- a/circle/dashboard/autocomplete_light_registry.py +++ b/circle/dashboard/views/autocomplete.py @@ -15,13 +15,16 @@ # You should have received a copy of the GNU General Public License along # with CIRCLE. If not, see <http://www.gnu.org/licenses/>. -import autocomplete_light +import json +from dal import autocomplete from django.contrib.auth.models import User from django.utils.html import escape from django.utils.translation import ugettext as _ +from django.db.models import Q +from django.http import HttpResponse -from .views import AclUpdateView -from .models import Profile +from ..views import AclUpdateView +from ..models import Profile def highlight(field, q, none_wo_match=True): @@ -48,13 +51,21 @@ def highlight(field, q, none_wo_match=True): return escape(field) -class AclUserGroupAutocomplete(autocomplete_light.AutocompleteGenericBase): - search_fields = ( - ('first_name', 'last_name', 'username', 'email', 'profile__org_id'), - ('name', 'groupprofile__org_id'), - ) - choice_html_format = (u'<span data-value="%s"><span style="display:none"' - u'>%s</span>%s</span>') +class AclUserAutocomplete(autocomplete.Select2ListView): + search_fields = ('first_name', 'last_name', 'username', + 'email', 'profile__org_id') + + def filter(self, qs, search_fields): + if self.q: + condition = Q() + for field in search_fields: + condition |= Q(**{field + '__icontains': unicode(self.q)}) + return list(qs.filter(condition)) + return [] + + def get_list(self): + users = AclUpdateView.get_allowed_users(self.request.user) + return self.filter(users, self.search_fields) def choice_displayed_text(self, choice): q = unicode(self.request.GET.get('q', '')) @@ -71,35 +82,17 @@ class AclUserGroupAutocomplete(autocomplete_light.AutocompleteGenericBase): else: return _('%s (group)') % name - def choice_html(self, choice): - return self.choice_html_format % ( - self.choice_value(choice), self.choice_label(choice), - self.choice_displayed_text(choice)) - - def choices_for_request(self): - user = self.request.user - self.choices = (AclUpdateView.get_allowed_users(user), - AclUpdateView.get_allowed_groups(user)) - return super(AclUserGroupAutocomplete, self).choices_for_request() - - def autocomplete_html(self): - html = [] - - for choice in self.choices_for_request(): - html.append(self.choice_html(choice)) - - if not html: - html = self.empty_html_format % _('no matches found').capitalize() - - return self.autocomplete_html_format % ''.join(html) - + def get(self, *args, **kwargs): + return HttpResponse(json.dumps({ + 'results': [dict(id=unicode(r), text=self.choice_displayed_text(r)) + for r in self.get_list()] + }), content_type="application/json") -class AclUserAutocomplete(AclUserGroupAutocomplete): - def choices_for_request(self): - user = self.request.user - self.choices = (AclUpdateView.get_allowed_users(user), ) - return super(AclUserGroupAutocomplete, self).choices_for_request() +class AclUserGroupAutocomplete(AclUserAutocomplete): + group_search_fields = ('name', 'groupprofile__org_id') -autocomplete_light.register(AclUserGroupAutocomplete) -autocomplete_light.register(AclUserAutocomplete) + def get_list(self): + groups = AclUpdateView.get_allowed_groups(self.request.user) + groups = self.filter(groups, self.group_search_fields) + return super(AclUserGroupAutocomplete, self).get_list() + groups diff --git a/circle/dashboard/views/graph.py b/circle/dashboard/views/graph.py index 07593ab..eadbffe 100644 --- a/circle/dashboard/views/graph.py +++ b/circle/dashboard/views/graph.py @@ -171,6 +171,7 @@ class TemplateVms(object): def get_minmax(self): return (0, None) + register_graph(TemplateVms, 'instances', TemplateGraphView) @@ -197,6 +198,7 @@ class Ram(object): def get_minmax(self): return (0, 105) + register_graph(Ram, 'memory', VmGraphView) register_graph(Ram, 'memory', NodeGraphView) @@ -212,6 +214,7 @@ class Cpu(object): else: return (0, self.obj.num_cores * 100 + 5) + register_graph(Cpu, 'cpu', VmGraphView) register_graph(Cpu, 'cpu', NodeGraphView) @@ -236,6 +239,7 @@ class VmNetwork(object): params)) return 'group(%s)' % ','.join(metrics) if metrics else None + register_graph(VmNetwork, 'network', VmGraphView) @@ -251,6 +255,7 @@ class NodeNetwork(object): '10), ".*\.bytes_(sent|recv)-([a-zA-Z0-9]+).*", "\\2 \\1")' % ( self.obj.metric_prefix)) + register_graph(NodeNetwork, 'network', NodeGraphView) @@ -262,6 +267,7 @@ class NodeVms(object): def get_minmax(self): return (0, None) + register_graph(NodeVms, 'vm', NodeGraphView) @@ -282,6 +288,7 @@ class NodeAllocated(object): def get_minmax(self): return (0, None) + register_graph(NodeAllocated, 'alloc', NodeGraphView) @@ -302,6 +309,7 @@ class NodeListAllocated(object): def get_minmax(self): return (0, None) + register_graph(NodeListAllocated, 'alloc', NodeListGraphView) @@ -315,4 +323,5 @@ class NodeListVms(object): def get_minmax(self): return (0, None) + register_graph(NodeListVms, 'vm', NodeListGraphView) diff --git a/circle/dashboard/views/index.py b/circle/dashboard/views/index.py index 24d964d..d125de7 100644 --- a/circle/dashboard/views/index.py +++ b/circle/dashboard/views/index.py @@ -18,7 +18,7 @@ from __future__ import unicode_literals, absolute_import import logging -from django.core.cache import get_cache +from django.core.cache import cache from django.core.urlresolvers import reverse from django.conf import settings from django.contrib.auth.models import Group, User @@ -103,7 +103,6 @@ class IndexView(LoginRequiredMixin, TemplateView): # toplist if settings.STORE_URL: cache_key = "files-%d" % self.request.user.pk - cache = get_cache("default") files = cache.get(cache_key) if not files: try: diff --git a/circle/dashboard/views/node.py b/circle/dashboard/views/node.py index 3fed127..7561f5c 100644 --- a/circle/dashboard/views/node.py +++ b/circle/dashboard/views/node.py @@ -27,7 +27,6 @@ from django.db.models import Count from django.forms.models import inlineformset_factory from django.http import HttpResponse from django.shortcuts import redirect -from django.template import RequestContext from django.template.loader import render_to_string from django.utils.translation import ugettext as _ from django.views.generic import DetailView, TemplateView, View @@ -330,8 +329,12 @@ class NodeActivityView(LoginRequiredMixin, SuperuserRequiredMixin, View): response = { 'activities': render_to_string( "dashboard/node-detail/_activity-timeline.html", - RequestContext(request, {'activities': activities, - 'show_show_all': show_show_all})) + { + 'activities': activities, + 'show_show_all': show_show_all + }, + request + ) } return HttpResponse( diff --git a/circle/dashboard/views/store.py b/circle/dashboard/views/store.py index d7acf3c..be26d41 100644 --- a/circle/dashboard/views/store.py +++ b/circle/dashboard/views/store.py @@ -24,12 +24,11 @@ from django.conf import settings from django.contrib import messages from django.contrib.auth.decorators import login_required from django.template.defaultfilters import urlencode -from django.core.cache import get_cache +from django.core.cache import cache from django.core.exceptions import SuspiciousOperation from django.core.urlresolvers import reverse from django.http import HttpResponse from django.shortcuts import redirect, render_to_response, render -from django.template import RequestContext from django.utils.translation import ugettext as _ from django.views.decorators.http import require_GET, require_POST from django.views.generic import TemplateView @@ -65,7 +64,7 @@ class StoreList(LoginRequiredMixin, TemplateView): context = self.get_context_data(**kwargs) return render_to_response( "dashboard/store/_list-box.html", - RequestContext(self.request, context), + context, self.request ) else: return super(StoreList, self).get(*args, **kwargs) @@ -193,7 +192,6 @@ def store_new_directory(request): @login_required def store_refresh_toplist(request): cache_key = "files-%d" % request.user.pk - cache = get_cache("default") try: store = Store(request.user) toplist = store.toplist() diff --git a/circle/dashboard/views/user.py b/circle/dashboard/views/user.py index 0e78b4b..120bdbd 100644 --- a/circle/dashboard/views/user.py +++ b/circle/dashboard/views/user.py @@ -231,7 +231,7 @@ class MyPreferencesView(UpdateView): def get(self, request, form=None, *args, **kwargs): # if this is not here, it won't work self.object = self.get_object() - context = self.get_context_data(*args, **kwargs) + context = self.get_context_data(form=form, *args, **kwargs) if form is not None: # a little cheating, users can't post invalid # language selection forms (without modifying the HTML) diff --git a/circle/dashboard/views/vm.py b/circle/dashboard/views/vm.py index 20c706c..294223a 100644 --- a/circle/dashboard/views/vm.py +++ b/circle/dashboard/views/vm.py @@ -32,7 +32,6 @@ from django.http import ( HttpResponse, Http404, HttpResponseRedirect, JsonResponse ) from django.shortcuts import redirect, get_object_or_404 -from django.template import RequestContext from django.template.loader import render_to_string from django.utils.translation import ( ugettext as _, ugettext_noop, ungettext_lazy, @@ -1289,15 +1288,15 @@ def vm_activity(request, pk): response['activities'] = render_to_string( "dashboard/vm-detail/_activity-timeline.html", - RequestContext(request, context), + context, request ) response['ops'] = render_to_string( "dashboard/vm-detail/_operations.html", - RequestContext(request, context), + context, request ) response['disk_ops'] = render_to_string( "dashboard/vm-detail/_disk-operations.html", - RequestContext(request, context), + context, request ) return HttpResponse( diff --git a/circle/firewall/admin.py b/circle/firewall/admin.py index 87a5e28..6d88e73 100644 --- a/circle/firewall/admin.py +++ b/circle/firewall/admin.py @@ -143,6 +143,7 @@ class SwitchPortAdmin(admin.ModelAdmin): class EthernetDeviceAdmin(admin.ModelAdmin): list_display = ('name', ) + admin.site.register(Host, HostAdmin) admin.site.register(Vlan, VlanAdmin) admin.site.register(Rule, RuleAdmin) diff --git a/circle/firewall/fields.py b/circle/firewall/fields.py index 8f93b33..c6ed7fb 100644 --- a/circle/firewall/fields.py +++ b/circle/firewall/fields.py @@ -52,12 +52,19 @@ class MACAddressFormField(forms.Field): class MACAddressField(models.Field): description = _('MAC Address object') - __metaclass__ = models.SubfieldBase def __init__(self, *args, **kwargs): kwargs['max_length'] = 17 super(MACAddressField, self).__init__(*args, **kwargs) + def deconstruct(self): + name, path, args, kwargs = super(MACAddressField, self).deconstruct() + del kwargs['max_length'] + return name, path, args, kwargs + + def from_db_value(self, value, expression, connection, context): + return self.to_python(value) + def to_python(self, value): if not value: return None @@ -105,16 +112,25 @@ class IPAddressFormField(forms.Field): class IPAddressField(models.Field): description = _('IP Network object') - __metaclass__ = models.SubfieldBase def __init__(self, version=4, serialize=True, *args, **kwargs): kwargs['max_length'] = 100 self.version = version super(IPAddressField, self).__init__(*args, **kwargs) + def deconstruct(self): + name, path, args, kwargs = super(IPAddressField, self).deconstruct() + del kwargs['max_length'] + if self.version != 4: + kwargs['version'] = self.version + return name, path, args, kwargs + def get_internal_type(self): return "CharField" + def from_db_value(self, value, expression, connection, context): + return self.to_python(value) + def to_python(self, value): if not value: return None @@ -163,13 +179,22 @@ class IPNetworkFormField(forms.Field): class IPNetworkField(models.Field): description = _('IP Network object') - __metaclass__ = models.SubfieldBase def __init__(self, version=4, serialize=True, *args, **kwargs): kwargs['max_length'] = 100 self.version = version super(IPNetworkField, self).__init__(*args, **kwargs) + def deconstruct(self): + name, path, args, kwargs = super(IPNetworkField, self).deconstruct() + del kwargs['max_length'] + if self.version != 4: + kwargs['version'] = self.version + return name, path, args, kwargs + + def from_db_value(self, value, expression, connection, context): + return self.to_python(value) + def to_python(self, value): if not value: return None diff --git a/circle/firewall/fw.py b/circle/firewall/fw.py index 2cec2f4..ce21f57 100644 --- a/circle/firewall/fw.py +++ b/circle/firewall/fw.py @@ -25,7 +25,7 @@ from .models import (Host, Rule, Vlan, Domain, Record, BlacklistItem, SwitchPort) from .iptables import IptRule, IptChain import django.conf -from django.template import loader, Context +from django.template import loader from django.utils import timezone @@ -152,9 +152,9 @@ class BuildFirewall: template = loader.get_template('firewall/iptables.conf') context['proto'] = 'ipv4' - ipv4 = unicode(template.render(Context(context))) + ipv4 = unicode(template.render(context)) context['proto'] = 'ipv6' - ipv6 = unicode(template.render(Context(context))) + ipv6 = unicode(template.render(context)) return (ipv4, ipv6) diff --git a/circle/firewall/migrations/0006_auto_20170707_1909.py b/circle/firewall/migrations/0006_auto_20170707_1909.py new file mode 100644 index 0000000..3973a85 --- /dev/null +++ b/circle/firewall/migrations/0006_auto_20170707_1909.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.3 on 2017-07-07 19:09 +from __future__ import unicode_literals + +from django.db import migrations +import firewall.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('firewall', '0005_auto_20150520_2250'), + ] + + operations = [ + migrations.AlterField( + model_name='host', + name='ipv6', + field=firewall.fields.IPAddressField(blank=True, help_text='The global IPv6 address of the host, for example 2001:db:88:200::10.', null=True, unique=True, verbose_name='IPv6 address', version=6), + ), + migrations.AlterField( + model_name='vlan', + name='network6', + field=firewall.fields.IPNetworkField(blank=True, help_text='The IPv6 address and the prefix length of the gateway.', null=True, verbose_name='IPv6 address/prefix', version=6), + ), + ] diff --git a/circle/firewall/tests/test_firewall.py b/circle/firewall/tests/test_firewall.py index 4e9a035..f037bcd 100644 --- a/circle/firewall/tests/test_firewall.py +++ b/circle/firewall/tests/test_firewall.py @@ -80,7 +80,7 @@ class GetNewAddressTestCase(MockCeleryMixin, TestCase): self.vlan = Vlan(vid=1, name='test', network4='10.0.0.1/29', network6='2001:738:2001:4031::/80', domain=d, owner=self.u1) - self.vlan.clean() + self.vlan.full_clean() self.vlan.save() self.vlan.host_set.all().delete() for i in range(3, 6): diff --git a/circle/network/urls.py b/circle/network/urls.py index dc13f31..cdedbbf 100644 --- a/circle/network/urls.py +++ b/circle/network/urls.py @@ -15,7 +15,7 @@ # You should have received a copy of the GNU General Public License along # with CIRCLE. If not, see <http://www.gnu.org/licenses/>. -from django.conf.urls import patterns, url +from django.conf.urls import url from .views import ( IndexView, HostList, HostDetail, HostCreate, HostDelete, @@ -33,8 +33,7 @@ from .views import ( VlanAclUpdateView ) -urlpatterns = patterns( - '', +urlpatterns = [ url('^$', IndexView.as_view(), name='network.index'), # blacklist url('^blacklist/$', BlacklistList.as_view(), @@ -135,4 +134,4 @@ urlpatterns = patterns( remove_switch_port_device, name='network.remove_switch_port_device'), url('^switchports/(?P<pk>\d+)/add/$', add_switch_port_device, name='network.add_switch_port_device'), -) +] diff --git a/circle/request/forms.py b/circle/request/forms.py index 443d0b3..587cc7e 100644 --- a/circle/request/forms.py +++ b/circle/request/forms.py @@ -19,7 +19,6 @@ from django.forms import ( Textarea, ValidationError ) from django.utils.translation import ugettext_lazy as _ -from django.template import RequestContext from django.template.loader import render_to_string from sizefield.widgets import FileSizeWidget @@ -68,8 +67,7 @@ class InitialFromFileMixin(object): super(InitialFromFileMixin, self).__init__(*args, **kwargs) self.initial['message'] = render_to_string( - self.initial_template, - RequestContext(request, {}), + self.initial_template, {}, request ) def clean_message(self): diff --git a/circle/request/urls.py b/circle/request/urls.py index c69aa8d..7bf5cf5 100644 --- a/circle/request/urls.py +++ b/circle/request/urls.py @@ -16,7 +16,7 @@ # with CIRCLE. If not, see <http://www.gnu.org/licenses/>. from __future__ import absolute_import -from django.conf.urls import patterns, url +from django.conf.urls import url from .views import ( RequestList, RequestDetail, RequestTypeList, @@ -26,8 +26,7 @@ from .views import ( LeaseTypeDelete, TemplateAccessTypeDelete, ResizeRequestView, ) -urlpatterns = patterns( - '', +urlpatterns = [ url(r'^list/$', RequestList.as_view(), name="request.views.request-list"), url(r'^(?P<pk>\d+)/$', RequestDetail.as_view(), @@ -62,4 +61,4 @@ urlpatterns = patterns( name="request.views.request-resource"), url(r'resize/(?P<vm_pk>\d+)/(?P<disk_pk>\d+)/$', ResizeRequestView.as_view(), name="request.views.request-resize"), -) +] diff --git a/circle/vm/__init__.py b/circle/vm/__init__.py index 62e09b9..4ede8e6 100644 --- a/circle/vm/__init__.py +++ b/circle/vm/__init__.py @@ -1,2 +1 @@ -# This import is responsible for running the operations' registration code. -from . import operations # noqa +# noqa diff --git a/circle/vm/tests/test_models.py b/circle/vm/tests/test_models.py index 65e24be..5442280 100644 --- a/circle/vm/tests/test_models.py +++ b/circle/vm/tests/test_models.py @@ -177,9 +177,9 @@ class InterfaceTestCase(MockCeleryMixin, TestCase): i = Instance(id=10, owner=owner, access_method='rdp') d = Domain(owner=owner) d.save() - v = Vlan(vid=55, network4='127.0.0.1/8', + v = Vlan(name='vlan', vid=55, network4='127.0.0.1/8', network6='2001::1/32', domain=d) - v.clean() + v.full_clean() v.save() Interface.create(i, v, managed=True, owner=owner) diff --git a/requirements/base.txt b/requirements/base.txt index 683880e..6ea96b1 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -4,18 +4,19 @@ arrow==0.7.0 billiard==3.3.0.20 bpython==0.14.1 celery==3.1.18 -Django==1.8.15 -django-appconf==1.0.1 -django-autocomplete-light==2.1.1 -django-braces==1.8.0 -django-crispy-forms==1.6.0 -django-model-utils==2.2 -djangosaml2==0.13.0 -django-sizefield==0.7 +Django==1.11.3 +django-appconf==1.0.2 +django-autocomplete-light==3.2.9 +django-braces==1.11.0 +django-crispy-forms==1.6.1 +django-model-utils==3.0.0 +django-pipeline==1.6.13 +django-sizefield==0.9.1 +django-statici18n==1.4.0 +django-tables2==1.10.0 +django-taggit==0.22.1 +djangosaml2==0.16.0 git+https://git.ik.bme.hu/circle/django-sshkey.git -django-statici18n==1.1.3 -django-tables2==0.16.0 -django-taggit==0.14.0 docutils==0.12 Jinja2==2.7.3 jsonfield==1.0.3 @@ -39,6 +40,6 @@ six==1.9.0 slimit==0.8.1 sqlparse==0.1.15 pika==0.9.14 -django-pipeline==1.4.7 Fabric==1.10.1 lxml==3.4.4 +python-memcached==1.58 \ No newline at end of file diff --git a/requirements/local.txt b/requirements/local.txt index db1a4c2..f24d055 100644 --- a/requirements/local.txt +++ b/requirements/local.txt @@ -1,6 +1,6 @@ # Local development dependencies go here -r base.txt coverage==3.7.1 -django-debug-toolbar==1.3.0 -django-rosetta==0.7.6 +django-debug-toolbar==1.8 +django-rosetta==0.7.13 Sphinx==1.3.1 diff --git a/requirements/test.txt b/requirements/test.txt index 247ee04..5075ee3 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -3,9 +3,9 @@ coverage==3.7.1 factory-boy==2.4.1 mock==1.0.1 -django-nose==1.4 -nose==1.3.6 -nose-exclude==0.2.0 +django-nose==1.4.4 +nose==1.3.7 +nose-exclude==0.5.0 selenium==2.45.0 selenose==1.3 -e git+https://github.com/kmmbvnr/django-jenkins.git@019774dc2f668bc66b66f90f97eb8e14ae9566a4#egg=django_jenkins-dev