9 from django.db.models.related import RelatedObject |
9 from django.db.models.related import RelatedObject |
10 from django.db.models.fields.related import ManyToManyRel |
10 from django.db.models.fields.related import ManyToManyRel |
11 from django.db.models.fields import AutoField, FieldDoesNotExist |
11 from django.db.models.fields import AutoField, FieldDoesNotExist |
12 from django.db.models.fields.proxy import OrderWrt |
12 from django.db.models.fields.proxy import OrderWrt |
13 from django.db.models.loading import get_models, app_cache_ready |
13 from django.db.models.loading import get_models, app_cache_ready |
14 from django.db.models import Manager |
|
15 from django.utils.translation import activate, deactivate_all, get_language, string_concat |
14 from django.utils.translation import activate, deactivate_all, get_language, string_concat |
16 from django.utils.encoding import force_unicode, smart_str |
15 from django.utils.encoding import force_unicode, smart_str |
17 from django.utils.datastructures import SortedDict |
16 from django.utils.datastructures import SortedDict |
18 |
17 |
19 # Calculate the verbose_name by converting from InitialCaps to "lowercase with spaces". |
18 # Calculate the verbose_name by converting from InitialCaps to "lowercase with spaces". |
23 'unique_together', 'permissions', 'get_latest_by', |
22 'unique_together', 'permissions', 'get_latest_by', |
24 'order_with_respect_to', 'app_label', 'db_tablespace', |
23 'order_with_respect_to', 'app_label', 'db_tablespace', |
25 'abstract') |
24 'abstract') |
26 |
25 |
27 class Options(object): |
26 class Options(object): |
28 def __init__(self, meta): |
27 def __init__(self, meta, app_label=None): |
29 self.local_fields, self.local_many_to_many = [], [] |
28 self.local_fields, self.local_many_to_many = [], [] |
|
29 self.virtual_fields = [] |
30 self.module_name, self.verbose_name = None, None |
30 self.module_name, self.verbose_name = None, None |
31 self.verbose_name_plural = None |
31 self.verbose_name_plural = None |
32 self.db_table = '' |
32 self.db_table = '' |
33 self.ordering = [] |
33 self.ordering = [] |
34 self.unique_together = [] |
34 self.unique_together = [] |
35 self.permissions = [] |
35 self.permissions = [] |
36 self.object_name, self.app_label = None, None |
36 self.object_name, self.app_label = None, app_label |
37 self.get_latest_by = None |
37 self.get_latest_by = None |
38 self.order_with_respect_to = None |
38 self.order_with_respect_to = None |
39 self.db_tablespace = settings.DEFAULT_TABLESPACE |
39 self.db_tablespace = settings.DEFAULT_TABLESPACE |
40 self.admin = None |
40 self.admin = None |
41 self.meta = meta |
41 self.meta = meta |
42 self.pk = None |
42 self.pk = None |
43 self.has_auto_field, self.auto_field = False, None |
43 self.has_auto_field, self.auto_field = False, None |
44 self.one_to_one_field = None |
44 self.one_to_one_field = None |
45 self.abstract = False |
45 self.abstract = False |
46 self.parents = SortedDict() |
46 self.parents = SortedDict() |
|
47 self.duplicate_targets = {} |
|
48 # Managers that have been inherited from abstract base classes. These |
|
49 # are passed onto any children. |
|
50 self.abstract_managers = [] |
47 |
51 |
48 def contribute_to_class(self, cls, name): |
52 def contribute_to_class(self, cls, name): |
|
53 from django.db import connection |
|
54 from django.db.backends.util import truncate_name |
|
55 |
49 cls._meta = self |
56 cls._meta = self |
50 self.installed = re.sub('\.models$', '', cls.__module__) in settings.INSTALLED_APPS |
57 self.installed = re.sub('\.models$', '', cls.__module__) in settings.INSTALLED_APPS |
51 # First, construct the default values for these options. |
58 # First, construct the default values for these options. |
52 self.object_name = cls.__name__ |
59 self.object_name = cls.__name__ |
53 self.module_name = self.object_name.lower() |
60 self.module_name = self.object_name.lower() |
54 self.verbose_name = get_verbose_name(self.object_name) |
61 self.verbose_name = get_verbose_name(self.object_name) |
55 |
62 |
56 # Next, apply any overridden values from 'class Meta'. |
63 # Next, apply any overridden values from 'class Meta'. |
57 if self.meta: |
64 if self.meta: |
58 meta_attrs = self.meta.__dict__.copy() |
65 meta_attrs = self.meta.__dict__.copy() |
59 del meta_attrs['__module__'] |
66 for name in self.meta.__dict__: |
60 del meta_attrs['__doc__'] |
67 # Ignore any private attributes that Django doesn't care about. |
|
68 # NOTE: We can't modify a dictionary's contents while looping |
|
69 # over it, so we loop over the *original* dictionary instead. |
|
70 if name.startswith('_'): |
|
71 del meta_attrs[name] |
61 for attr_name in DEFAULT_NAMES: |
72 for attr_name in DEFAULT_NAMES: |
62 if attr_name in meta_attrs: |
73 if attr_name in meta_attrs: |
63 setattr(self, attr_name, meta_attrs.pop(attr_name)) |
74 setattr(self, attr_name, meta_attrs.pop(attr_name)) |
64 elif hasattr(self.meta, attr_name): |
75 elif hasattr(self.meta, attr_name): |
65 setattr(self, attr_name, getattr(self.meta, attr_name)) |
76 setattr(self, attr_name, getattr(self.meta, attr_name)) |
81 raise TypeError, "'class Meta' got invalid attribute(s): %s" % ','.join(meta_attrs.keys()) |
92 raise TypeError, "'class Meta' got invalid attribute(s): %s" % ','.join(meta_attrs.keys()) |
82 else: |
93 else: |
83 self.verbose_name_plural = string_concat(self.verbose_name, 's') |
94 self.verbose_name_plural = string_concat(self.verbose_name, 's') |
84 del self.meta |
95 del self.meta |
85 |
96 |
|
97 # If the db_table wasn't provided, use the app_label + module_name. |
|
98 if not self.db_table: |
|
99 self.db_table = "%s_%s" % (self.app_label, self.module_name) |
|
100 self.db_table = truncate_name(self.db_table, connection.ops.max_name_length()) |
|
101 |
|
102 |
86 def _prepare(self, model): |
103 def _prepare(self, model): |
87 from django.db import connection |
|
88 from django.db.backends.util import truncate_name |
|
89 if self.order_with_respect_to: |
104 if self.order_with_respect_to: |
90 self.order_with_respect_to = self.get_field(self.order_with_respect_to) |
105 self.order_with_respect_to = self.get_field(self.order_with_respect_to) |
91 self.ordering = ('_order',) |
106 self.ordering = ('_order',) |
92 else: |
107 else: |
93 self.order_with_respect_to = None |
108 self.order_with_respect_to = None |
96 if self.parents: |
111 if self.parents: |
97 # Promote the first parent link in lieu of adding yet another |
112 # Promote the first parent link in lieu of adding yet another |
98 # field. |
113 # field. |
99 field = self.parents.value_for_index(0) |
114 field = self.parents.value_for_index(0) |
100 field.primary_key = True |
115 field.primary_key = True |
101 self.pk = field |
116 self.setup_pk(field) |
102 else: |
117 else: |
103 auto = AutoField(verbose_name='ID', primary_key=True, |
118 auto = AutoField(verbose_name='ID', primary_key=True, |
104 auto_created=True) |
119 auto_created=True) |
105 model.add_to_class('id', auto) |
120 model.add_to_class('id', auto) |
106 |
121 |
107 # If the db_table wasn't provided, use the app_label + module_name. |
122 # Determine any sets of fields that are pointing to the same targets |
108 if not self.db_table: |
123 # (e.g. two ForeignKeys to the same remote model). The query |
109 self.db_table = "%s_%s" % (self.app_label, self.module_name) |
124 # construction code needs to know this. At the end of this, |
110 self.db_table = truncate_name(self.db_table, connection.ops.max_name_length()) |
125 # self.duplicate_targets will map each duplicate field column to the |
|
126 # columns it duplicates. |
|
127 collections = {} |
|
128 for column, target in self.duplicate_targets.iteritems(): |
|
129 try: |
|
130 collections[target].add(column) |
|
131 except KeyError: |
|
132 collections[target] = set([column]) |
|
133 self.duplicate_targets = {} |
|
134 for elt in collections.itervalues(): |
|
135 if len(elt) == 1: |
|
136 continue |
|
137 for column in elt: |
|
138 self.duplicate_targets[column] = elt.difference(set([column])) |
111 |
139 |
112 def add_field(self, field): |
140 def add_field(self, field): |
113 # Insert the given field in the order in which it was created, using |
141 # Insert the given field in the order in which it was created, using |
114 # the "creation_counter" attribute of the field. |
142 # the "creation_counter" attribute of the field. |
115 # Move many-to-many related fields from self.fields into |
143 # Move many-to-many related fields from self.fields into |
254 % (self.object_name, name)) |
285 % (self.object_name, name)) |
255 |
286 |
256 def get_all_field_names(self): |
287 def get_all_field_names(self): |
257 """ |
288 """ |
258 Returns a list of all field names that are possible for this model |
289 Returns a list of all field names that are possible for this model |
259 (including reverse relation names). |
290 (including reverse relation names). This is used for pretty printing |
|
291 debugging output (a list of choices), so any internal-only field names |
|
292 are not included. |
260 """ |
293 """ |
261 try: |
294 try: |
262 cache = self._name_map |
295 cache = self._name_map |
263 except AttributeError: |
296 except AttributeError: |
264 cache = self.init_name_map() |
297 cache = self.init_name_map() |
265 names = cache.keys() |
298 names = cache.keys() |
266 names.sort() |
299 names.sort() |
267 return names |
300 # Internal-only names end with "+" (symmetrical m2m related names being |
|
301 # the main example). Trim them. |
|
302 return [val for val in names if not val.endswith('+')] |
268 |
303 |
269 def init_name_map(self): |
304 def init_name_map(self): |
270 """ |
305 """ |
271 Initialises the field name -> field object mapping. |
306 Initialises the field name -> field object mapping. |
272 """ |
307 """ |
273 cache = dict([(f.name, (f, m, True, False)) for f, m in |
308 cache = {} |
274 self.get_fields_with_model()]) |
309 # We intentionally handle related m2m objects first so that symmetrical |
275 for f, model in self.get_m2m_with_model(): |
310 # m2m accessor names can be overridden, if necessary. |
276 cache[f.name] = (f, model, True, True) |
|
277 for f, model in self.get_all_related_m2m_objects_with_model(): |
311 for f, model in self.get_all_related_m2m_objects_with_model(): |
278 cache[f.field.related_query_name()] = (f, model, False, True) |
312 cache[f.field.related_query_name()] = (f, model, False, True) |
279 for f, model in self.get_all_related_objects_with_model(): |
313 for f, model in self.get_all_related_objects_with_model(): |
280 cache[f.field.related_query_name()] = (f, model, False, False) |
314 cache[f.field.related_query_name()] = (f, model, False, False) |
|
315 for f, model in self.get_m2m_with_model(): |
|
316 cache[f.name] = (f, model, True, True) |
|
317 for f, model in self.get_fields_with_model(): |
|
318 cache[f.name] = (f, model, True, False) |
281 if self.order_with_respect_to: |
319 if self.order_with_respect_to: |
282 cache['_order'] = OrderWrt(), None, True, False |
320 cache['_order'] = OrderWrt(), None, True, False |
283 if app_cache_ready(): |
321 if app_cache_ready(): |
284 self._name_map = cache |
322 self._name_map = cache |
285 return cache |
323 return cache |
367 cache[RelatedObject(f.rel.to, klass, f)] = None |
405 cache[RelatedObject(f.rel.to, klass, f)] = None |
368 if app_cache_ready(): |
406 if app_cache_ready(): |
369 self._related_many_to_many_cache = cache |
407 self._related_many_to_many_cache = cache |
370 return cache |
408 return cache |
371 |
409 |
372 def get_followed_related_objects(self, follow=None): |
|
373 if follow == None: |
|
374 follow = self.get_follow() |
|
375 return [f for f in self.get_all_related_objects() if follow.get(f.name, None)] |
|
376 |
|
377 def get_data_holders(self, follow=None): |
|
378 if follow == None: |
|
379 follow = self.get_follow() |
|
380 return [f for f in self.fields + self.many_to_many + self.get_all_related_objects() if follow.get(f.name, None)] |
|
381 |
|
382 def get_follow(self, override=None): |
|
383 follow = {} |
|
384 for f in self.fields + self.many_to_many + self.get_all_related_objects(): |
|
385 if override and f.name in override: |
|
386 child_override = override[f.name] |
|
387 else: |
|
388 child_override = None |
|
389 fol = f.get_follow(child_override) |
|
390 if fol != None: |
|
391 follow[f.name] = fol |
|
392 return follow |
|
393 |
|
394 def get_base_chain(self, model): |
410 def get_base_chain(self, model): |
395 """ |
411 """ |
396 Returns a list of parent classes leading to 'model' (order from closet |
412 Returns a list of parent classes leading to 'model' (order from closet |
397 to most distant ancestor). This has to handle the case were 'model' is |
413 to most distant ancestor). This has to handle the case were 'model' is |
398 a granparent or even more distant relation. |
414 a granparent or even more distant relation. |
430 # if opts.order_with_respect_to and opts.order_with_respect_to.rel \ |
446 # if opts.order_with_respect_to and opts.order_with_respect_to.rel \ |
431 # and self == opts.order_with_respect_to.rel.to._meta: |
447 # and self == opts.order_with_respect_to.rel.to._meta: |
432 # objects.append(opts) |
448 # objects.append(opts) |
433 self._ordered_objects = objects |
449 self._ordered_objects = objects |
434 return self._ordered_objects |
450 return self._ordered_objects |
435 |
|
436 def has_field_type(self, field_type, follow=None): |
|
437 """ |
|
438 Returns True if this object's admin form has at least one of the given |
|
439 field_type (e.g. FileField). |
|
440 """ |
|
441 # TODO: follow |
|
442 if not hasattr(self, '_field_types'): |
|
443 self._field_types = {} |
|
444 if field_type not in self._field_types: |
|
445 try: |
|
446 # First check self.fields. |
|
447 for f in self.fields: |
|
448 if isinstance(f, field_type): |
|
449 raise StopIteration |
|
450 # Failing that, check related fields. |
|
451 for related in self.get_followed_related_objects(follow): |
|
452 for f in related.opts.fields: |
|
453 if isinstance(f, field_type): |
|
454 raise StopIteration |
|
455 except StopIteration: |
|
456 self._field_types[field_type] = True |
|
457 else: |
|
458 self._field_types[field_type] = False |
|
459 return self._field_types[field_type] |
|
460 |
|
461 class AdminOptions(object): |
|
462 def __init__(self, fields=None, js=None, list_display=None, list_display_links=None, list_filter=None, |
|
463 date_hierarchy=None, save_as=False, ordering=None, search_fields=None, |
|
464 save_on_top=False, list_select_related=False, manager=None, list_per_page=100): |
|
465 self.fields = fields |
|
466 self.js = js or [] |
|
467 self.list_display = list_display or ['__str__'] |
|
468 self.list_display_links = list_display_links or [] |
|
469 self.list_filter = list_filter or [] |
|
470 self.date_hierarchy = date_hierarchy |
|
471 self.save_as, self.ordering = save_as, ordering |
|
472 self.search_fields = search_fields or [] |
|
473 self.save_on_top = save_on_top |
|
474 self.list_select_related = list_select_related |
|
475 self.list_per_page = list_per_page |
|
476 self.manager = manager or Manager() |
|
477 |
|
478 def get_field_sets(self, opts): |
|
479 "Returns a list of AdminFieldSet objects for this AdminOptions object." |
|
480 if self.fields is None: |
|
481 field_struct = ((None, {'fields': [f.name for f in opts.fields + opts.many_to_many if f.editable and not isinstance(f, AutoField)]}),) |
|
482 else: |
|
483 field_struct = self.fields |
|
484 new_fieldset_list = [] |
|
485 for fieldset in field_struct: |
|
486 fs_options = fieldset[1] |
|
487 classes = fs_options.get('classes', ()) |
|
488 description = fs_options.get('description', '') |
|
489 new_fieldset_list.append(AdminFieldSet(fieldset[0], classes, |
|
490 opts.get_field, fs_options['fields'], description)) |
|
491 return new_fieldset_list |
|
492 |
|
493 def contribute_to_class(self, cls, name): |
|
494 cls._meta.admin = self |
|
495 # Make sure the admin manager has access to the model |
|
496 self.manager.model = cls |
|
497 |
|
498 class AdminFieldSet(object): |
|
499 def __init__(self, name, classes, field_locator_func, line_specs, description): |
|
500 self.name = name |
|
501 self.field_lines = [AdminFieldLine(field_locator_func, line_spec) for line_spec in line_specs] |
|
502 self.classes = classes |
|
503 self.description = description |
|
504 |
|
505 def __repr__(self): |
|
506 return "FieldSet: (%s, %s)" % (self.name, self.field_lines) |
|
507 |
|
508 def bind(self, field_mapping, original, bound_field_set_class): |
|
509 return bound_field_set_class(self, field_mapping, original) |
|
510 |
|
511 def __iter__(self): |
|
512 for field_line in self.field_lines: |
|
513 yield field_line |
|
514 |
|
515 def __len__(self): |
|
516 return len(self.field_lines) |
|
517 |
|
518 class AdminFieldLine(object): |
|
519 def __init__(self, field_locator_func, linespec): |
|
520 if isinstance(linespec, basestring): |
|
521 self.fields = [field_locator_func(linespec)] |
|
522 else: |
|
523 self.fields = [field_locator_func(field_name) for field_name in linespec] |
|
524 |
|
525 def bind(self, field_mapping, original, bound_field_line_class): |
|
526 return bound_field_line_class(self, field_mapping, original) |
|
527 |
|
528 def __iter__(self): |
|
529 for field in self.fields: |
|
530 yield field |
|
531 |
|
532 def __len__(self): |
|
533 return len(self.fields) |
|