~kkubasik/django/aggregation-branch

« back to all changes in this revision

Viewing changes to django/contrib/admin/views/main.py

  • Committer: adrian
  • Date: 2006-05-02 01:31:56 UTC
  • Revision ID: vcs-imports@canonical.com-20060502013156-2941fcd40d080649
MERGED MAGIC-REMOVAL BRANCH TO TRUNK. This change is highly backwards-incompatible. Please read http://code.djangoproject.com/wiki/RemovingTheMagic for upgrade instructions.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Generic admin views.
 
1
from django import forms, template
 
2
from django.conf import settings
 
3
from django.contrib.admin.filterspecs import FilterSpec
2
4
from django.contrib.admin.views.decorators import staff_member_required
3
 
from django.contrib.admin.filterspecs import FilterSpec
4
 
from django.core import formfields, meta, template
5
 
from django.core.template import loader
6
 
from django.core.meta.fields import BoundField, BoundFieldLine, BoundFieldSet
7
 
from django.core.exceptions import Http404, ImproperlyConfigured, ObjectDoesNotExist, PermissionDenied
8
 
from django.core.extensions import DjangoContext as Context
9
 
from django.core.extensions import get_object_or_404, render_to_response
 
5
from django.views.decorators.cache import never_cache
 
6
from django.contrib.contenttypes.models import ContentType
 
7
from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist, PermissionDenied
10
8
from django.core.paginator import ObjectPaginator, InvalidPage
11
 
from django.conf.settings import ADMIN_MEDIA_PREFIX
12
 
try:
13
 
    from django.models.admin import log
14
 
except ImportError:
15
 
    raise ImproperlyConfigured, "You don't have 'django.contrib.admin' in INSTALLED_APPS."
16
 
from django.utils.html import escape
17
 
from django.utils.httpwrappers import HttpResponse, HttpResponseRedirect
18
 
from django.utils.text import capfirst, get_text_list
 
9
from django.shortcuts import get_object_or_404, render_to_response
 
10
from django.db import models
 
11
from django.db.models.query import handle_legacy_orderlist, QuerySet
 
12
from django.http import Http404, HttpResponse, HttpResponseRedirect
 
13
from django.template import loader
19
14
from django.utils import dateformat
20
15
from django.utils.dates import MONTHS
21
16
from django.utils.html import escape
 
17
from django.utils.text import capfirst, get_text_list
22
18
import operator
23
19
 
24
 
# The system will display a "Show all" link only if the total result count
25
 
# is less than or equal to this setting.
 
20
from django.contrib.admin.models import LogEntry, ADDITION, CHANGE, DELETION
 
21
if not LogEntry._meta.installed:
 
22
    raise ImproperlyConfigured, "You'll need to put 'django.contrib.admin' in your INSTALLED_APPS setting before you can use the admin application."
 
23
 
 
24
if 'django.core.context_processors.auth' not in settings.TEMPLATE_CONTEXT_PROCESSORS:
 
25
    raise ImproperlyConfigured, "You'll need to put 'django.core.context_processors.auth' in your TEMPLATE_CONTEXT_PROCESSORS setting before you can use the admin application."
 
26
 
 
27
# The system will display a "Show all" link on the change list only if the
 
28
# total result count is less than or equal to this setting.
26
29
MAX_SHOW_ALL_ALLOWED = 200
27
30
 
28
 
DEFAULT_RESULTS_PER_PAGE = 100
29
 
 
 
31
# Changelist settings
30
32
ALL_VAR = 'all'
31
33
ORDER_VAR = 'o'
32
34
ORDER_TYPE_VAR = 'ot'
34
36
SEARCH_VAR = 'q'
35
37
IS_POPUP_VAR = 'pop'
36
38
 
37
 
# Text to display within changelist table cells if the value is blank.
 
39
# Text to display within change-list table cells if the value is blank.
38
40
EMPTY_CHANGELIST_VALUE = '(None)'
39
41
 
40
 
def _get_mod_opts(app_label, module_name):
41
 
    "Helper function that returns a tuple of (module, opts), raising Http404 if necessary."
42
 
    try:
43
 
        mod = meta.get_module(app_label, module_name)
44
 
    except ImportError:
45
 
        raise Http404 # Invalid app or module name. Maybe it's not in INSTALLED_APPS.
46
 
    opts = mod.Klass._meta
47
 
    if not opts.admin:
48
 
        raise Http404 # This object is valid but has no admin interface.
49
 
    return mod, opts
50
 
 
51
 
def index(request):
52
 
    return render_to_response('admin/index', {'title': _('Site administration')}, context_instance=Context(request))
53
 
index = staff_member_required(index)
 
42
use_raw_id_admin = lambda field: isinstance(field.rel, (models.ManyToOneRel, models.ManyToManyRel)) and field.rel.raw_id_admin
54
43
 
55
44
class IncorrectLookupParameters(Exception):
56
45
    pass
57
46
 
58
 
class ChangeList(object):
59
 
    def __init__(self, request, app_label, module_name):
60
 
        self.get_modules_and_options(app_label, module_name, request)
61
 
        self.get_search_parameters(request)
62
 
        self.get_ordering()
63
 
        self.query = request.GET.get(SEARCH_VAR, '')
64
 
        self.get_lookup_params()
65
 
        self.get_results(request)
66
 
        self.title = (self.is_popup
67
 
                      and _('Select %s') % self.opts.verbose_name
68
 
                      or _('Select %s to change') % self.opts.verbose_name)
69
 
        self.get_filters(request)
70
 
        self.pk_attname = self.lookup_opts.pk.attname
71
 
 
72
 
    def get_filters(self, request):
73
 
        self.filter_specs = []
74
 
        if self.lookup_opts.admin.list_filter and not self.opts.one_to_one_field:
75
 
            filter_fields = [self.lookup_opts.get_field(field_name) \
76
 
                              for field_name in self.lookup_opts.admin.list_filter]
77
 
            for f in filter_fields:
78
 
                spec = FilterSpec.create(f, request, self.params)
79
 
                if spec and spec.has_output():
80
 
                    self.filter_specs.append(spec)
81
 
        self.has_filters = bool(self.filter_specs)
82
 
 
83
 
    def get_query_string(self, new_params={}, remove=[]):
84
 
        p = self.params.copy()
85
 
        for r in remove:
86
 
            for k in p.keys():
87
 
                if k.startswith(r):
88
 
                    del p[k]
89
 
        for k, v in new_params.items():
90
 
            if p.has_key(k) and v is None:
91
 
                del p[k]
92
 
            elif v is not None:
93
 
                p[k] = v
94
 
        return '?' + '&'.join(['%s=%s' % (k, v) for k, v in p.items()]).replace(' ', '%20')
95
 
 
96
 
    def get_modules_and_options(self, app_label, module_name, request):
97
 
        self.mod, self.opts = _get_mod_opts(app_label, module_name)
98
 
        if not request.user.has_perm(app_label + '.' + self.opts.get_change_permission()):
99
 
            raise PermissionDenied
100
 
 
101
 
        self.lookup_mod, self.lookup_opts = self.mod, self.opts
102
 
 
103
 
    def get_search_parameters(self, request):
104
 
        # Get search parameters from the query string.
105
 
        try:
106
 
            self.page_num = int(request.GET.get(PAGE_VAR, 0))
107
 
        except ValueError:
108
 
            self.page_num = 0
109
 
        self.show_all = request.GET.has_key(ALL_VAR)
