Commit 86c16fe8 by Kálmán Viktor

Merge branch 'master' into feature-new-tour

parents 96fe17ce ea3039d6
......@@ -214,6 +214,14 @@ class ActivityModel(TimeStampedModel):
self.result_data = None if value is None else value.to_dict()
@classmethod
def construct_activity_code(cls, code_suffix, sub_suffix=None):
code = join_activity_code(cls.ACTIVITY_CODE_BASE, code_suffix)
if sub_suffix:
return join_activity_code(code, sub_suffix)
else:
return code
@celery.task()
def compute_cached(method, instance, memcached_seconds,
......
import autocomplete_light
from django.contrib.auth.models import User
from django.utils.html import escape
from django.utils.translation import ugettext as _
from .views import AclUpdateView
from .models import Profile
class AclUserAutocomplete(autocomplete_light.AutocompleteGenericBase):
def highlight(field, q, none_wo_match=True):
"""
>>> highlight('<b>Akkount Krokodil', 'kro', False)
u'&lt;b&gt;Akkount <span class="autocomplete-hl">Kro</span>kodil'
"""
if not field:
return None
try:
match = field.lower().index(q.lower())
except ValueError:
match = None
if q and match is not None:
match_end = match + len(q)
return (escape(field[:match])
+ '<span class="autocomplete-hl">'
+ escape(field[match:match_end])
+ '</span>' + escape(field[match_end:]))
elif none_wo_match:
return None
else:
return escape(field)
class AclUserGroupAutocomplete(autocomplete_light.AutocompleteGenericBase):
search_fields = (
('^first_name', 'last_name', 'username', '^email', 'profile__org_id'),
('^name', 'groupprofile__org_id'),
('first_name', 'last_name', 'username', 'email', 'profile__org_id'),
('name', 'groupprofile__org_id'),
)
autocomplete_js_attributes = {'placeholder': _("Name of group or user")}
choice_html_format = u'<span data-value="%s"><span>%s</span> %s</span>'
choice_html_format = (u'<span data-value="%s"><span style="display:none"'
u'>%s</span>%s</span>')
def choice_html(self, choice):
def choice_displayed_text(self, choice):
q = unicode(self.request.GET.get('q', ''))
name = highlight(unicode(choice), q, False)
if isinstance(choice, User):
extra_fields = [highlight(choice.get_full_name(), q, False),
highlight(choice.email, q)]
try:
name = choice.get_full_name()
except AttributeError:
name = _('group')
if name:
name = u'(%s)' % name
extra_fields.append(highlight(choice.profile.org_id, q))
except Profile.DoesNotExist:
pass
return '%s (%s)' % (name, ', '.join(f for f in extra_fields
if f))
else:
return _('%s (group)') % name
def choice_html(self, choice):
return self.choice_html_format % (
self.choice_value(choice), self.choice_label(choice), name)
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(AclUserAutocomplete, self).choices_for_request()
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)
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()
autocomplete_light.register(AclUserGroupAutocomplete)
autocomplete_light.register(AclUserAutocomplete)
......@@ -143,7 +143,7 @@ class VmCustomizeForm(forms.Form):
self.template = kwargs.pop("template", None)
super(VmCustomizeForm, self).__init__(*args, **kwargs)
if self.user.has_perm("vm_set_resouces"):
if self.user.has_perm("vm.set_resources"):
self.allowed_fields = tuple(self.fields.keys())
# set displayed disk and network list
self.fields['disks'].queryset = self.template.disks.all()
......@@ -481,7 +481,7 @@ class TemplateForm(forms.ModelForm):
else:
self.allowed_fields = (
'name', 'access_method', 'description', 'system', 'tags',
'arch', 'lease')
'arch', 'lease', 'has_agent')
if (self.user.has_perm('vm.change_template_resources')
or not self.instance.pk):
self.allowed_fields += tuple(set(self.fields.keys()) -
......@@ -1055,9 +1055,29 @@ class UserCreationForm(OrgUserCreationForm):
return user
class AclUserAddForm(forms.Form):
class AclUserOrGroupAddForm(forms.Form):
name = forms.CharField(widget=autocomplete_light.TextWidget(
'AclUserAutocomplete', attrs={'class': 'form-control'}))
'AclUserGroupAutocomplete',
autocomplete_js_attributes={'placeholder': _("Name of group or user")},
attrs={'class': 'form-control'}))
class TransferOwnershipForm(forms.Form):
name = forms.CharField(
widget=autocomplete_light.TextWidget(
'AclUserAutocomplete',
autocomplete_js_attributes={"placeholder": _("Name of user")},
attrs={'class': 'form-control'}),
label=_("E-mail address or identifier of user"))
class AddGroupMemberForm(forms.Form):
new_member = forms.CharField(
widget=autocomplete_light.TextWidget(
'AclUserAutocomplete',
autocomplete_js_attributes={"placeholder": _("Name of user")},
attrs={'class': 'form-control'}),
label=_("E-mail address or identifier of user"))
class UserKeyForm(forms.ModelForm):
......
......@@ -261,7 +261,7 @@ def get_or_create_profile(self):
Group.profile = property(get_or_create_profile)
def create_profile(sender, user, request, **kwargs):
def create_profile(user):
if not user.pk:
return False
profile, created = Profile.objects.get_or_create(user=user)
......@@ -272,7 +272,11 @@ def create_profile(sender, user, request, **kwargs):
logger.exception("Can't create user %s", unicode(user))
return created
user_logged_in.connect(create_profile)
def create_profile_hook(sender, user, request, **kwargs):
return create_profile(user)
user_logged_in.connect(create_profile_hook)
if hasattr(settings, 'SAML_ORG_ID_ATTRIBUTE'):
logger.debug("Register save_org_id to djangosaml2 pre_user_save")
......
......@@ -591,11 +591,15 @@ footer a, footer a:hover, footer a:visited {
width: 100px;
}
#group-detail-user-table tr:last-child td:nth-child(2) {
text-align: left;
}
#group-detail-perm-header {
margin-top: 25px;
}
textarea[name="list-new-namelist"] {
textarea[name="new_members"] {
max-width: 500px;
min-height: 80px;
margin-bottom: 10px;
......@@ -968,3 +972,12 @@ textarea[name="list-new-namelist"] {
#vm-activity-state {
margin-bottom: 15px;
}
.autocomplete-hl {
color: #b20000;
font-weight: bold;
}
.hilight .autocomplete-hl {
color: orange;
}
......@@ -2,6 +2,12 @@
<div class="modal fade" id="confirmation-modal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
{% if box_title and ajax_title %}
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title">{{ box_title }}</h4>
</div>
{% endif %}
<div class="modal-body">
{% if template %}
{% include template %}
......
......@@ -89,13 +89,12 @@
<tr>
<td><i class="fa fa-plus"></i></td>
<td colspan="2">
<input type="text" class="form-control" name="list-new-name"
placeholder="{% trans "Name of user" %}">
{{addmemberform.new_member}}
</td>
</tr>
</tbody>
</table>
<textarea name="list-new-namelist" class="form-control"
<textarea name="new_members" class="form-control"
placeholder="{% trans "Add multiple users at once (one identifier per line)." %}"></textarea>
<div class="form-actions">
<button type="submit" class="btn btn-success">{% trans "Save" %}</button>
......
......@@ -51,6 +51,7 @@
{{ form.req_traits|as_crispy_field }}
{{ form.description|as_crispy_field }}
{{ form.system|as_crispy_field }}
{{ form.has_agent|as_crispy_field }}
</fieldset>
<fieldset>
<legend>{% trans "External resources" %}</legend>
......
......@@ -9,8 +9,10 @@
{% endblocktrans %}
{% endif %}
{% if user == instance.owner or user.is_superuser %}
<span class="operation-wrapper">
<a href="{% url "dashboard.views.vm-transfer-ownership" instance.pk %}"
class="btn btn-link">{% trans "Transfer ownership..." %}</a>
class="btn btn-link operation">{% trans "Transfer ownership..." %}</a>
</span>
{% endif %}
</p>
<h3>{% trans "Permissions"|capfirst %}</h3>
......
{% extends "dashboard/base.html" %}
{% load i18n %}
{% block content %}
<div class="body-content">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="no-margin">
{% trans "Transfer ownership" %}
</h3>
</div>
<div class="panel-body">
<div class="pull-right">
<form action="" method="POST">
<div class="pull-right">
<form action="{% url "dashboard.views.vm-transfer-ownership" pk=instance.pk %}" method="POST" style="max-width: 400px;">
{% csrf_token %}
<label>
{% trans "E-mail address or identifier of user" %}:
<input name="name">
{{ form.name.label }}
</label>
<input type="submit">
</form>
<div class="input-group">
{{form.name}}
<div class="input-group-btn">
<input type="submit" value="{% trans "Save" %}" class="btn btn-primary">
</div>
</div>
</div>
{% endblock %}
</form>
</div>
......@@ -1134,7 +1134,7 @@ class GroupDetailTest(LoginMixin, TestCase):
c = Client()
user_in_group = self.g1.user_set.count()
response = c.post('/dashboard/group/' +
str(self.g1.pk) + '/', {'list-new-name': 'user3'})
str(self.g1.pk) + '/', {'new_member': 'user3'})
self.assertEqual(user_in_group,
self.g1.user_set.count())
self.assertEqual(response.status_code, 302)
......@@ -1144,7 +1144,7 @@ class GroupDetailTest(LoginMixin, TestCase):
self.login(c, 'user3')
user_in_group = self.g1.user_set.count()
response = c.post('/dashboard/group/' +
str(self.g1.pk) + '/', {'list-new-name': 'user3'})
str(self.g1.pk) + '/', {'new_member': 'user3'})
self.assertEqual(user_in_group, self.g1.user_set.count())
self.assertEqual(response.status_code, 403)
......@@ -1153,7 +1153,7 @@ class GroupDetailTest(LoginMixin, TestCase):
self.login(c, 'superuser')
user_in_group = self.g1.user_set.count()
response = c.post('/dashboard/group/' +
str(self.g1.pk) + '/', {'list-new-name': 'user3'})
str(self.g1.pk) + '/', {'new_member': 'user3'})
self.assertEqual(user_in_group + 1, self.g1.user_set.count())
self.assertEqual(response.status_code, 302)
......@@ -1162,7 +1162,7 @@ class GroupDetailTest(LoginMixin, TestCase):
self.login(c, 'user0')
user_in_group = self.g1.user_set.count()
response = c.post('/dashboard/group/' +
str(self.g1.pk) + '/', {'list-new-name': 'user3'})
str(self.g1.pk) + '/', {'new_member': 'user3'})
self.assertEqual(user_in_group + 1, self.g1.user_set.count())
self.assertEqual(response.status_code, 302)
......@@ -1172,7 +1172,7 @@ class GroupDetailTest(LoginMixin, TestCase):
user_in_group = self.g1.user_set.count()
response = c.post('/dashboard/group/' +
str(self.g1.pk) + '/',
{'list-new-namelist': 'user1\r\nuser2'})
{'new_members': 'user1\r\nuser2'})
self.assertEqual(user_in_group + 2, self.g1.user_set.count())
self.assertEqual(response.status_code, 302)
......@@ -1182,7 +1182,7 @@ class GroupDetailTest(LoginMixin, TestCase):
user_in_group = self.g1.user_set.count()
response = c.post('/dashboard/group/' +
str(self.g1.pk) + '/',
{'list-new-namelist': 'user1\r\nnoname\r\nuser2'})
{'new_members': 'user1\r\nnoname\r\nuser2'})
self.assertEqual(user_in_group + 2, self.g1.user_set.count())
self.assertEqual(response.status_code, 302)
......@@ -1192,7 +1192,7 @@ class GroupDetailTest(LoginMixin, TestCase):
user_in_group = self.g1.user_set.count()
response = c.post('/dashboard/group/' +
str(self.g1.pk) + '/',
{'list-new-namelist': 'user1\r\nuser2'})
{'new_members': 'user1\r\nuser2'})
self.assertEqual(user_in_group, self.g1.user_set.count())
self.assertEqual(response.status_code, 403)
......@@ -1201,7 +1201,7 @@ class GroupDetailTest(LoginMixin, TestCase):
user_in_group = self.g1.user_set.count()
response = c.post('/dashboard/group/' +
str(self.g1.pk) + '/',
{'list-new-namelist': 'user1\r\nuser2'})
{'new_members': 'user1\r\nuser2'})
self.assertEqual(user_in_group, self.g1.user_set.count())
self.assertEqual(response.status_code, 302)
......@@ -1471,8 +1471,8 @@ class TransferOwnershipViewTest(LoginMixin, TestCase):
c2 = self.u2.notification_set.count()
c = Client()
self.login(c, 'user2')
response = c.post('/dashboard/vm/1/tx/')
assert response.status_code == 400
response = c.post('/dashboard/vm/1/tx/', {'name': 'userx'})
assert response.status_code == 403
self.assertEqual(self.u2.notification_set.count(), c2)
def test_owned_offer(self):
......
......@@ -70,9 +70,10 @@ from .forms import (
UserCreationForm, GroupProfileUpdateForm, UnsubscribeForm,
VmSaveForm, UserKeyForm, VmRenewForm, VmStateChangeForm,
CirclePasswordChangeForm, VmCreateDiskForm, VmDownloadDiskForm,
TraitsForm, RawDataForm, GroupPermissionForm, AclUserAddForm,
TraitsForm, RawDataForm, GroupPermissionForm, AclUserOrGroupAddForm,
VmResourcesForm, VmAddInterfaceForm, VmListSearchForm,
TemplateListSearchForm, ConnectCommandForm
TemplateListSearchForm, ConnectCommandForm,
TransferOwnershipForm, AddGroupMemberForm
)
from .tables import (
......@@ -90,7 +91,7 @@ from vm.models import (
from storage.models import Disk
from firewall.models import Vlan, Host, Rule
from .models import (Favourite, Profile, GroupProfile, FutureMember,
ConnectCommand)
ConnectCommand, create_profile)
from .store_api import Store, NoStoreException, NotOkException
......@@ -393,7 +394,7 @@ class VmDetailView(CheckedDetailView):
).all()
context['acl'] = AclUpdateView.get_acl_data(
instance, self.request.user, 'dashboard.views.vm-acl')
context['aclform'] = AclUserAddForm()
context['aclform'] = AclUserOrGroupAddForm()
context['os_type_icon'] = instance.os_type.replace("unknown",
"question")
# ipv6 infos
......@@ -1285,7 +1286,8 @@ class GroupDetailView(CheckedDetailView):
context['acl'] = AclUpdateView.get_acl_data(
self.object.profile, self.request.user,
'dashboard.views.group-acl')
context['aclform'] = AclUserAddForm()
context['aclform'] = AclUserOrGroupAddForm()
context['addmemberform'] = AddGroupMemberForm()
context['group_profile_form'] = GroupProfileUpdate.get_form_object(
self.request, self.object.profile)
......@@ -1302,17 +1304,15 @@ class GroupDetailView(CheckedDetailView):
if request.POST.get('new_name'):
return self.__set_name(request)
if request.POST.get('list-new-name'):
if request.POST.get('new_member'):
return self.__add_user(request)
if request.POST.get('list-new-namelist'):
if request.POST.get('new_members'):
return self.__add_list(request)
if (request.POST.get('list-new-name') is not None) and \
(request.POST.get('list-new-namelist') is not None):
return redirect(reverse_lazy("dashboard.views.group-detail",
kwargs={'pk': self.get_object().pk}))
def __add_user(self, request):
name = request.POST['list-new-name']
name = request.POST['new_member']
self.__add_username(request, name)
return redirect(reverse_lazy("dashboard.views.group-detail",
kwargs={'pk': self.object.pk}))
......@@ -1331,9 +1331,7 @@ class GroupDetailView(CheckedDetailView):
messages.warning(request, _('User "%s" not found.') % name)
def __add_list(self, request):
if not self.get_has_level()(request.user, 'operator'):
raise PermissionDenied()
userlist = request.POST.get('list-new-namelist').split('\r\n')
userlist = request.POST.get('new_members').split('\r\n')
for line in userlist:
self.__add_username(request, line)
return redirect(reverse_lazy("dashboard.views.group-detail",
......@@ -1720,7 +1718,7 @@ class TemplateDetail(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
obj, self.request.user, 'dashboard.views.template-acl')
context['disks'] = obj.disks.all()
context['is_owner'] = obj.has_level(self.request.user, 'owner')
context['aclform'] = AclUserAddForm()
context['aclform'] = AclUserOrGroupAddForm()
return context
def get_success_url(self):
......@@ -2771,11 +2769,30 @@ class FavouriteView(TemplateView):
return HttpResponse("Added.")
class TransferOwnershipView(LoginRequiredMixin, DetailView):
class TransferOwnershipView(CheckedDetailView, DetailView):
model = Instance
template_name = 'dashboard/vm-detail/tx-owner.html'
def get_template_names(self):
if self.request.is_ajax():
return ['dashboard/_modal.html']
else:
return ['dashboard/nojs-wrapper.html']
def get_context_data(self, *args, **kwargs):
context = super(TransferOwnershipView, self).get_context_data(
*args, **kwargs)
context['form'] = TransferOwnershipForm()
context.update({
'box_title': _("Transfer ownership"),
'ajax_title': True,
'template': "dashboard/vm-detail/tx-owner.html",
})
return context
def post(self, request, *args, **kwargs):
form = TransferOwnershipForm(request.POST)
if not form.is_valid():
return self.get(request)
try:
new_owner = search_user(request.POST['name'])
except User.DoesNotExist:
......@@ -3274,6 +3291,7 @@ class UserCreationView(LoginRequiredMixin, PermissionRequiredMixin,
self.get_group(group_pk)
ret = super(UserCreationView, self).post(*args, **kwargs)
if self.object:
create_profile(self.object)
self.object.groups.add(self.group)
return redirect(
reverse('dashboard.views.group-detail', args=[group_pk]))
......
......@@ -17,6 +17,7 @@
from django.forms import ModelForm
from django.core.urlresolvers import reverse_lazy
from django.utils.translation import ugettext_lazy as _
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Fieldset, Div, Submit, BaseInput
......@@ -56,8 +57,9 @@ class BlacklistItemForm(ModelForm):
)
),
FormActions(
Submit('submit', 'Save changes'),
LinkButton('back', 'Back', reverse_lazy('network.blacklist_list'))
Submit('submit', _('Save changes')),
LinkButton('back', _("Back"),
reverse_lazy('network.blacklist_list'))
)
)
......@@ -77,8 +79,8 @@ class DomainForm(ModelForm):
),
),
FormActions(
Submit('submit', 'Save'),
LinkButton('back', 'Back', reverse_lazy('network.domain_list'))
Submit('submit', _('Save')),
LinkButton('back', _("Back"), reverse_lazy('network.domain_list'))
)
)
......@@ -91,15 +93,15 @@ class GroupForm(ModelForm):
helper.layout = Layout(
Div(
Fieldset(
'Identity',
'',
'name',
'description',
'owner',
),
),
FormActions(
Submit('submit', 'Save'),
LinkButton('back', 'Back', reverse_lazy('network.group_list'))
Submit('submit', _('Save')),
LinkButton('back', _("Back"), reverse_lazy('network.group_list'))
)
)
......@@ -112,13 +114,13 @@ class HostForm(ModelForm):
helper.layout = Layout(
Div(
Fieldset(
'Identity',
'',
'hostname',
'reverse',
'mac',
),
Fieldset(
'Network',
_('Network'),
'vlan',
'ipv4',
'ipv6',
......@@ -126,7 +128,7 @@ class HostForm(ModelForm):
'external_ipv4',
),
Fieldset(
'Information',
_('Information'),
'description',
'location',
'comment',
......@@ -134,8 +136,8 @@ class HostForm(ModelForm):
),
),
FormActions(
Submit('submit', 'Save'),
LinkButton('back', 'Back', reverse_lazy('network.host_list')))
Submit('submit', _('Save')),
LinkButton('back', _('Back'), reverse_lazy('network.host_list')))
)
class Meta:
......@@ -159,8 +161,8 @@ class RecordForm(ModelForm):
)
),
FormActions(
Submit('submit', 'Save'),
LinkButton('back', 'Back', reverse_lazy('network.record_list'))
Submit('submit', _("Save")),
LinkButton('back', _("Back"), reverse_lazy('network.record_list'))
)
)
......@@ -173,7 +175,7 @@ class RuleForm(ModelForm):
helper.layout = Layout(
Div(
Fieldset(
'Identity',
'',
'direction',
'description',
'foreign_network',
......@@ -189,7 +191,7 @@ class RuleForm(ModelForm):
'nat_external_ipv4',
),
Fieldset(
'External',
_('External'),
'vlan',
'vlangroup',
'host',
......@@ -198,8 +200,8 @@ class RuleForm(ModelForm):
)
),
FormActions(
Submit('submit', 'Save'),
LinkButton('back', 'Back', reverse_lazy('network.rule_list'))
Submit('submit', _("Save")),
LinkButton('back', _("Back"), reverse_lazy('network.rule_list'))
)
)
......@@ -219,8 +221,8 @@ class SwitchPortForm(ModelForm):
)
),
FormActions(
Submit('submit', 'Save'),
LinkButton('back', 'Back',
Submit('submit', _("Save")),
LinkButton('back', _("Back"),
reverse_lazy('network.switch_port_list'))
)
)
......@@ -234,41 +236,42 @@ class VlanForm(ModelForm):
helper.layout = Layout(
Div(
Fieldset(
'Identity',
'',
'name',
'vid',
'network_type',
'managed',
),
Fieldset(
'IPv4',
_('IPv4'),
'network4',
'snat_to',
'snat_ip',
'dhcp_pool',
),
Fieldset(
'IPv6',
_('IPv6'),
'network6',
'ipv6_template',
'host_ipv6_prefixlen',
),
Fieldset(
'Domain name service',
_('Domain name service'),
'domain',
'reverse_domain',
),
Fieldset(
'Info',
_('Info'),
'description',
'comment',
'owner',
# 'created_at',
# 'modified_at',
),
),
FormActions(
Submit('submit', 'Save'),
LinkButton('back', 'Back', reverse_lazy('network.vlan_list'))
Submit('submit', _("Save")),
LinkButton('back', _("Back"), reverse_lazy('network.vlan_list'))
)
)
......@@ -289,8 +292,8 @@ class VlanGroupForm(ModelForm):
)
),
FormActions(
Submit('submit', 'Save'),
LinkButton('back', 'Back', reverse_lazy(
Submit('submit', _("Save")),
LinkButton('back', _("Back"), reverse_lazy(
'network.vlan_group_list'))
)
)
......
......@@ -23,7 +23,7 @@
{% if rule_list.data.data.count > 0 %}
{% render_table rule_list %}
{% else %}
{% trans "No rules associated with this host!" %}
{% trans "No rules associated with this host." %}
{% endif %}
<div class="page-header">
......@@ -48,7 +48,7 @@
<h3>{% trans "Add host group" %}</h3>
</div>
{% if not_used_groups|length == 0 %}
No more groups to add!
{% trans "No more groups to add" %}
{% else %}
<form action="{% url "network.add_host_group" pk=host_pk %}" method="POST">
{% csrf_token %}
......@@ -65,15 +65,19 @@
</form>
{% endif %}
<div class="page-header">
<a href="{% url "network.record_create" %}?host={{ host_pk }}"
class="btn btn-xs btn-success pull-right">
<i class="fa fa-plus-circle"></i>
{% trans "Add new CNAME record" %}
</a>
<h3>{% trans "Records" %}</h3>
</div>
{% render_table records_table %}
</div><!-- col-sm-5 -->
</div><!-- row -->
{% endblock %}
{% block extra_etc %}
<script src="{% static "js/host.js" %}"></script>
<script src="{% static "js/host.js" %}"></script>
{% endblock %}
......@@ -34,6 +34,7 @@ from .forms import (HostForm, VlanForm, DomainForm, GroupForm, RecordForm,
BlacklistItemForm, RuleForm, VlanGroupForm, SwitchPortForm)
from django.contrib import messages
from django.contrib.messages.views import SuccessMessageMixin
from django.views.generic.edit import FormMixin
from django.utils.translation import ugettext_lazy as _
from braces.views import LoginRequiredMixin, SuperuserRequiredMixin
......@@ -42,25 +43,14 @@ from operator import itemgetter
from itertools import chain
import json
from dashboard.views import AclUpdateView
from dashboard.forms import AclUserAddForm
from dashboard.forms import AclUserOrGroupAddForm
class SuccessMessageMixin(FormMixin):
"""
Adds a success message on successful form submission.
From django/contrib/messages/views.py@9a85ad89
"""
success_message = ''
def form_valid(self, form):
response = super(SuccessMessageMixin, self).form_valid(form)
success_message = self.get_success_message(form.cleaned_data)
if success_message:
messages.success(self.request, success_message)
return response
def get_success_message(self, cleaned_data):
return self.success_message % cleaned_data
class InitialOwnerMixin(FormMixin):
def get_initial(self):
initial = super(InitialOwnerMixin, self).get_initial()
initial['owner'] = self.request.user
return initial
class IndexView(LoginRequiredMixin, SuperuserRequiredMixin, TemplateView):
......@@ -190,7 +180,7 @@ class DomainDetail(LoginRequiredMixin, SuperuserRequiredMixin,
class DomainCreate(LoginRequiredMixin, SuperuserRequiredMixin,
SuccessMessageMixin, CreateView):
SuccessMessageMixin, InitialOwnerMixin, CreateView):
model = Domain
template_name = "network/domain-create.html"
form_class = DomainForm
......@@ -274,7 +264,7 @@ class GroupList(LoginRequiredMixin, SuperuserRequiredMixin, SingleTableView):
class GroupCreate(LoginRequiredMixin, SuperuserRequiredMixin,
SuccessMessageMixin, CreateView):
SuccessMessageMixin, InitialOwnerMixin, CreateView):
model = Group
template_name = "network/group-create.html"
form_class = GroupForm
......@@ -413,7 +403,7 @@ class HostDetail(LoginRequiredMixin, SuperuserRequiredMixin,
class HostCreate(LoginRequiredMixin, SuperuserRequiredMixin,
SuccessMessageMixin, CreateView):
SuccessMessageMixin, InitialOwnerMixin, CreateView):
model = Host
template_name = "network/host-create.html"
form_class = HostForm
......@@ -497,7 +487,7 @@ class RecordDetail(LoginRequiredMixin, SuperuserRequiredMixin,
class RecordCreate(LoginRequiredMixin, SuperuserRequiredMixin,
SuccessMessageMixin, CreateView):
SuccessMessageMixin, InitialOwnerMixin, CreateView):
model = Record
template_name = "network/record-create.html"
form_class = RecordForm
......@@ -505,10 +495,23 @@ class RecordCreate(LoginRequiredMixin, SuperuserRequiredMixin,
success_message = _(u'Successfully created record!')
def get_initial(self):
return {
# 'owner': 1,
'domain': self.request.GET.get('domain'),
}
initial = super(RecordCreate, self).get_initial()
initial['domain'] = self.request.GET.get('domain')
host_pk = self.request.GET.get("host")
try:
host = Host.objects.get(pk=host_pk)
except (Host.DoesNotExist, ValueError):
host = None
if host:
initial.update({
'type': "CNAME",
'host': host,
'address': host.get_fqdn(),
})
return initial
class RecordDelete(LoginRequiredMixin, SuperuserRequiredMixin, DeleteView):
......@@ -556,18 +559,19 @@ class RuleDetail(LoginRequiredMixin, SuperuserRequiredMixin,
class RuleCreate(LoginRequiredMixin, SuperuserRequiredMixin,
SuccessMessageMixin, CreateView):
SuccessMessageMixin, InitialOwnerMixin, CreateView):
model = Rule
template_name = "network/rule-create.html"
form_class = RuleForm
success_message = _(u'Successfully created rule!')
def get_initial(self):
return {
# 'owner': 1,
initial = super(RuleCreate, self).get_initial()
initial.update({
'host': self.request.GET.get('host'),
'hostgroup': self.request.GET.get('hostgroup')
}
})
return initial
class RuleDelete(LoginRequiredMixin, SuperuserRequiredMixin, DeleteView):
......@@ -660,14 +664,14 @@ class VlanDetail(LoginRequiredMixin, SuperuserRequiredMixin,
context['vlan_vid'] = self.kwargs.get('vid')
context['acl'] = AclUpdateView.get_acl_data(
self.object, self.request.user, 'network.vlan-acl')
context['aclform'] = AclUserAddForm()
context['aclform'] = AclUserOrGroupAddForm()
return context
success_url = reverse_lazy('network.vlan_list')
class VlanCreate(LoginRequiredMixin, SuperuserRequiredMixin,
SuccessMessageMixin, CreateView):
SuccessMessageMixin, InitialOwnerMixin, CreateView):
model = Vlan
template_name = "network/vlan-create.html"
form_class = VlanForm
......@@ -747,7 +751,7 @@ class VlanGroupDetail(LoginRequiredMixin, SuperuserRequiredMixin,
class VlanGroupCreate(LoginRequiredMixin, SuperuserRequiredMixin,
SuccessMessageMixin, CreateView):
SuccessMessageMixin, InitialOwnerMixin, CreateView):
model = VlanGroup
template_name = "network/vlan-group-create.html"
form_class = VlanGroupForm
......
......@@ -367,7 +367,8 @@ class Disk(TimeStampedModel):
disk = cls.__create(user, params)
disk.clean()
disk.save()
logger.debug("Disk created: %s", params)
logger.debug(u"Disk created from: %s",
unicode(params.get("base", "nobase")))
return disk
@classmethod
......
......@@ -24,7 +24,7 @@ from celery.signals import worker_ready
from celery.contrib.abortable import AbortableAsyncResult
from django.core.urlresolvers import reverse
from django.db.models import CharField, ForeignKey
from django.db.models import CharField, ForeignKey, BooleanField
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _, ugettext_noop
......@@ -70,6 +70,8 @@ class InstanceActivity(ActivityModel):
help_text=_('Instance this activity works on.'),
verbose_name=_('instance'))
resultant_state = CharField(blank=True, max_length=20, null=True)
interruptible = BooleanField(default=False, help_text=_(
'Other activities can interrupt this one.'))
class Meta:
app_label = 'vm'
......@@ -91,24 +93,30 @@ class InstanceActivity(ActivityModel):
@classmethod
def create(cls, code_suffix, instance, task_uuid=None, user=None,
concurrency_check=True, readable_name=None,
resultant_state=None):
resultant_state=None, interruptible=False):
readable_name = _normalize_readable_name(readable_name, code_suffix)
# Check for concurrent activities
active_activities = instance.activity_log.filter(finished__isnull=True)
if concurrency_check and active_activities.exists():
raise ActivityInProgressError.create(active_activities[0])
for i in active_activities:
if i.interruptible:
i.finish(False, result=ugettext_noop(
"Interrupted by other activity."))
else:
raise ActivityInProgressError.create(i)
activity_code = join_activity_code(cls.ACTIVITY_CODE_BASE, code_suffix)
activity_code = cls.construct_activity_code(code_suffix)
act = cls(activity_code=activity_code, instance=instance, parent=None,
resultant_state=resultant_state, started=timezone.now(),
readable_name_data=readable_name.to_dict(),
task_uuid=task_uuid, user=user)
task_uuid=task_uuid, user=user, interruptible=interruptible)
act.save()
return act
def create_sub(self, code_suffix, task_uuid=None, concurrency_check=True,
readable_name=None, resultant_state=None):
readable_name=None, resultant_state=None,
interruptible=False):
readable_name = _normalize_readable_name(readable_name, code_suffix)
# Check for concurrent activities
......@@ -119,7 +127,7 @@ class InstanceActivity(ActivityModel):
act = InstanceActivity(
activity_code=join_activity_code(self.activity_code, code_suffix),
instance=self.instance, parent=self,
resultant_state=resultant_state,
resultant_state=resultant_state, interruptible=interruptible,
readable_name_data=readable_name.to_dict(), started=timezone.now(),
task_uuid=task_uuid, user=self.user)
act.save()
......@@ -183,13 +191,14 @@ class InstanceActivity(ActivityModel):
@contextmanager
def sub_activity(self, code_suffix, on_abort=None, on_commit=None,
readable_name=None, task_uuid=None,
concurrency_check=True):
concurrency_check=True, interruptible=False):
"""Create a transactional context for a nested instance activity.
"""
if not readable_name:
warn("Set readable_name", stacklevel=3)
act = self.create_sub(code_suffix, task_uuid, concurrency_check,
readable_name=readable_name)
readable_name=readable_name,
interruptible=interruptible)
return activitycontextimpl(act, on_abort=on_abort, on_commit=on_commit)
def get_operation(self):
......
......@@ -123,6 +123,10 @@ class VirtualMachineDescModel(BaseResourceConfigModel):
'format like "%s".') %
'Ubuntu 12.04 LTS Desktop amd64'))
tags = TaggableManager(blank=True, verbose_name=_("tags"))
has_agent = BooleanField(verbose_name=_('has agent'), default=True,
help_text=_(
'If the machine has agent installed, and '
'the manager should wait for its start.'))
class Meta:
abstract = True
......@@ -424,7 +428,8 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
# prepare parameters
common_fields = ['name', 'description', 'num_cores', 'ram_size',
'max_ram_size', 'arch', 'priority', 'boot_menu',
'raw_data', 'lease', 'access_method', 'system']
'raw_data', 'lease', 'access_method', 'system',
'has_agent']
params = dict(template=template, owner=owner, pw=pwgen())
params.update([(f, getattr(template, f)) for f in common_fields])
params.update(kwargs) # override defaults w/ user supplied values
......
......@@ -299,16 +299,20 @@ class DeployOperation(InstanceOperation):
"deploy network")):
self.instance.deploy_net()
try:
self.instance.renew(parent_activity=activity)
except:
pass
# Resume vm
with activity.sub_activity(
'booting', readable_name=ugettext_noop(
"boot virtual machine")):
self.instance.resume_vm(timeout=timeout)
try:
self.instance.renew(parent_activity=activity)
except:
pass
if self.instance.has_agent:
activity.sub_activity('os_boot', readable_name=ugettext_noop(
"wait operating system loading"), interruptible=True)
register_operation(DeployOperation)
......@@ -425,8 +429,11 @@ class RebootOperation(InstanceOperation):
required_perms = ()
accept_states = ('RUNNING', )
def _operation(self, timeout=5):
def _operation(self, activity, timeout=5):
self.instance.reboot_vm(timeout=timeout)
if self.instance.has_agent:
activity.sub_activity('os_boot', readable_name=ugettext_noop(
"wait operating system loading"), interruptible=True)
register_operation(RebootOperation)
......@@ -499,8 +506,11 @@ class ResetOperation(InstanceOperation):
required_perms = ()
accept_states = ('RUNNING', )
def _operation(self, timeout=5):
def _operation(self, activity, timeout=5):
self.instance.reset_vm(timeout=timeout)
if self.instance.has_agent:
activity.sub_activity('os_boot', readable_name=ugettext_noop(
"wait operating system loading"), interruptible=True)
register_operation(ResetOperation)
......
......@@ -85,16 +85,22 @@ def agent_started(vm, version=None):
from vm.models import Instance, instance_activity, InstanceActivity
instance = Instance.objects.get(id=int(vm.split('-')[-1]))
queue = instance.get_remote_queue_name("agent")
initialized = InstanceActivity.objects.filter(
instance=instance, activity_code='vm.Instance.agent.cleanup').exists()
initialized = instance.activity_log.filter(
activity_code='vm.Instance.agent.cleanup').exists()
with instance_activity(code_suffix='agent',
readable_name=ugettext_noop('agent'),
concurrency_check=False,
instance=instance) as act:
with act.sub_activity('starting',
readable_name=ugettext_noop('starting')):
pass
for i in InstanceActivity.objects.filter(
instance=instance, activity_code__endswith='.os_boot',
finished__isnull=True):
i.finish(True)
if version and version != settings.AGENT_VERSION:
try:
update_agent(instance, act)
......@@ -108,10 +114,9 @@ def agent_started(vm, version=None):
send_init_commands(instance, act)
send_networking_commands(instance, act)
with act.sub_activity(
'start_access_server',
readable_name=ugettext_noop('start access server')
):
with act.sub_activity('start_access_server',
readable_name=ugettext_noop(
'start access server')):
start_access_server.apply_async(queue=queue, args=(vm, ))
......
......@@ -217,6 +217,8 @@ class InstanceActivityTestCase(TestCase):
def test_create_concurrency_check(self):
instance = MagicMock(spec=Instance)
instance.activity_log.filter.return_value.__iter__.return_value = iter(
[MagicMock(spec=InstanceActivity, interruptible=False)])
instance.activity_log.filter.return_value.exists.return_value = True
with self.assertRaises(ActivityInProgressError):
......
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