2005-11-26 05:20:09 +08:00
from django . contrib . admin . filterspecs import FilterSpec
2008-07-19 07:54:34 +08:00
from django . contrib . admin . options import IncorrectLookupParameters
2010-11-22 03:29:15 +08:00
from django . contrib . admin . util import quote , get_fields_from_path
2010-12-23 11:44:38 +08:00
from django . core . exceptions import SuspiciousOperation
2008-07-08 10:08:33 +08:00
from django . core . paginator import Paginator , InvalidPage
2006-05-02 09:31:56 +08:00
from django . db import models
Merged Unicode branch into trunk (r4952:5608). This should be fully
backwards compatible for all practical purposes.
Fixed #2391, #2489, #2996, #3322, #3344, #3370, #3406, #3432, #3454, #3492, #3582, #3690, #3878, #3891, #3937, #4039, #4141, #4227, #4286, #4291, #4300, #4452, #4702
git-svn-id: http://code.djangoproject.com/svn/django/trunk@5609 bcc190cf-cafb-0310-a4f2-bffc1f526a37
2007-07-04 20:11:04 +08:00
from django . utils . encoding import force_unicode , smart_str
2008-07-19 07:54:34 +08:00
from django . utils . translation import ugettext
from django . utils . http import urlencode
2005-07-13 09:25:57 +08:00
import operator
2006-05-02 09:31:56 +08:00
# The system will display a "Show all" link on the change list only if the
# total result count is less than or equal to this setting.
MAX_SHOW_ALL_ALLOWED = 200
2005-11-26 05:20:09 +08:00
2006-05-02 09:31:56 +08:00
# Changelist settings
2005-11-26 05:20:09 +08:00
ALL_VAR = ' all '
ORDER_VAR = ' o '
ORDER_TYPE_VAR = ' ot '
PAGE_VAR = ' p '
SEARCH_VAR = ' q '
2008-09-02 06:43:38 +08:00
TO_FIELD_VAR = ' t '
2005-11-26 05:20:09 +08:00
IS_POPUP_VAR = ' pop '
2006-05-31 23:25:23 +08:00
ERROR_FLAG = ' e '
2005-11-26 05:20:09 +08:00
2006-05-02 09:31:56 +08:00
# Text to display within change-list table cells if the value is blank.
2005-07-13 09:25:57 +08:00
EMPTY_CHANGELIST_VALUE = ' (None) '
2006-05-02 09:31:56 +08:00
class ChangeList ( object ) :
2010-12-22 03:19:04 +08:00
def __init__ ( self , request , model , list_display , list_display_links , list_filter , date_hierarchy , search_fields , list_select_related , list_per_page , list_editable , model_admin ) :
2006-05-02 09:31:56 +08:00
self . model = model
self . opts = model . _meta
self . lookup_opts = self . opts
2008-07-19 07:54:34 +08:00
self . root_query_set = model_admin . queryset ( request )
self . list_display = list_display
self . list_display_links = list_display_links
self . list_filter = list_filter
self . date_hierarchy = date_hierarchy
self . search_fields = search_fields
self . list_select_related = list_select_related
self . list_per_page = list_per_page
self . model_admin = model_admin
2006-05-02 09:31:56 +08:00
# Get search parameters from the query string.
try :
self . page_num = int ( request . GET . get ( PAGE_VAR , 0 ) )
except ValueError :
self . page_num = 0
2007-04-26 21:30:48 +08:00
self . show_all = ALL_VAR in request . GET
self . is_popup = IS_POPUP_VAR in request . GET
2008-09-02 06:43:38 +08:00
self . to_field = request . GET . get ( TO_FIELD_VAR )
2006-05-02 09:31:56 +08:00
self . params = dict ( request . GET . items ( ) )
2007-04-26 21:30:48 +08:00
if PAGE_VAR in self . params :
2006-05-02 09:31:56 +08:00
del self . params [ PAGE_VAR ]
2008-09-02 06:43:38 +08:00
if TO_FIELD_VAR in self . params :
del self . params [ TO_FIELD_VAR ]
2007-04-26 21:30:48 +08:00
if ERROR_FLAG in self . params :
2006-05-31 23:25:23 +08:00
del self . params [ ERROR_FLAG ]
2006-05-02 09:31:56 +08:00
2011-01-02 09:32:40 +08:00
if self . is_popup :
self . list_editable = ( )
else :
self . list_editable = list_editable
2006-05-02 09:31:56 +08:00
self . order_field , self . order_type = self . get_ordering ( )
self . query = request . GET . get ( SEARCH_VAR , ' ' )
self . query_set = self . get_query_set ( )
self . get_results ( request )
2008-07-19 07:54:34 +08:00
self . title = ( self . is_popup and ugettext ( ' Select %s ' ) % force_unicode ( self . opts . verbose_name ) or ugettext ( ' Select %s to change ' ) % force_unicode ( self . opts . verbose_name ) )
2006-05-02 09:31:56 +08:00
self . filter_specs , self . has_filters = self . get_filters ( request )
self . pk_attname = self . lookup_opts . pk . attname
def get_filters ( self , request ) :
filter_specs = [ ]
2008-08-16 03:15:20 +08:00
if self . list_filter :
2010-11-22 03:29:15 +08:00
for filter_name in self . list_filter :
field = get_fields_from_path ( self . model , filter_name ) [ - 1 ]
spec = FilterSpec . create ( field , request , self . params ,
self . model , self . model_admin ,
field_path = filter_name )
2006-05-02 09:31:56 +08:00
if spec and spec . has_output ( ) :
filter_specs . append ( spec )
return filter_specs , bool ( filter_specs )
2006-06-03 21:37:34 +08:00
def get_query_string ( self , new_params = None , remove = None ) :
if new_params is None : new_params = { }
if remove is None : remove = [ ]
2006-05-02 09:31:56 +08:00
p = self . params . copy ( )
for r in remove :
for k in p . keys ( ) :
if k . startswith ( r ) :
del p [ k ]
for k , v in new_params . items ( ) :
2008-07-19 07:54:34 +08:00
if v is None :
if k in p :
del p [ k ]
else :
2006-05-02 09:31:56 +08:00
p [ k ] = v
2008-07-19 07:54:34 +08:00
return ' ? %s ' % urlencode ( p )
2006-05-02 09:31:56 +08:00
def get_results ( self , request ) :
2010-12-22 03:19:04 +08:00
paginator = self . model_admin . get_paginator ( request , self . query_set , self . list_per_page )
2006-05-02 09:31:56 +08:00
# Get the number of objects, with admin filters applied.
2008-10-23 07:09:35 +08:00
result_count = paginator . count
2006-05-02 09:31:56 +08:00
# Get the total number of objects, with no admin filters applied.
# Perform a slight optimization: Check to see whether any filters were
# given. If not, use paginator.hits to calculate the number of objects,
# because we've already done paginator.hits and the value is cached.
Merged the queryset-refactor branch into trunk.
This is a big internal change, but mostly backwards compatible with existing
code. Also adds a couple of new features.
Fixed #245, #1050, #1656, #1801, #2076, #2091, #2150, #2253, #2306, #2400, #2430, #2482, #2496, #2676, #2737, #2874, #2902, #2939, #3037, #3141, #3288, #3440, #3592, #3739, #4088, #4260, #4289, #4306, #4358, #4464, #4510, #4858, #5012, #5020, #5261, #5295, #5321, #5324, #5325, #5555, #5707, #5796, #5817, #5987, #6018, #6074, #6088, #6154, #6177, #6180, #6203, #6658
git-svn-id: http://code.djangoproject.com/svn/django/trunk@7477 bcc190cf-cafb-0310-a4f2-bffc1f526a37
2008-04-27 10:50:16 +08:00
if not self . query_set . query . where :
2006-05-02 09:31:56 +08:00
full_result_count = result_count
else :
2008-07-19 07:54:34 +08:00
full_result_count = self . root_query_set . count ( )
2006-05-02 09:31:56 +08:00
can_show_all = result_count < = MAX_SHOW_ALL_ALLOWED
2008-07-19 07:54:34 +08:00
multi_page = result_count > self . list_per_page
2006-05-02 09:31:56 +08:00
# Get the list of objects to display on this page.
if ( self . show_all and can_show_all ) or not multi_page :
2009-04-23 06:16:32 +08:00
result_list = self . query_set . _clone ( )
2006-05-02 09:31:56 +08:00
else :
try :
2008-03-25 13:20:55 +08:00
result_list = paginator . page ( self . page_num + 1 ) . object_list
2006-05-02 09:31:56 +08:00
except InvalidPage :
2010-12-13 06:58:47 +08:00
raise IncorrectLookupParameters
2006-05-02 09:31:56 +08:00
self . result_count = result_count
self . full_result_count = full_result_count
self . result_list = result_list
self . can_show_all = can_show_all
self . multi_page = multi_page
self . paginator = paginator
def get_ordering ( self ) :
lookup_opts , params = self . lookup_opts , self . params
Merged the queryset-refactor branch into trunk.
This is a big internal change, but mostly backwards compatible with existing
code. Also adds a couple of new features.
Fixed #245, #1050, #1656, #1801, #2076, #2091, #2150, #2253, #2306, #2400, #2430, #2482, #2496, #2676, #2737, #2874, #2902, #2939, #3037, #3141, #3288, #3440, #3592, #3739, #4088, #4260, #4289, #4306, #4358, #4464, #4510, #4858, #5012, #5020, #5261, #5295, #5321, #5324, #5325, #5555, #5707, #5796, #5817, #5987, #6018, #6074, #6088, #6154, #6177, #6180, #6203, #6658
git-svn-id: http://code.djangoproject.com/svn/django/trunk@7477 bcc190cf-cafb-0310-a4f2-bffc1f526a37
2008-04-27 10:50:16 +08:00
# For ordering, first check the "ordering" parameter in the admin
# options, then check the object's default ordering. If neither of
# those exist, order descending by ID by default. Finally, look for
# manually-specified ordering from the query string.
2008-07-19 07:54:34 +08:00
ordering = self . model_admin . ordering or lookup_opts . ordering or [ ' - ' + lookup_opts . pk . name ]
2006-05-02 09:31:56 +08:00
if ordering [ 0 ] . startswith ( ' - ' ) :
order_field , order_type = ordering [ 0 ] [ 1 : ] , ' desc '
else :
order_field , order_type = ordering [ 0 ] , ' asc '
2007-04-26 21:30:48 +08:00
if ORDER_VAR in params :
2006-05-02 09:31:56 +08:00
try :
2008-07-19 07:54:34 +08:00
field_name = self . list_display [ int ( params [ ORDER_VAR ] ) ]
2006-05-02 09:31:56 +08:00
try :
2007-02-26 13:37:24 +08:00
f = lookup_opts . get_field ( field_name )
2006-05-02 09:31:56 +08:00
except models . FieldDoesNotExist :
2008-07-19 07:54:34 +08:00
# See whether field_name is a name of a non-field
# that allows sorting.
2007-02-26 13:37:24 +08:00
try :
2008-10-08 22:47:01 +08:00
if callable ( field_name ) :
attr = field_name
elif hasattr ( self . model_admin , field_name ) :
attr = getattr ( self . model_admin , field_name )
else :
attr = getattr ( self . model , field_name )
2007-02-26 13:37:24 +08:00
order_field = attr . admin_order_field
2007-08-04 22:41:49 +08:00
except AttributeError :
2007-02-26 13:37:24 +08:00
pass
2006-05-02 09:31:56 +08:00
else :
2008-09-22 00:57:26 +08:00
order_field = f . name
2006-05-02 09:31:56 +08:00
except ( IndexError , ValueError ) :
pass # Invalid ordering specified. Just use the default.
2007-04-26 21:30:48 +08:00
if ORDER_TYPE_VAR in params and params [ ORDER_TYPE_VAR ] in ( ' asc ' , ' desc ' ) :
2006-05-02 09:31:56 +08:00
order_type = params [ ORDER_TYPE_VAR ]
return order_field , order_type
def get_query_set ( self ) :
2011-02-14 06:51:40 +08:00
use_distinct = False
2008-07-19 07:54:34 +08:00
qs = self . root_query_set
2006-05-02 09:31:56 +08:00
lookup_params = self . params . copy ( ) # a dictionary of the query string
for i in ( ALL_VAR , ORDER_VAR , ORDER_TYPE_VAR , SEARCH_VAR , IS_POPUP_VAR ) :
2007-04-26 21:30:48 +08:00
if i in lookup_params :
2006-05-02 09:31:56 +08:00
del lookup_params [ i ]
Merged Unicode branch into trunk (r4952:5608). This should be fully
backwards compatible for all practical purposes.
Fixed #2391, #2489, #2996, #3322, #3344, #3370, #3406, #3432, #3454, #3492, #3582, #3690, #3878, #3891, #3937, #4039, #4141, #4227, #4286, #4291, #4300, #4452, #4702
git-svn-id: http://code.djangoproject.com/svn/django/trunk@5609 bcc190cf-cafb-0310-a4f2-bffc1f526a37
2007-07-04 20:11:04 +08:00
for key , value in lookup_params . items ( ) :
if not isinstance ( key , str ) :
# 'key' will be used as a keyword argument later, so Python
# requires it to be a string.
del lookup_params [ key ]
lookup_params [ smart_str ( key ) ] = value
2006-05-02 09:31:56 +08:00
2011-02-14 06:51:40 +08:00
if not use_distinct :
# Check if it's a relationship that might return more than one
# instance
field_name = key . split ( ' __ ' , 1 ) [ 0 ]
try :
f = self . lookup_opts . get_field_by_name ( field_name ) [ 0 ]
except models . FieldDoesNotExist :
raise IncorrectLookupParameters
if hasattr ( f , ' rel ' ) and isinstance ( f . rel , models . ManyToManyRel ) :
use_distinct = True
2008-08-30 00:13:17 +08:00
# if key ends with __in, split parameter into separate values
if key . endswith ( ' __in ' ) :
2011-01-28 22:08:25 +08:00
value = value . split ( ' , ' )
lookup_params [ key ] = value
2008-08-30 00:13:17 +08:00
2010-03-17 03:01:40 +08:00
# if key ends with __isnull, special case '' and false
if key . endswith ( ' __isnull ' ) :
if value . lower ( ) in ( ' ' , ' false ' ) :
2011-01-28 22:08:25 +08:00
value = False
2010-03-17 03:01:40 +08:00
else :
2011-01-28 22:08:25 +08:00
value = True
lookup_params [ key ] = value
2010-03-17 03:01:40 +08:00
2011-01-28 22:08:25 +08:00
if not self . model_admin . lookup_allowed ( key , value ) :
2010-12-23 11:44:38 +08:00
raise SuspiciousOperation (
" Filtering by %s not allowed " % key
)
2006-05-02 09:31:56 +08:00
# Apply lookup parameters from the query string.
2008-10-23 07:09:35 +08:00
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,
2010-12-23 11:44:38 +08:00
# ValicationError, or ? from a custom field that raises yet something else
2008-10-23 07:09:35 +08:00
# when handed impossible data.
except :
raise IncorrectLookupParameters
2006-05-02 09:31:56 +08:00
# Use select_related() if one of the list_display options is a field
2009-05-14 23:09:33 +08:00
# with a relationship and the provided queryset doesn't already have
# select_related defined.
if not qs . query . select_related :
if self . list_select_related :
qs = qs . select_related ( )
else :
for field_name in self . list_display :
try :
f = self . lookup_opts . get_field ( field_name )
except models . FieldDoesNotExist :
pass
else :
if isinstance ( f . rel , models . ManyToOneRel ) :
qs = qs . select_related ( )
break
2006-05-02 09:31:56 +08:00
# Set ordering.
2008-04-28 10:40:57 +08:00
if self . order_field :
qs = qs . order_by ( ' %s %s ' % ( ( self . order_type == ' desc ' and ' - ' or ' ' ) , self . order_field ) )
2006-05-02 09:31:56 +08:00
# Apply keyword searches.
2006-08-18 10:48:34 +08:00
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
2006-08-19 05:39:29 +08:00
2008-07-19 07:54:34 +08:00
if self . search_fields and self . query :
2006-05-02 09:31:56 +08:00
for bit in self . query . split ( ) :
2009-04-11 15:41:59 +08:00
or_queries = [ models . Q ( * * { construct_search ( str ( field_name ) ) : bit } ) for field_name in self . search_fields ]
2009-05-07 20:52:43 +08:00
qs = qs . filter ( reduce ( operator . or_ , or_queries ) )
2011-02-14 06:51:40 +08:00
if not use_distinct :
for search_field in self . search_fields :
field_name = search_field . split ( ' __ ' , 1 ) [ 0 ]
f = self . lookup_opts . get_field_by_name ( field_name ) [ 0 ]
if hasattr ( f , ' rel ' ) and isinstance ( f . rel , models . ManyToManyRel ) :
use_distinct = True
break
2006-05-02 09:31:56 +08:00
2011-02-14 06:51:40 +08:00
if use_distinct :
return qs . distinct ( )
else :
return qs
2006-05-02 09:31:56 +08:00
def url_for_result ( self , result ) :
return " %s / " % quote ( getattr ( result , self . pk_attname ) )