110
 
        self.is_popup = request.GET.has_key(IS_POPUP_VAR)
111
 
        self.params = dict(request.GET.items())
112
 
        if self.params.has_key(PAGE_VAR):
113
 
            del self.params[PAGE_VAR]
114
 
 
115
 
    def get_results(self, request):
116
 
        lookup_mod, lookup_params, show_all, page_num = \
117
 
            self.lookup_mod, self.lookup_params, self.show_all, self.page_num
118
 
        # Get the results.
119
 
        try:
120
 
            paginator = ObjectPaginator(lookup_mod, lookup_params, DEFAULT_RESULTS_PER_PAGE)
121
 
        # Naked except! Because we don't have any other way of validating "params".
122
 
        # They might be invalid if the keyword arguments are incorrect, or if the
123
 
        # values are not in the correct type (which would result in a database
124
 
        # error).
125
 
        except:
126
 
            raise IncorrectLookupParameters()
127
 
 
128
 
        # Get the total number of objects, with no filters applied.
129
 
        real_lookup_params = lookup_params.copy()
130
 
        del real_lookup_params['order_by']
131
 
        if real_lookup_params:
132
 
            full_result_count = lookup_mod.get_count()
133
 
        else:
134
 
            full_result_count = paginator.hits
135
 
        del real_lookup_params
136
 
        result_count = paginator.hits
137
 
        can_show_all = result_count <= MAX_SHOW_ALL_ALLOWED
138
 
        multi_page = result_count > DEFAULT_RESULTS_PER_PAGE
139
 
 
140
 
        # Get the list of objects to display on this page.
141
 
        if (show_all and can_show_all) or not multi_page:
142
 
            result_list = lookup_mod.get_list(**lookup_params)
143
 
        else:
144
 
            try:
145
 
                result_list = paginator.get_page(page_num)
146
 
            except InvalidPage:
147
 
                result_list = []
148
 
        (self.result_count, self.full_result_count, self.result_list,
149
 
            self.can_show_all, self.multi_page, self.paginator) = (result_count,
150
 
                  full_result_count, result_list, can_show_all, multi_page, paginator )
151
 
 
152
 
    def url_for_result(self, result):
153
 
        return "%s/" % getattr(result, self.pk_attname)
154
 
 
155
 
    def get_ordering(self):
156
 
        lookup_opts, params = self.lookup_opts, self.params
157
 
        # For ordering, first check the "ordering" parameter in the admin options,
158
 
        # then check the object's default ordering. If neither of those exist,
159
 
        # order descending by ID by default. Finally, look for manually-specified
160
 
        # ordering from the query string.
161
 
        ordering = lookup_opts.admin.ordering or lookup_opts.ordering or ['-' + lookup_opts.pk.name]
162
 
 
163
 
        # Normalize it to new-style ordering.
164
 
        ordering = meta.handle_legacy_orderlist(ordering)
165
 
 
166
 
        if ordering[0].startswith('-'):
167
 
            order_field, order_type = ordering[0][1:], 'desc'
168
 
        else:
169
 
            order_field, order_type = ordering[0], 'asc'
170
 
        if params.has_key(ORDER_VAR):
171
 
            try:
172
 
                try:
173
 
                    f = lookup_opts.get_field(lookup_opts.admin.list_display[int(params[ORDER_VAR])])
174
 
                except meta.FieldDoesNotExist:
175
 
                    pass
176
 
                else:
177
 
                    if not isinstance(f.rel, meta.ManyToOneRel) or not f.null:
178
 
                        order_field = f.name
179
 
            except (IndexError, ValueError):
180
 
                pass # Invalid ordering specified. Just use the default.
181
 
        if params.has_key(ORDER_TYPE_VAR) and params[ORDER_TYPE_VAR] in ('asc', 'desc'):
182
 
            order_type = params[ORDER_TYPE_VAR]
183
 
        self.order_field, self.order_type = order_field, order_type
184
 
 
185
 
    def get_lookup_params(self):
186
 
        # Prepare the lookup parameters for the API lookup.
187
 
        (params, order_field, lookup_opts, order_type, opts, query) = \
188
 
           (self.params, self.order_field, self.lookup_opts, self.order_type, self.opts, self.query)
189
 
 
190
 
        lookup_params = params.copy()
191
 
        for i in (ALL_VAR, ORDER_VAR, ORDER_TYPE_VAR, SEARCH_VAR, IS_POPUP_VAR):
192
 
            if lookup_params.has_key(i):
193
 
                del lookup_params[i]
194
 
        # If the order-by field is a field with a relationship, order by the value
195
 
        # in the related table.
196
 
        lookup_order_field = order_field
197
 
        try:
198
 
            f = lookup_opts.get_field(order_field)
199
 
        except meta.FieldDoesNotExist:
200
 
            pass
201
 
        else:
202
 
            if isinstance(lookup_opts.get_field(order_field).rel, meta.ManyToOneRel):
203
 
                f = lookup_opts.get_field(order_field)
204
 
                rel_ordering = f.rel.to.ordering and f.rel.to.ordering[0] or f.rel.to.pk.column
205
 
                lookup_order_field = '%s.%s' % (f.rel.to.db_table, rel_ordering)
206
 
        # Use select_related if one of the list_display options is a field with a
207
 
        # relationship.
208
 
        if lookup_opts.admin.list_select_related:
209
 
            lookup_params['select_related'] = True
210
 
        else:
211
 
            for field_name in lookup_opts.admin.list_display:
212
 
                try:
213
 
                    f = lookup_opts.get_field(field_name)
214
 
                except meta.FieldDoesNotExist:
215
 
                    pass
216
 
                else:
217
 
                    if isinstance(f.rel, meta.ManyToOneRel):
218
 
                        lookup_params['select_related'] = True
219
 
                        break
220
 
        lookup_params['order_by'] = ((order_type == 'desc' and '-' or '') + lookup_order_field,)
221
 
        if lookup_opts.admin.search_fields and query:
222
 
            complex_queries = []
223
 
            for bit in query.split():
224
 
                or_queries = []
225
 
                for field_name in lookup_opts.admin.search_fields:
226
 
                    or_queries.append(meta.Q(**{'%s__icontains' % field_name: bit}))
227
 
                complex_queries.append(reduce(operator.or_, or_queries))
228
 
            lookup_params['complex'] = reduce(operator.and_, complex_queries)
229
 
        if opts.one_to_one_field:
230
 
            lookup_params.update(opts.one_to_one_field.rel.limit_choices_to)
231
 
        self.lookup_params = lookup_params
232
 
 
233
 
def change_list(request, app_label, module_name):
234
 
    try:
235
 
        cl = ChangeList(request, app_label, module_name)
236
 
    except IncorrectLookupParameters:
237
 
        return HttpResponseRedirect(request.path)
238
 
 
239
 
    c = Context(request, {
240
 
        'title': cl.title,
241
 
        'is_popup': cl.is_popup,
242
 
        'cl' : cl
243
 
    })
244
 
    c.update({'has_add_permission': c['perms'][app_label][cl.opts.get_add_permission()]}),
245
 
    return render_to_response(['admin/%s/%s/change_list' % (app_label, cl.opts.object_name.lower()),
246
 
                               'admin/%s/change_list' % app_label,
247
 
                               'admin/change_list'], context_instance=c)
248
 
change_list = staff_member_required(change_list)
249
 
 
250
 
