Opened 19 years ago
Closed 14 years ago
#3777 closed New feature (duplicate)
Persistent change_list filtering in admin
| Reported by: | Owned by: | nobody | |
|---|---|---|---|
| Component: | contrib.admin | Version: | dev |
| Severity: | Normal | Keywords: | filter session |
| Cc: | Mikhail Korobov | Triage Stage: | Accepted |
| Has patch: | yes | Needs documentation: | yes |
| Needs tests: | yes | Patch needs improvement: | yes |
| Easy pickings: | no | UI/UX: | yes |
Description
Attached is a patch which allows the list_filters on the change_list pages of the admin to be persistent (as described in e.g. this thread on django-users). Basically, it does this:
- Saves the elements of the query string in the session each time the change_list is viewed.
- Looks for a special GET variable, and if found, restores the items last saved from the query string.
- Add the special GET variable to links and redirects from the change_form, so that when saving an object, the user is redirected back to the filtered list s/he was last viewing.
I'm not entirely sure this is the best way to solve this issue; something more transparent (i.e. without a magic GET variable) might be better, in which case the change_list filter links would have to explicitly unfilter themselves, rather than simply removing the query variable.
The patch admittedly also adds a setdefault method to request.session; the rest of the patch could trivially be adapted to not having this method available, of course.. I've found it useful on many occasions, though, and it seems to fit the "use session as dictionary" idea. If folks find the patch useful, I can resubmit with documentation and/or without request.session.setdefault.
Attachments (2)
Change History (12)
by , 19 years ago
| Attachment: | admin-filters-in-session.diff added |
|---|
comment:1 by , 19 years ago
Oops.. updated patch that will actually clear unset filter variables when saving the session.
comment:2 by , 19 years ago
I've implemented this also. I tried to do it without modifying any django code. I was mostly successful except for a bit of javascript in the filter.html file.
Below are the middleware class and filter.html. If you don't make the changes to the filter.html file you are unable to clear the last filter. Not shown in these files is a link I placed on the change list page that simply calls with the ?reset-filter=y to force a reset.
I also am not sure this is the best way. I would like to see this type of functionality built into django.
{% load i18n %}
<h3>{% blocktrans with title|escape as filter_title %} By {{ filter_title }} {% endblocktrans %}</h3>
<script>
function checkForReset(url){
sArgs = url.search.slice(1).split('&');
if (sArgs.length == 1){
if (sArgs[0].length ==0 ){
document.location = url+'reset-filter=true'
}
}
}</script>
<ul>
{% for choice in choices %}
<li{% if choice.selected %} class="selected"{% endif %}>
<a href="{{ choice.query_string }}" onclick="checkForReset(this)">{{ choice.display|escape }}</a></li>
{% endfor %}
</ul>
from django import http
class FilterPersistMiddleware(object):
def process_request(self, request):
query_string = request.META['QUERY_STRING']
path = request.path
session = request.session
key = 'key'+path.replace('/','_')
# check for the reset-filter flag
if request.has_key('reset-filter'):
if session.get(key,False):
del session[key]
return None
# if we are not in the /media/ area then ignore
# todo: this should be dynamic not hardcoded
if path.find('/media/') < 0:
return None
if len(query_string) > 0:
# save the filter under the key in session
request.session[key] = query_string
return None
if session.get(key, False):
query_string=request.session.get(key)
redirect_to = path+'?'+query_string
return http.HttpResponseRedirect(redirect_to)
comment:3 by , 18 years ago
| Resolution: | → worksforme |
|---|---|
| Status: | new → closed |
Wow.. that's much nicer, IMO.. and if you just override the filter.html admin template in the project, you don't need to patch Django at all. Thanks!
comment:4 by , 17 years ago
Here is how I did it (based on the previous post by tony.perkins)
This does not require any modification to the templates, just the middleware
(tested With django 1.02)
from django import http
class FilterPersistMiddleware(object):
def process_request(self, request):
path = request.path
if path.find('/admin/') != -1: #Dont waste time if we are not in admin
query_string = request.META['QUERY_STRING']
if not request.META.has_key('HTTP_REFERER'):
return None
session = request.session
if session.get('redirected', False):#so that we dont loop once redirected
del session['redirected']
return None
referrer = request.META['HTTP_REFERER'].split('?')[0]
referrer = referrer[referrer.find('/admin'):len(referrer)]
key = 'key'+path.replace('/','_')
if path == referrer: #We are in same page as before
if query_string == '': #Filter is empty, delete it
if session.get(key,False):
del session[key]
return None
request.session[key] = query_string
else: #We are are coming from another page, restore filter if available
if session.get(key, False):
query_string=request.session.get(key)
redirect_to = path+'?'+query_string
request.session['redirected'] = True
return http.HttpResponseRedirect(redirect_to)
else:
return None
else:
return None
comment:5 by , 15 years ago
I modified the above middleware so that is doesn't break admin's related object popups. This method tracks popup query strings separately from regular pages. This way you can use filters inside the popup windows as well. Hope it helps someone else.
from django import http
# based on http://code.djangoproject.com/ticket/3777#comment:4
class FilterPersistMiddleware(object):
def process_request(self, request):
if '/admin/' not in request.path:
return None
if not request.META.has_key('HTTP_REFERER'):
return None
popup = 'pop=1' in request.META['QUERY_STRING']
path = request.path
query_string = request.META['QUERY_STRING']
session = request.session
if session.get('redirected', False):#so that we dont loop once redirected
del session['redirected']
return None
referrer = request.META['HTTP_REFERER'].split('?')[0]
referrer = referrer[referrer.find('/admin'):len(referrer)]
key = 'key'+path.replace('/','_')
if popup:
key = 'popup'+path.replace('/','_')
if path == referrer: #We are in same page as before
if query_string == '': #Filter is empty, delete it
if session.get(key,False):
del session[key]
return None
request.session[key] = query_string
else: #We are are coming from another page, restore filter if available
if session.get(key, False):
query_string=request.session.get(key)
redirect_to = path+'?'+query_string
request.session['redirected'] = True
return http.HttpResponseRedirect(redirect_to)
return None
comment:6 by , 14 years ago
| Easy pickings: | unset |
|---|---|
| Severity: | → Normal |
| Type: | → Uncategorized |
| UI/UX: | unset |
Thanks a lot! Added functionality to set filters that are enabled by default.
class FilterPersistMiddleware(object):
def _get_default(self, key):
""" Gets any set default filters for the admin. Returns None if no
default is set. """
default = settings.ADMIN_DEFAULT_FILTERS.get(key, None)
# Filters are allowed to be functions. If this key is one, call it.
if hasattr(default, '__call__'):
default = default()
return default
def process_request(self, request):
if '/admin/' not in request.path or request.method == 'POST':
return None
if request.META.has_key('HTTP_REFERER'):
referrer = request.META['HTTP_REFERER'].split('?')[0]
referrer = referrer[referrer.find('/admin'):len(referrer)]
else:
referrer = u''
popup = 'pop=1' in request.META['QUERY_STRING']
path = request.path
query_string = request.META['QUERY_STRING']
session = request.session
if session.get('redirected', False):#so that we dont loop once redirected
del session['redirected']
return None
key = 'key'+path.replace('/','_')
if popup:
key = 'popup'+key
if path == referrer:
""" We are in the same page as before. We assume that filters were
changed and update them. """
if query_string == '': #Filter is empty, delete it
if session.has_key(key):
del session[key]
return None
else:
request.session[key] = query_string
else:
""" We are are coming from another page. Set querystring to
saved or default value. """
query_string=session.get(key, self._get_default(key))
if query_string is not None:
redirect_to = path+'?'+query_string
request.session['redirected'] = True
return http.HttpResponseRedirect(redirect_to)
else:
return None
Sample default filters:
from datetime import date
def _today():
return 'starttime__gte=' + date.today().isoformat()
# Default filters. Format: 'key_$url', where $url has slashes replaced
# with underscores
# value can either be a function or a string
ADMIN_DEFAULT_FILTERS= {
# display only events starting today
'key_admin_event_calendar_event_': _today,
# display active members
'key_admin_users_member_': 'is_active__exact=1',
# only show new suggestions
'key_admin_suggestions_suggestion_': 'status__exact=new',
}
comment:7 by , 14 years ago
| Cc: | added |
|---|---|
| Needs tests: | set |
| Patch needs improvement: | set |
| Resolution: | worksforme |
| Status: | closed → reopened |
| Type: | Uncategorized → New feature |
| UI/UX: | set |
I think that shouldn't be closed as 'worksforme' because change_list filters are not persistent in django admin and provided workarounds have their own issues.
comment:8 by , 14 years ago
Yes, this was closed by the original reporter after he found a workaround, not by a core developer.
I think it would be nice to preseve both filters and ordering.
comment:9 by , 14 years ago
| Triage Stage: | Unreviewed → Accepted |
|---|
Let's accept this feature request. If it turns out to be controversial, the ticket can be moved to DDN.
comment:10 by , 14 years ago
| Resolution: | → duplicate |
|---|---|
| Status: | reopened → closed |
Closing in favour of #6903 which is essentially trying to solve the same problem and has more recent patches.
persistent list_filter patch