Commit 338c3960 by Kálmán Viktor

Merge branch 'master' into issue-282

Conflicts:
	circle/network/views.py
parents 999a46e0 65fd2ea6
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):
try:
name = choice.get_full_name()
except AttributeError:
name = _('group')
if name:
name = u'(%s)' % name
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:
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)
......@@ -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):
......
......@@ -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;
......@@ -960,3 +964,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>
......
......@@ -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">
{% csrf_token %}
<label>
{% trans "E-mail address or identifier of user" %}:
<input name="name">
</label>
<input type="submit">
</form>
<div class="pull-right">
<form action="{% url "dashboard.views.vm-transfer-ownership" pk=instance.pk %}" method="POST" style="max-width: 400px;">
{% csrf_token %}
<label>
{{ form.name.label }}
</label>
<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 %}
</div>
</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 (
......@@ -390,7 +391,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
......@@ -1282,7 +1283,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)
......@@ -1299,17 +1301,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}))
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}))
......@@ -1328,9 +1328,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",
......@@ -1717,7 +1715,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):
......@@ -2768,11 +2766,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:
......
......@@ -7,73 +7,77 @@
{% block content %}
<div class="page-header">
<a href="{% url "network.host_delete" pk=host_pk%}" class="btn btn-danger pull-right"><i class="fa fa-times-circle"></i> {% trans "Delete this host" %}</a>
<h2>{{ form.hostname.value }}</h2>
<a href="{% url "network.host_delete" pk=host_pk%}" class="btn btn-danger pull-right"><i class="fa fa-times-circle"></i> {% trans "Delete this host" %}</a>
<h2>{{ form.hostname.value }}</h2>
</div>
<div class="row">
<div class="col-md-6">
{% crispy form %}
<div class="col-md-6">
{% crispy form %}
</div>
<div class="col-md-6">
<div class="page-header">
<a href="{% url "network.rule_create" %}?host={{ host_pk }}" class="btn btn-success pull-right btn-xs"><i class="fa fa-plus-circle"></i> {% trans "Add new rule" %}</a>
<h3>{% trans "Rules" %}</h3>
</div>
<div class="col-md-6">
<div class="page-header">
<a href="{% url "network.rule_create" %}?host={{ host_pk }}" class="btn btn-success pull-right btn-xs"><i class="fa fa-plus-circle"></i> {% trans "Add new rule" %}</a>
<h3>{% trans "Rules" %}</h3>
</div>
{% if rule_list.data.data.count > 0 %}
{% render_table rule_list %}
{% else %}
{% trans "No rules associated with this host!" %}
{% endif %}
{% if rule_list.data.data.count > 0 %}
{% render_table rule_list %}
{% else %}
{% trans "No rules associated with this host." %}
{% endif %}
<div class="page-header">
<h3>{% trans "Groups" %}</h3>
<div class="page-header">
<h3>{% trans "Groups" %}</h3>
</div>
{% if group_rule_list|length > 0 %}
{% for group in group_rule_list %}
<div>
<h4 id="{{ group.pk }}_group_pk">{{ group.name }}
<a href="{% url "network.remove_host_group" pk=host_pk group_pk=group.pk %}?from={{ request.path }}">
<i class="fa fa-times" style="vertical-align: middle;"></i></a>
<a href="{% url "network.group" group.pk %}">
<i class="fa fa-pencil" style="vertical-align: middle;"></i></a>
</h4>
</div>
{% if group_rule_list|length > 0 %}
{% for group in group_rule_list %}
<div>
<h4 id="{{ group.pk }}_group_pk">{{ group.name }}
<a href="{% url "network.remove_host_group" pk=host_pk group_pk=group.pk %}?from={{ request.path }}">
<i class="fa fa-times" style="vertical-align: middle;"></i></a>
<a href="{% url "network.group" group.pk %}">
<i class="fa fa-pencil" style="vertical-align: middle;"></i></a>
</h4>
</div>
{% endfor %}
{% else %}
{% trans "This host is not added to any host groups!" %}
{% endif %}
{% endfor %}
{% else %}
{% trans "This host is not added to any host groups!" %}
{% endif %}
<div class="page-header">
<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">
<form action="{% url "network.add_host_group" pk=host_pk %}" method="POST">
{% csrf_token %}
<div class="input-group">
<select name="group" id="add_group" class="form-control">
{% for rest in not_used_groups %}
<option value="{{ rest.pk }}">{{ rest }}</option>
{% endfor %}
</select>
<div class="input-group-btn">
<input type="submit" value="{% trans "Add group" %}" class="btn btn-default"></input>
</div>
</div><!-- input-group -->
</form>
<div class="input-group">
<select name="group" id="add_group" class="form-control">
{% for rest in not_used_groups %}
<option value="{{ rest.pk }}">{{ rest }}</option>
{% endfor %}
</select>
<div class="input-group-btn">
<input type="submit" value="{% trans "Add group" %}" class="btn btn-default"></input>
</div>
</div><!-- input-group -->
</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><!-- col-sm-5 -->
</div><!-- row -->
{% endblock %}
{% block extra_etc %}
<script src="{% static "js/host.js" %}"></script>
<script src="{% static "js/host.js" %}"></script>
{% endblock %}
......@@ -43,7 +43,7 @@ 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 InitialOwnerMixin(FormMixin):
......@@ -495,8 +495,22 @@ class RecordCreate(LoginRequiredMixin, SuperuserRequiredMixin,
success_message = _(u'Successfully created record!')
def get_initial(self):
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
initial = {'owner': self.request.user}
if host:
initial.update({
'type': "CNAME",
'host': host,
'address': host.get_fqdn(),
})
else:
initial['domain'] = self.request.GET.get('domain')
return initial
......@@ -650,7 +664,7 @@ 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')
......
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