use_raw_id_admin = lambda field: isinstance(field.rel, (meta.ManyToOneRel, meta.ManyToManyRel)) and field.rel.raw_id_admin
251
 
 
252
 
def get_javascript_imports(opts,auto_populated_fields, ordered_objects, field_sets):
 
47
def quote(s):
 
48
    """
 
49
    Ensure that primary key values do not confuse the admin URLs by escaping
 
50
    any '/', '_' and ':' characters. Similar to urllib.quote, except that the
 
51
    quoting is slightly different so that it doesn't get autoamtically
 
52
    unquoted by the web browser.
 
53
    """
 
54
    if type(s) != type(''):
 
55
        return s
 
56
    res = list(s)
 
57
    for i in range(len(res)):
 
58
        c = res[i]
 
59
        if c in ':/_':
 
60
            res[i] = '_%02X' % ord(c)
 
61
    return ''.join(res)
 
62
 
 
63
def unquote(s):
 
64
    """
 
65
    Undo the effects of quote(). Based heavily on urllib.unquote().
 
66
    """
 
67
    mychr = chr
 
68
    myatoi = int
 
69
    list = s.split('_')
 
70
    res = [list[0]]
 
71
    myappend = res.append
 
72
    del list[0]
 
73
    for item in list:
 
74
        if item[1:2]:
 
75
            try:
 
76
                myappend(mychr(myatoi(item[:2], 16))
 
77
                     + item[2:])
 
78
            except ValueError:
 
79
                myappend('_' + item)
 
80
        else:
 
81
            myappend('_' + item)
 
82
    return "".join(res)
 
83
 
 
84
def get_javascript_imports(opts, auto_populated_fields, field_sets):
253
85
# Put in any necessary JavaScript imports.
254
86
    js = ['js/core.js', 'js/admin/RelatedObjectLookups.js']
255
87
    if auto_populated_fields:
256
88
        js.append('js/urlify.js')
257
 
    if opts.has_field_type(meta.DateTimeField) or opts.has_field_type(meta.TimeField) or opts.has_field_type(meta.DateField):
 
89
    if opts.has_field_type(models.DateTimeField) or opts.has_field_type(models.TimeField) or opts.has_field_type(models.DateField):
258
90
        js.extend(['js/calendar.js', 'js/admin/DateTimeShortcuts.js'])
259
 
    if ordered_objects:
 
91
    if opts.get_ordered_objects():
260
92
        js.extend(['js/getElementsBySelector.js', 'js/dom-drag.js' , 'js/admin/ordering.js'])
261
93
    if opts.admin.js:
262
94
        js.extend(opts.admin.js)
264
96
    for field_set in field_sets:
265
97
        if not seen_collapse and 'collapse' in field_set.classes:
266
98
            seen_collapse = True
267
 
            js.append('js/admin/CollapsedFieldsets.js' )
 
99
            js.append('js/admin/CollapsedFieldsets.js')
268
100
 
269
101
        for field_line in field_set:
270
102
            try:
271
103
                for f in field_line:
272
 
                    if f.rel and isinstance(f, meta.ManyToManyField) and f.rel.filter_interface:
 
104
                    if f.rel and isinstance(f, models.ManyToManyField) and f.rel.filter_interface:
273
105
                        js.extend(['js/SelectBox.js' , 'js/SelectFilter2.js'])
274
106
                        raise StopIteration
275
107
            except StopIteration:
276
108
                break
277
109
    return js
278
110
 
279
 
class AdminBoundField(BoundField):
 
111
class AdminBoundField(object):
280
112
    def __init__(self, field, field_mapping, original):
281
 
        super(AdminBoundField, self).__init__(field, field_mapping, original)
282
 
 
 
113
        self.field = field
 
114
        self.original = original
 
115
        self.form_fields = [field_mapping[name] for name in self.field.get_manipulator_field_names('')]
283
116
        self.element_id = self.form_fields[0].get_id()
284
 
        self.has_label_first = not isinstance(self.field, meta.BooleanField)
 
117
        self.has_label_first = not isinstance(self.field, models.BooleanField)
285
118
        self.raw_id_admin = use_raw_id_admin(field)
286
 
        self.is_date_time = isinstance(field, meta.DateTimeField)
287
 
        self.is_file_field = isinstance(field, meta.FileField)
288
 
        self.needs_add_label = field.rel and isinstance(field.rel, meta.ManyToOneRel) or isinstance(field.rel, meta.ManyToManyRel) and field.rel.to.admin
289
 
        self.hidden = isinstance(self.field, meta.AutoField)
 
119
        self.is_date_time = isinstance(field, models.DateTimeField)
 
120
        self.is_file_field = isinstance(field, models.FileField)
 
121
        self.needs_add_label = field.rel and isinstance(field.rel, models.ManyToOneRel) or isinstance(field.rel, models.ManyToManyRel) and field.rel.to._meta.admin
 
122
        self.hidden = isinstance(self.field, models.AutoField)
290
123
        self.first = False
291
124
 
292
125
        classes = []
298
131
            self.cell_class_attribute = ' class="%s" ' % ' '.join(classes)
299
132
        self._repr_filled = False
300
133
 
301
 
    def _fetch_existing_display(self, func_name):
302
 
        class_dict = self.original.__class__.__dict__
303
 
        func = class_dict.get(func_name)
304
 
        return func(self.original)
 
134
        if field.rel:
 
135
            self.related_url = '../../../%s/%s/' % (field.rel.to._meta.app_label, field.rel.to._meta.object_name.lower())
305
136
 
306
 
    def _fill_existing_display(self):
307
 
        if getattr(self, '_display_filled', False):
308
 
            return
309
 
        # HACK
310
 
        if isinstance(self.field.rel, meta.ManyToOneRel):
311
 
             func_name = 'get_%s' % self.field.name
312
 
             self._display = self._fetch_existing_display(func_name)
313
 
        elif isinstance(self.field.rel, meta.ManyToManyRel):
314
 
            func_name = 'get_%s_list' % self.field.rel.singular
315
 
            self._display =  ", ".join([str(obj) for obj in self._fetch_existing_display(func_name)])
316
 
        self._display_filled = True
 
137
    def original_value(self):
 
138
        if self.original:
 
139
            return self.original.__dict__[self.field.column]
317
140
 
318
141
    def existing_display(self):
319
 
        self._fill_existing_display()
320
 
        return self._display
 
142
        try:
 
143
            return self._display
 
144
        except AttributeError:
 
145
            if isinstance(self.field.rel, models.ManyToOneRel):
 
146
                self._display = getattr(self.original, 'get_%s' % self.field.name)()
 
147
            elif isinstance(self.field.rel, models.ManyToManyRel):
 
148
                self._display = ", ".join([str(obj) for obj in getattr(self.original, 'get_%s_list' % self.field.rel.singular)()])
 
149
            return self._display
321
150
 
322
151
    def __repr__(self):
323
152
        return repr(self.__dict__)
325
154
    def html_error_list(self):
326
155
        return " ".join([form_field.html_error_list() for form_field in self.form_fields if form_field.errors])
327
156
 
328
 
class AdminBoundFieldLine(BoundFieldLine):
 
157
    def original_url(self):
 
158
        if self.is_file_field and self.original and self.field.attname:
 
159
            url_method = getattr(self.original, 'get_%s_url' % self.field.attname)
 
160
            if callable(url_method):
 
161
                return url_method()
 
162
        return ''
 
