Commit 0136016f by Bach Dániel

Merge branch 'fix-autocomplete' into 'master'

Fix Autocomplete

Closes #273
parents efdebdff 3c056c38
import autocomplete_light import autocomplete_light
from django.contrib.auth.models import User
from django.utils.html import escape
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from .views import AclUpdateView 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 = ( search_fields = (
('^first_name', 'last_name', 'username', '^email', 'profile__org_id'), ('first_name', 'last_name', 'username', 'email', 'profile__org_id'),
('^name', 'groupprofile__org_id'), ('name', 'groupprofile__org_id'),
) )
autocomplete_js_attributes = {'placeholder': _("Name of group or user")} choice_html_format = (u'<span data-value="%s"><span style="display:none"'
choice_html_format = u'<span data-value="%s"><span>%s</span> %s</span>' 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: try:
name = choice.get_full_name() extra_fields.append(highlight(choice.profile.org_id, q))
except AttributeError: except Profile.DoesNotExist:
name = _('group') pass
if name: return '%s (%s)' % (name, ', '.join(f for f in extra_fields
name = u'(%s)' % name if f))
else:
return _('%s (group)') % name
def choice_html(self, choice):
return self.choice_html_format % ( 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): def choices_for_request(self):
user = self.request.user user = self.request.user
self.choices = (AclUpdateView.get_allowed_users(user), self.choices = (AclUpdateView.get_allowed_users(user),
AclUpdateView.get_allowed_groups(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) autocomplete_light.register(AclUserAutocomplete)
...@@ -1055,9 +1055,29 @@ class UserCreationForm(OrgUserCreationForm): ...@@ -1055,9 +1055,29 @@ class UserCreationForm(OrgUserCreationForm):
return user return user
class AclUserAddForm(forms.Form): class AclUserOrGroupAddForm(forms.Form):
name = forms.CharField(widget=autocomplete_light.TextWidget( 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): class UserKeyForm(forms.ModelForm):
......
...@@ -591,11 +591,15 @@ footer a, footer a:hover, footer a:visited { ...@@ -591,11 +591,15 @@ footer a, footer a:hover, footer a:visited {
width: 100px; width: 100px;
} }
#group-detail-user-table tr:last-child td:nth-child(2) {
text-align: left;
}
#group-detail-perm-header { #group-detail-perm-header {
margin-top: 25px; margin-top: 25px;
} }
textarea[name="list-new-namelist"] { textarea[name="new_members"] {
max-width: 500px; max-width: 500px;
min-height: 80px; min-height: 80px;
margin-bottom: 10px; margin-bottom: 10px;
...@@ -960,3 +964,12 @@ textarea[name="list-new-namelist"] { ...@@ -960,3 +964,12 @@ textarea[name="list-new-namelist"] {
#vm-activity-state { #vm-activity-state {
margin-bottom: 15px; margin-bottom: 15px;
} }
.autocomplete-hl {
color: #b20000;
font-weight: bold;
}
.hilight .autocomplete-hl {
color: orange;
}
...@@ -2,6 +2,12 @@ ...@@ -2,6 +2,12 @@
<div class="modal fade" id="confirmation-modal" tabindex="-1" role="dialog"> <div class="modal fade" id="confirmation-modal" tabindex="-1" role="dialog">
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <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"> <div class="modal-body">
{% if template %} {% if template %}
{% include template %} {% include template %}
......
...@@ -89,13 +89,12 @@ ...@@ -89,13 +89,12 @@
<tr> <tr>
<td><i class="fa fa-plus"></i></td> <td><i class="fa fa-plus"></i></td>
<td colspan="2"> <td colspan="2">
<input type="text" class="form-control" name="list-new-name" {{addmemberform.new_member}}
placeholder="{% trans "Name of user" %}">
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </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> placeholder="{% trans "Add multiple users at once (one identifier per line)." %}"></textarea>
<div class="form-actions"> <div class="form-actions">
<button type="submit" class="btn btn-success">{% trans "Save" %}</button> <button type="submit" class="btn btn-success">{% trans "Save" %}</button>
......
...@@ -9,8 +9,10 @@ ...@@ -9,8 +9,10 @@
{% endblocktrans %} {% endblocktrans %}
{% endif %} {% endif %}
{% if user == instance.owner or user.is_superuser %} {% if user == instance.owner or user.is_superuser %}
<span class="operation-wrapper">
<a href="{% url "dashboard.views.vm-transfer-ownership" instance.pk %}" <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 %} {% endif %}
</p> </p>
<h3>{% trans "Permissions"|capfirst %}</h3> <h3>{% trans "Permissions"|capfirst %}</h3>
......
{% extends "dashboard/base.html" %}
{% load i18n %} {% load i18n %}
{% block content %} <div class="pull-right">
<div class="body-content"> <form action="{% url "dashboard.views.vm-transfer-ownership" pk=instance.pk %}" method="POST" style="max-width: 400px;">
<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 %} {% csrf_token %}
<label> <label>
{% trans "E-mail address or identifier of user" %}: {{ form.name.label }}
<input name="name">
</label> </label>
<input type="submit"> <div class="input-group">
</form> {{form.name}}
<div class="input-group-btn">
<input type="submit" value="{% trans "Save" %}" class="btn btn-primary">
</div> </div>
</div> </div>
</div> </form>
{% endblock %} </div>
...@@ -1134,7 +1134,7 @@ class GroupDetailTest(LoginMixin, TestCase): ...@@ -1134,7 +1134,7 @@ class GroupDetailTest(LoginMixin, TestCase):
c = Client() c = Client()
user_in_group = self.g1.user_set.count() user_in_group = self.g1.user_set.count()
response = c.post('/dashboard/group/' + 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.assertEqual(user_in_group,
self.g1.user_set.count()) self.g1.user_set.count())
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)
...@@ -1144,7 +1144,7 @@ class GroupDetailTest(LoginMixin, TestCase): ...@@ -1144,7 +1144,7 @@ class GroupDetailTest(LoginMixin, TestCase):
self.login(c, 'user3') self.login(c, 'user3')
user_in_group = self.g1.user_set.count() user_in_group = self.g1.user_set.count()
response = c.post('/dashboard/group/' + 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(user_in_group, self.g1.user_set.count())
self.assertEqual(response.status_code, 403) self.assertEqual(response.status_code, 403)
...@@ -1153,7 +1153,7 @@ class GroupDetailTest(LoginMixin, TestCase): ...@@ -1153,7 +1153,7 @@ class GroupDetailTest(LoginMixin, TestCase):
self.login(c, 'superuser') self.login(c, 'superuser')
user_in_group = self.g1.user_set.count() user_in_group = self.g1.user_set.count()
response = c.post('/dashboard/group/' + 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(user_in_group + 1, self.g1.user_set.count())
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)
...@@ -1162,7 +1162,7 @@ class GroupDetailTest(LoginMixin, TestCase): ...@@ -1162,7 +1162,7 @@ class GroupDetailTest(LoginMixin, TestCase):
self.login(c, 'user0') self.login(c, 'user0')
user_in_group = self.g1.user_set.count() user_in_group = self.g1.user_set.count()
response = c.post('/dashboard/group/' + 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(user_in_group + 1, self.g1.user_set.count())
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)
...@@ -1172,7 +1172,7 @@ class GroupDetailTest(LoginMixin, TestCase): ...@@ -1172,7 +1172,7 @@ class GroupDetailTest(LoginMixin, TestCase):
user_in_group = self.g1.user_set.count() user_in_group = self.g1.user_set.count()
response = c.post('/dashboard/group/' + response = c.post('/dashboard/group/' +
str(self.g1.pk) + '/', 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(user_in_group + 2, self.g1.user_set.count())
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)
...@@ -1182,7 +1182,7 @@ class GroupDetailTest(LoginMixin, TestCase): ...@@ -1182,7 +1182,7 @@ class GroupDetailTest(LoginMixin, TestCase):
user_in_group = self.g1.user_set.count() user_in_group = self.g1.user_set.count()
response = c.post('/dashboard/group/' + response = c.post('/dashboard/group/' +
str(self.g1.pk) + '/', 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(user_in_group + 2, self.g1.user_set.count())
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)
...@@ -1192,7 +1192,7 @@ class GroupDetailTest(LoginMixin, TestCase): ...@@ -1192,7 +1192,7 @@ class GroupDetailTest(LoginMixin, TestCase):
user_in_group = self.g1.user_set.count() user_in_group = self.g1.user_set.count()
response = c.post('/dashboard/group/' + response = c.post('/dashboard/group/' +
str(self.g1.pk) + '/', 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(user_in_group, self.g1.user_set.count())
self.assertEqual(response.status_code, 403) self.assertEqual(response.status_code, 403)
...@@ -1201,7 +1201,7 @@ class GroupDetailTest(LoginMixin, TestCase): ...@@ -1201,7 +1201,7 @@ class GroupDetailTest(LoginMixin, TestCase):
user_in_group = self.g1.user_set.count() user_in_group = self.g1.user_set.count()
response = c.post('/dashboard/group/' + response = c.post('/dashboard/group/' +
str(self.g1.pk) + '/', 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(user_in_group, self.g1.user_set.count())
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)
...@@ -1471,8 +1471,8 @@ class TransferOwnershipViewTest(LoginMixin, TestCase): ...@@ -1471,8 +1471,8 @@ class TransferOwnershipViewTest(LoginMixin, TestCase):
c2 = self.u2.notification_set.count() c2 = self.u2.notification_set.count()
c = Client() c = Client()
self.login(c, 'user2') self.login(c, 'user2')
response = c.post('/dashboard/vm/1/tx/') response = c.post('/dashboard/vm/1/tx/', {'name': 'userx'})
assert response.status_code == 400 assert response.status_code == 403
self.assertEqual(self.u2.notification_set.count(), c2) self.assertEqual(self.u2.notification_set.count(), c2)
def test_owned_offer(self): def test_owned_offer(self):
......
...@@ -70,9 +70,10 @@ from .forms import ( ...@@ -70,9 +70,10 @@ from .forms import (
UserCreationForm, GroupProfileUpdateForm, UnsubscribeForm, UserCreationForm, GroupProfileUpdateForm, UnsubscribeForm,
VmSaveForm, UserKeyForm, VmRenewForm, VmStateChangeForm, VmSaveForm, UserKeyForm, VmRenewForm, VmStateChangeForm,
CirclePasswordChangeForm, VmCreateDiskForm, VmDownloadDiskForm, CirclePasswordChangeForm, VmCreateDiskForm, VmDownloadDiskForm,
TraitsForm, RawDataForm, GroupPermissionForm, AclUserAddForm, TraitsForm, RawDataForm, GroupPermissionForm, AclUserOrGroupAddForm,
VmResourcesForm, VmAddInterfaceForm, VmListSearchForm, VmResourcesForm, VmAddInterfaceForm, VmListSearchForm,
TemplateListSearchForm, ConnectCommandForm TemplateListSearchForm, ConnectCommandForm,
TransferOwnershipForm, AddGroupMemberForm
) )
from .tables import ( from .tables import (
...@@ -390,7 +391,7 @@ class VmDetailView(CheckedDetailView): ...@@ -390,7 +391,7 @@ class VmDetailView(CheckedDetailView):
).all() ).all()
context['acl'] = AclUpdateView.get_acl_data( context['acl'] = AclUpdateView.get_acl_data(
instance, self.request.user, 'dashboard.views.vm-acl') instance, self.request.user, 'dashboard.views.vm-acl')
context['aclform'] = AclUserAddForm() context['aclform'] = AclUserOrGroupAddForm()
context['os_type_icon'] = instance.os_type.replace("unknown", context['os_type_icon'] = instance.os_type.replace("unknown",
"question") "question")
# ipv6 infos # ipv6 infos
...@@ -1282,7 +1283,8 @@ class GroupDetailView(CheckedDetailView): ...@@ -1282,7 +1283,8 @@ class GroupDetailView(CheckedDetailView):
context['acl'] = AclUpdateView.get_acl_data( context['acl'] = AclUpdateView.get_acl_data(
self.object.profile, self.request.user, self.object.profile, self.request.user,
'dashboard.views.group-acl') 'dashboard.views.group-acl')
context['aclform'] = AclUserAddForm() context['aclform'] = AclUserOrGroupAddForm()
context['addmemberform'] = AddGroupMemberForm()
context['group_profile_form'] = GroupProfileUpdate.get_form_object( context['group_profile_form'] = GroupProfileUpdate.get_form_object(
self.request, self.object.profile) self.request, self.object.profile)
...@@ -1299,17 +1301,15 @@ class GroupDetailView(CheckedDetailView): ...@@ -1299,17 +1301,15 @@ class GroupDetailView(CheckedDetailView):
if request.POST.get('new_name'): if request.POST.get('new_name'):
return self.__set_name(request) return self.__set_name(request)
if request.POST.get('list-new-name'): if request.POST.get('new_member'):
return self.__add_user(request) return self.__add_user(request)
if request.POST.get('list-new-namelist'): if request.POST.get('new_members'):
return self.__add_list(request) 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", return redirect(reverse_lazy("dashboard.views.group-detail",
kwargs={'pk': self.get_object().pk})) kwargs={'pk': self.get_object().pk}))
def __add_user(self, request): def __add_user(self, request):
name = request.POST['list-new-name'] name = request.POST['new_member']
self.__add_username(request, name) self.__add_username(request, name)
return redirect(reverse_lazy("dashboard.views.group-detail", return redirect(reverse_lazy("dashboard.views.group-detail",
kwargs={'pk': self.object.pk})) kwargs={'pk': self.object.pk}))
...@@ -1328,9 +1328,7 @@ class GroupDetailView(CheckedDetailView): ...@@ -1328,9 +1328,7 @@ class GroupDetailView(CheckedDetailView):
messages.warning(request, _('User "%s" not found.') % name) messages.warning(request, _('User "%s" not found.') % name)
def __add_list(self, request): def __add_list(self, request):
if not self.get_has_level()(request.user, 'operator'): userlist = request.POST.get('new_members').split('\r\n')
raise PermissionDenied()
userlist = request.POST.get('list-new-namelist').split('\r\n')
for line in userlist: for line in userlist:
self.__add_username(request, line) self.__add_username(request, line)
return redirect(reverse_lazy("dashboard.views.group-detail", return redirect(reverse_lazy("dashboard.views.group-detail",
...@@ -1717,7 +1715,7 @@ class TemplateDetail(LoginRequiredMixin, SuccessMessageMixin, UpdateView): ...@@ -1717,7 +1715,7 @@ class TemplateDetail(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
obj, self.request.user, 'dashboard.views.template-acl') obj, self.request.user, 'dashboard.views.template-acl')
context['disks'] = obj.disks.all() context['disks'] = obj.disks.all()
context['is_owner'] = obj.has_level(self.request.user, 'owner') context['is_owner'] = obj.has_level(self.request.user, 'owner')
context['aclform'] = AclUserAddForm() context['aclform'] = AclUserOrGroupAddForm()
return context return context
def get_success_url(self): def get_success_url(self):
...@@ -2768,11 +2766,30 @@ class FavouriteView(TemplateView): ...@@ -2768,11 +2766,30 @@ class FavouriteView(TemplateView):
return HttpResponse("Added.") return HttpResponse("Added.")
class TransferOwnershipView(LoginRequiredMixin, DetailView): class TransferOwnershipView(CheckedDetailView, DetailView):
model = Instance 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): def post(self, request, *args, **kwargs):
form = TransferOwnershipForm(request.POST)
if not form.is_valid():
return self.get(request)
try: try:
new_owner = search_user(request.POST['name']) new_owner = search_user(request.POST['name'])
except User.DoesNotExist: except User.DoesNotExist:
......
...@@ -42,7 +42,7 @@ from operator import itemgetter ...@@ -42,7 +42,7 @@ from operator import itemgetter
from itertools import chain from itertools import chain
import json import json
from dashboard.views import AclUpdateView from dashboard.views import AclUpdateView
from dashboard.forms import AclUserAddForm from dashboard.forms import AclUserOrGroupAddForm
class SuccessMessageMixin(FormMixin): class SuccessMessageMixin(FormMixin):
...@@ -660,7 +660,7 @@ class VlanDetail(LoginRequiredMixin, SuperuserRequiredMixin, ...@@ -660,7 +660,7 @@ class VlanDetail(LoginRequiredMixin, SuperuserRequiredMixin,
context['vlan_vid'] = self.kwargs.get('vid') context['vlan_vid'] = self.kwargs.get('vid')
context['acl'] = AclUpdateView.get_acl_data( context['acl'] = AclUpdateView.get_acl_data(
self.object, self.request.user, 'network.vlan-acl') self.object, self.request.user, 'network.vlan-acl')
context['aclform'] = AclUserAddForm() context['aclform'] = AclUserOrGroupAddForm()
return context return context
success_url = reverse_lazy('network.vlan_list') 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