Commit 34b8db95 authored by Christos Stavrakakis's avatar Christos Stavrakakis

Merge branch 'release-0.16-admin-filters-compact-view' of...

Merge branch 'release-0.16-admin-filters-compact-view' of https://github.com/AthinaB/synnefo into release-0.16
parents 04c41ff3 12d2c503
......@@ -22,6 +22,9 @@ from django.core.exceptions import FieldError
from django.conf import settings
from synnefo_admin.admin.utils import model_dict
from synnefo_admin import admin_settings
sign = admin_settings.ADMIN_FIELD_SIGN
def prefix_strip(query):
......@@ -66,12 +69,45 @@ def model_filter(func):
The purpose of the decorator is to:
a) Split the queries into multiple keywords (space/tab separated).
b) Concatenate terms that have the ADMIN_FIELD_SIGN ("=") between them.
b) Ignore any empty queries.
"""
def process_terms(terms):
"""Generic term processing.
This function does the following:
* Concatenate terms that have the admin_settings.ADMIN_FIELD_SIGN ("=")
between them. E.g. the following list:
['first_name', '=', 'john', 'doe', 'last_name=', 'd']
becomes:
['first_name=john', 'doe', 'last_name=d']
"""
new_terms = []
cand = ''
for term in terms:
# Check if the current term can be concatenated with the previous
# (candidate) ones.
if term.startswith(sign) or cand.endswith(sign):
cand = cand + term
continue
# If the candidate cannot be concatenated with the current term,
# append it to the `new_terms` list.
if cand:
new_terms.append(cand)
cand = term
# Always append the last candidate, if valid
if cand:
new_terms.append(cand)
return new_terms
@functools.wraps(func)
def wrapper(queryset, query, *args, **kwargs):
if isinstance(query, str) or isinstance(query, unicode):
if isinstance(query, basestring):
query = query.split()
query = process_terms(query)
if query:
try:
......@@ -96,14 +132,14 @@ def malicious(field):
def update_queries(**queries):
"""Extract nested queries from a single query.
Check if the query is actually a nested query, by searching for the "="
operator.
Check if the query is actually a nested query, by searching for the
admin_settings.ADMIN_FIELD_SIGN (commonly "=").
FIXME: This is not the best/cleaner/intuitive approach to do this.
"""
new_queries = queries.copy()
for key, value in queries.iteritems():
if isinstance(value, str) or isinstance(value, unicode):
nested_query = value.split('=', 1)
nested_query = value.split(sign, 1)
if len(nested_query) == 1:
continue
field = nested_query[0]
......
......@@ -93,7 +93,7 @@ def catalog(request):
"""List view for Cyclades ip log."""
context = {}
context['action_dict'] = None
context['filter_dict'] = IPLogFilterSet().filters.itervalues()
context['filter_dict'] = IPLogFilterSet().filters.values()
context['columns'] = ["Address", "Server ID", "Network ID",
"Allocation date", "Release date", "Active", ""]
context['item_type'] = 'ip_log'
......
......@@ -171,7 +171,7 @@ def catalog(request):
context = {}
context['action_dict'] = get_permitted_actions(cached_actions,
request.user)
context['filter_dict'] = IPFilterSet().filters.itervalues()
context['filter_dict'] = IPFilterSet().filters.values()
context['columns'] = ["ID", "Address", "Floating",
"Creation date", "User ID", ""]
context['item_type'] = 'ip'
......
......@@ -64,7 +64,7 @@ class NetworkFilterSet(django_filters.FilterSet):
This filter collection is based on django-filter's FilterSet.
"""
network = django_filters.CharFilter(label='Network', action=filter_network)
net = django_filters.CharFilter(label='Network', action=filter_network)
user = django_filters.CharFilter(label='OF User', action=filter_user)
vm = django_filters.CharFilter(label='HAS VM', action=filter_vm)
ip = django_filters.CharFilter(label='HAS IP', action=filter_ip)
......@@ -74,5 +74,5 @@ class NetworkFilterSet(django_filters.FilterSet):
class Meta:
model = Network
fields = ('network', 'state', 'public', 'drained', 'user', 'vm', 'ip',
fields = ('net', 'state', 'public', 'drained', 'user', 'vm', 'ip',
'proj')
......@@ -163,7 +163,7 @@ def catalog(request):
context = {}
context['action_dict'] = get_permitted_actions(cached_actions,
request.user)
context['filter_dict'] = NetworkFilterSet().filters.itervalues()
context['filter_dict'] = NetworkFilterSet().filters.values()
context['columns'] = ["ID", "Name", "Status", "Public",
"Drained", ""]
context['item_type'] = 'network'
......
......@@ -116,11 +116,10 @@ class ProjectFilterSet(django_filters.FilterSet):
This filter collection is based on django-filter's FilterSet.
"""
project = django_filters.CharFilter(label='Project', action=filter_project)
proj = django_filters.CharFilter(label='Project', action=filter_project)
user = django_filters.CharFilter(label='OF User', action=filter_user)
vm = django_filters.CharFilter(label='HAS VM', action=filter_vm)
volume = django_filters.CharFilter(label='HAS Volume',
action=filter_volume)
vol = django_filters.CharFilter(label='HAS Volume', action=filter_volume)
net = django_filters.CharFilter(label='HAS Network', action=filter_network)
ip = django_filters.CharFilter(label='HAS IP', action=filter_ip)
project_status = django_filters.MultipleChoiceFilter(
......@@ -133,5 +132,5 @@ class ProjectFilterSet(django_filters.FilterSet):
class Meta:
model = Project
fields = ('project', 'project_status', 'application_status', 'is_base',
'user', 'vm', 'volume', 'net', 'ip',)
fields = ('proj', 'project_status', 'application_status', 'is_base',
'user', 'vm', 'vol', 'net', 'ip',)
......@@ -212,7 +212,7 @@ def catalog(request):
context = {}
context['action_dict'] = get_permitted_actions(cached_actions,
request.user)
context['filter_dict'] = ProjectFilterSet().filters.itervalues()
context['filter_dict'] = ProjectFilterSet().filters.values()
context['columns'] = ["ID", "Name", "Owner Name", "Project Status",
"Application Status", "Creation date", "End date",
""]
......
......@@ -140,13 +140,10 @@ class UserFilterSet(django_filters.FilterSet):
user = django_filters.CharFilter(label='User', action=filter_user)
vm = django_filters.CharFilter(label='HAS VM', action=filter_vm)
volume = django_filters.CharFilter(label='HAS Volume',
action=filter_volume)
network = django_filters.CharFilter(label='HAS Network',
action=filter_network)
vol = django_filters.CharFilter(label='HAS Volume', action=filter_volume)
net = django_filters.CharFilter(label='HAS Network', action=filter_network)
ip = django_filters.CharFilter(label='HAS IP', action=filter_ip)
proj = django_filters.CharFilter(label='IN Project',
action=filter_project)
proj = django_filters.CharFilter(label='IN Project', action=filter_project)
status = django_filters.MultipleChoiceFilter(
label='Status', action=filter_status, choices=choice2query.keys())
groups = django_filters.MultipleChoiceFilter(
......@@ -161,5 +158,4 @@ class UserFilterSet(django_filters.FilterSet):
class Meta:
model = AstakosUser
fields = ('user', 'status', 'groups', 'has_auth_providers',
'has_not_auth_providers', 'vm', 'volume', 'network', 'ip',
'proj')
'has_not_auth_providers', 'vm', 'vol', 'net', 'ip', 'proj')
......@@ -182,7 +182,7 @@ def catalog(request):
context = {}
context['action_dict'] = get_permitted_actions(cached_actions,
request.user)
context['filter_dict'] = UserFilterSet().filters.itervalues()
context['filter_dict'] = UserFilterSet().filters.values()
context['columns'] = ["ID", "E-mail", "First Name", "Last Name", "Active",
"Rejected", "Moderated", "Verified", ""]
context['item_type'] = 'user'
......
......@@ -190,7 +190,7 @@ def catalog(request):
context = {}
context['action_dict'] = get_permitted_actions(cached_actions,
request.user)
context['filter_dict'] = VMFilterSet().filters.itervalues()
context['filter_dict'] = VMFilterSet().filters.values()
context['columns'] = ["ID", "Name", "State", "Suspended", ""]
context['item_type'] = 'vm'
......
......@@ -83,6 +83,12 @@ def filter_disk_template(queryset, choices):
return queryset.filter(q)
def filter_index(queryset, query):
if not query.isdigit():
return queryset.none()
return queryset.filter(index=query)
class VolumeFilterSet(django_filters.FilterSet):
"""A collection of filters for volumes.
......@@ -90,20 +96,20 @@ class VolumeFilterSet(django_filters.FilterSet):
This filter collection is based on django-filter's FilterSet.
"""
volume = django_filters.CharFilter(label='Volume', action=filter_volume)
vol = django_filters.CharFilter(label='Volume', action=filter_volume)
user = django_filters.CharFilter(label='OF User', action=filter_user)
vm = django_filters.CharFilter(label='OF VM', action=filter_vm)
project = django_filters.CharFilter(label='OF Project',
action=filter_project)
proj = django_filters.CharFilter(label='OF Project', action=filter_project)
status = django_filters.MultipleChoiceFilter(
label='Status', name='status', choices=Volume.STATUS_VALUES)
disk_template = django_filters.MultipleChoiceFilter(
label="Disk template", choices=get_disk_template_choices(),
action=filter_disk_template)
index = django_filters.CharFilter(label="Index", action=filter_index)
source = django_filters.CharFilter(label="Soure image", name="source",
lookup_type='icontains')
class Meta:
model = Volume
fields = ('volume', 'status', 'disk_template', 'index', 'source',
'user', 'vm', 'project')
fields = ('vol', 'status', 'disk_template', 'index', 'source',
'user', 'vm', 'proj')
......@@ -185,7 +185,7 @@ def catalog(request):
context = {}
context['action_dict'] = get_permitted_actions(cached_actions,
request.user)
context['filter_dict'] = VolumeFilterSet().filters.itervalues()
context['filter_dict'] = VolumeFilterSet().filters.values()
context['columns'] = ["ID", "Name", "Status", "Size (GB)", "Disk template",
"VM ID", "Created at", "Updated at", ""]
context['item_type'] = 'volume'
......
......@@ -103,7 +103,7 @@ and light gray font colors
@mixin line-btn($color:$btn-line-bg, $padding:$default-btn-padding, $border:$btn-line-border ) {
@include default-btn($padding);
background-color: $color;
border-bottom: 2px solid $color;
border-bottom: 2px solid $color;
color: $border;
&:hover,
&:focus{
......@@ -148,6 +148,42 @@ and light gray font colors
}
}
.search-btn {
@include line-btn();
position: relative;
top: -2px;
margin-left: 20px;
span {
padding: 7px;
}
cursor: pointer;
}
.search-mode-btn {
float: right;
line-height: 30px;
&:hover {
cursor: pointer;
}
}
.instructions .line-btn {
padding: 8px 9px;
span {
padding: 0 4px;
}
&:hover {
.arrow {
font-weight: bold;
}
}
&.open:hover {
border-bottom-color: transparent;
}
.arrow {
vertical-align: middle;
}
}
// ====== STUFF ====== //
......@@ -198,12 +234,12 @@ body .custom-buttons {
.disabled {
display: none;
}
.snf-font-reload {
@extend .snf-refresh-outline;
}
.snf-font-remove {
@extend .snf-remove;
}
.snf-font-reload {
@extend .snf-refresh-outline;
}
.snf-font-remove {
@extend .snf-remove;
}
}
......@@ -212,7 +248,9 @@ body .custom-buttons {
Extra-button is used to show total selected rows
*/
.extra-btn {
body .custom-buttons .extra-btn {
float: right;
margin-right: 0;
span {
display: inline-block;
}
......@@ -279,3 +317,81 @@ Extra-button is used to show total selected rows
vertical-align: middle;
cursor: pointer;
}
/* Switch in filters */
.onoffswitch {
display: inline-block;
float: right;
position: relative;
width: 134px;
-webkit-user-select:none;
-moz-user-select:none;
-ms-user-select: none;
}
.onoffswitch-checkbox {
display: none;
}
.onoffswitch-label {
display: block;
overflow: hidden;
cursor: pointer;
/*border: 2px solid #F7EFEF;*/
border-radius: 20px;
}
.onoffswitch-inner {
display: block; width: 200%; margin-left: -100%;
-moz-transition: margin 0.3s ease-in 0s;
-webkit-transition: margin 0.3s ease-in 0s;
-o-transition: margin 0.3s ease-in 0s;
transition: margin 0.3s ease-in 0s;
}
.onoffswitch-inner:before, .onoffswitch-inner:after {
display: block;
float: left;
width: 50%;
height: 30px;
padding: 0;
line-height: 30px;
font-size: 12px;
color: white;
font-family: Trebuchet, Arial, sans-serif;
font-weight: normal;
-moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box;
}
.onoffswitch-inner:before {
content: "Standard View";
padding-left: 10px;
background-color: $btn-line-bg;
color: $btn-line-border;
}
.onoffswitch-inner:after {
content: "Compact View";
padding-right: 10px;
background-color: $btn-line-bg;
color: $btn-line-border;
text-align: right;
}
.onoffswitch-switch {
display: block;
width: 19px;
margin: 6px;
background: $btn-line-border;
border: 2px solid #F7EFEF;
border-radius: 20px;
position: absolute;
top: 0;
bottom: 4px;
right: 103px;
-moz-transition: all 0.3s ease-in 0s;
-webkit-transition: all 0.3s ease-in 0s;
-o-transition: all 0.3s ease-in 0s;
transition: all 0.3s ease-in 0s;
}
.onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-inner {
margin-left: 0;
}
.onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-switch {
right: 0px;
}
......@@ -271,33 +271,84 @@ svg>text:last-child {
.shortcuts-btn {
.book-icon {
padding-right: 8px;
padding-right: 2px;
vertical-align: sub;
font-size: 17px;
}
}
.shortcuts {
body .shortcuts {
dt {
width: 119px;
margin-bottom: 12px;
}
dd {
margin-left: 139px;
}
.key {
padding: 2px 9px;
font-style: normal;
font-weight: bold;
border: 1px solid gray;
border: 1px solid $gray-light-extra-2;
background: $gray-light-extra-3;
border-radius: 6px;
}
}
.filters-examples {
dt {
font-weight: normal;
margin-bottom: 0;
}
dd {
margin-bottom: 12px;
.highlight {
background: $gray-light-extra-3;
padding: 2px 6px;
border-bottom: 1px solid $gray-light-extra-2;
}
&.divider {
margin-bottom: 8px;
border-bottom: 1px solid $gray-light-extra-2;
}
}
}
.notes {
dt {
width: 50px;
}
dd {
margin-left: 60px;
}
}
.popover {
z-index: 1999;
max-width: none;
color: $popover-color;
color: $popover-color;
margin-bottom: 20px;
h2 {
text-align: center;
font-size: 1.3em;
font-weight: bold;
margin-top: 0;
}
h3 {
font-size: 1.2em;
font-weight: bold;
}
h4 {
font-size: 1.1em;
font-weight: bold;
}
dt {
margin-bottom: 8px;
overflow: visible;
}
.panel-default {
border-color: transparent;
box-shadow: none;
}
}
.sign-out {
......@@ -379,3 +430,6 @@ svg>text:last-child {
color: #222;
}
}
.popover-content {
max-width: 800px;
}
......@@ -32,7 +32,7 @@ $filter-height: 30px;
margin: 0;
height: $filter-height;
}
label,
label,
.dropdown{
height: $filter-height;
line-height: $filter-height;
......@@ -100,3 +100,72 @@ $filter-height: 30px;
.filters.subnav-fixed {
top: $navbar-height;
}
.input-with-btn {
border-width: 0px;
background-color: transparent;
input {
@media screen and (min-width: 400px) {
width: 200px;
}
@media screen and (min-width: 600px) {
width: 300px;
}
@media screen and (min-width: 800px) {
width: 500px;
}
@media screen and (min-width: 1000px) {
width: 700px;
}
}
.form-group {
display: inline-block;
background: $filter-bg;
border: 1px solid $filter-border-color;
margin-bottom: 0.6em;
}
.error-sign {
display: block;
opacity: 0;
position: static;
display: inline-block;
margin-right: 6px;
margin-left: 6;
vertical-align: bottom;
}
.form-group, .filter-error, .instructions {
margin-left: $sidebar-width + 30px;
@media (max-width: $screen-lg-min) {
margin: 0 $filter-margin $filter-margin 0;
}
}
&.no-margin-left .form-group, &.no-margin-left .filter-error, &.no-margin-left .instructions {
margin-left: 0;
}
.instructions {
margin-top: 0.6em;
* {
color: $btn-line-border;
}
.content-area {
display: none;
background: $btn-line-bg;
padding: 12px 13px 18px;
dt {
width: 200px;
}
dd {
margin-left: 220px;