163
 
 
164
class AdminBoundFieldLine(object):
329
165
    def __init__(self, field_line, field_mapping, original):
330
 
        super(AdminBoundFieldLine, self).__init__(field_line, field_mapping, original, AdminBoundField)
 
166
        self.bound_fields = [field.bind(field_mapping, original, AdminBoundField) for field in field_line]
331
167
        for bound_field in self:
332
168
            bound_field.first = True
333
169
            break
334
170
 
335
 
class AdminBoundFieldSet(BoundFieldSet):
 
171
    def __iter__(self):
 
172
        for bound_field in self.bound_fields:
 
173
            yield bound_field
 
174
 
 
175
    def __len__(self):
 
176
        return len(self.bound_fields)
 
177
 
 
178
class AdminBoundFieldSet(object):
336
179
    def __init__(self, field_set, field_mapping, original):
337
 
        super(AdminBoundFieldSet, self).__init__(field_set, field_mapping, original, AdminBoundFieldLine)
338
 
 
339
 
class BoundManipulator(object):
340
 
    def __init__(self, opts, manipulator, field_mapping):
341
 
        self.inline_related_objects = opts.get_followed_related_objects(manipulator.follow)
342
 
        self.original = hasattr(manipulator, 'original_object') and manipulator.original_object or None
343
 
        self.bound_field_sets = [field_set.bind(field_mapping, self.original, AdminBoundFieldSet)
344
 
                                 for field_set in opts.admin.get_field_sets(opts)]
345
 
        self.ordered_objects = opts.get_ordered_objects()[:]
346
 
 
347
 
class AdminBoundManipulator(BoundManipulator):
348
 
    def __init__(self, opts, manipulator, field_mapping):
349
 
        super(AdminBoundManipulator, self).__init__(opts, manipulator, field_mapping)
350
 
        field_sets = opts.admin.get_field_sets(opts)
351
 
 
352
 
        self.auto_populated_fields = [f for f in opts.fields if f.prepopulate_from]
353
 
        self.javascript_imports = get_javascript_imports(opts, self.auto_populated_fields, self.ordered_objects, field_sets);
354
 
 
355
 
        self.coltype = self.ordered_objects and 'colMS' or 'colM'
356
 
        self.has_absolute_url = hasattr(opts.get_model_module().Klass, 'get_absolute_url')
357
 
        self.form_enc_attrib = opts.has_field_type(meta.FileField) and \
358
 
                                'enctype="multipart/form-data" ' or ''
359
 
 
360
 
        self.first_form_field_id = self.bound_field_sets[0].bound_field_lines[0].bound_fields[0].form_fields[0].get_id();
361
 
        self.ordered_object_pk_names = [o.pk.name for o in self.ordered_objects]
362
 
 
363
 
        self.save_on_top = opts.admin.save_on_top
364
 
        self.save_as = opts.admin.save_as
365
 
 
366
 
        self.content_type_id = opts.get_content_type_id()
367
 
        self.verbose_name_plural = opts.verbose_name_plural
368
 
        self.verbose_name = opts.verbose_name
369
 
        self.object_name = opts.object_name
370
 
 
371
 
    def get_ordered_object_pk(self, ordered_obj):
372
 
        for name in self.ordered_object_pk_names:
373
 
            if hasattr(ordered_obj, name):
374
 
                return str(getattr(ordered_obj, name))
375
 
        return ""
376
 
 
377
 
def render_change_form(opts, manipulator, app_label, context, add=False, change=False, show_delete=False, form_url=''):
 
180
        self.name = field_set.name
 
181
        self.classes = field_set.classes
 
182
        self.description = field_set.description
 
183
        self.bound_field_lines = [field_line.bind(field_mapping, original, AdminBoundFieldLine) for field_line in field_set]
 
184
 
 
185
    def __iter__(self):
 
186
        for bound_field_line in self.bound_field_lines:
 
187
            yield bound_field_line
 
188
 
 
189
    def __len__(self):
 
190
        return len(self.bound_field_lines)
 
191
 
 
192
def render_change_form(model, manipulator, context, add=False, change=False, form_url=''):
 
193
    opts = model._meta
 
194
    app_label = opts.app_label
 
195
    auto_populated_fields = [f for f in opts.fields if f.prepopulate_from]
 
196
    field_sets = opts.admin.get_field_sets(opts)
 
197
    original = getattr(manipulator, 'original_object', None)
 
198
    bound_field_sets = [field_set.bind(context['form'], original, AdminBoundFieldSet) for field_set in field_sets]
 
199
    first_form_field_id = bound_field_sets[0].bound_field_lines[0].bound_fields[0].form_fields[0].get_id();
 
200
    ordered_objects = opts.get_ordered_objects()
 
201
    inline_related_objects = opts.get_followed_related_objects(manipulator.follow)
378
202
    extra_context = {
379
203
        'add': add,
380
204
        'change': change,
381
 
        'bound_manipulator': AdminBoundManipulator(opts, manipulator, context['form']),
382
205
        'has_delete_permission': context['perms'][app_label][opts.get_delete_permission()],
 
206
        'has_change_permission': context['perms'][app_label][opts.get_change_permission()],
 
207
        'has_file_field': opts.has_field_type(models.FileField),
 
208
        'has_absolute_url': hasattr(model, 'get_absolute_url'),
 
209
        'auto_populated_fields': auto_populated_fields,
 
210
        'bound_field_sets': bound_field_sets,
 
211
        'first_form_field_id': first_form_field_id,
 
212
        'javascript_imports': get_javascript_imports(opts, auto_populated_fields, field_sets),
 
213
        'ordered_objects': ordered_objects,
 
214
        'inline_related_objects': inline_related_objects,
383
215
        'form_url': form_url,
384
 
        'app_label': app_label,
 
216
        'opts': opts,
 
217
        'content_type_id': ContentType.objects.get_for_model(model).id,
385
218
    }
386
219
    context.update(extra_context)
387
 
    return render_to_response(["admin/%s/%s/change_form" % (app_label, opts.object_name.lower() ),
388
 
                               "admin/%s/change_form" % app_label ,
389
 
                               "admin/change_form"], context_instance=context)
390
 
 
391
 
def log_add_message(user, opts,manipulator,new_object):
392
 
    pk_value = getattr(new_object, opts.pk.attname)
393
 
    log.log_action(user.id, opts.get_content_type_id(), pk_value, str(new_object), log.ADDITION)
394
 
 
395
 
def add_stage(request, app_label, module_name, show_delete=False, form_url='', post_url='../', post_url_continue='../%s/', object_id_override=None):
396
 
    mod, opts = _get_mod_opts(app_label, module_name)
 
220
    return render_to_response([
 
221
        "admin/%s/%s/change_form.html" % (app_label, opts.object_name.lower()),
 
222
        "admin/%s/change_form.html" % app_label,
 
223
        "admin/change_form.html"], context_instance=context)
 
224
 
 
225
def index(request):
 
226
    return render_to_response('admin/index.html', {'title': _('Site administration')}, context_instance=template.RequestContext(request))
 
227
index = staff_member_required(never_cache(index))
 
228
 
 
229
def add_stage(request, app_label, model_name, show_delete=False, form_url='', post_url=None, post_url_continue='../%s/', object_id_override=None):
 
230
    model = models.get_model(app_label, model_name)
 
231
    if model is None:
 
232
        raise Http404, "App %r, model %r, not found" % (app_label, model_name)
 
233
    opts = model._meta
 
234
 
