Skip to content

Commit a2dc431

Browse files
committed
initial example project WiP
1 parent 1a32a61 commit a2dc431

File tree

14 files changed

+387
-0
lines changed

14 files changed

+387
-0
lines changed

admin_adv_search_builder/__init__.py

Whitespace-only changes.

admin_adv_search_builder/apps.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from django.apps import AppConfig
2+
3+
4+
class AdminAdvSearchBuilderConfig(AppConfig):
5+
name = 'admin_adv_search_builder'
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
from django.contrib import admin
2+
from django.contrib import messages
3+
from django.utils.translation import gettext as _
4+
5+
6+
class AdvancedSearchBuilder(admin.SimpleListFilter):
7+
title = _('Custom Search')
8+
parameter_name = 'custom_search'
9+
template = 'filters/custom_search.html'
10+
11+
def lookups(self, request, model_admin):
12+
""" it can be overloaded as follows
13+
return (('uid', 'uid'),
14+
('mail', 'mail'),
15+
('sn', 'sn'),
16+
('schacPersonalUniqueID','schacPersonalUniqueID'),
17+
('schacPersonalUniqueCode','schacPersonalUniqueCode'),
18+
)
19+
"""
20+
l = []
21+
for i in model_admin.model._meta.fields:
22+
l.append((i.name, i.name))
23+
return l
24+
25+
def queryset(self, request, queryset):
26+
"""?custom_search=filter,mail__exact,peppelinux%40thatmail.it||
27+
"""
28+
if request.GET.get(self.parameter_name):
29+
post = dict(request.GET)[self.parameter_name][0]
30+
search_list = []
31+
search_list = post.split('||')
32+
for se in search_list:
33+
sple = se.split(',')
34+
se_dict = {'{}__{}'.format(sple[0], sple[2]): sple[3]}
35+
try:
36+
queryset = getattr(queryset, sple[1])(**se_dict)
37+
except Exception as e:
38+
messages.add_message(request, messages.ERROR,
39+
_('Search filter {} failed: {}').format(se, e))
40+
return queryset
41+
42+
43+
def choices(self, changelist):
44+
for lookup, title in self.lookup_choices:
45+
yield {
46+
'selected': self.value() == str(lookup),
47+
'query_string': changelist.get_query_string({self.parameter_name: lookup}),
48+
'display': title,
49+
}

admin_adv_search_builder/migrations/__init__.py

