Commit bbd8020b by Kálmán Viktor

Merge branch 'feature-vm-list-filtering'

Conflicts:
	circle/dashboard/static/dashboard/dashboard.css
parents e6d9359f 11bf4569
...@@ -663,3 +663,23 @@ textarea[name="list-new-namelist"] { ...@@ -663,3 +663,23 @@ textarea[name="list-new-namelist"] {
display: none; display: none;
} }
/* vm list css */
.vm-list-selected, .vm-list-selected td {
background-color: #e8e8e8 !important;
}
.vm-list-selected:hover, .vm-list-selected:hover td {
background-color: #d0d0d0 !important;
}
.vm-list-selected td:first-child {
font-weight: bold;
}
.vm-list-table-thin {
width: 10px;
}
.vm-list-table-admin {
width: 130px;
}
...@@ -70,7 +70,15 @@ ...@@ -70,7 +70,15 @@
{% endif %} {% endif %}
</tr> </tr>
{% empty %} {% empty %}
<tr><td colspan="5"><strong>{% trans "You have no virtual machines." %}</strong></td></tr> <tr>
<td colspan="5">
{% if request.GET.s %}
<strong>{% trans "No result." %}</strong>
{% else %}
<strong>{% trans "You have no virtual machines." %}</strong>
{% endif %}
</td>
</tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
...@@ -85,31 +93,6 @@ ...@@ -85,31 +93,6 @@
{% trans "If you want to select multiple instances by one click select an instance then hold down <strong>SHIFT</strong> key and select another one!" %} {% trans "If you want to select multiple instances by one click select an instance then hold down <strong>SHIFT</strong> key and select another one!" %}
</div> </div>
<style>
.popover {
max-width: 600px;
}
.vm-list-selected, .vm-list-selected td {
background-color: #e8e8e8 !important;
}
.vm-list-selected:hover, .vm-list-selected:hover td {
background-color: #d0d0d0 !important;
}
.vm-list-selected td:first-child {
font-weight: bold;
}
.vm-list-table-thin {
width: 10px;
}
.vm-list-table-admin {
width: 130px;
}
</style>
{% endblock %} {% endblock %}
{% block extra_js %} {% block extra_js %}
......
...@@ -1968,3 +1968,26 @@ class AclViewTest(LoginMixin, TestCase): ...@@ -1968,3 +1968,26 @@ class AclViewTest(LoginMixin, TestCase):
self.assertEqual(self.ut, InstanceTemplate.objects.get(id=1).owner) self.assertEqual(self.ut, InstanceTemplate.objects.get(id=1).owner)
self.assertTrue((self.ut, "owner") in tmpl.get_users_with_level()) self.assertTrue((self.ut, "owner") in tmpl.get_users_with_level())
self.assertEqual(resp.status_code, 302) self.assertEqual(resp.status_code, 302)
class VmListTest(LoginMixin, TestCase):
fixtures = ['test-vm-fixture.json', 'node.json']
def setUp(self):
Instance.get_remote_queue_name = Mock(return_value='test')
self.u1 = User.objects.create(username='user1')
self.u1.set_password('password')
self.u1.save()
def tearDown(self):
super(VmListTest, self).tearDown()
self.u1.delete()
def test_filter_w_invalid_input(self):
c = Client()
self.login(c, self.u1)
resp = c.get("/dashboard/vm/list/", {
's': "A:B:C:D:"
})
self.assertEqual(200, resp.status_code)
...@@ -27,6 +27,7 @@ from django.conf import settings ...@@ -27,6 +27,7 @@ from django.conf import settings
from django.contrib.auth.models import User, Group from django.contrib.auth.models import User, Group
from django.contrib.auth.views import login, redirect_to_login from django.contrib.auth.views import login, redirect_to_login
from django.contrib.messages import warning from django.contrib.messages import warning
from django.contrib.messages.views import SuccessMessageMixin
from django.core.exceptions import ( from django.core.exceptions import (
PermissionDenied, SuspiciousOperation, PermissionDenied, SuspiciousOperation,
) )
...@@ -84,22 +85,18 @@ def search_user(keyword): ...@@ -84,22 +85,18 @@ def search_user(keyword):
return User.objects.get(email=keyword) return User.objects.get(email=keyword)
# github.com/django/django/blob/stable/1.6.x/django/contrib/messages/views.py class FilterMixin(object):
class SuccessMessageMixin(object):
"""
Adds a success message on successful form submission.
"""
success_message = ''
def form_valid(self, form): def get_queryset_filters(self):
response = super(SuccessMessageMixin, self).form_valid(form) filters = {}
success_message = self.get_success_message(form.cleaned_data) for item in self.allowed_filters:
if success_message: if item in self.request.GET:
messages.success(self.request, success_message) filters[self.allowed_filters[item]] = self.request.GET[item]
return response return filters
def get_success_message(self, cleaned_data): def get_queryset(self):
return self.success_message % cleaned_data return super(FilterMixin,
self).get_queryset().filter(**self.get_queryset_filters())
class IndexView(LoginRequiredMixin, TemplateView): class IndexView(LoginRequiredMixin, TemplateView):
...@@ -1093,8 +1090,15 @@ class TemplateDelete(LoginRequiredMixin, DeleteView): ...@@ -1093,8 +1090,15 @@ class TemplateDelete(LoginRequiredMixin, DeleteView):
return HttpResponseRedirect(success_url) return HttpResponseRedirect(success_url)
class VmList(LoginRequiredMixin, ListView): class VmList(LoginRequiredMixin, FilterMixin, ListView):
template_name = "dashboard/vm-list.html" template_name = "dashboard/vm-list.html"
allowed_filters = {
'name': "name__icontains",
'node': "node__name__icontains",
'status': "status__iexact",
'tags': "tags__name__in", # note: use it as ?tags[]=a,b
'owner': "owner__username",
}
def get(self, *args, **kwargs): def get(self, *args, **kwargs):
if self.request.is_ajax(): if self.request.is_ajax():
...@@ -1122,10 +1126,8 @@ class VmList(LoginRequiredMixin, ListView): ...@@ -1122,10 +1126,8 @@ class VmList(LoginRequiredMixin, ListView):
unicode(self.request.user)) unicode(self.request.user))
queryset = Instance.get_objects_with_level( queryset = Instance.get_objects_with_level(
'user', self.request.user).filter(destroyed_at=None) 'user', self.request.user).filter(destroyed_at=None)
s = self.request.GET.get("s")
if s:
queryset = queryset.filter(name__icontains=s)
self.create_fake_get()
sort = self.request.GET.get("sort") sort = self.request.GET.get("sort")
# remove "-" that means descending order # remove "-" that means descending order
# also check if the column name is valid # also check if the column name is valid
...@@ -1133,7 +1135,43 @@ class VmList(LoginRequiredMixin, ListView): ...@@ -1133,7 +1135,43 @@ class VmList(LoginRequiredMixin, ListView):
(sort[1:] if sort[0] == "-" else sort) (sort[1:] if sort[0] == "-" else sort)
in [i.name for i in Instance._meta.fields] + ["pk"]): in [i.name for i in Instance._meta.fields] + ["pk"]):
queryset = queryset.order_by(sort) queryset = queryset.order_by(sort)
return queryset.select_related('owner', 'node') return queryset.filter(**self.get_queryset_filters()
).select_related('owner', 'node')
def create_fake_get(self):
"""
Updates the request's GET dict to filter the vm list
For example: "name:xy node:1" updates the GET dict
to resemble this URL ?name=xy&node=1
"name:xy node:1".split(":") becomes ["name", "xy node", "1"]
we pop the the first element and use it as the first dict key
then we iterate over the rest of the list and split by the last
whitespace, the first part of this list will be the previous key's
value, then last part of the list will be the next key.
The final dict looks like this: {'name': xy, 'node':1}
"""
s = self.request.GET.get("s")
if s:
s = s.split(":")
if len(s) < 2: # if there is no ':' in the string, filter by name
got = {'name': s[0]}
else:
latest = s.pop(0)
got = {'%s' % latest: None}
for i in s[:-1]:
new = i.rsplit(" ", 1)
got[latest] = new[0]
latest = new[1] if len(new) > 1 else None
got[latest] = s[-1]
# generate a new GET request, that is kinda fake
fake = self.request.GET.copy()
for k, v in got.iteritems():
fake["%s%s" % (
k, "[]" if len(v.split(",")) > 1 else "")] = v
self.request.GET = fake
class NodeList(LoginRequiredMixin, SuperuserRequiredMixin, SingleTableView): class NodeList(LoginRequiredMixin, SuperuserRequiredMixin, SingleTableView):
......
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