397
235
    if not request.user.has_perm(app_label + '.' + opts.get_add_permission()):
398
236
        raise PermissionDenied
399
 
    manipulator = mod.AddManipulator()
 
237
 
 
238
    if post_url is None:
 
239
        if request.user.has_perm(app_label + '.' + opts.get_change_permission()):
 
240
            # redirect to list view
 
241
            post_url = '../'
 
242
        else:
 
243
            # Object list will give 'Permission Denied', so go back to admin home
 
244
            post_url = '../../../'
 
245
 
 
246
    manipulator = model.AddManipulator()
400
247
    if request.POST:
401
248
        new_data = request.POST.copy()
402
 
        if opts.has_field_type(meta.FileField):
 
249
 
 
250
        if opts.has_field_type(models.FileField):
403
251
            new_data.update(request.FILES)
 
252
 
404
253
        errors = manipulator.get_validation_errors(new_data)
405
254
        manipulator.do_html2python(new_data)
406
255
 
407
 
        if not errors and not request.POST.has_key("_preview"):
 
256
        if not errors:
408
257
            new_object = manipulator.save(new_data)
409
 
            log_add_message(request.user, opts,manipulator,new_object)
410
 
            msg = _('The %(name)s "%(obj)s" was added successfully.') % {'name':opts.verbose_name, 'obj':new_object}
411
 
            pk_value = getattr(new_object,opts.pk.attname)
 
258
            pk_value = new_object._get_pk_val()
 
259
            LogEntry.objects.log_action(request.user.id, ContentType.objects.get_for_model(model).id, pk_value, str(new_object), ADDITION)
 
260
            msg = _('The %(name)s "%(obj)s" was added successfully.') % {'name': opts.verbose_name, 'obj': new_object}
412
261
            # Here, we distinguish between different save types by checking for
413
262
            # the presence of keys in request.POST.
414
263
            if request.POST.has_key("_continue"):
415
 
                request.user.add_message(msg + ' ' + _("You may edit it again below."))
 
264
                request.user.message_set.create(message=msg + ' ' + _("You may edit it again below."))
416
265
                if request.POST.has_key("_popup"):
417
266
                    post_url_continue += "?_popup=1"
418
267
                return HttpResponseRedirect(post_url_continue % pk_value)
420
269
                return HttpResponse('<script type="text/javascript">opener.dismissAddAnotherPopup(window, %s, "%s");</script>' % \
421
270
                    (pk_value, str(new_object).replace('"', '\\"')))
422
271
            elif request.POST.has_key("_addanother"):
423
 
                request.user.add_message(msg + ' ' + (_("You may add another %s below.") % opts.verbose_name))
 
272
                request.user.message_set.create(message=msg + ' ' + (_("You may add another %s below.") % opts.verbose_name))
424
273
                return HttpResponseRedirect(request.path)
425
274
            else:
426
 
                request.user.add_message(msg)
 
275
                request.user.message_set.create(message=msg)
427
276
                return HttpResponseRedirect(post_url)
428
277
    else:
429
278
        # Add default data.
435
284
        errors = {}
436
285
 
437
286
    # Populate the FormWrapper.
438
 
    form = formfields.FormWrapper(manipulator, new_data, errors, edit_inline=True)
 
287
    form = forms.FormWrapper(manipulator, new_data, errors)
439
288
 