Whitespace-only changes.
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
{% load i18n admin_urls %}
2+
{% load static %}
3+
4+
<script>
5+
window.addEventListener('load', function() {
6+
console.log('Django ModelAdmin Filterset builder loaded');
7+
qs_to_filterset();
8+
});
9+
10+
function qs_to_filterset() {
11+
params = getParams(location);
12+
if (params.custom_search){
13+
filters = params.custom_search.split('||');
14+
for (var i = 0; i < filters.length; i++) {
15+
var filter = filters[i].split(',');
16+
filterset_dom = filterset_duplicate();
17+
filterset_dom.getElementsByClassName('cs_name')[0].value = filter[0];
18+
filterset_dom.getElementsByClassName('cs_action')[0].value = filter[1];
19+
filterset_dom.getElementsByClassName('cs_operator')[0].value = filter[2];
20+
filterset_dom.getElementsByClassName('cs_value')[0].value = filter[3];
21+
}
22+
}
23+
}
24+
25+
/**
26+
* source: https://css-tricks.com/snippets/javascript/get-url-variables/
27+
* @param {String} url The URL
28+
* @return {Object} The URL parameters
29+
*/
30+
var getParams = function (url) {
31+
var params = {};
32+
var parser = document.createElement('a');
33+
parser.href = url;
34+
var query = parser.search.substring(1);
35+
var vars = query.split('&');
36+
for (var i = 0; i < vars.length; i++) {
37+
var pair = vars[i].split('=');
38+
params[pair[0]] = decodeURIComponent(pair[1]);
39+
}
40+
return params;
41+
};
42+
43+
function build_filter(){
44+
// ?custom_search=filter,mail__exact,peppelinux%40yahoo.it
45+
cs_list = []
46+
47+
// check if there are others params
48+
params = getParams(location);
49+
50+
// clean preexistent custom_searches
51+
if (params.custom_search){delete(params.custom_search)}
52+
53+
params_len = Object.keys(params).length;
54+
if (params_len > 1){
55+
other_filters = getParams(location)
56+
url = new URL(location);
57+
first_val = url.search +'&';
58+
}
59+
else {first_val = '?';}
60+
first_val += 'custom_search=';
61+
// first_val = '?custom_search=';
62+
63+
for (i = 0; i < 20; i++) {
64+
if (!document.getElementsByClassName('cs_name')[i]) {break;}
65+
if (!document.getElementsByClassName('cs_value')[i].value) {continue;}
66+
search = [document.getElementsByClassName('cs_name')[i].value,
67+
document.getElementsByClassName('cs_action')[i].value,
68+
document.getElementsByClassName('cs_operator')[i].value,
69+
document.getElementsByClassName('cs_value')[i].value]
70+
// console.log(search);
71+
// alert(search);
72+
cs_list.push(search.join(','));
73+
}
74+
params = cs_list.join('||');
75+
// console.log(params);
76+
window.location = first_val + params;
77+
}
78+
79+
var dup_cnt = 0;
80+
function filterset_duplicate() {
81+
filterset_node = document.getElementsByClassName('cs_container')[0];
82+
clone = filterset_node.cloneNode(true); // "deep" clone
83+
84+
// create delete button
85+
filter_delete_div = document.createElement('div');
86+
filter_delete_div.innerHTML = '<button>{% trans "delete" %}</button>'.trim();
87+
filter_delete_div.setAttribute('onclick', 'filterset_remove(this.parentNode);');
88+
clone.append(filter_delete_div)
89+
filterset_node.parentNode.appendChild(clone);
90+
return clone;
91+
}
92+
93+
function filterset_remove(el) {
94+
el.remove();
95+
}
96+
</script>
97+
98+
<div onload="filterset_url_qs_init">
99+
<h3 id="ldap_search" style="">LDAP {% trans "Search" %}</h3>
100+
101+
<div div="filterset-0" class="cs_container" style="padding: 15px">
102+
<select name="cs_name_0" class="cs_name" style="margin-right: 13px;">
103+
{% for choice in choices %}
104+
<option value="{{ choice.display }}">{{ choice.display }}</option>
105+
{% endfor %}
106+
</select>
107+
108+
<select name="cs_operator_0" class="cs_operator" required style="margin-right: 13px;">
109+
<option value="exact">exact</option>
110+
<option value="iexact">iexact</option>
111+
<option value="icontains">icontains</option>
112+
<option value="contains">contains</option>
113+
<option value="startswith">startswith</option>
114+
<option value="endswith">endswith</option>
115+
<option value="gte">gte</option>
116+
<option value="lte">lte</option>
117+
<option value="is_null">is_null</option>
118+
</select>
119+
120+
<select name="cs_action_0" class="cs_action" style="margin-right: 13px;">
121+
<option value="filter">filter</option>
122+
<option value="exclude">exclude</option>
123+
</select>
124+
125+
<input name="cs_value_0" class="cs_value" placeholder="{% trans "value" %}" type="text">
126+
</div>
127+
</div>
128+
129+
<div class="controls" style="padding:15px;">
130+
<input type="button" onclick="filterset_duplicate()" value="{% trans 'Add' %}">
131+
<input type="submit" onclick="javascript:build_filter();" value="{% trans 'Search' %}">
132+
</div>

admin_adv_search_builder/tests.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from django.test import TestCase
2+
3+
# Create your tests here.

example/admin_adv_search_builder

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../admin_adv_search_builder

example/example/__init__.py

Whitespace-only changes.

