Opened 18 years ago
Closed 13 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 , 18 years ago
Attachment: | admin-filters-in-session.diff added |
---|
comment:1 by , 18 years ago
Oops.. updated patch that will actually clear unset filter variables when saving the session.
comment:2 by , 18 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 , 17 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 , 16 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 , 14 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 , 13 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 , 13 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