440
 
    c = Context(request, {
 
289
    c = template.RequestContext(request, {
441
290
        'title': _('Add %s') % opts.verbose_name,
442
291
        'form': form,
443
292
        'is_popup': request.REQUEST.has_key('_popup'),
444
293
        'show_delete': show_delete,
445
294
    })
 
295
 
446
296
    if object_id_override is not None:
447
297
        c['object_id'] = object_id_override
448
298
 
449
 
    return render_change_form(opts, manipulator, app_label, c, add=True)
450
 
add_stage = staff_member_required(add_stage)
451
 
 
452
 
def log_change_message(user, opts,manipulator,new_object):
453
 
    pk_value = getattr(new_object, opts.pk.column)
454
 
    # Construct the change message.
455
 
    change_message = []
456
 
    if manipulator.fields_added:
457
 
        change_message.append(_('Added %s.') % get_text_list(manipulator.fields_added, _('and')))
458
 
    if manipulator.fields_changed:
459
 
        change_message.append(_('Changed %s.') % get_text_list(manipulator.fields_changed, _('and')))
460
 
    if manipulator.fields_deleted:
461
 
        change_message.append(_('Deleted %s.') % get_text_list(manipulator.fields_deleted, _('and')))
462
 
    change_message = ' '.join(change_message)
463
 
    if not change_message:
464
 
        change_message = _('No fields changed.')
465
 
    log.log_action(user.id, opts.get_content_type_id(), pk_value, str(new_object), log.CHANGE, change_message)
466
 
 
467
 
def change_stage(request, app_label, module_name, object_id):
468
 
    mod, opts = _get_mod_opts(app_label, module_name)
 
299
    return render_change_form(model, manipulator, c, add=True)
 
300
add_stage = staff_member_required(never_cache(add_stage))
 
301
 
 
302
def change_stage(request, app_label, model_name, object_id):
 
303
    model = models.get_model(app_label, model_name)
 
304
    object_id = unquote(object_id)
 
305
    if model is None:
 
306
        raise Http404, "App %r, model %r, not found" % (app_label, model_name)
 
307
    opts = model._meta
 
308
 
469
309
    if not request.user.has_perm(app_label + '.' + opts.get_change_permission()):
470
310
        raise PermissionDenied
 
311
 
471
312
    if request.POST and request.POST.has_key("_saveasnew"):
472
 
        return add_stage(request, app_label, module_name, form_url='../add/')
 
313
        return add_stage(request, app_label, model_name, form_url='../../add/')
 
314
 
473
315
    try:
474
 
        manipulator = mod.ChangeManipulator(object_id)
 
316
        manipulator = model.ChangeManipulator(object_id)
475
317
    except ObjectDoesNotExist:
476
318
        raise Http404
477
319
 
478
320
    if request.POST:
479
321
        new_data = request.POST.copy()
480
 
        if opts.has_field_type(meta.FileField):
 
322
 
 
323
        if opts.has_field_type(models.FileField):
481
324
            new_data.update(request.FILES)
482
325
 
483
326
        errors = manipulator.get_validation_errors(new_data)
484
 
 
485
327
        manipulator.do_html2python(new_data)
486
 
        if not errors and not request.POST.has_key("_preview"):
 
328
 
 
329
        if not errors:
487
330
            new_object = manipulator.save(new_data)
488
 
            log_change_message(request.user,opts,manipulator,new_object)
489
 
            msg = _('The %(name)s "%(obj)s" was changed successfully.') % {'name': opts.verbose_name, 'obj':new_object}
490
 
            pk_value = getattr(new_object,opts.pk.attname)
 
331
            pk_value = new_object._get_pk_val()
 
332
 
 
333
            # Construct the change message.
 
334
            change_message = []
 
335
            if manipulator.fields_added:
 
336
                change_message.append(_('Added %s.') % get_text_list(manipulator.fields_added, _('and')))
 
337
            if manipulator.fields_changed:
 
338
                change_message.append(_('Changed %s.') % get_text_list(manipulator.fields_changed, _('and')))
 
339
            if manipulator.fields_deleted:
 
340
                change_message.append(_('Deleted %s.') % get_text_list(manipulator.fields_deleted, _('and')))
 
341
            change_message = ' '.join(change_message)
 
342
            if not change_message:
 
343
                change_message = _('No fields changed.')
 
344
            LogEntry.objects.log_action(request.user.id, ContentType.objects.get_for_model(model).id, pk_value, str(new_object), CHANGE, change_message)
 
345
 
 
346
            msg = _('The %(name)s "%(obj)s" was changed successfully.') % {'name': opts.verbose_name, 'obj': new_object}
491
347
            if request.POST.has_key("_continue"):
492
 
                request.user.add_message(msg + ' ' + _("You may edit it again below."))
 
348
                request.user.message_set.create(message=msg + ' ' + _("You may edit it again below."))
493
349
                if request.REQUEST.has_key('_popup'):
494
350
                    return HttpResponseRedirect(request.path + "?_popup=1")
495
351
                else:
496
352
                    return HttpResponseRedirect(request.path)
497
353
            elif request.POST.has_key("_saveasnew"):
498
 
                request.user.add_message(_('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % {'name': opts.verbose_name, 'obj': new_object})
 
354
                request.user.message_set.create(message=_('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % {'name': opts.verbose_name, 'obj': new_object})
499
355
                return HttpResponseRedirect("../%s/" % pk_value)
500
356
            elif request.POST.has_key("_addanother"):
501
 
                request.user.add_message(msg + ' ' + (_("You may add another %s below.") % opts.verbose_name))
 
357
                request.user.message_set.create(message=msg + ' ' + (_("You may add another %s below.") % opts.verbose_name))
502
358
                return HttpResponseRedirect("../add/")
503
359
            else:
504
 
                request.user.add_message(msg)
 
360
                request.user.message_set.create(message=msg)
505
361
                return HttpResponseRedirect("../")
506
362
    else:
507
363
        # Populate new_data with a "flattened" version of the current data.
519
375
        errors = {}
520
376
 
521
377
    # Populate the FormWrapper.
522
 
    form = formfields.FormWrapper(manipulator, new_data, errors, edit_inline = True)
 
378
    form = forms.FormWrapper(manipulator, new_data, errors)
523
379
    form.original = manipulator.original_object
524
380
    form.order_objects = []
525
381
 
528
384
        wrt = related.opts.order_with_respect_to
529
385
        if wrt and wrt.rel and wrt.rel.to == opts:
530
386
            func = getattr(manipulator.original_object, 'get_%s_list' %
531
 
                    related.get_method_name_part())
 
387
                    related.get_accessor_name())
532
388
            orig_list = func()
533
389
            form.order_objects.extend(orig_list)
534
390
 
535
 
    c = Context(request, {
 
391
    c = template.RequestContext(request, {
536
392
        'title': _('Change %s') % opts.verbose_name,
537
393
        'form': form,
538
394
        'object_id': object_id,
539
395
        'original': manipulator.original_object,
540
 
        'is_popup' : request.REQUEST.has_key('_popup')
 
396
        'is_popup': request.REQUEST.has_key('_popup'),
541
397
    })
542
 
 
543
 
    return render_change_form(opts,manipulator, app_label, c, change=True)
544
 
change_stage = staff_member_required(change_stage)
 
398
    return render_change_form(model, manipulator, c, change=True)
 
399
change_stage = staff_member_required(never_cache(change_stage))
545
400
 
546
401
def _nest_help(obj, depth, val):
547
402
    current = obj
559
414
        if related.opts in opts_seen:
560
415
            continue
561
416
        opts_seen.append(related.opts)
562
 
        rel_opts_name = related.get_method_name_part()
563
 
        if isinstance(related.field.rel, meta.OneToOneRel):
 
417
        rel_opts_name = related.get_accessor_name()
 
418
        if isinstance(related.field.rel, models.OneToOneRel):
564
419
            try:
565
 
                sub_obj = getattr(obj, 'get_%s' % rel_opts_name)()
 
420
                sub_obj = getattr(obj, rel_opts_name)
566
421
            except ObjectDoesNotExist:
567
422
                pass
568
423
            else:
579
434
                else:
580
435
                    # Display a link to the admin page.
581
436
                    nh(deleted_objects, current_depth, ['%s: <a href="../../../../%s/%s/%s/">%s</a>' % \
582
 
                        (capfirst(related.opts.verbose_name), related.opts.app_label, related.opts.module_name,
583
 
                        getattr(sub_obj, related.opts.pk.attname), sub_obj), []])
 
437
                        (capfirst(related.opts.verbose_name), related.opts.app_label, related.opts.object_name.lower(),
 
438
                        sub_obj._get_pk_val(), sub_obj), []])
584
439
                _get_deleted_objects(deleted_objects, perms_needed, user, sub_obj, related.opts, current_depth+2)
585
440
        else:
586
441
            has_related_objs = False
587
 
            for sub_obj in getattr(obj, 'get_%s_list' % rel_opts_name)():
 
442
            for sub_obj in getattr(obj, rel_opts_name).all():
588
443
                has_related_objs = True
589
444
                if related.field.rel.edit_inline or not related.opts.admin:
590
445
                    # Don't display link to edit, because it either has no
593
448
                else:
594
449
                    # Display a link to the admin page.
595
450
                    nh(deleted_objects, current_depth, ['%s: <a href="../../../../%s/%s/%s/">%s</a>' % \
596
 
                        (capfirst(related.opts.verbose_name), related.opts.app_label, related.opts.module_name, getattr(sub_obj, related.opts.pk.attname), escape(str(sub_obj))), []])
 
451
                        (capfirst(related.opts.verbose_name), related.opts.app_label, related.opts.object_name.lower(), sub_obj._get_pk_val(), escape(str(sub_obj))), []])
597
452
                _get_deleted_objects(deleted_objects, perms_needed, user, sub_obj, related.opts, current_depth+2)
598
453
            # If there were related objects, and the user doesn't have
599
454
            # permission to delete them, add the missing perm to perms_needed.
605
460
        if related.opts in opts_seen:
606
461
            continue
607
462
        opts_seen.append(related.opts)
608
 
        rel_opts_name = related.get_method_name_part()
 
463
        rel_opts_name = related.get_accessor_name()
609
464
        has_related_objs = False
610
 
        for sub_obj in getattr(obj, 'get_%s_list' % rel_opts_name)():
 
465
        for sub_obj in getattr(obj, rel_opts_name).all():
611
466
            has_related_objs = True
612
467
            if related.field.rel.edit_inline or not related.opts.admin:
613
468
                # Don't display link to edit, because it either has no
614
469
                # admin or is edited inline.
615
470
                nh(deleted_objects, current_depth, [_('One or more %(fieldname)s in %(name)s: %(obj)s') % \
616
 
                    {'fieldname': related.field.name, 'name': related.opts.verbose_name, 'obj': escape(str(sub_obj))}, []])
 
471
                    {'fieldname': related.field.verbose_name, 'name': related.opts.verbose_name, 'obj': escape(str(sub_obj))}, []])
617
472
            else:
618
473
                # Display a link to the admin page.
619
474
                nh(deleted_objects, current_depth, [
620
 
                    (_('One or more %(fieldname)s in %(name)s:') % {'fieldname': related.field.name, 'name':related.opts.verbose_name}) + \
 
475
                    (_('One or more %(fieldname)s in %(name)s:') % {'fieldname': related.field.verbose_name, 'name':related.opts.verbose_name}) + \
621
476
                    (' <a href="../../../../%s/%s/%s/">%s</a>' % \
622
 
                        (related.opts.app_label, related.opts.module_name, getattr(sub_obj, related.opts.pk.attname), escape(str(sub_obj)))), []])
 
477
                        (related.opts.app_label, related.opts.module_name, sub_obj._get_pk_val(), escape(str(sub_obj)))), []])
