Commit 10261b9a by Czémán Arnold

Merge branch 'master' of https://git.ik.bme.hu/circle/cloud into add_rule

parents c7fd2925 dad93220
Pipeline #7 passed with stage
in 0 seconds
...@@ -246,8 +246,8 @@ class AclBase(Model): ...@@ -246,8 +246,8 @@ class AclBase(Model):
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
super(AclBase, self).save(*args, **kwargs) super(AclBase, self).save(*args, **kwargs)
if 'owner' in dict(self.ACL_LEVELS) and (hasattr(self, 'owner') if 'owner' in dict(self.ACL_LEVELS) and (hasattr(self, 'owner') and
and self.owner): self.owner):
self.set_user_level(self.owner, 'owner') self.set_user_level(self.owner, 'owner')
class Meta: class Meta:
......
...@@ -505,6 +505,8 @@ if get_env_variable('DJANGO_SAML', 'FALSE') == 'TRUE': ...@@ -505,6 +505,8 @@ if get_env_variable('DJANGO_SAML', 'FALSE') == 'TRUE':
if get_env_variable('DJANGO_SAML_ORG_ID_ATTRIBUTE', False) is not False: if get_env_variable('DJANGO_SAML_ORG_ID_ATTRIBUTE', False) is not False:
SAML_ORG_ID_ATTRIBUTE = get_env_variable( SAML_ORG_ID_ATTRIBUTE = get_env_variable(
'DJANGO_SAML_ORG_ID_ATTRIBUTE') 'DJANGO_SAML_ORG_ID_ATTRIBUTE')
SAML_MAIN_ATTRIBUTE_MAX_LENGTH = int(get_env_variable(
"DJANGO_SAML_MAIN_ATTRIBUTE_MAX_LENGTH", 0))
LOGIN_REDIRECT_URL = "/" LOGIN_REDIRECT_URL = "/"
......
...@@ -71,3 +71,5 @@ STORE_URL = "" ...@@ -71,3 +71,5 @@ STORE_URL = ""
# buildbot doesn't love pipeline # buildbot doesn't love pipeline
STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.StaticFilesStorage' STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.StaticFilesStorage'
SAML_MAIN_ATTRIBUTE_MAX_LENGTH=0 # doctest on SAML2 backend runs either way
...@@ -25,7 +25,7 @@ from django.shortcuts import redirect ...@@ -25,7 +25,7 @@ from django.shortcuts import redirect
from circle.settings.base import get_env_variable from circle.settings.base import get_env_variable
from dashboard.views import circle_login, HelpView from dashboard.views import circle_login, HelpView, ResizeHelpView
from dashboard.forms import CirclePasswordResetForm, CircleSetPasswordForm from dashboard.forms import CirclePasswordResetForm, CircleSetPasswordForm
from firewall.views import add_blacklist_item from firewall.views import add_blacklist_item
...@@ -65,6 +65,8 @@ urlpatterns = patterns( ...@@ -65,6 +65,8 @@ urlpatterns = patterns(
url(r'^info/support/$', url(r'^info/support/$',
TemplateView.as_view(template_name="info/support.html"), TemplateView.as_view(template_name="info/support.html"),
name="info.support"), name="info.support"),
url(r'^info/resize-how-to/$', ResizeHelpView.as_view(),
name="info.resize"),
) )
......
...@@ -46,7 +46,7 @@ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "circle.settings.production") ...@@ -46,7 +46,7 @@ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "circle.settings.production")
# This application object is used by any WSGI server configured to use this # This application object is used by any WSGI server configured to use this
# file. This includes Django's development server, if the WSGI_APPLICATION # file. This includes Django's development server, if the WSGI_APPLICATION
# setting points here. # setting points here.
from django.core.wsgi import get_wsgi_application from django.core.wsgi import get_wsgi_application # noqa
_application = get_wsgi_application() _application = get_wsgi_application()
......
...@@ -17,9 +17,14 @@ ...@@ -17,9 +17,14 @@
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>. # with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
import re import re
import logging
import sha
from django.conf import settings
from djangosaml2.backends import Saml2Backend as Saml2BackendBase from djangosaml2.backends import Saml2Backend as Saml2BackendBase
logger = logging.getLogger(__name__)
class Saml2Backend(Saml2BackendBase): class Saml2Backend(Saml2BackendBase):
u""" u"""
...@@ -41,7 +46,14 @@ class Saml2Backend(Saml2BackendBase): ...@@ -41,7 +46,14 @@ class Saml2Backend(Saml2BackendBase):
if isinstance(main_attribute, str): if isinstance(main_attribute, str):
main_attribute = main_attribute.decode('UTF-8') main_attribute = main_attribute.decode('UTF-8')
assert isinstance(main_attribute, unicode) assert isinstance(main_attribute, unicode)
return re.sub(r'[^\w.@-]', replace, main_attribute) attr = re.sub(r'[^\w.@-]', replace, main_attribute)
max_length = settings.SAML_MAIN_ATTRIBUTE_MAX_LENGTH
if max_length > 0 and len(attr) > max_length:
logger.info("Main attribute '%s' is too long." % attr)
hashed = sha.new(attr).hexdigest()
attr = hashed[:max_length]
logger.info("New main attribute: %s" % attr)
return attr
def _set_attribute(self, obj, attr, value): def _set_attribute(self, obj, attr, value):
if attr == 'username': if attr == 'username':
......
...@@ -97,7 +97,7 @@ def has_prefix(activity_code, *prefixes): ...@@ -97,7 +97,7 @@ def has_prefix(activity_code, *prefixes):
>>> assert has_prefix('foo.bar.buz', 'foo', 'bar', 'buz') >>> assert has_prefix('foo.bar.buz', 'foo', 'bar', 'buz')
>>> assert not has_prefix('foo.bar.buz', 'foo', 'buz') >>> assert not has_prefix('foo.bar.buz', 'foo', 'buz')
""" """
equal = lambda a, b: a == b def equal(a, b): return a == b
act_code_parts = split_activity_code(activity_code) act_code_parts = split_activity_code(activity_code)
prefixes = chain(*imap(split_activity_code, prefixes)) prefixes = chain(*imap(split_activity_code, prefixes))
return all(imap(equal, act_code_parts, prefixes)) return all(imap(equal, act_code_parts, prefixes))
...@@ -112,7 +112,7 @@ def has_suffix(activity_code, *suffixes): ...@@ -112,7 +112,7 @@ def has_suffix(activity_code, *suffixes):
>>> assert has_suffix('foo.bar.buz', 'foo', 'bar', 'buz') >>> assert has_suffix('foo.bar.buz', 'foo', 'bar', 'buz')
>>> assert not has_suffix('foo.bar.buz', 'foo', 'buz') >>> assert not has_suffix('foo.bar.buz', 'foo', 'buz')
""" """
equal = lambda a, b: a == b def equal(a, b): return a == b
act_code_parts = split_activity_code(activity_code) act_code_parts = split_activity_code(activity_code)
suffixes = list(chain(*imap(split_activity_code, suffixes))) suffixes = list(chain(*imap(split_activity_code, suffixes)))
return all(imap(equal, reversed(act_code_parts), reversed(suffixes))) return all(imap(equal, reversed(act_code_parts), reversed(suffixes)))
...@@ -441,8 +441,8 @@ class HumanReadableObject(object): ...@@ -441,8 +441,8 @@ class HumanReadableObject(object):
@classmethod @classmethod
def create(cls, user_text_template, admin_text_template=None, **params): def create(cls, user_text_template, admin_text_template=None, **params):
return cls(user_text_template=user_text_template, return cls(user_text_template=user_text_template,
admin_text_template=(admin_text_template admin_text_template=(admin_text_template or
or user_text_template), params=params) user_text_template), params=params)
def set(self, user_text_template, admin_text_template=None, **params): def set(self, user_text_template, admin_text_template=None, **params):
self._set_values(user_text_template, self._set_values(user_text_template,
......
# -*- coding: utf-8 -*-
# Copyright 2014 Budapest University of Technology and Economics (BME IK)
#
# This file is part of CIRCLE Cloud.
#
# CIRCLE is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# CIRCLE is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
from arrow import locales
class HungarianLocale(locales.Locale):
names = ['hu', 'HU']
past = '{0} ezelőtt'
future = '{0} múlva'
timeframes = {
'now': 'éppen most',
'seconds': {
'past': 'másodpercekkel',
'future': 'pár másodperc'},
'minute': {'past': 'egy perccel', 'future': 'egy perc'},
'minutes': {'past': '{0} perccel', 'future': '{0} perc'},
'hour': {'past': 'egy órával', 'future': 'egy óra'},
'hours': {'past': '{0} órával', 'future': '{0} óra'},
'day': {
'past': 'egy nappal',
'future': 'egy nap'
},
'days': {
'past': '{0} nappal',
'future': '{0} nap'
},
'month': {'past': 'egy hónappal', 'future': 'egy hónap'},
'months': {'past': '{0} hónappal', 'future': '{0} hónap'},
'year': {'past': 'egy évvel', 'future': 'egy év'},
'years': {'past': '{0} évvel', 'future': '{0} év'},
}
month_names = ['', 'Január', 'Február', 'Március', 'Április', 'Május',
'Június', 'Július', 'Augusztus', 'Szeptember',
'Október', 'November', 'December']
month_abbreviations = ['', 'Jan', 'Febr', 'Márc', 'Ápr', 'Máj', 'Jún',
'Júl', 'Aug', 'Szept', 'Okt', 'Nov', 'Dec']
day_names = ['', 'Hétfő', 'Kedd', 'Szerda', 'Csütörtök', 'Péntek',
'Szombat', 'Vasárnap']
day_abbreviations = ['', 'Hét', 'Kedd', 'Szer', 'Csüt', 'Pént',
'Szom', 'Vas']
meridians = {
'am': 'de',
'pm': 'du',
'AM': 'DE',
'PM': 'DU',
}
def _format_timeframe(self, timeframe, delta):
form = self.timeframes[timeframe]
if isinstance(form, dict):
if delta > 0:
form = form['future']
else:
form = form['past']
delta = abs(delta)
return form.format(delta)
...@@ -38,10 +38,10 @@ def highlight(field, q, none_wo_match=True): ...@@ -38,10 +38,10 @@ def highlight(field, q, none_wo_match=True):
match = None match = None
if q and match is not None: if q and match is not None:
match_end = match + len(q) match_end = match + len(q)
return (escape(field[:match]) return (escape(field[:match]) +
+ '<span class="autocomplete-hl">' '<span class="autocomplete-hl">' +
+ escape(field[match:match_end]) escape(field[match:match_end]) +
+ '</span>' + escape(field[match_end:])) '</span>' + escape(field[match_end:]))
elif none_wo_match: elif none_wo_match:
return None return None
else: else:
......
...@@ -506,8 +506,8 @@ class TemplateForm(forms.ModelForm): ...@@ -506,8 +506,8 @@ class TemplateForm(forms.ModelForm):
self.allowed_fields = ( self.allowed_fields = (
'name', 'access_method', 'description', 'system', 'tags', 'name', 'access_method', 'description', 'system', 'tags',
'arch', 'lease', 'has_agent') 'arch', 'lease', 'has_agent')
if (self.user.has_perm('vm.change_template_resources') if (self.user.has_perm('vm.change_template_resources') or
or not self.instance.pk): not self.instance.pk):
self.allowed_fields += tuple(set(self.fields.keys()) - self.allowed_fields += tuple(set(self.fields.keys()) -
set(['raw_data'])) set(['raw_data']))
if self.user.is_superuser: if self.user.is_superuser:
...@@ -523,8 +523,8 @@ class TemplateForm(forms.ModelForm): ...@@ -523,8 +523,8 @@ class TemplateForm(forms.ModelForm):
self.initial['max_ram_size'] = 512 self.initial['max_ram_size'] = 512
lease_queryset = ( lease_queryset = (
Lease.get_objects_with_level("operator", self.user).distinct() Lease.get_objects_with_level("operator", self.user).distinct() |
| Lease.objects.filter(pk=self.instance.lease_id).distinct()) Lease.objects.filter(pk=self.instance.lease_id).distinct())
self.fields["lease"].queryset = lease_queryset self.fields["lease"].queryset = lease_queryset
......
...@@ -64,8 +64,8 @@ class Command(BaseCommand): ...@@ -64,8 +64,8 @@ class Command(BaseCommand):
def handle(self, *args, **options): def handle(self, *args, **options):
self.changed = False self.changed = False
if (DataStore.objects.exists() and Vlan.objects.exists() if (DataStore.objects.exists() and Vlan.objects.exists() and
and not options['force']): not options['force']):
return self.print_state() return self.print_state()
admin = self.create(User, 'username', username=options['admin_user'], admin = self.create(User, 'username', username=options['admin_user'],
......
...@@ -1488,3 +1488,38 @@ textarea[name="new_members"] { ...@@ -1488,3 +1488,38 @@ textarea[name="new_members"] {
.acl-table td:first-child { .acl-table td:first-child {
text-align: center; text-align: center;
} }
#resize-help {
table {
background-color: #f5f5f5;
}
.panel {
padding: 2px 20px;
background-color: #f5f5f5;
margin: 20px 0px;
}
ol li {
margin-top: 15px;
}
img {
display: block;
margin: 15px 0 5px 0;
}
pre {
margin-top: 5px;
}
hr {
margin: 50px 0;
}
}
#vm-details-resize-how-to {
font-size: 1.5em;
text-align: center;
width: 100%;
}
...@@ -4,24 +4,33 @@ ...@@ -4,24 +4,33 @@
<i class="fa fa-file"></i> <i class="fa fa-file"></i>
{{ d.name }} (#{{ d.id }}) - {{ d.size|filesize }} {{ d.name }} (#{{ d.id }}) - {{ d.size|filesize }}
{% if op.remove_disk %}
<span class="operation-wrapper"> <span class="operation-wrapper pull-right">
{% if d.is_resizable %}
{% if op.resize_disk %}
<a href="{{ op.resize_disk.get_url }}?disk={{d.pk}}"
class="btn btn-xs btn-{{ op.resize_disk.effect }} operation disk-resize-btn
{% if op.resize_disk.disabled %}disabled{% endif %}">
<i class="fa fa-{{ op.resize_disk.icon }} fa-fw-12"></i> {% trans "Resize" %}
</a>
{% else %}
<a href="{% url "request.views.request-resize" vm_pk=instance.pk disk_pk=d.pk %}" class="btn btn-xs btn-primary operation">
<i class="fa fa-arrows-alt fa-fw-12"></i> {% trans "Request resize" %}
</a>
{% endif %}
{% else %}
<small class="btn-xs">
{% trans "Not resizable" %}
</small>
{% endif %}
{% if op.remove_disk %}
<a href="{{ op.remove_disk.get_url }}?disk={{d.pk}}" <a href="{{ op.remove_disk.get_url }}?disk={{d.pk}}"
class="btn btn-xs btn-{{ op.remove_disk.effect}} pull-right operation disk-remove-btn class="btn btn-xs btn-{{ op.remove_disk.effect}} operation disk-remove-btn
{% if op.remove_disk.disabled %}disabled{% endif %}"> {% if op.remove_disk.disabled %}disabled{% endif %}">
<i class="fa fa-{{ op.remove_disk.icon }} fa-fw-12"></i> {% trans "Remove" %} <i class="fa fa-{{ op.remove_disk.icon }} fa-fw-12"></i> {% trans "Remove" %}
</a> </a>
</span> {% endif %}
{% endif %} </span>
{% if op.resize_disk %}
<span class="operation-wrapper">
<a href="{{ op.resize_disk.get_url }}?disk={{d.pk}}"
class="btn btn-xs btn-{{ op.resize_disk.effect }} pull-right operation disk-resize-btn
{% if op.resize_disk.disabled %}disabled{% endif %}">
<i class="fa fa-{{ op.resize_disk.icon }} fa-fw-12"></i> {% trans "Resize" %}
</a>
</span>
{% endif %}
<div style="clear: both;"></div> <div style="clear: both;"></div>
{% if request.user.is_superuser %} {% if request.user.is_superuser %}
......
...@@ -166,6 +166,28 @@ ...@@ -166,6 +166,28 @@
</ul> </ul>
</div> </div>
</div> </div>
{% if show_graph %}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="no-margin"><i class="fa fa-area-chart"></i> {% trans "Graphs" %}</h3>
</div>
<div class="text-center panel-body">
<div class="graph-buttons">
{% include "dashboard/_graph-time-buttons.html" %}
</div>
<div class="text-center graph-images">
<img src="{% url "dashboard.views.template-graph" object.pk "instances" graph_time %}"/>
</div>
{% if request.user.is_superuser %}
<a href="{% url "dashboard.views.vm-list" %}?s=template:{{object.pk}}&stype=all">
{% trans "List all template instances" %}
</a>
{% endif %}
</div>
</div>
{% endif %}
</div><!-- .col-md-4 --> </div><!-- .col-md-4 -->
</div><!-- .row --> </div><!-- .row -->
......
...@@ -66,6 +66,17 @@ ...@@ -66,6 +66,17 @@
{% endfor %} {% endfor %}
</div> </div>
<hr />
{% if instance.disks.all %}
<div id="vm-details-resize-how-to">
<i class="fa fa-question"></i>
{% url "info.resize" as resize_url %}
{% blocktrans with url=resize_url %}
If you need help resizing the disks check out our <a href="{{ url }}">resize how-to.</a>
{% endblocktrans %}
</div>
{% endif %}
{% if user.is_superuser %} {% if user.is_superuser %}
<hr/> <hr/>
......
...@@ -18,9 +18,6 @@ ...@@ -18,9 +18,6 @@
from django.template import Library from django.template import Library
import arrow import arrow
from dashboard.arrow_local import HungarianLocale
for name in HungarianLocale.names:
arrow.locales._locales[name] = HungarianLocale
register = Library() register = Library()
......
...@@ -601,8 +601,8 @@ class CircleSeleniumMixin(SeleniumMixin): ...@@ -601,8 +601,8 @@ class CircleSeleniumMixin(SeleniumMixin):
choices = self.driver.find_elements_by_css_selector( choices = self.driver.find_elements_by_css_selector(
"input[type='radio']") "input[type='radio']")
choice_list = [item for item in choices if ( choice_list = [item for item in choices if (
'test' not in item.get_attribute('value') 'test' not in item.get_attribute('value') and
and item.get_attribute('value') != 'base_vm')] item.get_attribute('value') != 'base_vm')]
chosen = random.randint(0, len(choice_list) - 1) chosen = random.randint(0, len(choice_list) - 1)
choice_list[chosen].click() choice_list[chosen].click()
self.driver.find_element_by_id( self.driver.find_element_by_id(
......
...@@ -47,7 +47,7 @@ from .views import ( ...@@ -47,7 +47,7 @@ from .views import (
LeaseAclUpdateView, LeaseAclUpdateView,
toggle_template_tutorial, toggle_template_tutorial,
ClientCheck, TokenLogin, ClientCheck, TokenLogin,
VmGraphView, NodeGraphView, NodeListGraphView, VmGraphView, NodeGraphView, NodeListGraphView, TemplateGraphView,
TransferInstanceOwnershipView, TransferInstanceOwnershipConfirmView, TransferInstanceOwnershipView, TransferInstanceOwnershipConfirmView,
TransferTemplateOwnershipView, TransferTemplateOwnershipConfirmView, TransferTemplateOwnershipView, TransferTemplateOwnershipConfirmView,
OpenSearchDescriptionView, OpenSearchDescriptionView,
...@@ -152,6 +152,10 @@ urlpatterns = patterns( ...@@ -152,6 +152,10 @@ urlpatterns = patterns(
r'(?P<time>[0-9]{1,2}[hdwy])$'), r'(?P<time>[0-9]{1,2}[hdwy])$'),
NodeListGraphView.as_view(), NodeListGraphView.as_view(),
name='dashboard.views.node-list-graph'), name='dashboard.views.node-list-graph'),
url((r'^template/(?P<pk>\d+)/graph/(?P<metric>[a-z]+)/'
r'(?P<time>[0-9]{1,2}[hdwy])$'),
TemplateGraphView.as_view(),
name='dashboard.views.template-graph'),
url(r'^group/(?P<pk>\d+)/$', GroupDetailView.as_view(), url(r'^group/(?P<pk>\d+)/$', GroupDetailView.as_view(),
name='dashboard.views.group-detail'), name='dashboard.views.group-detail'),
url(r'^group/(?P<pk>\d+)/update/$', GroupProfileUpdate.as_view(), url(r'^group/(?P<pk>\d+)/update/$', GroupProfileUpdate.as_view(),
......
...@@ -28,7 +28,7 @@ from django.views.generic import View ...@@ -28,7 +28,7 @@ from django.views.generic import View
from braces.views import LoginRequiredMixin from braces.views import LoginRequiredMixin
from vm.models import Instance, Node from vm.models import Instance, Node, InstanceTemplate
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
...@@ -152,6 +152,28 @@ class NodeGraphView(GraphViewBase): ...@@ -152,6 +152,28 @@ class NodeGraphView(GraphViewBase):
return self.model.objects.get(id=pk) return self.model.objects.get(id=pk)
class TemplateGraphView(GraphViewBase):
model = InstanceTemplate
base = Metric
def get_object(self, request, pk):
instance = super(TemplateGraphView, self).get_object(request, pk)
if not instance.has_level(request.user, 'operator'):
raise PermissionDenied()
return instance
class TemplateVms(object):
metric_name = "instances.running"
title = _("Instance count")
label = _("instance count")
def get_minmax(self):
return (0, None)
register_graph(TemplateVms, 'instances', TemplateGraphView)
class NodeListGraphView(GraphViewBase): class NodeListGraphView(GraphViewBase):
model = Node model = Node
base = Metric base = Metric
......
...@@ -136,6 +136,10 @@ class HelpView(TemplateView): ...@@ -136,6 +136,10 @@ class HelpView(TemplateView):
return ctx return ctx
class ResizeHelpView(TemplateView):
template_name = "info/resize.html"
class OpenSearchDescriptionView(TemplateView): class OpenSearchDescriptionView(TemplateView):
template_name = "dashboard/vm-opensearch.xml" template_name = "dashboard/vm-opensearch.xml"
content_type = "application/opensearchdescription+xml" content_type = "application/opensearchdescription+xml"
......
...@@ -47,7 +47,8 @@ from ..tables import TemplateListTable, LeaseListTable ...@@ -47,7 +47,8 @@ from ..tables import TemplateListTable, LeaseListTable
from .util import ( from .util import (
AclUpdateView, FilterMixin, AclUpdateView, FilterMixin,
TransferOwnershipConfirmView, TransferOwnershipView, TransferOwnershipConfirmView, TransferOwnershipView,
DeleteViewBase DeleteViewBase,
GraphMixin
) )
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
...@@ -258,7 +259,8 @@ class TemplateDelete(DeleteViewBase): ...@@ -258,7 +259,8 @@ class TemplateDelete(DeleteViewBase):
object.delete() object.delete()
class TemplateDetail(LoginRequiredMixin, SuccessMessageMixin, UpdateView): class TemplateDetail(LoginRequiredMixin, GraphMixin,
SuccessMessageMixin, UpdateView):
model = InstanceTemplate model = InstanceTemplate
template_name = "dashboard/template-edit.html" template_name = "dashboard/template-edit.html"
form_class = TemplateForm form_class = TemplateForm
...@@ -300,6 +302,7 @@ class TemplateDetail(LoginRequiredMixin, SuccessMessageMixin, UpdateView): ...@@ -300,6 +302,7 @@ class TemplateDetail(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
context['is_owner'] = obj.has_level(self.request.user, 'owner') context['is_owner'] = obj.has_level(self.request.user, 'owner')
context['aclform'] = AclUserOrGroupAddForm() context['aclform'] = AclUserOrGroupAddForm()
context['parent'] = obj.parent context['parent'] = obj.parent
context['show_graph'] = obj.has_level(self.request.user, 'operator')
return context return context
def get_success_url(self): def get_success_url(self):
......
...@@ -545,8 +545,8 @@ class UserList(LoginRequiredMixin, PermissionRequiredMixin, SingleTableView): ...@@ -545,8 +545,8 @@ class UserList(LoginRequiredMixin, PermissionRequiredMixin, SingleTableView):
q = self.search_form.cleaned_data.get('s') q = self.search_form.cleaned_data.get('s')
if q: if q:
filters = (Q(username__icontains=q) | Q(email__icontains=q) filters = (Q(username__icontains=q) | Q(email__icontains=q) |
| Q(profile__org_id__icontains=q)) Q(profile__org_id__icontains=q))
for w in q.split()[:3]: for w in q.split()[:3]:
filters |= ( filters |= (
Q(first_name__icontains=w) | Q(last_name__icontains=w)) Q(first_name__icontains=w) | Q(last_name__icontains=w))
......
...@@ -150,8 +150,8 @@ class VmDetailView(GraphMixin, CheckedDetailView): ...@@ -150,8 +150,8 @@ class VmDetailView(GraphMixin, CheckedDetailView):
# resources forms # resources forms
can_edit = ( can_edit = (
instance.has_level(user, "owner") instance.has_level(user, "owner") and
and self.request.user.has_perm("vm.change_resources")) self.request.user.has_perm("vm.change_resources"))
context['resources_form'] = VmResourcesForm( context['resources_form'] = VmResourcesForm(
can_edit=can_edit, instance=instance) can_edit=can_edit, instance=instance)
...@@ -174,8 +174,10 @@ class VmDetailView(GraphMixin, CheckedDetailView): ...@@ -174,8 +174,10 @@ class VmDetailView(GraphMixin, CheckedDetailView):
context['is_owner'] = is_owner context['is_owner'] = is_owner
# operation also allows RUNNING (if with_shutdown is present) # operation also allows RUNNING (if with_shutdown is present)
context['save_resources_enabled'] = instance.status not in ("RUNNING", context['save_resources_enabled'] = instance.status in (
"PENDING") "STOPPED",
"PENDING",
)
return context return context
...@@ -567,8 +569,8 @@ class VmResourcesChangeView(VmOperationView): ...@@ -567,8 +569,8 @@ class VmResourcesChangeView(VmOperationView):
content_type="application=json" content_type="application=json"
) )
else: else:
return HttpResponseRedirect(instance.get_absolute_url() return HttpResponseRedirect(instance.get_absolute_url() +
+ "#resources") "#resources")
else: else:
extra = form.cleaned_data extra = form.cleaned_data
extra['max_ram_size'] = extra['ram_size'] extra['max_ram_size'] = extra['ram_size']
...@@ -1259,8 +1261,9 @@ def vm_activity(request, pk): ...@@ -1259,8 +1261,9 @@ def vm_activity(request, pk):
response['status'] = instance.status response['status'] = instance.status
response['icon'] = instance.get_status_icon() response['icon'] = instance.get_status_icon()
latest = instance.get_latest_activity_in_progress() latest = instance.get_latest_activity_in_progress()
response['is_new_state'] = (latest and latest.resultant_state is not None response['is_new_state'] = (latest and
and instance.status != latest.resultant_state) latest.resultant_state is not None and
instance.status != latest.resultant_state)
context = { context = {
'instance': instance, 'instance': instance,
......
...@@ -188,11 +188,11 @@ class IPNetworkField(models.Field): ...@@ -188,11 +188,11 @@ class IPNetworkField(models.Field):
if isinstance(value, IPNetwork): if isinstance(value, IPNetwork