example/example/asgi.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
"""
2+
ASGI config for example project.
3+
4+
It exposes the ASGI callable as a module-level variable named ``application``.
5+
6+
For more information on this file, see
7+
https://docs.djangoproject.com/en/3.0/howto/deployment/asgi/
8+
"""
9+
10+
import os
11+
12+
from django.core.asgi import get_asgi_application
13+
14+
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'example.settings')
15+
16+
application = get_asgi_application()

example/example/settings.py

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
"""
2+
Django settings for example project.
3+
4+
Generated by 'django-admin startproject' using Django 3.0.6.
5+
6+
For more information on this file, see
7+
https://docs.djangoproject.com/en/3.0/topics/settings/
8+
9+
For the full list of settings and their values, see
10+
https://docs.djangoproject.com/en/3.0/ref/settings/
11+
"""
12+
13+
import os
14+
15+
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
16+
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
17+
18+
19+
# Quick-start development settings - unsuitable for production
20+
# See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/
21+
22+
# SECURITY WARNING: keep the secret key used in production secret!
23+
SECRET_KEY = 'x!y_kl4ou+6*(+!t^(by6cbp3oxc@^9vnf+wc8@#@*9u@k+3m7'
24+
25+
# SECURITY WARNING: don't run with debug turned on in production!
26+
DEBUG = True
27+
28+
ALLOWED_HOSTS = []
29+
30+
31+
# Application definition
32+
33+
INSTALLED_APPS = [
34+
'django.contrib.admin',
35+
'django.contrib.auth',
36+
'django.contrib.contenttypes',
37+
'django.contrib.sessions',
38+
'django.contrib.messages',
39+
'django.contrib.staticfiles',
40+
41+
'admin_adv_search_builder'
42+
]
43+
44+
MIDDLEWARE = [
45+
'django.middleware.security.SecurityMiddleware',
46+
'django.contrib.sessions.middleware.SessionMiddleware',
47+
'django.middleware.common.CommonMiddleware',
48+
'django.middleware.csrf.CsrfViewMiddleware',
49+
'django.contrib.auth.middleware.AuthenticationMiddleware',
50+
'django.contrib.messages.middleware.MessageMiddleware',
51+
'django.middleware.clickjacking.XFrameOptionsMiddleware',
52+
]
53+
54+
ROOT_URLCONF = 'example.urls'
55+
56+
TEMPLATES = [
57+
{
58+
'BACKEND': 'django.template.backends.django.DjangoTemplates',
59+
'DIRS': [],
60+
'APP_DIRS': True,
61+
'OPTIONS': {
62+
'context_processors': [
63+
'django.template.context_processors.debug',
64+
'django.template.context_processors.request',
65+
'django.contrib.auth.context_processors.auth',
66+
'django.contrib.messages.context_processors.messages',
67+
],
68+
},
69+
},
70+
]
71+
72+
WSGI_APPLICATION = 'example.wsgi.application'
73+
74+
75+
# Database
76+
# https://docs.djangoproject.com/en/3.0/ref/settings/#databases
77+
78+
DATABASES = {
79+
'default': {
80+
'ENGINE': 'django.db.backends.sqlite3',
81+
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
82+
}
83+
}
84+
85+
86+
# Password validation
87+
# https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators
88+
89+
AUTH_PASSWORD_VALIDATORS = [
90+
{
91+
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
92+
},
93+
{
94+
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
95+
},
96+
{
97+
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
98+
},
99+
{
100+
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
101+
},
102+
]
103+
104+
105+
# Internationalization
106+
# https://docs.djangoproject.com/en/3.0/topics/i18n/
107+
108+
LANGUAGE_CODE = 'en-us'
109+
110+
TIME_ZONE = 'UTC'
111+
112+
USE_I18N = True
113+
114+
USE_L10N = True
115+
116+
USE_TZ = True
117+
118+
119+
# Static files (CSS, JavaScript, Images)
120+
# https://docs.djangoproject.com/en/3.0/howto/static-files/
121+
122+
STATIC_URL = '/static/'

0 commit comments

Comments
 (0)