623
478
        # If there were related objects, and the user doesn't have
624
479
        # permission to change them, add the missing perm to perms_needed.
625
480
        if related.opts.admin and has_related_objs:
627
482
            if not user.has_perm(p):
628
483
                perms_needed.add(related.opts.verbose_name)
629
484
 
630
 
def delete_stage(request, app_label, module_name, object_id):
 
485
def delete_stage(request, app_label, model_name, object_id):
631
486
    import sets
632
 
    mod, opts = _get_mod_opts(app_label, module_name)
 
487
    model = models.get_model(app_label, model_name)
 
488
    object_id = unquote(object_id)
 
489
    if model is None:
 
490
        raise Http404, "App %r, model %r, not found" % (app_label, model_name)
 
491
    opts = model._meta
633
492
    if not request.user.has_perm(app_label + '.' + opts.get_delete_permission()):
634
493
        raise PermissionDenied
635
 
    obj = get_object_or_404(mod, pk=object_id)
 
494
    obj = get_object_or_404(model, pk=object_id)
636
495
 
637
496
    # Populate deleted_objects, a data structure of all related objects that
638
497
    # will also be deleted.
645
504
            raise PermissionDenied
646
505
        obj_display = str(obj)
647
506
        obj.delete()
648
 
        log.log_action(request.user.id, opts.get_content_type_id(), object_id, obj_display, log.DELETION)
649
 
        request.user.add_message(_('The %(name)s "%(obj)s" was deleted successfully.') % {'name':opts.verbose_name, 'obj':obj_display})
 
507
        LogEntry.objects.log_action(request.user.id, ContentType.objects.get_for_model(model).id, object_id, obj_display, DELETION)
 
508
        request.user.message_set.create(message=_('The %(name)s "%(obj)s" was deleted successfully.') % {'name': opts.verbose_name, 'obj': obj_display})
650
509
        return HttpResponseRedirect("../../")
651
 
    return render_to_response('admin/delete_confirmation', {
 
510
    extra_context = {
652
511
        "title": _("Are you sure?"),
653
512
        "object_name": opts.verbose_name,
654
513
        "object": obj,
655
514
        "deleted_objects": deleted_objects,
656
515
        "perms_lacking": perms_needed,
657
 
    }, context_instance=Context(request))
658
 
delete_stage = staff_member_required(delete_stage)
 
516
        "opts": model._meta,
 
517
    }
 
518
    return render_to_response(["admin/%s/%s/delete_confirmation.html" % (app_label, opts.object_name.lower() ),
 
519
                               "admin/%s/delete_confirmation.html" % app_label ,
 
520
                               "admin/delete_confirmation.html"], extra_context, context_instance=template.RequestContext(request))
 
521
delete_stage = staff_member_required(never_cache(delete_stage))
659
522
 
660
 
def history(request, app_label, module_name, object_id):
661
 
    mod, opts = _get_mod_opts(app_label, module_name)
662
 
    action_list = log.get_list(object_id__exact=object_id, content_type__id__exact=opts.get_content_type_id(),
663
 
        order_by=("action_time",), select_related=True)
 
523
def history(request, app_label, model_name, object_id):
 
524
    model = models.get_model(app_label, model_name)
 
525
    object_id = unquote(object_id)
 
526
    if model is None:
 
527
        raise Http404, "App %r, model %r, not found" % (app_label, model_name)
 
528
    action_list = LogEntry.objects.filter(object_id=object_id,
 
529
        content_type__id__exact=ContentType.objects.get_for_model(model).id).select_related().order_by('action_time')
664
530
    # If no history was found, see whether this object even exists.
665
 
    obj = get_object_or_404(mod, pk=object_id)
666
 
    return render_to_response('admin/object_history', {
 
531
    obj = get_object_or_404(model, pk=object_id)
 
532
    extra_context = {
667
533
        'title': _('Change history: %s') % obj,
668
534
        'action_list': action_list,
669
 
        'module_name': capfirst(opts.verbose_name_plural),
 
535
        'module_name': capfirst(model._meta.verbose_name_plural),
670
536
        'object': obj,
671
 
    }, context_instance=Context(request))
672
 
history = staff_member_required(history)
 
537
    }
 
538
    return render_to_response(["admin/%s/%s/object_history.html" % (app_label, model._meta.object_name.lower()),
 
539
                               "admin/%s/object_history.html" % app_label ,
 
540
                               "admin/object_history.html"], extra_context, context_instance=template.RequestContext(request))
 
541
history = staff_member_required(never_cache(history))
 
542
 
 
543
class ChangeList(object):
 
544
    def __init__(self, request, model):
 
545
        self.model = model
 
546
        self.opts = model._meta
 
547
        self.lookup_opts = self.opts
 
548
        self.manager = self.opts.admin.manager
 
549
 
 
550
        # Get search parameters from the query string.
 
551
        try:
 
552
            self.page_num = int(request.GET.get(PAGE_VAR, 0))
 
553
        except ValueError:
 
554
            self.page_num = 0
 
555
        self.show_all = request.GET.has_key(ALL_VAR)
 
556
        self.is_popup = request.GET.has_key(IS_POPUP_VAR)
 
557
        self.params = dict(request.GET.items())
 
558
        if self.params.has_key(PAGE_VAR):
 
559
            del self.params[PAGE_VAR]
 
560
 
 
561
        self.order_field, self.order_type = self.get_ordering()
 
562
        self.query = request.GET.get(SEARCH_VAR, '')
 
563
        self.query_set = self.get_query_set()
 
564
        self.get_results(request)
 
565
        self.title = (self.is_popup and _('Select %s') % self.opts.verbose_name or _('Select %s to change') % self.opts.verbose_name)
 
566
        self.filter_specs, self.has_filters = self.get_filters(request)
 
567
        self.pk_attname = self.lookup_opts.pk.attname
 
568
 
 
569
    def get_filters(self, request):
 
570
        filter_specs = []
 
571
        if self.lookup_opts.admin.list_filter and not self.opts.one_to_one_field:
 
572
            filter_fields = [self.lookup_opts.get_field(field_name) \
 
573
                              for field_name in self.lookup_opts.admin.list_filter]
 
574
            for f in filter_fields:
 
575
                spec = FilterSpec.create(f, request, self.params)
 
576
                if spec and spec.has_output():
 
577
                    filter_specs.append(spec)
 
578
        return filter_specs, bool(filter_specs)
 
579
 
 
580
    def get_query_string(self, new_params={}, remove=[]):
 
581
        p = self.params.copy()
 
582
        for r in remove:
 
583
            for k in p.keys():
 
584
                if k.startswith(r):
 
585
                    del p[k]
 
586
        for k, v in new_params.items():
 
587
            if p.has_key(k) and v is None:
 
588
                del p[k]
 
589
            elif v is not None:
 
590
                p[k] = v
 
591
        return '?' + '&amp;'.join(['%s=%s' % (k, v) for k, v in p.items()]).replace(' ', '%20')
 
592
 
 
593
    def get_results(self, request):
 
594
        paginator = ObjectPaginator(self.query_set, self.lookup_opts.admin.list_per_page)
 
