Mercurial > hg > savane-forge
changeset 175:492005721817
Add list of users and groups
author | Sylvain Beucler <beuc@beuc.net> |
---|---|
date | Fri, 23 Jul 2010 23:25:39 +0200 |
parents | d785065c4423 |
children | 27559c1989f9 |
files | savane/filters.py savane/svmain/models.py savane/svmain/urls.py templates/base.html templates/svmain/group_list.html templates/svmain/pagination.inc.html templates/svmain/user_list.html |
diffstat | 7 files changed, 246 insertions(+), 16 deletions(-) [+] |
line wrap: on
line diff
new file mode 100644 --- /dev/null +++ b/savane/filters.py @@ -0,0 +1,127 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2010 Sylvain Beucler +# Copyright ??? Django team +# +# This file is part of Savane. +# +# Savane is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# Savane is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from django.db import models +import operator +from django.http import HttpResponse + +# Copy/paste these: +#from django.contrib.admin.views.main import +#ALL_VAR, ORDER_VAR, ORDER_TYPE_VAR, PAGE_VAR, +#SEARCH_VAR, TO_FIELD_VAR, IS_POPUP_VAR, ERROR_FLAG +ALL_VAR = 'all' +ORDER_VAR = 'o' +ORDER_TYPE_VAR = 'ot' +PAGE_VAR = 'p' +SEARCH_VAR = 'q' +TO_FIELD_VAR = 't' +IS_POPUP_VAR = 'pop' +ERROR_FLAG = 'e' + +class ChangeList: + """ + Object to pass views configuration to (e.g.: search string, ordering...) + -Draft- + """ + def __init__(model_admin, request): + self.query = request.GET.get(SEARCH_VAR, '') + self.list_display = model_admin.list_display + +def search(f): + """ + Inspired by Django's admin interface, filter queryset based on GET + parameters (contrib.admin.views.main.*_VAR): + + - o=N: order by ModelAdmin.display_fields[N] + - ot=xxx: order type: 'asc' or 'desc' + - q=xxx: plain text search on ModelAdmin.search_fields (^ -> istartswith, = -> iexact, @ -> search, each word ANDed) + - everything else: name of a Q filter + + exceptions: + - p=N: current page + - all=: disable pagination + - pop: popup + - e: error + - to: ? (related to making JS-friendly PK values?) + + additional exclusions: + - page: used by django.views.generic.list_detail + + We could also try and deduce filters from the Model, or avoid + using some declared parameters as Q filters, or find a better + idea. + """ + def _decorator(request, *args, **kwargs): + qs = kwargs['queryset'] + model_admin = kwargs['model_admin'] + + lookup_params = request.GET.copy() + for i in (ALL_VAR, ORDER_VAR, ORDER_TYPE_VAR, SEARCH_VAR, IS_POPUP_VAR, 'page'): + if lookup_params.has_key(i): + del lookup_params[i] + + try: + qs = qs.filter(**lookup_params) + # Naked except! Because we don't have any other way of validating "params". + # They might be invalid if the keyword arguments are incorrect, or if the + # values are not in the correct type, so we might get FieldError, ValueError, + # ValicationError, or ? from a custom field that raises yet something else + # when handed impossible data. + except: + return HttpResponse("Erreur: paramètres de recherche invalides.") + #raise IncorrectLookupParameters + + # TODO: order - but maybe in another, separate filter? + + ## + # Search string + ## + def construct_search(field_name): + if field_name.startswith('^'): + return "%s__istartswith" % field_name[1:] + elif field_name.startswith('='): + return "%s__iexact" % field_name[1:] + elif field_name.startswith('@'): + return "%s__search" % field_name[1:] + else: + return "%s__icontains" % field_name + + query = request.GET.get(SEARCH_VAR, '') + search_fields = model_admin.search_fields + if search_fields and query: + for bit in query.split(): + or_queries = [models.Q(**{construct_search(str(field_name)): bit}) for field_name in search_fields] + qs = qs.filter(reduce(operator.or_, or_queries)) + for field_name in search_fields: + if '__' in field_name: + qs = qs.distinct() + break + + kwargs['queryset'] = qs + + # TODO: pass order params + if not kwargs.has_key('extra_context'): + kwargs['extra_context'] = {} + kwargs['extra_context']['q'] = query + + # TODO: move in a clean-up decorator + del kwargs['model_admin'] + return f(request, *args, **kwargs) + return _decorator
--- a/savane/svmain/models.py +++ b/savane/svmain/models.py @@ -28,7 +28,7 @@ http://mirobetm.blogspot.com/2007/04/en-django-extending-user_2281.html However profiles were mainly useful in Django < 1.0 where you couldn't -subclass User as we do. +subclass User. Profiles also have a few drawbacks, namely they are site-specific, which means you cannot have multiple applications have different @@ -53,6 +53,11 @@ Note that Scott's authentication backend has the same issue than profiles: only one profile class can be used on a single website, so we don't use it. + +The current solution is to use AutoOneToOneField: OneToOneField is +similar to extending a model class (at the SQL tables level), and +AutoOneToOneField is a trick from django-annoying to automatically +create the extended data on first access. """ from django.db import models
--- a/savane/svmain/urls.py +++ b/savane/svmain/urls.py @@ -18,12 +18,16 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. from django.conf.urls.defaults import * +from django.views.generic.list_detail import object_list, object_detail import savane.svmain.models as svmain_models import django.contrib.auth.models as auth_models import views +from savane.filters import search -urlpatterns = patterns ('', +urlpatterns = patterns ('',) + +urlpatterns += patterns ('', url(r'^$', 'django.views.generic.simple.direct_to_template', { 'template' : 'index.html', 'extra_context' : { 'has_left_menu': False } }, @@ -32,11 +36,41 @@ { 'template' : 'svmain/text.html', 'extra_context' : { 'title' : 'Contact', }, }, name='contact'), +) - # TODO: not sure about the views naming convention - all this - # "models in 'svmain', views in 'my'" is getting messy, probably a - # mistake from me (Beuc) :P - url(r'^p/(?P<slug>[-\w]+)$', 'django.views.generic.list_detail.object_detail', +# TODO: not sure about the views naming convention - all this +# "models in 'svmain', views in 'my'" is getting messy, probably a +# mistake from me (Beuc) :P +from django.contrib.auth.admin import UserAdmin +urlpatterns += patterns ('', + url(r'^u/$', + search(object_list), + { 'queryset': auth_models.User.objects.all(), + 'paginate_by': 20, + 'model_admin': UserAdmin, + 'extra_context' : { 'title' : 'Users' }, + 'template_name' : 'svmain/user_list.html' }, + name='savane.svmain.user_list'), + url(r'^u/(?P<slug>[-\w]+)$', object_detail, + { 'queryset' : auth_models.User.objects.all(), + 'slug_field' : 'username', + 'template_name' : 'svmain/user_detail.html', }, + name='savane.svmain.user_detail'), + url(r'^us/(?P<slug>[-\w]+)$', views.user_redir), + url(r'^users/(?P<slug>[-\w]+)/?$', views.user_redir), +) + +from django.contrib.auth.admin import GroupAdmin +urlpatterns += patterns ('', + url(r'^p/$', + search(object_list), + { 'queryset': auth_models.Group.objects.all(), + 'paginate_by': 20, + 'model_admin': GroupAdmin, + 'extra_context' : { 'title' : 'Projects' }, + 'template_name' : 'svmain/group_list.html' }, + name='savane.svmain.group_list'), + url(r'^p/(?P<slug>[-\w]+)$', object_detail, { 'queryset' : auth_models.Group.objects.all(), 'slug_field' : 'name', 'template_name' : 'svmain/group_detail.html', }, @@ -44,18 +78,10 @@ url(r'^pr/(?P<slug>[-\w]+)$', views.group_redir), url(r'^projects/(?P<slug>[-\w]+)$', views.group_redir), - url(r'^u/(?P<slug>[-\w]+)$', 'django.views.generic.list_detail.object_detail', - { 'queryset' : auth_models.User.objects.all(), - 'slug_field' : 'username', - 'template_name' : 'svmain/user_detail.html', }, - name='savane.svmain.user_detail'), - url(r'^us/(?P<slug>[-\w]+)$', views.user_redir), - url(r'^users/(?P<slug>[-\w]+)/?$', views.user_redir), - url(r'^license/$', 'django.views.generic.list_detail.object_list', { 'queryset' : svmain_models.License.objects.all(), }, name='savane.svmain.license_list'), - url(r'^license/(?P<slug>[-\w]+)$', 'django.views.generic.list_detail.object_detail', + url(r'^license/(?P<slug>[-\w]+)$', object_detail, { 'queryset' : svmain_models.License.objects.all(), }, name='savane.svmain.license_detail'), )
--- a/templates/base.html +++ b/templates/base.html @@ -27,6 +27,10 @@ {% endif %} <!-- /sitemenu --> + <li class="menutitle">Search</li> + <li class="menuitem"><a href="{% url savane.svmain.user_list %}">Users</a></li> + <li class="menuitem"><a href="{% url savane.svmain.group_list %}">Projects</a></li> + <li class="menutitle">Site help</li> <li class="menuitem"><a href="{% url contact %}">Contact us</a></li> </ul>
--- a/templates/svmain/group_list.html +++ b/templates/svmain/group_list.html @@ -2,6 +2,12 @@ {% block content %} +{% include "svmain/pagination.inc.html" %} + +<form action="." method="GET"> + Rechercher: <input type="text" name="q" value="{{q}}" /> +</form> + {% if object_list %} <ul> {% for object in object_list %} @@ -9,7 +15,7 @@ {% endfor %} </ul> {% else %} - <p>No group.</p> + <p>No groups.</p> {% endif %} {% endblock %}
new file mode 100644 --- /dev/null +++ b/templates/svmain/pagination.inc.html @@ -0,0 +1,33 @@ +{% if page_obj %} +<div class="pagination"> + <span class="step-links"> + {% if paginator.num_pages > 1 %} + {% if not page_obj.has_previous %} + préc. + {% else %} + <a href="?page={{ page_obj.previous_page_number }}">préc.</a> + {% endif %} + < + {% for number in paginator.page_range %} + {% ifequal number page_obj.number %} + {{ number }} + {% else %} + <a href="?page={{ number }}">{{ number }}</a> + {% endifequal %} + {% endfor %} + > + {% if not page_obj.has_next %} + suiv. + {% else %} + <a href="?page={{ page_obj.next_page_number }}">suiv.</a> + {% endif %} + + <span class="current"> + - Page {{ page_obj.number }} sur {{ paginator.num_pages }} ({{paginator.count}}) + </span> + {% else %} + ({{paginator.count}} élément(s)) + {% endif %} + </span> +</div> +{% endif %}
new file mode 100644 --- /dev/null +++ b/templates/svmain/user_list.html @@ -0,0 +1,29 @@ +{% extends "base.html" %} + +{% block content %} + +{% include "svmain/pagination.inc.html" %} + +<form action="." method="GET"> + Rechercher: <input type="text" name="q" value="{{q}}" /> +</form> + +{% if object_list %} + <ul> + {% for object in object_list %} + <li><a href="{% url savane.svmain.user_detail object.username %}">{{ object.username }}</a></li> + {% endfor %} + </ul> +{% else %} + <p>No users.</p> +{% endif %} + +{% endblock %} + +{% comment %} +Local Variables: ** +mode: django-html ** +tab-width: 4 ** +indent-tabs-mode: nil ** +End: ** +{% endcomment %}