595
 
 
596
        # Get the number of objects, with admin filters applied.
 
597
        try:
 
598
            result_count = paginator.hits
 
599
        # Naked except! Because we don't have any other way of validating
 
600
        # "params". They might be invalid if the keyword arguments are
 
601
        # incorrect, or if the values are not in the correct type (which would
 
602
        # result in a database error).
 
603
        except:
 
604
            raise IncorrectLookupParameters
 
605
 
 
606
        # Get the total number of objects, with no admin filters applied.
 
607
        # Perform a slight optimization: Check to see whether any filters were
 
608
        # given. If not, use paginator.hits to calculate the number of objects,
 
609
        # because we've already done paginator.hits and the value is cached.
 
610
        if isinstance(self.query_set._filters, models.Q) and not self.query_set._filters.kwargs:
 
611
            full_result_count = result_count
 
612
        else:
 
613
            full_result_count = self.manager.count()
 
614
 
 
615
        can_show_all = result_count <= MAX_SHOW_ALL_ALLOWED
 
616
        multi_page = result_count > self.lookup_opts.admin.list_per_page
 
617
 
 
618
        # Get the list of objects to display on this page.
 
619
        if (self.show_all and can_show_all) or not multi_page:
 
620
            result_list = list(self.query_set)
 
621
        else:
 
622
            try:
 
623
                result_list = paginator.get_page(self.page_num)
 
624
            except InvalidPage:
 
625
                result_list = ()
 
626
 
 
627
        self.result_count = result_count
 
628
        self.full_result_count = full_result_count
 
629
        self.result_list = result_list
 
630
        self.can_show_all = can_show_all
 
631
        self.multi_page = multi_page
 
632
        self.paginator = paginator
 
633
 
 
634
    def get_ordering(self):
 
635
        lookup_opts, params = self.lookup_opts, self.params
 
636
        # For ordering, first check the "ordering" parameter in the admin options,
 
637
        # then check the object's default ordering. If neither of those exist,
 
638
        # order descending by ID by default. Finally, look for manually-specified
 
639
        # ordering from the query string.
 
640
        ordering = lookup_opts.admin.ordering or lookup_opts.ordering or ['-' + lookup_opts.pk.name]
 
641
 
 
642
        # Normalize it to new-style ordering.
 
643
        ordering = handle_legacy_orderlist(ordering)
 
644
 
 
645
        if ordering[0].startswith('-'):
 
646
            order_field, order_type = ordering[0][1:], 'desc'
 
647
        else:
 
648
            order_field, order_type = ordering[0], 'asc'
 
649
        if params.has_key(ORDER_VAR):
 
650
            try:
 
651
                try:
 
652
                    f = lookup_opts.get_field(lookup_opts.admin.list_display[int(params[ORDER_VAR])])
 
653
                except models.FieldDoesNotExist:
 
654
                    pass
 
655
                else:
 
656
                    if not isinstance(f.rel, models.ManyToOneRel) or not f.null:
 
657
                        order_field = f.name
 
658
            except (IndexError, ValueError):
 
659
                pass # Invalid ordering specified. Just use the default.
 
660
        if params.has_key(ORDER_TYPE_VAR) and params[ORDER_TYPE_VAR] in ('asc', 'desc'):
 
661
            order_type = params[ORDER_TYPE_VAR]
 
662
        return order_field, order_type
 
663
 
 
664
    def get_query_set(self):
 
665
        qs = self.manager.get_query_set()
 
666
        lookup_params = self.params.copy() # a dictionary of the query string
 
667
        for i in (ALL_VAR, ORDER_VAR, ORDER_TYPE_VAR, SEARCH_VAR, IS_POPUP_VAR):
 
668
            if lookup_params.has_key(i):
 
669
                del lookup_params[i]
 
670
 
 
671
        # Apply lookup parameters from the query string.
 
672
        qs = qs.filter(**lookup_params)
 
673
 
 
674
        # Use select_related() if one of the list_display options is a field
 
675
        # with a relationship.
 
676
        if self.lookup_opts.admin.list_select_related:
 
677
            qs = qs.select_related()
 
678
        else:
 
679
            for field_name in self.lookup_opts.admin.list_display:
 
680
                try:
 
681
                    f = self.lookup_opts.get_field(field_name)
 
682
                except models.FieldDoesNotExist:
 
683
                    pass
 
684
                else:
 
685
                    if isinstance(f.rel, models.ManyToOneRel):
 
686
                        qs = qs.select_related()
 
687
                        break
 
688
 
 
689
        # Calculate lookup_order_field.
 
690
        # If the order-by field is a field with a relationship, order by the
 
691
        # value in the related table.
 
692
        lookup_order_field = self.order_field
 
693
        try:
 
694
            f = self.lookup_opts.get_field(self.order_field, many_to_many=False)
 
695
        except models.FieldDoesNotExist:
 
696
            pass
 
697
        else:
 
698
            if isinstance(f.rel, models.OneToOneRel):
 
699
                # For OneToOneFields, don't try to order by the related object's ordering criteria.
 
700
                pass
 
701
            elif isinstance(f.rel, models.ManyToOneRel):
 
702
                rel_ordering = f.rel.to._meta.ordering and f.rel.to._meta.ordering[0] or f.rel.to._meta.pk.column
 
703
                lookup_order_field = '%s.%s' % (f.rel.to._meta.db_table, rel_ordering)
 
704
 
 
705
        # Set ordering.
 
706
        qs = qs.order_by((self.order_type == 'desc' and '-' or '') + lookup_order_field)
 
707
 
 
708
        # Apply keyword searches.
 
709
        if self.lookup_opts.admin.search_fields and self.query:
 
710
            for bit in self.query.split():
 
711
                or_queries = [models.Q(**{'%s__icontains' % field_name: bit}) for field_name in self.lookup_opts.admin.search_fields]
 
712
                other_qs = QuerySet(self.model)
 
713
                other_qs = other_qs.filter(reduce(operator.or_, or_queries))
 
714
                qs = qs & other_qs
 
715
 
 
716
        if self.opts.one_to_one_field:
 
717
            qs = qs.filter(**self.opts.one_to_one_field.rel.limit_choices_to)
 
718
 
 
719
        return qs
 
720
 
 
721
    def url_for_result(self, result):
 
722
        return "%s/" % quote(getattr(result, self.pk_attname))
 
723
 
 
724
def change_list(request, app_label, model_name):
 
725
    model = models.get_model(app_label, model_name)
 
726
    if model is None:
 
727
        raise Http404, "App %r, model %r, not found" % (app_label, model_name)
 
728
    if not request.user.has_perm(app_label + '.' + model._meta.get_change_permission()):
 
729
        raise PermissionDenied
 
730
    try:
 
731
        cl = ChangeList(request, model)
 
732
    except IncorrectLookupParameters:
 
733
        return HttpResponseRedirect(request.path)
 
734
    c = template.RequestContext(request, {
 
735
        'title': cl.title,
 
736
        'is_popup': cl.is_popup,
 
737
        'cl': cl,
 
738
    })
 
739
    c.update({'has_add_permission': c['perms'][app_label][cl.opts.get_add_permission()]}),
 
740
    return render_to_response(['admin/%s/%s/change_list.html' % (app_label, cl.opts.object_name.lower()),
 
741
                               'admin/%s/change_list.html' % app_label,
 
742
                               'admin/change_list.html'], context_instance=c)
 
743
change_list = staff_member_required(never_cache(change_list))