Changeset 1434
- Timestamp:
- 11/25/05 15:20:09 (4 years ago)
- Files:
-
- django/trunk/django/bin/validate.py (modified) (1 diff)
- django/trunk/django/contrib/admin/filterspecs.py (copied) (copied from django/branches/new-admin/django/contrib/admin/filterspecs.py)
- django/trunk/django/contrib/admin/templates/admin/change_form.html (copied) (copied from django/branches/new-admin/django/contrib/admin/templates/admin/change_form.html)
- django/trunk/django/contrib/admin/templates/admin/change_list.html (copied) (copied from django/branches/new-admin/django/contrib/admin/templates/admin/change_list.html)
- django/trunk/django/contrib/admin/templates/admin/change_list_results.html (copied) (copied from django/branches/new-admin/django/contrib/admin/templates/admin/change_list_results.html)
- django/trunk/django/contrib/admin/templates/admin/date_hierarchy.html (copied) (copied from django/branches/new-admin/django/contrib/admin/templates/admin/date_hierarchy.html)
- django/trunk/django/contrib/admin/templates/admin_doc/bookmarklets.html (modified) (2 diffs)
- django/trunk/django/contrib/admin/templates/admin/edit_inline_stacked.html (copied) (copied from django/branches/new-admin/django/contrib/admin/templates/admin/edit_inline_stacked.html)
- django/trunk/django/contrib/admin/templates/admin/edit_inline_tabular.html (copied) (copied from django/branches/new-admin/django/contrib/admin/templates/admin/edit_inline_tabular.html)
- django/trunk/django/contrib/admin/templates/admin/field_line.html (copied) (copied from django/branches/new-admin/django/contrib/admin/templates/admin/field_line.html)
- django/trunk/django/contrib/admin/templates/admin/filter.html (copied) (copied from django/branches/new-admin/django/contrib/admin/templates/admin/filter.html)
- django/trunk/django/contrib/admin/templates/admin/filters.html (copied) (copied from django/branches/new-admin/django/contrib/admin/templates/admin/filters.html)
- django/trunk/django/contrib/admin/templates/admin/pagination.html (copied) (copied from django/branches/new-admin/django/contrib/admin/templates/admin/pagination.html)
- django/trunk/django/contrib/admin/templates/admin/search_form.html (copied) (copied from django/branches/new-admin/django/contrib/admin/templates/admin/search_form.html)
- django/trunk/django/contrib/admin/templates/admin/submit_line.html (copied) (copied from django/branches/new-admin/django/contrib/admin/templates/admin/submit_line.html)
- django/trunk/django/contrib/admin/templates/widget (copied) (copied from django/branches/new-admin/django/contrib/admin/templates/widget)
- django/trunk/django/contrib/admin/templates/widget/date_time.html (copied) (copied from django/branches/new-admin/django/contrib/admin/templates/widget/date_time.html)
- django/trunk/django/contrib/admin/templates/widget/default.html (copied) (copied from django/branches/new-admin/django/contrib/admin/templates/widget/default.html)
- django/trunk/django/contrib/admin/templates/widget/file.html (copied) (copied from django/branches/new-admin/django/contrib/admin/templates/widget/file.html)
- django/trunk/django/contrib/admin/templates/widget/foreign.html (copied) (copied from django/branches/new-admin/django/contrib/admin/templates/widget/foreign.html)
- django/trunk/django/contrib/admin/templates/widget/many_to_many.html (copied) (copied from django/branches/new-admin/django/contrib/admin/templates/widget/many_to_many.html)
- django/trunk/django/contrib/admin/templatetags/admin_list.py (copied) (copied from django/branches/new-admin/django/contrib/admin/templatetags/admin_list.py)
- django/trunk/django/contrib/admin/templatetags/admin_modify.py (copied) (copied from django/branches/new-admin/django/contrib/admin/templatetags/admin_modify.py)
- django/trunk/django/contrib/admin/views/main.py (modified) (14 diffs)
- django/trunk/django/core/formfields.py (modified) (11 diffs)
- django/trunk/django/core/management.py (modified) (1 diff)
- django/trunk/django/core/meta/fields.py (modified) (16 diffs)
- django/trunk/django/core/meta/__init__.py (modified) (18 diffs)
- django/trunk/django/models/__init__.py (modified) (1 diff)
- django/trunk/django/views/generic/create_update.py (modified) (7 diffs)
- django/trunk/setup.py (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
django/trunk/django/bin/validate.py
r1420 r1434 17 17 "ManyToManyField %s should have 'rel' set to a ManyToMany instance." % f.name 18 18 # Inline related objects. 19 for rel _opts, rel_field in opts.get_inline_related_objects():20 assert len([f for f in rel _opts.fields if f.core]) > 0, \19 for related in opts.get_followed_related_objects(): 20 assert len([f for f in related.opts.fields if f.core]) > 0, \ 21 21 "At least one field in %s should have core=True, because it's being edited inline by %s." % \ 22 (rel _opts.object_name, opts.object_name)22 (related.opts.object_name, opts.object_name) 23 23 # All related objects. 24 24 related_apps_seen = [] 25 for rel _opts, rel_field in opts.get_all_related_objects():26 if rel _opts in related_apps_seen:27 assert rel _field.rel.related_name is not None, \25 for related in opts.get_all_related_objects(): 26 if related.opts in related_apps_seen: 27 assert related.field.rel.related_name is not None, \ 28 28 "Relationship in field %s.%s needs to set 'related_name' because more than one" \ 29 29 " %s object is referenced in %s." % \ 30 (rel _opts.object_name, rel_field.name, opts.object_name, rel_opts.object_name)31 related_apps_seen.append(rel _opts)30 (related.opts.object_name, related.field.name, opts.object_name, rel_opts.object_name) 31 related_apps_seen.append(related.opts) 32 32 # Etc. 33 33 if opts.admin is not None: django/trunk/django/contrib/admin/templates/admin_doc/bookmarklets.html
r948 r1434 1 1 {% extends "admin/base_site" %} 2 2 3 {% block breadcrumbs %}<div class="breadcrumbs"><a href="../../"> Home</a> › <a href="../">Documentation</a> › Bookmarklets</div>{% endblock %}3 {% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">{% trans "Home" %}</a> › <a href="../">{% trans "Documentation" %}</a> › {% trans "Bookmarklets" %}</div>{% endblock %} 4 4 5 {% block title %} Documentation bookmarklets{% endblock %}5 {% block title %}{% trans "Documentation bookmarklets" %}{% endblock %} 6 6 7 7 {% block content %} 8 8 9 {% blocktrans %} 9 10 <p class="help">To install bookmarklets, drag the link to your bookmarks 10 11 toolbar, or right-click the link and add it to your bookmarks. Now you can … … 13 14 as "internal" (talk to your system administrator if you aren't sure if 14 15 your computer is "internal").</p> 16 {% endblocktrans %} 15 17 16 18 <div id="content-main"> 17 <h3><a href="javascript:(function(){if(typeof ActiveXObject!='undefined'){x=new ActiveXObject('Microsoft.XMLHTTP')}else if(typeof XMLHttpRequest!='undefined'){x=new XMLHttpRequest()}else{return;}x.open('HEAD',location.href,false);x.send(null);try{view=x.getResponseHeader('x-view');}catch(e){alert('No view found for this page');return;}if(view=="undefined"){alert("No view found for this page");}document.location='{{ admin_url }}/doc/views/'+view+'/';})()"> Documentation for this page</a></h3>18 <p> Jumps you from any page to the documentation for the view that generates that page.</p>19 <h3><a href="javascript:(function(){if(typeof ActiveXObject!='undefined'){x=new ActiveXObject('Microsoft.XMLHTTP')}else if(typeof XMLHttpRequest!='undefined'){x=new XMLHttpRequest()}else{return;}x.open('HEAD',location.href,false);x.send(null);try{view=x.getResponseHeader('x-view');}catch(e){alert('No view found for this page');return;}if(view=="undefined"){alert("No view found for this page");}document.location='{{ admin_url }}/doc/views/'+view+'/';})()">{% trans "Documentation for this page" %}</a></h3> 20 <p>{% trans "Jumps you from any page to the documentation for the view that generates that page." %}</p> 19 21 20 <h3><a href="javascript:(function(){if(typeof ActiveXObject!='undefined'){x=new ActiveXObject('Microsoft.XMLHTTP')}else if(typeof XMLHttpRequest!='undefined'){x=new XMLHttpRequest()}else{return;}x.open('GET',location.href,false);x.send(null);try{type=x.getResponseHeader('x-object-type');id=x.getResponseHeader('x-object-id');}catch(e){type='(none)';id='(none)';}d=document;b=d.body;e=d.createElement('div');e.id='xxxhhh';s=e.style;s.position='absolute';s.left='10px';s.top='10px';s.font='10px monospace';s.border='1px black solid';s.padding='4px';s.backgroundColor='#eee';e.appendChild(d.createTextNode('Type: '+type));e.appendChild(d.createElement('br'));e.appendChild(d.createTextNode('ID: '+id));e.appendChild(d.createElement('br'));l=d.createElement('a');l.href='#';l.onclick=function(){b.removeChild(e);};l.appendChild(d.createTextNode('[close]'));l.style.textDecoration='none';e.appendChild(l);b.appendChild(e);})();"> Show object ID</a></h3>21 <p> Shows the content-type and unique ID for pages that represent a single object.</p>22 <h3><a href="javascript:(function(){if(typeof ActiveXObject!='undefined'){x=new ActiveXObject('Microsoft.XMLHTTP')}else if(typeof XMLHttpRequest!='undefined'){x=new XMLHttpRequest()}else{return;}x.open('GET',location.href,false);x.send(null);try{type=x.getResponseHeader('x-object-type');id=x.getResponseHeader('x-object-id');}catch(e){type='(none)';id='(none)';}d=document;b=d.body;e=d.createElement('div');e.id='xxxhhh';s=e.style;s.position='absolute';s.left='10px';s.top='10px';s.font='10px monospace';s.border='1px black solid';s.padding='4px';s.backgroundColor='#eee';e.appendChild(d.createTextNode('Type: '+type));e.appendChild(d.createElement('br'));e.appendChild(d.createTextNode('ID: '+id));e.appendChild(d.createElement('br'));l=d.createElement('a');l.href='#';l.onclick=function(){b.removeChild(e);};l.appendChild(d.createTextNode('[close]'));l.style.textDecoration='none';e.appendChild(l);b.appendChild(e);})();">{% trans "Show object ID" %}</a></h3> 23 <p>{% trans "Shows the content-type and unique ID for pages that represent a single object." %}</p> 22 24 23 <h3><a href="javascript:(function(){if(typeof ActiveXObject!='undefined'){var x=new ActiveXObject('Microsoft.XMLHTTP')}else if(typeof XMLHttpRequest!='undefined'){var x=new XMLHttpRequest()}else{return;}x.open('GET',location.href,false);x.send(null);try{var type=x.getResponseHeader('x-object-type');var id=x.getResponseHeader('x-object-id');}catch(e){return;}document.location='{{ admun_url }}/'+type.split('.').join('/')+'/'+id+'/';})()"> Edit this object (current window)</a></h3>24 <p> Jumps to the admin page for pages that represent a single object.</p>25 <h3><a href="javascript:(function(){if(typeof ActiveXObject!='undefined'){var x=new ActiveXObject('Microsoft.XMLHTTP')}else if(typeof XMLHttpRequest!='undefined'){var x=new XMLHttpRequest()}else{return;}x.open('GET',location.href,false);x.send(null);try{var type=x.getResponseHeader('x-object-type');var id=x.getResponseHeader('x-object-id');}catch(e){return;}document.location='{{ admun_url }}/'+type.split('.').join('/')+'/'+id+'/';})()">{% trans "Edit this object (current window)" %}</a></h3> 26 <p>{% trans "Jumps to the admin page for pages that represent a single object." %}</p> 25 27 26 <h3><a href="javascript:(function(){if(typeof ActiveXObject!='undefined'){var x=new ActiveXObject('Microsoft.XMLHTTP')}else if(typeof XMLHttpRequest!='undefined'){var x=new XMLHttpRequest()}else{return;}x.open('GET',location.href,false);x.send(null);try{var type=x.getResponseHeader('x-object-type');var id=x.getResponseHeader('x-object-id');}catch(e){return;}window.open('{{ admun_url }}/'+type.split('.').join('/')+'/'+id+'/');})()"> Edit this object (new window)</a></h3>27 <p> As above, but opens the admin page in a new window.</p>28 <h3><a href="javascript:(function(){if(typeof ActiveXObject!='undefined'){var x=new ActiveXObject('Microsoft.XMLHTTP')}else if(typeof XMLHttpRequest!='undefined'){var x=new XMLHttpRequest()}else{return;}x.open('GET',location.href,false);x.send(null);try{var type=x.getResponseHeader('x-object-type');var id=x.getResponseHeader('x-object-id');}catch(e){return;}window.open('{{ admun_url }}/'+type.split('.').join('/')+'/'+id+'/');})()">{% trans "Edit this object (new window)" %}</a></h3> 29 <p>{% trans "As above, but opens the admin page in a new window." %}</p> 28 30 </div> 29 31 django/trunk/django/contrib/admin/views/main.py
r1322 r1434 1 # Generic admin views, with admin templates created dynamically at runtime. 2 1 # Generic admin views. 3 2 from django.contrib.admin.views.decorators import staff_member_required 4 from django.core import formfields, meta 3 from django.contrib.admin.filterspecs import FilterSpec 4 from django.core import formfields, meta, template 5 5 from django.core.template import loader 6 from django.core.meta.fields import BoundField, BoundFieldLine, BoundFieldSet 6 7 from django.core.exceptions import Http404, ObjectDoesNotExist, PermissionDenied 7 8 from django.core.extensions import DjangoContext as Context 8 9 from django.core.extensions import get_object_or_404, render_to_response 10 from django.core.paginator import ObjectPaginator, InvalidPage 11 from django.conf.settings import ADMIN_MEDIA_PREFIX 9 12 from django.models.admin import log 10 13 from django.utils.html import strip_tags 11 14 from django.utils.httpwrappers import HttpResponse, HttpResponseRedirect 12 15 from django.utils.text import capfirst, get_text_list 13 from django.conf.settings import ADMIN_MEDIA_PREFIX 14 from django.utils.translation import get_date_formats 16 from django.utils import dateformat 17 from django.utils.dates import MONTHS 18 from django.utils.html import escape 15 19 import operator 20 21 # The system will display a "Show all" link only if the total result count 22 # is less than or equal to this setting. 23 MAX_SHOW_ALL_ALLOWED = 200 24 25 DEFAULT_RESULTS_PER_PAGE = 100 26 27 ALL_VAR = 'all' 28 ORDER_VAR = 'o' 29 ORDER_TYPE_VAR = 'ot' 30 PAGE_VAR = 'p' 31 SEARCH_VAR = 'q' 32 IS_POPUP_VAR = 'pop' 16 33 17 34 # Text to display within changelist table cells if the value is blank. … … 29 46 return mod, opts 30 47 31 def get_query_string(original_params, new_params={}, remove=[]): 32 """ 33 >>> get_query_string({'first_name': 'adrian', 'last_name': 'smith'}) 34 '?first_name=adrian&last_name=smith' 35 >>> get_query_string({'first_name': 'adrian', 'last_name': 'smith'}, {'first_name': 'john'}) 36 '?first_name=john&last_name=smith' 37 >>> get_query_string({'test': 'yes'}, {'blah': 'no'}, ['te']) 38 '?blah=no' 39 """ 40 p = original_params.copy() 41 for r in remove: 42 for k in p.keys(): 43 if k.startswith(r): 48 def index(request): 49 return render_to_response('admin/index', {'title': _('Site administration')}, context_instance=Context(request)) 50 index = staff_member_required(index) 51 52 class IncorrectLookupParameters(Exception): 53 pass 54 55 class ChangeList(object): 56 def __init__(self, request, app_label, module_name): 57 self.get_modules_and_options(app_label, module_name, request) 58 self.get_search_parameters(request) 59 self.get_ordering() 60 self.query = request.GET.get(SEARCH_VAR,'') 61 self.get_lookup_params() 62 self.get_results(request) 63 self.title = (self.is_popup 64 and _('Select %s') % self.opts.verbose_name 65 or _('Select %s to change') % self.opts.verbose_name) 66 self.get_filters(request) 67 self.pk_attname = self.lookup_opts.pk.attname 68 69 def get_filters(self, request): 70 self.filter_specs = [] 71 if self.lookup_opts.admin.list_filter and not self.opts.one_to_one_field: 72 filter_fields = [self.lookup_opts.get_field(field_name) \ 73 for field_name in self.lookup_opts.admin.list_filter] 74 for f in filter_fields: 75 spec = FilterSpec.create(f, request, self.params) 76 if spec.has_output(): 77 self.filter_specs.append(spec) 78 self.has_filters = bool(self.filter_specs) 79 80 def get_query_string(self, new_params={}, remove=[]): 81 p = self.params.copy() 82 for r in remove: 83 for k in p.keys(): 84 if k.startswith(r): 85 del p[k] 86 for k, v in new_params.items(): 87 if p.has_key(k) and v is None: 44 88 del p[k] 45 for k, v in new_params.items(): 46 if p.has_key(k) and v is None: 47 del p[k] 48 elif v is not None: 49 p[k] = v 50 return '?' + '&'.join(['%s=%s' % (k, v) for k, v in p.items()]).replace(' ', '%20') 51 52 def index(request): 53 return render_to_response('admin/index', {'title': 'Site administration'}, context_instance=Context(request)) 54 index = staff_member_required(index) 55 56 def change_list(request, app_label, module_name): 57 from django.core import paginator 58 from django.utils import dateformat 59 from django.utils.dates import MONTHS 60 from django.utils.html import escape 61 import datetime 62 63 # The system will display a "Show all" link only if the total result count 64 # is less than or equal to this setting. 65 MAX_SHOW_ALL_ALLOWED = 200 66 67 DEFAULT_RESULTS_PER_PAGE = 100 68 69 ALL_VAR = 'all' 70 ORDER_VAR = 'o' 71 ORDER_TYPE_VAR = 'ot' 72 PAGE_VAR = 'p' 73 SEARCH_VAR = 'q' 74 IS_POPUP_VAR = 'pop' 75 76 mod, opts = _get_mod_opts(app_label, module_name) 77 if not request.user.has_perm(app_label + '.' + opts.get_change_permission()): 78 raise PermissionDenied 79 80 lookup_mod, lookup_opts = mod, opts 81 82 # Get search parameters from the query string. 83 try: 84 page_num = int(request.GET.get(PAGE_VAR, 0)) 85 except ValueError: 86 page_num = 0 87 show_all = request.GET.has_key(ALL_VAR) 88 is_popup = request.GET.has_key(IS_POPUP_VAR) 89 params = dict(request.GET.copy()) 90 if params.has_key(PAGE_VAR): 91 del params[PAGE_VAR] 92 # For ordering, first check the "ordering" parameter in the admin options, 93 # then check the object's default ordering. If neither of those exist, 94 # order descending by ID by default. Finally, look for manually-specified 95 # ordering from the query string. 96 ordering = lookup_opts.admin.ordering or lookup_opts.ordering or ['-' + lookup_opts.pk.name] 97 98 # Normalize it to new-style ordering. 99 ordering = meta.handle_legacy_orderlist(ordering) 100 101 if ordering[0].startswith('-'): 102 order_field, order_type = ordering[0][1:], 'desc' 103 else: 104 order_field, order_type = ordering[0], 'asc' 105 if params.has_key(ORDER_VAR): 89 elif v is not None: 90 p[k] = v 91 return '?' + '&'.join(['%s=%s' % (k, v) for k, v in p.items()]).replace(' ', '%20') 92 93 def get_modules_and_options(self, app_label, module_name, request): 94 self.mod, self.opts = _get_mod_opts(app_label, module_name) 95 if not request.user.has_perm(app_label + '.' + self.opts.get_change_permission()): 96 raise PermissionDenied 97 98 self.lookup_mod, self.lookup_opts = self.mod, self.opts 99 100 def get_search_parameters(self, request): 101 # Get search parameters from the query string. 106 102 try: 103 self.req_get = request.GET 104 self.page_num = int(request.GET.get(PAGE_VAR, 0)) 105 except ValueError: 106 self.page_num = 0 107 self.show_all = request.GET.has_key(ALL_VAR) 108 self.is_popup = request.GET.has_key(IS_POPUP_VAR) 109 self.params = dict(request.GET.copy()) 110 if self.params.has_key(PAGE_VAR): 111 del self.params[PAGE_VAR] 112 113 def get_results(self, request): 114 lookup_mod, lookup_params, show_all, page_num = \ 115 self.lookup_mod, self.lookup_params, self.show_all, self.page_num 116 # Get the results. 117 try: 118 paginator = ObjectPaginator(lookup_mod, lookup_params, DEFAULT_RESULTS_PER_PAGE) 119 # Naked except! Because we don't have any other way of validating "params". 120 # They might be invalid if the keyword arguments are incorrect, or if the 121 # values are not in the correct type (which would result in a database 122 # error). 123 except: 124 raise IncorrectLookupParameters() 125 126 # Get the total number of objects, with no filters applied. 127 real_lookup_params = lookup_params.copy() 128 del real_lookup_params['order_by'] 129 if real_lookup_params: 130 full_result_count = lookup_mod.get_count() 131 else: 132 full_result_count = paginator.hits 133 del real_lookup_params 134 result_count = paginator.hits 135 can_show_all = result_count <= MAX_SHOW_ALL_ALLOWED 136 multi_page = result_count > DEFAULT_RESULTS_PER_PAGE 137 138 # Get the list of objects to display on this page. 139 if (show_all and can_show_all) or not multi_page: 140 result_list = lookup_mod.get_list(**lookup_params) 141 else: 107 142 try: 108 f = lookup_opts.get_field(lookup_opts.admin.list_display[int(params[ORDER_VAR])]) 109 except meta.FieldDoesNotExist: 110 pass 111 else: 112 if not isinstance(f.rel, meta.ManyToOne) or not f.null: 113 order_field = f.name 114 except (IndexError, ValueError): 115 pass # Invalid ordering specified. Just use the default. 116 if params.has_key(ORDER_TYPE_VAR) and params[ORDER_TYPE_VAR] in ('asc', 'desc'): 117 order_type = params[ORDER_TYPE_VAR] 118 query = request.GET.get(SEARCH_VAR, '') 119 120 # Prepare the lookup parameters for the API lookup. 121 lookup_params = params.copy() 122 for i in (ALL_VAR, ORDER_VAR, ORDER_TYPE_VAR, SEARCH_VAR, IS_POPUP_VAR): 123 if lookup_params.has_key(i): 124 del lookup_params[i] 125 # If the order-by field is a field with a relationship, order by the value 126 # in the related table. 127 lookup_order_field = order_field 128 if isinstance(lookup_opts.get_field(order_field).rel, meta.ManyToOne): 129 f = lookup_opts.get_field(order_field) 130 rel_ordering = f.rel.to.ordering and f.rel.to.ordering[0] or f.rel.to.pk.column 131 lookup_order_field = '%s.%s' % (f.rel.to.db_table, rel_ordering) 132 if lookup_opts.admin.list_select_related: 133 lookup_params['select_related'] = True 134 else: 135 # Use select_related if one of the list_display options is a field with 136 # a relationship. 137 for field_name in lookup_opts.admin.list_display: 143 result_list = paginator.get_page(page_num) 144 except InvalidPage: 145 result_list = [] 146 (self.result_count, self.full_result_count, self.result_list, 147 self.can_show_all, self.multi_page, self.paginator) = (result_count, 148 full_result_count, result_list, can_show_all, multi_page, paginator ) 149 150 def url_for_result(self, result): 151 return "%s/" % getattr(result, self.pk_attname) 152 153 def get_ordering(self): 154 lookup_opts, params = self.lookup_opts, self.params 155 # For ordering, first check the "ordering" parameter in the admin options, 156 # then check the object's default ordering. If neither of those exist, 157 # order descending by ID by default. Finally, look for manually-specified 158 # ordering from the query string. 159 ordering = lookup_opts.admin.ordering or lookup_opts.ordering or ['-' + lookup_opts.pk.name] 160 161 # Normalize it to new-style ordering. 162 ordering = meta.handle_legacy_orderlist(ordering) 163 164 if ordering[0].startswith('-'): 165 order_field, order_type = ordering[0][1:], 'desc' 166 else: 167 order_field, order_type = ordering[0], 'asc' 168 if params.has_key(ORDER_VAR): 138 169 try: 139 f = lookup_opts.get_field(field_name) 140 except meta.FieldDoesNotExist: 141 pass 142 else: 143 if isinstance(f.rel, meta.ManyToOne): 144 lookup_params['select_related'] = True 145 break 146 lookup_params['order_by'] = ((order_type == 'desc' and '-' or '') + lookup_order_field,) 147 if lookup_opts.admin.search_fields and query: 148 or_queries = [] 149 for bit in query.split(): 150 or_query = [] 151 for field_name in lookup_opts.admin.search_fields: 152 or_query.append(('%s__icontains' % field_name, bit)) 153 or_queries.append(or_query) 154 lookup_params['_or'] = or_queries 155 156 if opts.one_to_one_field: 157 lookup_params.update(opts.one_to_one_field.rel.limit_choices_to) 158 159 # Get the results. 160 try: 161 p = paginator.ObjectPaginator(lookup_mod, lookup_params, DEFAULT_RESULTS_PER_PAGE) 162 # Naked except! Because we don't have any other way of validating "params". 163 # They might be invalid if the keyword arguments are incorrect, or if the 164 # values are not in the correct type (which would result in a database 165 # error). 166 except: 167 return HttpResponseRedirect(request.path) 168 169 # Get the total number of objects, with no filters applied. 170 real_lookup_params = lookup_params.copy() 171 del real_lookup_params['order_by'] 172 if real_lookup_params: 173 full_result_count = lookup_mod.get_count() 174 else: 175 full_result_count = p.hits 176 del real_lookup_params 177 result_count = p.hits 178 can_show_all = result_count <= MAX_SHOW_ALL_ALLOWED 179 multi_page = result_count > DEFAULT_RESULTS_PER_PAGE 180 181 # Get the list of objects to display on this page. 182 if (show_all and can_show_all) or not multi_page: 183 result_list = lookup_mod.get_list(**lookup_params) 184 else: 170 try: 171 f = lookup_opts.get_field(lookup_opts.admin.list_display[int(params[ORDER_VAR])]) 172 except meta.FieldDoesNotExist: 173 pass 174 else: 175 if not isinstance(f.rel, meta.ManyToOne) or not f.null: 176 order_field = f.name 177 except (IndexError, ValueError): 178 pass # Invalid ordering specified. Just use the default. 179 if params.has_key(ORDER_TYPE_VAR) and params[ORDER_TYPE_VAR] in ('asc', 'desc'): 180 order_type = params[ORDER_TYPE_VAR] 181 self.order_field, self.order_type = order_field, order_type 182 183 def get_lookup_params(self): 184 # Prepare the lookup parameters for the API lookup. 185 (params, order_field, lookup_opts, order_type, opts, query) = \ 186 (self.params, self.order_field, self.lookup_opts, self.order_type, self.opts, self.query) 187 188 lookup_params = params.copy() 189 for i in (ALL_VAR, ORDER_VAR, ORDER_TYPE_VAR, SEARCH_VAR, IS_POPUP_VAR): 190 if lookup_params.has_key(i): 191 del lookup_params[i] 192 # If the order-by field is a field with a relationship, order by the value 193 # in the related table. 194 lookup_order_field = order_field 185 195 try: 186 result_list = p.get_page(page_num) 187 except paginator.InvalidPage: 188 result_list = [] 189 190 # Calculate filters first, because a CSS class high in the document depends 191 # on whether they are available. 192 filter_template = [] 193 if lookup_opts.admin.list_filter and not opts.one_to_one_field: 194 filter_fields = [lookup_opts.get_field(field_name) for field_name in lookup_opts.admin.list_filter] 195 for f in filter_fields: 196 # Many-to-many or many-to-one filter. 197 if f.rel: 198 if isinstance(f, meta.ManyToManyField): 199 lookup_title = f.rel.to.verbose_name 200 else: 201 lookup_title = f.verbose_name 202 lookup_kwarg = '%s__%s__exact' % (f.name, f.rel.to.pk.name) 203 lookup_val = request.GET.get(lookup_kwarg, None) 204 lookup_choices = f.rel.to.get_model_module().get_list() 205 if len(lookup_choices) > 1: 206 filter_template.append('<h3>By %s:</h3>\n<ul>\n' % lookup_title) 207 filter_template.append('<li%s><a href="%s">All</a></li>\n' % \ 208 ((lookup_val is None and ' class="selected"' or ''), 209 get_query_string(params, {}, [lookup_kwarg]))) 210 for val in lookup_choices: 211 pk_val = getattr(val, f.rel.to.pk.attname) 212 filter_template.append('<li%s><a href="%s">%r</a></li>\n' % \ 213 ((lookup_val == str(pk_val) and ' class="selected"' or ''), 214 get_query_string(params, {lookup_kwarg: pk_val}), val)) 215 filter_template.append('</ul>\n\n') 216 # Field with choices. 217 elif f.choices: 218 lookup_kwarg = '%s__exact' % f.name 219 lookup_val = request.GET.get(lookup_kwarg, None) 220 filter_template.append('<h3>By %s:</h3><ul>\n' % f.verbose_name) 221 filter_template.append('<li%s><a href="%s">All</a></li>\n' % \ 222 ((lookup_val is None and ' class="selected"' or ''), 223 get_query_string(params, {}, [lookup_kwarg]))) 224 for k, v in f.choices: 225 filter_template.append('<li%s><a href="%s">%s</a></li>' % \ 226 ((str(k) == lookup_val) and ' class="selected"' or '', 227 get_query_string(params, {lookup_kwarg: k}), v)) 228 filter_template.append('</ul>\n\n') 229 # Date filter. 230 elif isinstance(f, meta.DateField): 231 today = datetime.date.today() 232 one_week_ago = today - datetime.timedelta(days=7) 233 field_generic = '%s__' % f.name 234 filter_template.append('<h3>By %s:</h3><ul>\n' % f.verbose_name) 235 date_params = dict([(k, v) for k, v in params.items() if k.startswith(field_generic)]) 236 today_str = isinstance(f, meta.DateTimeField) and today.strftime('%Y-%m-%d 23:59:59') or today.strftime('%Y-%m-%d') 237 for title, param_dict in ( 238 ('Any date', {}), 239 ('Today', {'%s__year' % f.name: str(today.year), '%s__month' % f.name: str(today.month), '%s__day' % f.name: str(today.day)}), 240 ('Past 7 days', {'%s__gte' % f.name: one_week_ago.strftime('%Y-%m-%d'), '%s__lte' % f.name: today_str}), 241 ('This month', {'%s__year' % f.name: str(today.year), '%s__month' % f.name: str(today.month)}), 242 ('This year', {'%s__year' % f.name: str(today.year)}) 243 ): 244 filter_template.append('<li%s><a href="%s">%s</a></li>\n' % \ 245 ((date_params == param_dict) and ' class="selected"' or '', 246 get_query_string(params, param_dict, field_generic), title)) 247 filter_template.append('</ul>\n\n') 248 elif isinstance(f, meta.BooleanField) or isinstance(f, meta.NullBooleanField): 249 lookup_kwarg = '%s__exact' % f.name 250 lookup_kwarg2 = '%s__isnull' % f.name 251 lookup_val = request.GET.get(lookup_kwarg, None) 252 lookup_val2 = request.GET.get(lookup_kwarg2, None) 253 filter_template.append('<h3>By %s:</h3><ul>\n' % f.verbose_name) 254 for k, v in (('All', None), ('Yes', '1'), ('No', '0')): 255 filter_template.append('<li%s><a href="%s">%s</a></li>\n' % \ 256 (((lookup_val == v and not lookup_val2) and ' class="selected"' or ''), 257 get_query_string(params, {lookup_kwarg: v}, [lookup_kwarg2]), k)) 258 if isinstance(f, meta.NullBooleanField): 259 filter_template.append('<li%s><a href="%s">%s</a></li>\n' % \ 260 (((lookup_val2 == 'True') and ' class="selected"' or ''), 261 get_query_string(params, {lookup_kwarg2: 'True'}, [lookup_kwarg]), 'Unknown')) 262 filter_template.append('</ul>\n\n') 263 else: 264 pass # Invalid argument to "list_filter" 265 266 raw_template = ['{% extends "admin/base_site" %}\n'] 267 raw_template.append('{% block bodyclass %}change-list{% endblock %}\n') 268 if not is_popup: 269 raw_template.append('{%% block breadcrumbs %%}<div class="breadcrumbs"><a href="../../">Home</a> › %s</div>{%% endblock %%}\n' % capfirst(opts.verbose_name_plural)) 270 raw_template.append('{% block coltype %}flex{% endblock %}') 271 raw_template.append('{% block content %}\n') 272 raw_template.append('<div id="content-main">\n') 273 if request.user.has_perm(app_label + '.' + lookup_opts.get_add_permission()): 274 raw_template.append('<ul class="object-tools"><li><a href="add/%s" class="addlink">Add %s</a></li></ul>\n' % ((is_popup and '?_popup=1' or ''), opts.verbose_name)) 275 raw_template.append('<div class="module%s" id="changelist">\n' % (filter_template and ' filtered' or '')) 276 277 # Search form. 278 if lookup_opts.admin.search_fields: 279 raw_template.append('<div id="toolbar">\n<form id="changelist-search" action="" method="get">\n') 280 raw_template.append('<label><img src="%simg/admin/icon_searchbox.png" /></label> ' % ADMIN_MEDIA_PREFIX) 281 raw_template.append('<input type="text" size="40" name="%s" value="%s" id="searchbar" /> ' % \ 282 (SEARCH_VAR, escape(query))) 283 raw_template.append('<input type="submit" value="Go" /> ') 284 if result_count != full_result_count and not opts.one_to_one_field: 285 raw_template.append('<span class="small quiet">%s result%s (<a href="?">%s total</a>)</span>' % \ 286 (result_count, (result_count != 1 and 's' or ''), full_result_count)) 287 for k, v in params.items(): 288 if k != SEARCH_VAR: 289 raw_template.append('<input type="hidden" name="%s" value="%s" />' % (escape(k), escape(v))) 290 raw_template.append('</form></div>\n') 291 raw_template.append('<script type="text/javascript">document.getElementById("searchbar").focus();</script>') 292 293 # Date-based navigation. 294 if lookup_opts.admin.date_hierarchy: 295 field_name = lookup_opts.admin.date_hierarchy 296 297 year_field = '%s__year' % field_name 298 month_field = '%s__month' % field_name 299 day_field = '%s__day' % field_name 300 field_generic = '%s__' % field_name 301 year_lookup = params.get(year_field) 302 month_lookup = params.get(month_field) 303 day_lookup = params.get(day_field) 304 305 raw_template.append('<div class="xfull">\n<ul class="toplinks">\n') 306 if year_lookup and month_lookup and day_lookup: 307 raw_template.append('<li class="date-back"><a href="%s">‹ %s %s </a></li>' % \ 308 (get_query_string(params, {year_field: year_lookup, month_field: month_lookup}, [field_generic]), MONTHS[int(month_lookup)], year_lookup)) 309 raw_template.append('<li>%s %s</li>' % (MONTHS[int(month_lookup)], day_lookup)) 310 elif year_lookup and month_lookup: 311 raw_template.append('<li class="date-back"><a href="%s">‹ %s</a></li>' % \ 312 (get_query_string(params, {year_field: year_lookup}, [field_generic]), year_lookup)) 313 date_lookup_params = lookup_params.copy() 314 date_lookup_params.update({year_field: year_lookup, month_field: month_lookup}) 315 for day in getattr(lookup_mod, 'get_%s_list' % field_name)('day', **date_lookup_params): 316 raw_template.append('<li><a href="%s">%s</a></li>' % \ 317 (get_query_string(params, {year_field: year_lookup, month_field: month_lookup, day_field: day.day}, [field_generic]), day.strftime('%B %d'))) 318 elif year_lookup: 319 raw_template.append('<li class="date-back"><a href="%s">‹ All dates</a></li>' % \ 320 get_query_string(params, {}, [year_field])) 321 date_lookup_params = lookup_params.copy() 322 date_lookup_params.update({year_field: year_lookup}) 323 for month in getattr(lookup_mod, 'get_%s_list' % field_name)('month', **date_lookup_params): 324 raw_template.append('<li><a href="%s">%s %s</a></li>' % \ 325 (get_query_string(params, {year_field: year_lookup, month_field: month.month}, [field_generic]), month.strftime('%B'), month.year)) 196 f = lookup_opts.get_field(order_field) 197 except meta.FieldDoesNotExist: 198 pass 326 199 else: 327 for year in getattr(lookup_mod, 'get_%s_list' % field_name)('year', **lookup_params): 328 raw_template.append('<li><a href="%s">%s</a></li>\n' % \ 329 (get_query_string(params, {year_field: year.year}, [field_generic]), year.year)) 330 raw_template.append('</ul><br class="clear" />\n</div>\n') 331 332 # Filters. 333 if filter_template: 334 raw_template.append('<div id="changelist-filter">\n<h2>Filter</h2>\n') 335 raw_template.extend(filter_template) 336 raw_template.append('</div>') 337 del filter_template 338 339 # Result table. 340 if result_list: 341 # Table headers. 342 raw_template.append('<table cellspacing="0">\n<thead>\n<tr>\n') 343 for i, field_name in enumerate(lookup_opts.admin.list_display): 344 try: 345 f = lookup_opts.get_field(field_name) 346 except meta.FieldDoesNotExist: 347 # For non-field list_display values, check for the function 348 # attribute "short_description". If that doesn't exist, fall 349 # back to the method name. And __repr__ is a special-case. 350 if field_name == '__repr__': 351 header = lookup_opts.verbose_name 352 else: 353 func = getattr(mod.Klass, field_name) # Let AttributeErrors propogate. 354 try: 355 header = func.short_description 356 except AttributeError: 357 header = func.__name__.replace('_', ' ') 358 # Non-field list_display values don't get ordering capability. 359 raw_template.append('<th>%s</th>' % capfirst(header)) 360 else: 361 if isinstance(f.rel, meta.ManyToOne) and f.null: 362 raw_template.append('<th>%s</th>' % capfirst(f.verbose_name)) 363 else: 364 th_classes = [] 365 new_order_type = 'asc' 366 if field_name == order_field: 367 th_classes.append('sorted %sending' % order_type.lower()) 368 new_order_type = {'asc': 'desc', 'desc': 'asc'}[order_type.lower()] 369 raw_template.append('<th%s><a href="%s">%s</a></th>' % \ 370 ((th_classes and ' class="%s"' % ' '.join(th_classes) or ''), 371 get_query_string(params, {ORDER_VAR: i, ORDER_TYPE_VAR: new_order_type}), 372 capfirst(f.verbose_name))) 373 raw_template.append('</tr>\n</thead>\n') 374 # Result rows. 375 pk = lookup_opts.pk.attname 376 for i, result in enumerate(result_list): 377 raw_template.append('<tr class="row%s">\n' % (i % 2 + 1)) 378 for j, field_name in enumerate(lookup_opts.admin.list_display): 379 row_class = '' 200 if isinstance(lookup_opts.get_field(order_field).rel, meta.ManyToOne): 201 f = lookup_opts.get_field(order_field) 202 rel_ordering = f.rel.to.ordering and f.rel.to.ordering[0] or f.rel.to.pk.column 203 lookup_order_field = '%s.%s' % (f.rel.to.db_table, rel_ordering) 204 # Use select_related if one of the list_display options is a field with a 205 # relationship. 206 if lookup_opts.admin.list_select_related: 207 lookup_params['select_related'] = True 208 else: 209 for field_name in lookup_opts.admin.list_display: 380 210 try: 381 211 f = lookup_opts.get_field(field_name) 382 212 except meta.FieldDoesNotExist: 383 # For non-field list_display values, the value is a method 384 # name. Execute the method. 385 func = getattr(result, field_name) 386 try: 387 result_repr = str(func()) 388 except ObjectDoesNotExist: 389 result_repr = EMPTY_CHANGELIST_VALUE 390 else: 391 # Strip HTML tags in the resulting text, except if the 392 # function has an "allow_tags" attribute set to True. 393 if not getattr(func, 'allow_tags', False): 394 result_repr = strip_tags(result_repr) 213 pass 395 214 else: 396 field_val = getattr(result, f.attname)397 # Foreign-key fields are special: Use the repr of the398 # related object.399 215 if isinstance(f.rel, meta.ManyToOne): 400 if field_val is not None: 401 result_repr = getattr(result, 'get_%s' % f.name)() 402 else: 403 result_repr = EMPTY_CHANGELIST_VALUE 404 # Dates and times are special: They're formatted in a certain way. 405 elif isinstance(f, meta.DateField) or isinstance(f, meta.TimeField): 406 if field_val: 407 (date_format, datetime_format, time_format) = get_date_formats() 408 if isinstance(f, meta.DateTimeField): 409 result_repr = capfirst(dateformat.format(field_val, datetime_format)) 410 elif isinstance(f, meta.TimeField): 411 result_repr = capfirst(dateformat.time_format(field_val, time_format)) 412 else: 413 result_repr = capfirst(dateformat.format(field_val, date_format)) 414 else: 415 result_repr = EMPTY_CHANGELIST_VALUE 416 row_class = ' class="nowrap"' 417 # Booleans are special: We use images. 418 elif isinstance(f, meta.BooleanField) or isinstance(f, meta.NullBooleanField): 419 BOOLEAN_MAPPING = {True: 'yes', False: 'no', None: 'unknown'} 420 result_repr = '<img src="%simg/admin/icon-%s.gif" alt="%s" />' % (ADMIN_MEDIA_PREFIX, BOOLEAN_MAPPING[field_val], field_val) 421 # ImageFields are special: Use a thumbnail. 422 elif isinstance(f, meta.ImageField): 423 from django.parts.media.photos import get_thumbnail_url 424 result_repr = '<img src="%s" alt="%s" title="%s" />' % (get_thumbnail_url(getattr(result, 'get_%s_url' % f.name)(), '120'), field_val, field_val) 425 # FloatFields are special: Zero-pad the decimals. 426 elif isinstance(f, meta.FloatField): 427 if field_val is not None: 428 result_repr = ('%%.%sf' % f.decimal_places) % field_val 429 else: 430 result_repr = EMPTY_CHANGELIST_VALUE 431 # Fields with choices are special: Use the representation 432 # of the choice. 433 elif f.choices: 434 result_repr = dict(f.choices).get(field_val, EMPTY_CHANGELIST_VALUE) 435 else: 436 result_repr = strip_tags(str(field_val)) 437 # Some browsers don't like empty "<td></td>"s. 438 if result_repr == '': 439 result_repr = ' ' 440 if j == 0: # First column is a special case 441 result_id = getattr(result, pk) 442 raw_template.append('<th%s><a href="%s/"%s>%s</a></th>' % \ 443 (row_class, result_id, (is_popup and ' onclick="opener.dismissRelatedLookupPopup(window, %r); return false;"' % result_id or ''), result_repr)) 444 else: 445 raw_template.append('<td%s>%s</td>' % (row_class, result_repr)) 446 raw_template.append('</tr>\n') 447 del result_list # to free memory 448 raw_template.append('</table>\n') 449 else: 450 raw_template.append('<p>No %s matched your search criteria.</p>' % opts.verbose_name_plural) 451 452 # Pagination. 453 raw_template.append('<p class="paginator">') 454 if (show_all and can_show_all) or not multi_page: 455 pass 456 else: 457 raw_template.append('Page › ') 458 ON_EACH_SIDE = 3 459 ON_ENDS = 2 460 DOT = '.' 461 # If there are 10 or fewer pages, display links to every page. 462 # Otherwise, do some fancy 463 if p.pages <= 10: 464 page_range = range(p.pages) 465 else: 466 # Insert "smart" pagination links, so that there are always ON_ENDS 467 # links at either end of the list of pages, and there are always 468 # ON_EACH_SIDE links at either end of the "current page" link. 469 page_range = [] 470 if page_num > (ON_EACH_SIDE + ON_ENDS): 471 page_range.extend(range(0, ON_EACH_SIDE - 1)) 472 page_range.append(DOT) 473 page_range.extend(range(page_num - ON_EACH_SIDE, page_num + 1)) 474 else: 475 page_range.extend(range(0, page_num + 1)) 476 if page_num < (p.pages - ON_EACH_SIDE - ON_ENDS - 1): 477 page_range.extend(range(page_num + 1, page_num + ON_EACH_SIDE + 1)) 478 page_range.append(DOT) 479 page_range.extend(range(p.pages - ON_ENDS, p.pages)) 480 else: 481 page_range.extend(range(page_num + 1, p.pages)) 482 for i in page_range: 483 if i == DOT: 484 raw_template.append('... ') 485 elif i == page_num: 486 raw_template.append('<span class="this-page">%d</span> ' % (i+1)) 487 else: 488 raw_template.append('<a href="%s"%s>%d</a> ' % \ 489 (get_query_string(params, {PAGE_VAR: i}), (i == p.pages-1 and ' class="end"' or ''), i+1)) 490 raw_template.append('%s %s' % (result_count, result_count == 1 and opts.verbose_name or opts.verbose_name_plural)) 491 if can_show_all and not show_all and multi_page: 492 raw_template.append(' <a href="%s" class="showall">Show all</a>' % \ 493 get_query_string(params, {ALL_VAR: ''})) 494 raw_template.append('</p>') 495 496 raw_template.append('</div>\n</div>') 497 raw_template.append('{% endblock %}\n') 498 t = loader.get_template_from_string(''.join(raw_template)) 216 lookup_params['select_related'] = True 217 break 218 lookup_params['order_by'] = ((order_type == 'desc' and '-' or '') + lookup_order_field,) 219 if lookup_opts.admin.search_fields and query: 220 or_queries = [] 221 for bit in query.split(): 222 or_query = [] 223 for field_name in lookup_opts.admin.search_fields: 224 or_query.append(('%s__icontains' % field_name, bit)) 225 or_queries.append(or_query) 226 lookup_params['_or'] = or_queries 227 228 if opts.one_to_one_field: 229 lookup_params.update(opts.one_to_one_field.rel.limit_choices_to) 230 self.lookup_params = lookup_params 231 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 499 239 c = Context(request, { 500 'title': (is_popup and 'Select %s' % opts.verbose_name or 'Select %s to change' % opts.verbose_name), 501 'is_popup': is_popup, 240 'title': cl.title, 241 'is_popup': cl.is_popup, 242 'cl' : cl 502 243 }) 503 return HttpResponse(t.render(c)) 244 c.update( { 'has_add_permission': c['perms'][app_label][cl.opts.get_add_permission()]}), 245 return render_to_response('admin/change_list', 246 context_instance = c) 504 247 change_list = staff_member_required(change_list) 505 248 506 def _get_flattened_data(field, val):507 """508 Returns a dictionary mapping the field's manipulator field names to its509 "flattened" string values for the admin view. "val" is an instance of the510 field's value.511 """512 if isinstance(field, meta.DateTimeField):513 date_field, time_field = field.get_manipulator_field_names('')514 return {date_field: (val is not None and val.strftime("%Y-%m-%d") or ''),515 time_field: (val is not None and val.strftime("%H:%M:%S") or '')}516 elif isinstance(field, meta.DateField):517 return {field.name: (val is not None and val.strftime("%Y-%m-%d") or '')}518 elif isinstance(field, meta.TimeField):519 return {field.name: (val is not None and val.strftime("%H:%M:%S") or '')}520 else:521 return {field.name: val}522 249 523 250 use_raw_id_admin = lambda field: isinstance(field.rel, (meta.ManyToOne, meta.ManyToMany)) and field.rel.raw_id_admin 524 251 525 def _get_submit_row_template(opts, app_label, add, change, show_delete, ordered_objects): 526 t = ['<div class="submit-row">'] 527 if change or show_delete: 528 t.append('{%% if perms.%s.%s %%}{%% if not is_popup %%}<p class="float-left"><a href="delete/" class="deletelink">Delete</a></p>{%% endif %%}{%% endif %%}' % \ 529 (app_label, opts.get_delete_permission())) 530 if change and opts.admin.save_as: 531 t.append('{%% if not is_popup %%}<input type="submit" value="Save as new" name="_saveasnew" %s/>{%% endif %%}' % \ 532 (ordered_objects and change and 'onclick="submitOrderForm();"' or '')) 533 if not opts.admin.save_as or add: 534 t.append('{%% if not is_popup %%}<input type="submit" value="Save and add another" name="_addanother" %s/>{%% endif %%}' % \ 535 (ordered_objects and change and 'onclick="submitOrderForm();"' or '')) 536 t.append('{%% if not is_popup %%}<input type="submit" value="Save and continue editing" name="_continue" %s/>{%% endif %%}' % \ 537 (ordered_objects and change and 'onclick="submitOrderForm();"' or '')) 538 t.append('<input type="submit" value="Save" class="default" %s/>' % \ 539 (ordered_objects and change and 'onclick="submitOrderForm();"' or '')) 540 t.append('</div>\n') 541 return t 542 543 def _get_template(opts, app_label, add=False, change=False, show_delete=False, form_url=''): 544 admin_field_objs = opts.admin.get_field_objs(opts) 545 ordered_objects = opts.get_ordered_objects()[:] 546 auto_populated_fields = [f for f in opts.fields if f.prepopulate_from] 547 t = ['{% extends "admin/base_site" %}\n'] 548 t.append('{% block extrahead %}') 549 550 # Put in any necessary JavaScript imports. 551 javascript_imports = ['%sjs/core.js' % ADMIN_MEDIA_PREFIX, '%sjs/admin/RelatedObjectLookups.js' % ADMIN_MEDIA_PREFIX] 252 253 def get_javascript_imports(opts,auto_populated_fields, ordered_objects, field_sets): 254 # Put in any necessary JavaScript imports. 255 js = ['js/core.js', 'js/admin/RelatedObjectLookups.js'] 552 256 if auto_populated_fields: 553 j avascript_imports.append('%sjs/urlify.js' % ADMIN_MEDIA_PREFIX)257 js.append('js/urlify.js') 554 258 if opts.has_field_type(meta.DateTimeField) or opts.has_field_type(meta.TimeField) or opts.has_field_type(meta.DateField): 555 j avascript_imports.extend(['%sjs/calendar.js' % ADMIN_MEDIA_PREFIX, '%sjs/admin/DateTimeShortcuts.js' % ADMIN_MEDIA_PREFIX])259 js.extend(['js/calendar.js', 'js/admin/DateTimeShortcuts.js']) 556 260 if ordered_objects: 557 j avascript_imports.extend(['%sjs/getElementsBySelector.js' % ADMIN_MEDIA_PREFIX, '%sjs/dom-drag.js' % ADMIN_MEDIA_PREFIX, '%sjs/admin/ordering.js' % ADMIN_MEDIA_PREFIX])261 js.extend(['js/getElementsBySelector.js', 'js/dom-drag.js' , 'js/admin/ordering.js']) 558 262 if opts.admin.js: 559 j avascript_imports.extend(opts.admin.js)263 js.extend(opts.admin.js) 560 264 seen_collapse = False 561 for _, options in admin_field_objs:562 if not seen_collapse and 'collapse' in options.get('classes', ''):265 for field_set in field_sets: 266 if not seen_collapse and 'collapse' in field_set.classes: 563 267 seen_collapse = True 564 javascript_imports.append('%sjs/admin/CollapsedFieldsets.js' % ADMIN_MEDIA_PREFIX) 565 for field_list in options['fields']: 268 js.append('js/admin/CollapsedFieldsets.js' ) 269 270 for field_line in field_set: 566 271 try: 567 for f in field_li st:272 for f in field_line: 568 273 if f.rel and isinstance(f, meta.ManyToManyField) and f.rel.filter_interface: 569 j avascript_imports.extend(['%sjs/SelectBox.js' % ADMIN_MEDIA_PREFIX, '%sjs/SelectFilter2.js' % ADMIN_MEDIA_PREFIX])274 js.extend(['js/SelectBox.js' , 'js/SelectFilter2.js']) 570 275 raise StopIteration 571 276 except StopIteration: 572 277 break 573 for j in javascript_imports: 574 t.append('<script type="text/javascript" src="%s"></script>' % j) 575 576 t.append('{% endblock %}\n') 577 if ordered_objects: 578 coltype = 'colMS' 579 else: 580 coltype = 'colM' 581 t.append('{%% block coltype %%}%s{%% endblock %%}\n' % coltype) 582 t.append('{%% block bodyclass %%}%s-%s change-form{%% endblock %%}\n' % (app_label, opts.object_name.lower())) 583 breadcrumb_title = add and "Add %s" % opts.verbose_name or '{{ original|striptags|truncatewords:"18" }}' 584 t.append('{%% block breadcrumbs %%}{%% if not is_popup %%}<div class="breadcrumbs"><a href="../../../">Home</a> › <a href="../">%s</a> › %s</div>{%% endif %%}{%% endblock %%}\n' % \ 585 (capfirst(opts.verbose_name_plural), breadcrumb_title)) 586 t.append('{% block content %}<div id="content-main">\n') 587 if change: 588 t.append('{% if not is_popup %}') 589 t.append('<ul class="object-tools"><li><a href="history/" class="historylink">History</a></li>') 590 if hasattr(opts.get_model_module().Klass, 'get_absolute_url'): 591 t.append('<li><a href="/r/%s/{{ object_id }}/" class="viewsitelink">View on site</a></li>' % opts.get_content_type_id()) 592 t.append('</ul>\n') 593 t.append('{% endif %}') 594 t.append('<form ') 595 if opts.has_field_type(meta.FileField): 596 t.append('enctype="multipart/form-data" ') 597 t.append('action="%s" method="post">\n' % form_url) 598 t.append('{% if is_popup %}<input type="hidden" name="_popup" value="1">{% endif %}') 599 if opts.admin.save_on_top: 600 t.extend(_get_submit_row_template(opts, app_label, add, change, show_delete, ordered_objects)) 601 t.append('{% if form.error_dict %}<p class="errornote">Please correct the error{{ form.error_dict.items|pluralize }} below.</p>{% endif %}\n') 602 for fieldset_name, options in admin_field_objs: 603 t.append('<fieldset class="module aligned %s">\n\n' % options.get('classes', '')) 604 if fieldset_name: 605 t.append('<h2>%s</h2>\n' % fieldset_name) 606 for field_list in options['fields']: 607 t.append(_get_admin_field(field_list, 'form.', False, add, change)) 608 for f in field_list: 609 if f.rel and isinstance(f, meta.ManyToManyField) and f.rel.filter_interface: 610 t.append('<script type="text/javascript">addEvent(window, "load", function(e) { SelectFilter.init("id_%s", "%s", %s, %r); });</script>\n' % (f.name, f.verbose_name, f.rel.filter_interface-1, ADMIN_MEDIA_PREFIX)) 611 t.append('</fieldset>\n') 612 if ordered_objects and change: 613 t.append('<fieldset class="module"><h2>Ordering</h2>') 614 t.append('<div class="form-row{% if form.order_.errors %} error{% endif %} ">\n') 615 t.append('{% if form.order_.errors %}{{ form.order_.html_error_list }}{% endif %}') 616 t.append('<p><label for="id_order_">Order:</label> {{ form.order_ }}</p>\n') 617 t.append('</div></fieldset>\n') 618 for rel_obj, rel_field in opts.get_inline_related_objects(): 619 var_name = rel_obj.object_name.lower() 620 field_list = [f for f in rel_obj.fields + rel_obj.many_to_many if f.editable and f != rel_field] 621 t.append('<fieldset class="module%s">\n' % ((rel_field.rel.edit_inline != meta.TABULAR) and ' aligned' or '')) 622 view_on_site = '' 623 if change and hasattr(rel_obj, 'get_absolute_url'): 624 view_on_site = '{%% if %s.original %%}<a href="/r/{{ %s.content_type_id }}/{{ %s.original.id }}/">View on site</a>{%% endif %%}' % (var_name, var_name, var_name) 625 if rel_field.rel.edit_inline == meta.TABULAR: 626 t.append('<h2>%s</h2>\n<table>\n' % capfirst(rel_obj.verbose_name_plural)) 627 t.append('<thead><tr>') 628 for f in field_list: 629 if isinstance(f, meta.AutoField): 630 continue 631 t.append('<th%s>%s</th>' % (f.blank and ' class="optional"' or '', capfirst(f.verbose_name))) 632 t.append('</tr></thead>\n') 633 t.append('{%% for %s in form.%s %%}\n' % (var_name, rel_obj.module_name)) 634 if change: 635 for f in field_list: 636 if use_raw_id_admin(f): 637 t.append('{%% if %s.original %%}' % var_name) 638 t.append('<tr class="row-label {% cycle row1,row2 %}">') 639 t.append('<td colspan="%s"><strong>{{ %s.original }}</strong></td>' % (30, var_name)) 640 t.append('</tr>{% endif %}\n') 641 break 642 t.append('{%% if %s %%}\n' % ' or '.join(['%s.%s.errors' % (var_name, f.name) for f in field_list])) 643 t.append('<tr class="errorlist"><td colspan="%s">%s</td></tr>\n{%% endif %%}\n' % \ 644 (len(field_list), ''.join(['{{ %s.%s.html_error_list }}' % (var_name, f.name) for f in field_list]))) 645 t.append('<tr class="{% cycle row1,row2 %}">\n') 646 hidden_fields = [] 647 for f in field_list: 648 form_widget = _get_admin_field_form_widget(f, var_name+'.', True, add, change) 649 # Don't put AutoFields within a <td>, because they're hidden. 650 if not isinstance(f, meta.AutoField): 651 # Fields with raw_id_admin=True get class="nowrap". 652 if use_raw_id_admin(f): 653 t.append('<td class="nowrap {%% if %s.%s.errors %%}error"{%% endif %%}">%s</td>\n' % (var_name, f.name, form_widget)) 654 else: 655 t.append('<td{%% if %s.%s.errors %%} class="error"{%% endif %%}>%s</td>\n' % (var_name, f.name, form_widget)) 656 else: 657 hidden_fields.append(form_widget) 658 if hasattr(rel_obj, 'get_absolute_url'): 659 t.append('<td>%s</td>\n' % view_on_site) 660 t.append('</tr>\n') 661 t.append('{% endfor %}\n</table>\n') 662 # Write out the hidden fields. We didn't write them out earlier 663 # because it would've been invalid HTML. 664 t.append('{%% for %s in form.%s %%}\n' % (var_name, rel_obj.module_name)) 665 t.extend(hidden_fields) 666 t.append('{% endfor %}\n') 667 else: # edit_inline == STACKED 668 t.append('{%% for %s in form.%s %%}' % (var_name, rel_obj.module_name)) 669 t.append('<h2>%s #{{ forloop.counter }}</h2>' % capfirst(rel_obj.verbose_name)) 670 if view_on_site: 671 t.append('<p>%s</p>' % view_on_site) 672 for f in field_list: 673 # Don't put AutoFields within the widget -- just use the field. 674 if isinstance(f, meta.AutoField): 675 t.append(_get_admin_field_form_widget(f, var_name+'.', True, add, change)) 676 else: 677 t.append(_get_admin_field([f], var_name+'.', True, add, change)) 678 t.append('{% endfor %}\n') 679 t.append('</fieldset>\n') 680 t.extend(_get_submit_row_template(opts, app_label, add, change, show_delete, ordered_objects)) 681 if add: 682 # Add focus to the first field on the form, if this is an "add" form. 683 t.append('<script type="text/javascript">document.getElementById("id_%s").focus();</script>' % \ 684 admin_field_objs[0][1]['fields'][0][0].get_manipulator_field_names('')[0]) 685 if auto_populated_fields: 686 t.append('<script type="text/javascript">') 687 for field in auto_populated_fields: 688 if change: 689 t.append('document.getElementById("id_%s")._changed = true;' % field.name) 690 else: 691 t.append('document.getElementById("id_%s").onchange = function() { this._changed = true; };' % field.name) 692 for f in field.prepopulate_from: 693 t.append('document.getElementById("id_%s").onkeyup = function() { var e = document.getElementById("id_%s"); if (!e._changed) { e.value = URLify(%s, %s);}};' % \ 694 (f, field.name, ' + " " + '.join(['document.getElementById("id_%s").value' % g for g in field.prepopulate_from]), field.maxlength)) 695 t.append('</script>\n') 696 if change and ordered_objects: 697 t.append('{% if form.order_objects %}<ul id="orderthese">{% for object in form.order_objects %}') 698 t.append('<li id="p{%% firstof %(x)s %%}"><span id="handlep{%% firstof %(x)s %%}">{{ object|truncatewords:"5" }}</span></li>' % \ 699 {'x': ' '.join(['object.%s' % o.pk.name for o in ordered_objects])}) 700 t.append('{% endfor %}</ul>{% endif %}\n') 701 t.append('</form>\n</div>\n{% endblock %}') 702 return ''.join(t) 703 704 def _get_admin_field(field_list, name_prefix, rel, add, change): 705 "Returns the template code for editing the given list of fields in the admin template." 706 field_names = [] 707 for f in field_list: 708 field_names.extend(f.get_manipulator_field_names(name_prefix)) 709 div_class_names = ['form-row', '{%% if %s %%} error{%% endif %%}' % ' or '.join(['%s.errors' % n for n in field_names])] 710 # Assumes BooleanFields won't be stacked next to each other! 711 if isinstance(field_list[0], meta.BooleanField): 712 div_class_names.append('checkbox-row') 713 t = [] 714 t.append('<div class="%s">\n' % ' '.join(div_class_names)) 715 for n in field_names: 716 t.append('{%% if %s.errors %%}{{ %s.html_error_list }}{%% endif %%}\n' % (n, n)) 717 for i, field in enumerate(field_list): 718 label_name = 'id_%s%s' % ((rel and "%s{{ forloop.counter0 }}." % name_prefix or ""), field.get_manipulator_field_names('')[0]) 719 # BooleanFields are a special case, because the checkbox widget appears to 720 # the *left* of the label. 721 if isinstance(field, meta.BooleanField): 722 t.append(_get_admin_field_form_widget(field, name_prefix, rel, add, change)) 723 t.append(' <label for="%s" class="vCheckboxLabel">%s</label>' % (label_name, capfirst(field.verbose_name))) 724 else: 725 class_names = [] 726 if not field.blank: 727 class_names.append('required') 728 if i > 0: 729 class_names.append('inline') 730 t.append('<label for="%s"%s>%s:</label> ' % (label_name, class_names and ' class="%s"' % ' '.join(class_names) or '', capfirst(field.verbose_name))) 731 t.append(_get_admin_field_form_widget(field, name_prefix, rel, add, change)) 732 if change and field.primary_key: 733 t.append('{{ %soriginal.%s }}' % ((rel and name_prefix or ''), field.name)) 734 if change and use_raw_id_admin(field): 735 if isinstance(field.rel, meta.ManyToOne): 736 if_bit = '%soriginal.get_%s' % (rel and name_prefix or '', field.name) 737 obj_repr = if_bit + '|truncatewords:"14"' 738 elif isinstance(field.rel, meta.ManyToMany): 739 if_bit = '%soriginal.get_%s_list' % (rel and name_prefix or '', field.name) 740 obj_repr = if_bit + '|join:", "|truncatewords:"14"' 741 t.append('{%% if %s %%} <strong>{{ %s }}</strong>{%% endif %%}' % (if_bit, obj_repr)) 742 if field.help_text: 743 t.append('<p class="help">%s</p>\n' % field.help_text) 744 t.append('</div>\n\n') 745 return ''.join(t) 746 747 def _get_admin_field_form_widget(field, name_prefix, rel, add, change): 748 "Returns JUST the formfield widget for the field's admin interface." 749 field_names = field.get_manipulator_field_names(name_prefix) 750 if isinstance(field, meta.DateTimeField): 751 return '<p class="datetime">Date: {{ %s }}<br />Time: {{ %s }}</p>' % tuple(field_names) 752 t = ['{{ %s }}' % n for n in field_names] 753 if change and isinstance(field, meta.FileField): 754 return '{%% if %soriginal.%s %%}Currently: <a href="{{ %soriginal.get_%s_url }}">{{ %soriginal.%s }}</a><br />Change: %s{%% else %%}%s{%% endif %%}' % \ 755 (name_prefix, field.name, name_prefix, field.name, name_prefix, field.name, ''.join(t), ''.join(t)) 756 field_id = 'id_%s%s' % ((rel and "%s{{ forloop.counter0 }}." % name_prefix or ""), field.get_manipulator_field_names('')[0]) 757 # raw_id_admin fields get the little lookup link next to them 758 if use_raw_id_admin(field): 759 t.append(' <a href="../../../%s/%s/" class="related-lookup" id="lookup_%s" onclick="return showRelatedObjectLookupPopup(this);">' % \ 760 (field.rel.to.app_label, field.rel.to.module_name, field_id)) 761 t.append('<img src="%simg/admin/selector-search.gif" width="16" height="16" alt="Lookup" /></a>' % ADMIN_MEDIA_PREFIX) 762 # fields with relationships to editable objects get an "add another" link, 763 # but only if the field doesn't have raw_admin ('cause in that case they get 764 # the "add" button in the popup) 765 elif field.rel and (isinstance(field.rel, meta.ManyToOne) or isinstance(field.rel, meta.ManyToMany)) and field.rel.to.admin: 766 t.append('{%% if perms.%s.%s %%}' % (field.rel.to.app_label, field.rel.to.get_add_permission())) 767 t.append(' <a href="../../../%s/%s/add/" class="add-another" id="add_%s" onclick="return showAddAnotherPopup(this);">' % \ 768 (field.rel.to.app_label, field.rel.to.module_name, field_id)) 769 t.append('<img src="%simg/admin/icon_addlink.gif" width="10" height="10" alt="Add Another" /></a>' % ADMIN_MEDIA_PREFIX) 770 t.append('{% endif %}') 771 return ''.join(t) 278 return js 279 280 281 class AdminBoundField(BoundField): 282 def __init__(self, field, field_mapping, original): 283 super(AdminBoundField, self).__init__(field,field_mapping,original) 284 285 self.element_id = self.form_fields[0].get_id() 286 self.has_label_first = not isinstance(self.field, meta.BooleanField) 287 self.raw_id_admin = use_raw_id_admin(field) 288 self.is_date_time = isinstance(field, meta.DateTimeField) 289 self.is_file_field = isinstance(field, meta.FileField) 290 self.needs_add_label = field.rel and isinstance(field.rel, meta.ManyToOne) or isinstance(field.rel, meta.ManyToMany) and field.rel.to.admin 291 self.hidden = isinstance(self.field, meta.AutoField) 292 self.first = False 293 294 classes = [] 295 if(self.raw_id_admin): 296 classes.append('nowrap') 297 if max([bool(f.errors()) for f in self.form_fields]): 298 classes.append('error') 299 if classes: 300 self.cell_class_attribute = ' class="%s" ' % ' '.join(classes) 301 self._repr_filled = False 302 303 def _fetch_existing_display(self, func_name): 304 class_dict = self.original.__class__.__dict__ 305 func = class_dict.get(func_name) 306 return func(self.original) 307 308 def _fill_existing_display(self): 309 if self._display_filled: 310 return 311 #HACK 312 if isinstance(self.field.rel, meta.ManyToOne): 313 func_name = 'get_%s' % self.field.name 314 self._display = self._fetch_existing_display(func_name) 315 elif isinstance(self.field.rel, meta.ManyToMany): 316 func_name = 'get_%s_list' % self.field.name 317 self._display = ",".join(self._fetch_existing_display(func_name)) 318 self._display_filled = True 319 320 def existing_display(self): 321 self._fill_existing_display() 322 return self._display 323 324 def __repr__(self): 325 return repr(self.__dict__) 326 327 def html_error_list(self): 328 return " ".join([form_field.html_error_list() for form_field in self.form_fields if form_field.errors]) 329 330 class AdminBoundFieldLine(BoundFieldLine): 331 def __init__(self, field_line, field_mapping, original): 332 super(AdminBoundFieldLine, self).__init__(field_line, field_mapping, original, AdminBoundField) 333 for bound_field in self: 334 bound_field.first = True 335 break 336 337 class AdminBoundFieldSet(BoundFieldSet): 338 def __init__(self, field_set, field_mapping, original): 339 super(AdminBoundFieldSet, self).__init__(field_set, field_mapping, original, AdminBoundFieldLine) 340 341 class BoundManipulator(object): 342 def __init__(self, opts, manipulator, field_mapping): 343 self.inline_related_objects = opts.get_followed_related_objects(manipulator.follow) 344 self.original = hasattr(manipulator, 'original_object') and manipulator.original_object or None 345 self.bound_field_sets = [field_set.bind(field_mapping, self.original, AdminBoundFieldSet) 346 for field_set in opts.admin.get_field_sets(opts)] 347 self.ordered_objects = opts.get_ordered_objects()[:] 348 349 class AdminBoundManipulator(BoundManipulator): 350 def __init__(self, opts, manipulator, field_mapping): 351 super(AdminBoundManipulator, self).__init__(opts, manipulator, field_mapping) 352 field_sets = opts.admin.get_field_sets(opts) 353 354 self.auto_populated_fields = [f for f in opts.fields if f.prepopulate_from] 355 self.javascript_imports = get_javascript_imports(opts, self.auto_populated_fields, self.ordered_objects, field_sets); 356 357 self.coltype = self.ordered_objects and 'colMS' or 'colM' 358 self.has_absolute_url = hasattr(opts.get_model_module().Klass, 'get_absolute_url') 359 self.form_enc_attrib = opts.has_field_type(meta.FileField) and \ 360 'enctype="multipart/form-data" ' or '' 361 362 self.first_form_field_id = self.bound_field_sets[0].bound_field_lines[0].bound_fields[0].form_fields[0].get_id(); 363 self.ordered_object_pk_names = [o.pk.name for o in self.ordered_objects] 364 365 self.save_on_top = opts.admin.save_on_top 366 self.save_as = opts.admin.save_as 367 368 self.content_type_id = opts.get_content_type_id() 369 self.verbose_name_plural = opts.verbose_name_plural 370 self.verbose_name = opts.verbose_name 371 self.object_name = opts.object_name 372 373 def get_ordered_object_pk(self, ordered_obj): 374 for name in self.ordered_object_pk_names: 375 if hasattr(ordered_obj, name): 376 return str(getattr(ordered_obj, name)) 377 return "" 378 379 def render_change_form(opts, manipulator, app_label, context, add=False, change=False, show_delete=False, form_url=''): 380 extra_context = { 381 'add': add, 382 'change': change, 383 'bound_manipulator' : AdminBoundManipulator(opts, manipulator, context['form']), 384 'has_delete_permission' : context['perms'][app_label][opts.get_delete_permission()], 385 'form_url' : form_url, 386 'app_label': app_label, 387 } 388 context.update(extra_context) 389 return render_to_response(["admin/%s/%s/change_form" % (app_label, opts.object_name.lower() ), 390 "admin/%s/change_form" % app_label , 391 "admin/change_form"], context_instance=context) 392 393 def log_add_message(user, opts,manipulator,new_object): 394 pk_value = getattr(new_object, opts.pk.attname) 395 log.log_action(user.id, opts.get_content_type_id(), pk_value, str(new_object), log.ADDITION) 772 396 773 397 def add_stage(request, app_label, module_name, show_delete=False, form_url='', post_url='../', post_url_continue='../%s/', object_id_override=None): … … 781 405 new_data.update(request.FILES) 782 406 errors = manipulator.get_validation_errors(new_data) 407 manipulator.do_html2python(new_data) 408 783 409 if not errors and not request.POST.has_key("_preview"): 784 for f in opts.many_to_many:785 if f.rel.raw_id_admin:786 new_data.setlist(f.name, new_data[f.name].split(","))787 manipulator.do_html2python(new_data)788 410 new_object = manipulator.save(new_data) 789 pk_value = getattr(new_object, opts.pk.attname)790 log.log_action(request.user.id, opts.get_content_type_id(), pk_value, repr(new_object), log.ADDITION)791 msg = 'The %s "%s" was added successfully.' % (opts.verbose_name, new_object)411 log_add_message(request.user, opts,manipulator,new_object) 412 msg = _('The %(name)s "%(obj)s" was added successfully.') % {'name':opts.verbose_name, 'obj':new_object} 413 pk_value = getattr(new_object,opts.pk.attname) 792 414 # Here, we distinguish between different save types by checking for 793 415 # the presence of keys in request.POST. 794 416 if request.POST.has_key("_continue"): 795 request.user.add_message( "%s You may edit it again below." % msg)417 request.user.add_message(msg + ' ' + _("You may edit it again below.")) 796 418 if request.POST.has_key("_popup"): 797 419 post_url_continue += "?_popup=1" … … 801 423 (pk_value, repr(new_object).replace('"', '\\"'))) 802 424 elif request.POST.has_key("_addanother"): 803 request.user.add_message( "%s You may add another %s below." % (msg,opts.verbose_name))425 request.user.add_message(msg + ' ' + (_("You may add another %s below.") % opts.verbose_name)) 804 426 return HttpResponseRedirect(request.path) 805 427 else: 806 428 request.user.add_message(msg) 807 429 return HttpResponseRedirect(post_url) 808 if request.POST.has_key("_preview"):809 manipulator.do_html2python(new_data)810 430 else: 811 new_data = {}812 431 # Add default data. 813 for f in opts.fields: 814 if f.has_default(): 815 new_data.update(_get_flattened_data(f, f.get_default())) 816 # In required many-to-one fields with only one available choice, 817 # select that one available choice. Note: We have to check that 818 # the length of choices is *2*, not 1, because SelectFields always 819 # have an initial "blank" value. 820 elif not f.blank and ((isinstance(f.rel, meta.ManyToOne) and not f.rel.raw_id_admin) or f.choices) and len(manipulator[f.name].choices) == 2: 821 new_data[f.name] = manipulator[f.name].choices[1][0] 822 # In required many-to-many fields with only one available choice, 823 # select that one available choice. 824 for f in opts.many_to_many: 825 if not f.blank and not f.rel.edit_inline and not f.rel.raw_id_admin and len(manipulator[f.name].choices) == 1: 826 new_data[f.name] = [manipulator[f.name].choices[0][0]] 827 # Add default data for related objects. 828 for rel_opts, rel_field in opts.get_inline_related_objects(): 829 var_name = rel_opts.object_name.lower() 830 for i in range(rel_field.rel.num_in_admin): 831 for f in rel_opts.fields + rel_opts.many_to_many: 832 if f.has_default(): 833 for field_name in f.get_manipulator_field_names(''): 834 new_data['%s.%d.%s' % (var_name, i, field_name)] = f.get_default() 432 new_data = manipulator.flatten_data() 433 835 434 # Override the defaults with request.GET, if it exists. 836 435 new_data.update(request.GET) … … 838 437 839 438 # Populate the FormWrapper. 840 form = formfields.FormWrapper(manipulator, new_data, errors) 841 for rel_opts, rel_field in opts.get_inline_related_objects(): 842 var_name = rel_opts.object_name.lower() 843 wrapper = [] 844 for i in range(rel_field.rel.num_in_admin): 845 collection = {} 846 for f in rel_opts.fields + rel_opts.many_to_many: 847 if f.editable and f != rel_field and not isinstance(f, meta.AutoField): 848 for field_name in f.get_manipulator_field_names(''): 849 full_field_name = '%s.%d.%s' % (var_name, i, field_name) 850 collection[field_name] = formfields.FormFieldWrapper(manipulator[full_field_name], new_data.get(full_field_name, ''), errors.get(full_field_name, [])) 851 wrapper.append(formfields.FormFieldCollection(collection)) 852 setattr(form, rel_opts.module_name, wrapper) 439 form = formfields.FormWrapper(manipulator, new_data, errors, edit_inline=True) 853 440 854 441 c = Context(request, { 855 'title': 'Add %s' % opts.verbose_name, 856 "form": form, 857 "is_popup": request.REQUEST.has_key("_popup"), 442 'title': _('Add %s') % opts.verbose_name, 443 'form': form, 444 'is_popup': request.REQUEST.has_key('_popup'), 445 'show_delete': show_delete, 858 446 }) 859 447 if object_id_override is not None: 860 448 c['object_id'] = object_id_override 861 raw_template = _get_template(opts, app_label, add=True, show_delete=show_delete, form_url=form_url) 862 # return HttpResponse(raw_template, mimetype='text/plain') 863 t = loader.get_template_from_string(raw_template) 864 return HttpResponse(t.render(c)) 449 450 return render_change_form(opts, manipulator, app_label, c, add=True) 865 451 add_stage = staff_member_required(add_stage) 452 453 def log_change_message(user, opts,manipulator,new_object): 454 pk_value = getattr(new_object, opts.pk.column) 455 # Construct the change message. 456 change_message = [] 457 if manipulator.fields_added: 458 change_message.append(_('Added %s.') % get_text_list(manipulator.fields_added, _('and'))) 459 if manipulator.fields_changed: 460 change_message.append(_('Changed %s.') % get_text_list(manipulator.fields_changed, _('and'))) 461 if manipulator.fields_deleted: 462 change_message.append(_('Deleted %s.') % get_text_list(manipulator.fields_deleted, _('and'))) 463 change_message = ' '.join(change_message) 464 if not change_message: 465 change_message = _('No fields changed.') 466 log.log_action(user.id, opts.get_content_type_id(), pk_value, str(new_object), log.CHANGE, change_message) 866 467 867 468 def change_stage(request, app_label, module_name, object_id): … … 876 477 raise Http404 877 478 878 inline_related_objects = opts.get_inline_related_objects()879 479 if request.POST: 880 480 new_data = request.POST.copy() … … 883 483 884 484 errors = manipulator.get_validation_errors(new_data) 485 486 manipulator.do_html2python(new_data) 885 487 if not errors and not request.POST.has_key("_preview"): 886 for f in opts.many_to_many:887 if f.rel.raw_id_admin:888 new_data.setlist(f.name, new_data[f.name].split(","))889 manipulator.do_html2python(new_data)890 488 new_object = manipulator.save(new_data) 891 pk_value = getattr(new_object, opts.pk.attname) 892 893 # Construct the change message. 894 change_message = [] 895 if manipulator.fields_added: 896 change_message.append('Added %s.' % get_text_list(manipulator.fields_added, 'and')) 897 if manipulator.fields_changed: 898 change_message.append('Changed %s.' % get_text_list(manipulator.fields_changed, 'and')) 899 if manipulator.fields_deleted: 900 change_message.append('Deleted %s.' % get_text_list(manipulator.fields_deleted, 'and')) 901 change_message = ' '.join(change_message) 902 if not change_message: 903 change_message = 'No fields changed.' 904 905 log.log_action(request.user.id, opts.get_content_type_id(), pk_value, repr(new_object), log.CHANGE, change_message) 906 msg = 'The %s "%s" was changed successfully.' % (opts.verbose_name, new_object) 489 log_change_message(request.user,opts,manipulator,new_object) 490 msg = _('The %(name)s "%(obj)s" was changed successfully.') % {'name': opts.verbose_name, 'obj':new_object} 491 pk_value = getattr(new_object,opts.pk.attname) 907 492 if request.POST.has_key("_continue"): 908 request.user.add_message( "%s You may edit it again below." % msg)493 request.user.add_message(msg + ' ' + _("You may edit it again below.")) 909 494 if request.REQUEST.has_key('_popup'): 910 495 return HttpResponseRedirect(request.path + "?_popup=1") … … 912 497 return HttpResponseRedirect(request.path) 913 498 elif request.POST.has_key("_saveasnew"): 914 request.user.add_message( 'The %s "%s" was added successfully. You may edit it again below.' % (opts.verbose_name, new_object))499 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}) 915 500 return HttpResponseRedirect("../%s/" % pk_value) 916 501 elif request.POST.has_key("_addanother"): 917 request.user.add_message( "%s You may add another %s below." % (msg,opts.verbose_name))502 request.user.add_message(msg + ' ' + (_("You may add another %s below.") % opts.verbose_name)) 918 503 return HttpResponseRedirect("../add/") 919 504 else: 920 505 request.user.add_message(msg) 921 506 return HttpResponseRedirect("../") 922 if request.POST.has_key("_preview"):923 manipulator.do_html2python(new_data)924 507 else: 925 508 # Populate new_data with a "flattened" version of the current data. 926 new_data = {} 927 obj = manipulator.original_object 928 for f in opts.fields: 929 new_data.update(_get_flattened_data(f, getattr(obj, f.attname))) 930 for f in opts.many_to_many: 931 get_list_func = getattr(obj, 'get_%s_list' % f.rel.singular) 932 if f.rel.raw_id_admin: 933 new_data[f.name] = ",".join([str(getattr(i, f.rel.to.pk.attname)) for i in get_list_func()]) 934 elif not f.rel.edit_inline: 935 new_data[f.name] = [getattr(i, f.rel.to.pk.attname) for i in get_list_func()] 936 for rel_obj, rel_field in inline_related_objects: 937 var_name = rel_obj.object_name.lower() 938 for i, rel_instance in enumerate(getattr(obj, 'get_%s_list' % opts.get_rel_object_method_name(rel_obj, rel_field))()): 939 for f in rel_obj.fields: 940 if f.editable and f != rel_field: 941 for k, v in _get_flattened_data(f, getattr(rel_instance, f.attname)).items(): 942 new_data['%s.%d.%s' % (var_name, i, k)] = v 943 for f in rel_obj.many_to_many: 944 new_data['%s.%d.%s' % (var_name, i, f.column)] = [j.id for j in getattr(rel_instance, 'get_%s_list' % f.rel.singular)()] 945 509 new_data = manipulator.flatten_data() 510 511 # TODO: do this in flatten_data... 946 512 # If the object has ordered objects on its admin page, get the existing 947 513 # order and flatten it into a comma-separated list of IDs. 514 948 515 id_order_list = [] 949 516 for rel_obj in opts.get_ordered_objects(): 950 id_order_list.extend(getattr( obj, 'get_%s_order' % rel_obj.object_name.lower())())517 id_order_list.extend(getattr(manipulator.original_object, 'get_%s_order' % rel_obj.object_name.lower())()) 951 518 if id_order_list: 952 519 new_data['order_'] = ','.join(map(str, id_order_list)) … … 954 521 955 522 # Populate the FormWrapper. 956 form = formfields.FormWrapper(manipulator, new_data, errors )523 form = formfields.FormWrapper(manipulator, new_data, errors, edit_inline = True) 957 524 form.original = manipulator.original_object 958 525 form.order_objects = [] 959 for rel_opts, rel_field in inline_related_objects: 960 var_name = rel_opts.object_name.lower() 961 wrapper = [] 962 orig_list = getattr(manipulator.original_object, 'get_%s_list' % opts.get_rel_object_method_name(rel_opts, rel_field))() 963 count = len(orig_list) + rel_field.rel.num_extra_on_change 964 if rel_field.rel.min_num_in_admin: 965 count = max(count, rel_field.rel.min_num_in_admin) 966 if rel_field.rel.max_num_in_admin: 967 count = min(count, rel_field.rel.max_num_in_admin) 968 for i in range(count): 969 collection = {'original': (i < len(orig_list) and orig_list[i] or None)} 970 for f in rel_opts.fields + rel_opts.many_to_many: 971 if f.editable and f != rel_field: 972 for field_name in f.get_manipulator_field_names(''): 973 full_field_name = '%s.%d.%s' % (var_name, i, field_name) 974 collection[field_name] = formfields.FormFieldWrapper(manipulator[full_field_name], new_data.get(full_field_name, f.get_default()), errors.get(full_field_name, [])) 975 wrapper.append(formfields.FormFieldCollection(collection)) 976 setattr(form, rel_opts.module_name, wrapper) 977 if rel_opts.order_with_respect_to and rel_opts.order_with_respect_to.rel and rel_opts.order_with_respect_to.rel.to == opts: 526 527 #TODO Should be done in flatten_data / FormWrapper construction 528 for related in opts.get_followed_related_objects(): 529 wrt = related.opts.order_with_respect_to 530 if wrt and wrt.rel and wrt.rel.to == opts: 531 func = getattr(manipulator.original_object, 'get_%s_list' % 532 related.get_method_name_part()) 533 orig_list = func() 978 534 form.order_objects.extend(orig_list) 979 535 980 536 c = Context(request, { 981 'title': 'Change %s'% opts.verbose_name,982 "form": form,537 'title': _('Change %s') % opts.verbose_name, 538 'form': form, 983 539 'object_id': object_id, 984 540 'original': manipulator.original_object, 985 'is_popup' : request.REQUEST.has_key('_popup') ,541 'is_popup' : request.REQUEST.has_key('_popup') 986 542 }) 987 raw_template = _get_template(opts, app_label, change=True) 988 # return HttpResponse(raw_template, mimetype='text/plain') 989 t = loader.get_template_from_string(raw_template) 990 return HttpResponse(t.render(c)) 991 change_stage = staff_member_required(change_stage) 543 544 return render_change_form(opts,manipulator, app_label, c, change=True) 992 545 993 546 def _nest_help(obj, depth, val): … … 1003 556 return # Avoid recursing too deep. 1004 557 objects_seen = [] 1005 for rel _opts, rel_field in opts.get_all_related_objects():1006 if rel _opts in objects_seen:558 for related in opts.get_all_related_objects(): 559 if related.opts in objects_seen: 1007 560 continue 1008 objects_seen.append(rel _opts)1009 rel_opts_name = opts.get_rel_object_method_name(rel_opts, rel_field)1010 if isinstance(rel _field.rel, meta.OneToOne):561 objects_seen.append(related.opts) 562 rel_opts_name = related.get_method_name_part() 563 if isinstance(related.field.rel, meta.OneToOne): 1011 564 try: 1012 565 sub_obj = getattr(obj, 'get_%s' % rel_opts_name)() … … 1015 568 else: 1016 569 if rel_opts.admin: 1017 p = '%s.%s' % (rel _opts.app_label, rel_opts.get_delete_permission())570 p = '%s.%s' % (related.opts.app_label, related.opts.get_delete_permission()) 1018 571 if not user.has_perm(p): 1019 perms_needed.add(rel _opts.verbose_name)572 perms_needed.add(related.opts.verbose_name) 1020 573 # We don't care about populating deleted_objects now. 1021 574 continue 1022 if rel _field.rel.edit_inline or not rel_opts.admin:575 if related.field.rel.edit_inline or not related.opts.admin: 1023 576 # Don't display link to edit, because it either has no 1024 577 # admin or is edited inline. 1025 nh(deleted_objects, current_depth, ['%s: % r' % (capfirst(rel_opts.verbose_name), sub_obj), []])578 nh(deleted_objects, current_depth, ['%s: %s' % (capfirst(related.opts.verbose_name), sub_obj), []]) 1026 579 else: 1027 580 # Display a link to the admin page. 1028 nh(deleted_objects, current_depth, ['%s: <a href="../../../../%s/%s/%s/">% r</a>' % \1029 (capfirst(rel _opts.verbose_name), rel_opts.app_label, rel_opts.module_name,1030 getattr(sub_obj, rel _opts.pk.attname), sub_obj), []])1031 _get_deleted_objects(deleted_objects, perms_needed, user, sub_obj, rel _opts, current_depth+2)581 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), []]) 584 _get_deleted_objects(deleted_objects, perms_needed, user, sub_obj, related.opts, current_depth+2) 1032 585 else: 1033 586 has_related_objs = False 1034 587 for sub_obj in getattr(obj, 'get_%s_list' % rel_opts_name)(): 1035 588 has_related_objs = True 1036 if rel _field.rel.edit_inline or not rel_opts.admin:589 if related.field.rel.edit_inline or not related.opts.admin: 1037 590 # Don't display link to edit, because it either has no 1038 591 # admin or is edited inline. 1039 nh(deleted_objects, current_depth, ['%s: %s' % (capfirst(rel _opts.verbose_name), strip_tags(repr(sub_obj))), []])592 nh(deleted_objects, current_depth, ['%s: %s' % (capfirst(related.opts.verbose_name), strip_tags(str(sub_obj))), []]) 1040 593 else: 1041 594 # Display a link to the admin page. 1042 595 nh(deleted_objects, current_depth, ['%s: <a href="../../../../%s/%s/%s/">%s</a>' % \ 1043 (capfirst(rel _opts.verbose_name), rel_opts.app_label, rel_opts.module_name, sub_obj.id, strip_tags(repr(sub_obj))), []])1044 _get_deleted_objects(deleted_objects, perms_needed, user, sub_obj, rel _opts, current_depth+2)596 (capfirst(related.opts.verbose_name), related.opts.app_label, related.opts.module_name, sub_obj.id, strip_tags(str(sub_obj))), []]) 597 _get_deleted_objects(deleted_objects, perms_needed, user, sub_obj, related.opts, current_depth+2) 1045 598 # If there were related objects, and the user doesn't have 1046 599 # permission to delete them, add the missing perm to perms_needed. 1047 if rel _opts.admin and has_related_objs:1048 p = '%s.%s' % (rel _opts.app_label, rel_opts.get_delete_permission())600 if related.opts.admin and has_related_objs: 601 p = '%s.%s' % (related.opts.app_label, related.opts.get_delete_permission()) 1049 602 if not user.has_perm(p): 1050 603 perms_needed.add(rel_opts.verbose_name) 1051 for rel _opts, rel_field in opts.get_all_related_many_to_many_objects():1052 if rel _opts in objects_seen:604 for related in opts.get_all_related_many_to_many_objects(): 605 if related.opts in objects_seen: 1053 606 continue 1054 objects_seen.append(rel _opts)1055 rel_opts_name = opts.get_rel_object_method_name(rel_opts, rel_field)607 objects_seen.append(related.opts) 608 rel_opts_name = related.get_method_name_part() 1056 609 has_related_objs = False 1057 610 for sub_obj in getattr(obj, 'get_%s_list' % rel_opts_name)(): 1058 611 has_related_objs = True 1059 if rel _field.rel.edit_inline or not rel_opts.admin:612 if related.field.rel.edit_inline or not related.opts.admin: 1060 613 # Don't display link to edit, because it either has no 1061 614 # admin or is edited inline. 1062 nh(deleted_objects, current_depth, [ 'One or more %s in %s: %s'% \1063 (rel_field.name, rel_opts.verbose_name, strip_tags(repr(sub_obj))), []])615 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': strip_tags(str(sub_obj))}, []]) 1064 617 else: 1065 618 # Display a link to the admin page. 1066 nh(deleted_objects, current_depth, ['One or more %s in %s: <a href="../../../../%s/%s/%s/">%s</a>' % \ 1067 (rel_field.name, rel_opts.verbose_name, rel_opts.app_label, rel_opts.module_name, sub_obj.id, strip_tags(repr(sub_obj))), []]) 619 nh(deleted_objects, current_depth, [ 620 (_('One or more %(fieldname)s in %(name)s:') % {'fieldname': related.field.name, 'name':related.opts.verbose_name}) + \ 621 (' <a href="../../../../%s/%s/%s/">%s</a>' % \ 622 (related.opts.app_label, related.opts.module_name, sub_obj.id, strip_tags(str(sub_obj)))), []]) 1068 623 # If there were related objects, and the user doesn't have 1069 624 # permission to change them, add the missing perm to perms_needed. 1070 if rel _opts.admin and has_related_objs:1071 p = '%s.%s' % (rel _opts.app_label, rel_opts.get_change_permission())625 if related.opts.admin and has_related_objs: 626 p = '%s.%s' % (related.opts.app_label, related.opts.get_change_permission()) 1072 627 if not user.has_perm(p): 1073 perms_needed.add(rel _opts.verbose_name)628 perms_needed.add(related.opts.verbose_name) 1074 629 1075 630 def delete_stage(request, app_label, module_name, object_id): … … 1082 637 # Populate deleted_objects, a data structure of all related objects that 1083 638 # will also be deleted. 1084 deleted_objects = ['%s: <a href="../../%s/">%s</a>' % (capfirst(opts.verbose_name), object_id, strip_tags( repr(obj))), []]639 deleted_objects = ['%s: <a href="../../%s/">%s</a>' % (capfirst(opts.verbose_name), object_id, strip_tags(str(obj))), []] 1085 640 perms_needed = sets.Set() 1086 641 _get_deleted_objects(deleted_objects, perms_needed, request.user, obj, opts, 1) … … 1089 644 if perms_needed: 1090 645 raise PermissionDenied 1091 obj_ repr = repr(obj)646 obj_display = str(obj) 1092 647 obj.delete() 1093 log.log_action(request.user.id, opts.get_content_type_id(), object_id, obj_ repr, log.DELETION)1094 request.user.add_message( 'The %s "%s" was deleted successfully.' % (opts.verbose_name, obj_repr))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}) 1095 650 return HttpResponseRedirect("../../") 1096 651 return render_to_response('admin/delete_confirmation', { 1097 "title": "Are you sure?",652 "title": _("Are you sure?"), 1098 653 "object_name": opts.verbose_name, 1099 654 "object": obj, … … 1110 665 obj = get_object_or_404(mod, pk=object_id) 1111 666 return render_to_response('admin/object_history', { 1112 'title': 'Change history: %r'% obj,667 'title': _('Change history: %s') % obj, 1113 668 'action_list': action_list, 1114 669 'module_name': capfirst(opts.verbose_name_plural), django/trunk/django/core/formfields.py
r1422 r1434 91 91 """ 92 92 for field in self.fields: 93 if new_data.has_key(field.field_name): 94 new_data.setlist(field.field_name, 95 [field.__class__.html2python(data) for data in new_data.getlist(field.field_name)]) 96 else: 97 try: 98 # individual fields deal with None values themselves 99 new_data.setlist(field.field_name, [field.__class__.html2python(None)]) 100 except EmptyValue: 101 new_data.setlist(field.field_name, []) 93 field.convert_post_data(new_data) 102 94 103 95 class FormWrapper: … … 107 99 prepopulated data and validation error messages to the formfield objects. 108 100 """ 109 def __init__(self, manipulator, data, error_dict ):101 def __init__(self, manipulator, data, error_dict, edit_inline=True): 110 102 self.manipulator, self.data = manipulator, data 111 103 self.error_dict = error_dict 104 self._inline_collections = None 105 self.edit_inline = edit_inline 112 106 113 107 def __repr__(self): 114 return repr(self. data)108 return repr(self.__dict__) 115 109 116 110 def __getitem__(self, key): 117 111 for field in self.manipulator.fields: 118 112 if field.field_name == key: 119 if hasattr(field, 'requires_data_list') and hasattr(self.data, 'getlist'): 120 data = self.data.getlist(field.field_name) 121 else: 122 data = self.data.get(field.field_name, None) 123 if data is None: 124 data = '' 113 data = field.extract_data(self.data) 125 114 return FormFieldWrapper(field, data, self.error_dict.get(field.field_name, [])) 126 raise KeyError 115 if self.edit_inline: 116 self.fill_inline_collections() 117 for inline_collection in self._inline_collections: 118 if inline_collection.name == key: 119 return inline_collection 120 raise KeyError, "Could not find Formfield or InlineObjectCollection named %r" % key 121 122 def fill_inline_collections(self): 123 if not self._inline_collections: 124 ic = [] 125 related_objects = self.manipulator.get_related_objects() 126 for rel_obj in related_objects: 127 data = rel_obj.extract_data(self.data) 128 inline_collection = InlineObjectCollection(self.manipulator, rel_obj, data, self.error_dict) 129 ic.append(inline_collection) 130 self._inline_collections = ic 127 131 128 132 def has_errors(self): … … 167 171 return '' 168 172 173 def get_id(self): 174 return self.formfield.get_id() 175 169 176 class FormFieldCollection(FormFieldWrapper): 170 177 "A utility class that gives the template access to a dict of FormFieldWrappers" … … 186 193 errors = [] 187 194 for field in self.formfield_dict.values(): 188 errors.extend(field.errors()) 195 if hasattr(field, 'errors'): 196 errors.extend(field.errors()) 189 197 return errors 198 199 def has_errors(self): 200 return bool(len(self.errors())) 201 202 def html_combined_error_list(self): 203 return ''.join([field.html_error_list() for field in self.formfield_dict.values() if hasattr(field, 'errors')]) 204 205 class InlineObjectCollection: 206 "An object that acts like a list of form field collections." 207 def __init__(self, parent_manipulator, rel_obj, data, errors): 208 self.parent_manipulator = parent_manipulator 209 self.rel_obj = rel_obj 210 self.data = data 211 self.errors = errors 212 self._collections = None 213 self.name = rel_obj.name 214 215 def __len__(self): 216 self.fill() 217 return self._collections.__len__() 218 219 def __getitem__(self, k): 220 self.fill() 221 return self._collections.__getitem__(k) 222 223 def __setitem__(self, k, v): 224 self.fill() 225 return self._collections.__setitem__(k,v) 226 227 def __delitem__(self, k): 228 self.fill() 229 return self._collections.__delitem__(k) 230 231 def __iter__(self): 232 self.fill() 233 return self._collections.__iter__() 234 235 def fill(self): 236 if self._collections: 237 return 238 else: 239 var_name = self.rel_obj.opts.object_name.lower() 240 wrapper = [] 241 orig = hasattr(self.parent_manipulator, 'original_object') and self.parent_manipulator.original_object or None 242 orig_list = self.rel_obj.get_list(orig) 243 for i, instance in enumerate(orig_list): 244 collection = {'original': instance} 245 for f in self.rel_obj.editable_fields(): 246 for field_name in f.get_manipulator_field_names(''): 247 full_field_name = '%s.%d.%s' % (var_name, i, field_name) 248 field = self.parent_manipulator[full_field_name] 249 data = field.extract_data(self.data) 250 errors = self.errors.get(full_field_name, []) 251 collection[field_name] = FormFieldWrapper(field, data, errors) 252 wrapper.append(FormFieldCollection(collection)) 253 self._collections = wrapper 190 254 191 255 class FormField: … … 221 285 raise NotImplementedError 222 286 287 def get_member_name(self): 288 if hasattr(self, 'member_name'): 289 return self.member_name 290 else: 291 return self.field_name 292 293 def extract_data(self, data_dict): 294 if hasattr(self, 'requires_data_list') and hasattr(data_dict, 'getlist'): 295 data = data_dict.getlist(self.get_member_name()) 296 else: 297 data = data_dict.get(self.get_member_name(), None) 298 if data is None: 299 data = '' 300 return data 301 302 def convert_post_data(self, new_data): 303 name = self.get_member_name() 304 if new_data.has_key(self.field_name): 305 d = new_data.getlist(self.field_name) 306 try: 307 converted_data = [self.__class__.html2python(data) for data in d] 308 except ValueError: 309 converted_data = d 310 new_data.setlist(name, converted_data) 311 else: 312 try: 313 # individual fields deal with None values themselves 314 new_data.setlist(name, [self.__class__.html2python(None)]) 315 except EmptyValue: 316 new_data.setlist(name, []) 317 223 318 def get_id(self): 224 319 "Returns the HTML 'id' attribute for this form field." … … 314 409 315 410 class SelectField(FormField): 316 def __init__(self, field_name, choices=[], size=1, is_required=False, validator_list=[] ):411 def __init__(self, field_name, choices=[], size=1, is_required=False, validator_list=[], member_name=None): 317 412 self.field_name = field_name 318 413 # choices is a list of (value, human-readable key) tuples because order matters 319 414 self.choices, self.size, self.is_required = choices, size, is_required 320 415 self.validator_list = [self.isValidChoice] + validator_list 416 if member_name != None: 417 self.member_name = member_name 321 418 322 419 def render(self, data): … … 348 445 349 446 class RadioSelectField(FormField): 350 def __init__(self, field_name, choices=[], ul_class='', is_required=False, validator_list=[] ):447 def __init__(self, field_name, choices=[], ul_class='', is_required=False, validator_list=[], member_name=None): 351 448 self.field_name = field_name 352 449 # choices is a list of (value, human-readable key) tuples because order matters … … 354 451 self.validator_list = [self.isValidChoice] + validator_list 355 452 self.ul_class = ul_class 453 if member_name != None: 454 self.member_name = member_name 356 455 357 456 def render(self, data): … … 484 583 field_name = '%s%s' % (self.field_name, value) 485 584 output.append('<li><input type="checkbox" id="%s" class="v%s" name="%s"%s /> <label for="%s">%s</label></li>' % \ 486 (self.get_id() , self.__class__.__name__, field_name, checked_html,487 self.get_id() , choice))585 (self.get_id() + value , self.__class__.__name__, field_name, checked_html, 586 self.get_id() + value, choice)) 488 587 output.append('</ul>') 489 588 return '\n'.join(output) … … 529 628 530 629 class IntegerField(TextField): 531 def __init__(self, field_name, length=10, maxlength=None, is_required=False, validator_list=[] ):630 def __init__(self, field_name, length=10, maxlength=None, is_required=False, validator_list=[], member_name=None): 532 631 validator_list = [self.isInteger] + validator_list 632 if member_name is not None: 633 self.member_name = member_name 533 634 TextField.__init__(self, field_name, length, maxlength, is_required, validator_list) 534 635 … … 785 886 raise validators.CriticalValidationError, e.messages 786 887 888 class RawIdAdminField(CommaSeparatedIntegerField): 889 def html2python(data): 890 return data.split(','); 891 html2python = classmethod(html2python) 892 787 893 class XMLLargeTextField(LargeTextField): 788 894 """ django/trunk/django/core/management.py
r1429 r1434 671 671 672 672 # Check core=True, if needed. 673 for rel _opts, rel_field in opts.get_inline_related_objects():673 for related in opts.get_followed_related_objects(): 674 674 try: 675 for f in rel _opts.fields:675 for f in related.opts.fields: 676 676 if f.core: 677 677 raise StopIteration 678 e.add(rel _opts, "At least one field in %s should have core=True, because it's being edited inline by %s.%s." % (rel_opts.object_name, opts.module_name, opts.object_name))678 e.add(related.opts, "At least one field in %s should have core=True, because it's being edited inline by %s.%s." % (related.opts.object_name, opts.module_name, opts.object_name)) 679 679 except StopIteration: 680 680 pass django/trunk/django/core/meta/fields.py
r1423 r1434 60 60 raise validators.ValidationError, _("%(optname)s with this %(fieldname)s already exists.") % {'optname': capfirst(opts.verbose_name), 'fieldname': f.verbose_name} 61 61 62 class BoundField(object): 63 def __init__(self, field, field_mapping, original): 64 self.field = field 65 self.original = original 66 self.form_fields = self.resolve_form_fields(field_mapping) 67 68 def resolve_form_fields(self, field_mapping): 69 return [field_mapping[name] for name in self.field.get_manipulator_field_names('')] 70 71 def as_field_list(self): 72 return [self.field] 73 74 def original_value(self): 75 if self.original: 76 return self.original.__dict__[self.field.column] 77 78 def __repr__(self): 79 return "BoundField:(%s, %s)" % (self.field.name, self.form_fields) 62 80 63 81 # A guide to Field parameters: … … 186 204 return self.default.__get_value__() 187 205 return self.default 188 if self.null:206 if not self.empty_strings_allowed or self.null: 189 207 return None 190 208 return "" … … 208 226 params['maxlength'] = self.maxlength 209 227 if isinstance(self.rel, ManyToOne): 228 params['member_name'] = name_prefix + self.attname 210 229 if self.rel.raw_id_admin: 211 230 field_objs = self.get_manipulator_field_objs() … … 214 233 if self.radio_admin: 215 234 field_objs = [formfields.RadioSelectField] 216 params['choices'] = self.get_choices(include_blank=self.blank, blank_choice=BLANK_CHOICE_NONE)217 235 params['ul_class'] = get_ul_class(self.radio_admin) 218 236 else: … … 221 239 else: 222 240 field_objs = [formfields.SelectField] 223 params['choices'] = self.get_choices()241 params['choices'] = self.get_choices_default() 224 242 elif self.choices: 225 243 if self.radio_admin: 226 244 field_objs = [formfields.RadioSelectField] 227 params['choices'] = self.get_choices(include_blank=self.blank, blank_choice=BLANK_CHOICE_NONE)228 245 params['ul_class'] = get_ul_class(self.radio_admin) 229 246 else: 230 247 field_objs = [formfields.SelectField] 231 params['choices'] = self.get_choices() 248 249 params['choices'] = self.get_choices_default() 232 250 else: 233 251 field_objs = self.get_manipulator_field_objs() … … 295 313 return first_choice + list(self.choices) 296 314 rel_obj = self.rel.to 297 return first_choice + [(getattr(x, rel_obj.pk.attname), repr(x)) for x in rel_obj.get_model_module().get_list(**self.rel.limit_choices_to)] 315 return first_choice + [(getattr(x, rel_obj.pk.attname), str(x)) 316 for x in rel_obj.get_model_module().get_list(**self.rel.limit_choices_to)] 317 318 def get_choices_default(self): 319 if(self.radio_admin): 320 return self.get_choices(include_blank=self.blank, blank_choice=BLANK_CHOICE_NONE) 321 else: 322 return self.get_choices() 323 324 def _get_val_from_obj(self, obj): 325 if obj: 326 return getattr(obj, self.attname) 327 else: 328 return self.get_default() 329 330 def flatten_data(self, follow, obj = None): 331 """ 332 Returns a dictionary mapping the field's manipulator field names to its 333 "flattened" string values for the admin view. obj is the instance to 334 extract the values from. 335 """ 336 return {self.attname: self._get_val_from_obj(obj)} 337 338 def get_follow(self, override=None): 339 if override != None: 340 return override 341 else: 342 return self.editable 343 344 def bind(self, fieldmapping, original, bound_field_class=BoundField): 345 return bound_field_class(self, fieldmapping, original) 298 346 299 347 class AutoField(Field): … … 336 384 def __init__(self, verbose_name=None, name=None, auto_now=False, auto_now_add=False, **kwargs): 337 385 self.auto_now, self.auto_now_add = auto_now, auto_now_add 386 #HACKs : auto_now_add/auto_now should be done as a default or a pre_save... 338 387 if auto_now or auto_now_add: 339 388 kwargs['editable'] = False 389 kwargs['blank'] = True 340 390 Field.__init__(self, verbose_name, name, **kwargs) 341 391 … … 351 401 return datetime.datetime.now() 352 402 return value 403 404 # Needed because of horrible auto_now[_add] behaviour wrt. editable 405 def get_follow(self, override=None): 406 if override != None: 407 return override 408 else: 409 return self.editable or self.auto_now or self.auto_now_add 353 410 354 411 def get_db_prep_save(self, value): … … 360 417 def get_manipulator_field_objs(self): 361 418 return [formfields.DateField] 419 420 def flatten_data(self, follow, obj = None): 421 val = self._get_val_from_obj(obj) 422 return {self.attname: (val is not None and val.strftime("%Y-%m-%d") or '')} 362 423 363 424 class DateTimeField(DateField): … … 389 450 return datetime.datetime.combine(d, t) 390 451 return self.get_default() 452 453 def flatten_data(self,follow, obj = None): 454 val = self._get_val_from_obj(obj) 455 date_field, time_field = self.get_manipulator_field_names('') 456 return {date_field: (val is not None and val.strftime("%Y-%m-%d") or ''), 457 time_field: (val is not None and val.strftime("%H:%M:%S") or '')} 391 458 392 459 class EmailField(Field): … … 588 655 return [formfields.TimeField] 589 656 657 def flatten_data(self,follow, obj = None): 658 val = self._get_val_from_obj(obj) 659 return {self.attname: (val is not None and val.strftime("%H:%M:%S") or '')} 660 590 661 class URLField(Field): 591 662 def __init__(self, verbose_name=None, name=None, verify_exists=True, **kwargs): … … 647 718 else: 648 719 return [formfields.IntegerField] 720 721 def get_db_prep_save(self,value): 722 if value == '' or value == None: 723 return None 724 else: 725 return int(value) 726 727 def flatten_data(self, follow, obj = None): 728 if not obj: 729 # In required many-to-one fields with only one available choice, 730 # select that one available choice. Note: We have to check that 731 # the length of choices is *2*, not 1, because SelectFields always 732 # have an initial "blank" value. 733 if not self.blank and not self.rel.raw_id_admin and self.choices: 734 choice_list = self.get_choices_default() 735 if len(choice_list) == 2: 736 return { self.attname : choice_list[1][0] } 737 return Field.flatten_data(self, follow, obj) 649 738 650 739 class ManyToManyField(Field): … … 663 752 def get_manipulator_field_objs(self): 664 753 if self.rel.raw_id_admin: 665 return [formfields. CommaSeparatedIntegerField]666 else: 667 choices = self.get_choices (include_blank=False)754 return [formfields.RawIdAdminField] 755 else: 756 choices = self.get_choices_default() 668 757 return [curry(formfields.SelectMultipleField, size=min(max(len(choices), 5), 15), choices=choices)] 758 759 def get_choices_default(self): 760 return Field.get_choices(self, include_blank=False) 669 761 670 762 def get_m2m_db_table(self, original_opts): … … 689 781 } 690 782 783 def flatten_data(self, follow, obj = None): 784 new_data = {} 785 if obj: 786 get_list_func = getattr(obj, 'get_%s_list' % self.rel.singular) 787 instance_ids = [getattr(instance, self.rel.to.pk.attname) for instance in get_list_func()] 788 if self.rel.raw_id_admin: 789 new_data[self.name] = ",".join([str(id) for id in instance_ids]) 790 else: 791 new_data[self.name] = instance_ids 792 else: 793 # In required many-to-many fields with only one available choice, 794 # select that one available choice. 795 if not self.blank and not self.rel.edit_inline and not self.rel.raw_id_admin: 796 choices_list = self.get_choices_default() 797 if len(choices_list) == 1: 798 print self.name, choices_list[0][0] 799 new_data[self.name] = [choices_list[0][0]] 800 return new_data 801 691 802 class OneToOneField(IntegerField): 692 803 def __init__(self, to, to_field=None, **kwargs): … … 754 865 self.raw_id_admin = raw_id_admin 755 866 867 class BoundFieldLine(object): 868 def __init__(self, field_line, field_mapping, original, bound_field_class=BoundField): 869 self.bound_fields = [field.bind(field_mapping, original, bound_field_class) for field in field_line] 870 871 def __iter__(self): 872 for bound_field in self.bound_fields: 873 yield bound_field 874 875 def __len__(self): 876 return len(self.bound_fields) 877 878 class FieldLine(object): 879 def __init__(self, field_locator_func, linespec): 880 if isinstance(linespec, basestring): 881 self.fields = [field_locator_func(linespec)] 882 else: 883 self.fields = [field_locator_func(field_name) for field_name in linespec] 884 885 def bind(self, field_mapping, original, bound_field_line_class=BoundFieldLine): 886 return bound_field_line_class(self, field_mapping, original) 887 888 def __iter__(self): 889 for field in self.fields: 890 yield field 891 892 def __len__(self): 893 return len(self.fields) 894 895 class BoundFieldSet(object): 896 def __init__(self, field_set, field_mapping, original, bound_field_line_class=BoundFieldLine): 897 self.name = field_set.name 898 self.classes = field_set.classes 899 self.bound_field_lines = [field_line.bind(field_mapping,original, bound_field_line_class) for field_line in field_set] 900 901 def __iter__(self): 902 for bound_field_line in self.bound_field_lines: 903 yield bound_field_line 904 905 def __len__(self): 906 return len(self.bound_field_lines) 907 908 class FieldSet(object): 909 def __init__(self, name, classes, field_locator_func, line_specs): 910 self.name = name 911 self.field_lines = [FieldLine(field_locator_func, line_spec) for line_spec in line_specs] 912 self.classes = classes 913 914 def __repr__(self): 915 return "FieldSet:(%s,%s)" % (self.name, self.field_lines) 916 917 def bind(self, field_mapping, original, bound_field_set_class=BoundFieldSet): 918 return bound_field_set_class(self, field_mapping, original) 919 920 def __iter__(self): 921 for field_line in self.field_lines: 922 yield field_line 923 924 def __len__(self): 925 return len(self.field_lines) 926 756 927 class Admin: 757 928 def __init__(self, fields=None, js=None, list_display=None, list_filter=None, date_hierarchy=None, … … 767 938 self.list_select_related = list_select_related 768 939 769 def get_field_objs(self, opts): 770 """ 771 Returns self.fields, except with fields as Field objects instead of 772 field names. If self.fields is None, defaults to putting every 773 non-AutoField field with editable=True in a single fieldset. 774 """ 940 def get_field_sets(self, opts): 775 941 if self.fields is None: 776 field_struct = ((None, {'fields': [f.name for f in opts.fields + opts.many_to_many if f.editable and not isinstance(f, AutoField)]}),) 942 field_struct = ((None, { 943 'fields': [f.name for f in opts.fields + opts.many_to_many if f.editable and not isinstance(f, AutoField)] 944 }),) 777 945 else: 778 946 field_struct = self.fields 779 947 new_fieldset_list = [] 780 948 for fieldset in field_struct: 781 new_fieldset = [fieldset[0], {}] 782 new_fieldset[1].update(fieldset[1]) 783 admin_fields = [] 784 for field_name_or_list in fieldset[1]['fields']: 785 if isinstance(field_name_or_list, basestring): 786 admin_fields.append([opts.get_field(field_name_or_list)]) 787 else: 788 admin_fields.append([opts.get_field(field_name) for field_name in field_name_or_list]) 789 new_fieldset[1]['fields'] = admin_fields 790 new_fieldset_list.append(new_fieldset) 949 name = fieldset[0] 950 fs_options = fieldset[1] 951 classes = fs_options.get('classes', ()) 952 line_specs = fs_options['fields'] 953 new_fieldset_list.append(FieldSet(name, classes, opts.get_field, line_specs)) 791 954 return new_fieldset_list django/trunk/django/core/meta/__init__.py
r1326 r1434 148 148 class BadKeywordArguments(Exception): 149 149 pass 150 151 class BoundRelatedObject(object): 152 def __init__(self, related_object, field_mapping, original): 153 self.relation = related_object 154 self.field_mappings = field_mapping[related_object.opts.module_name] 155 156 def template_name(self): 157 raise NotImplementedError 158 159 def __repr__(self): 160 return repr(self.__dict__) 161 162 class RelatedObject(object): 163 def __init__(self, parent_opts, opts, field): 164 self.parent_opts = parent_opts 165 self.opts = opts 166 self.field = field 167 self.edit_inline = field.rel.edit_inline 168 self.name = opts.module_name 169 self.var_name = opts.object_name.lower() 170 171 def flatten_data(self, follow, obj=None): 172 new_data = {} 173 rel_instances = self.get_list(obj) 174 for i, rel_instance in enumerate(rel_instances): 175 instance_data = {} 176 for f in self.opts.fields + self.opts.many_to_many: 177 # TODO: Fix for recursive manipulators. 178 fol = follow.get(f.name, None) 179 if fol: 180 field_data = f.flatten_data(fol, rel_instance) 181 for name, value in field_data.items(): 182 instance_data['%s.%d.%s' % (self.var_name, i, name)] = value 183 new_data.update(instance_data) 184 return new_data 185 186 def extract_data(self, data): 187 """ 188 Pull out the data meant for inline objects of this class, 189 i.e. anything starting with our module name. 190 """ 191 return data # TODO 192 193 def get_list(self, parent_instance=None): 194 "Get the list of this type of object from an instance of the parent class." 195 if parent_instance != None: 196 func_name = 'get_%s_list' % self.get_method_name_part() 197 func = getattr(parent_instance, func_name) 198 list = func() 199 200 count = len(list) + self.field.rel.num_extra_on_change 201 if self.field.rel.min_num_in_admin: 202 count = max(count, self.field.rel.min_num_in_admin) 203 if self.field.rel.max_num_in_admin: 204 count = min(count, self.field.rel.max_num_in_admin) 205 206 change = count - len(list) 207 if change > 0: 208 return list + [None for _ in range(change)] 209 if change < 0: 210 return list[:change] 211 else: # Just right 212 return list 213 else: 214 return [None for _ in range(self.field.rel.num_in_admin)] 215 216 217 def editable_fields(self): 218 "Get the fields in this class that should be edited inline." 219 return [f for f in self.opts.fields + self.opts.many_to_many if f.editable and f != self.field] 220 221 def get_follow(self, override=None): 222 if isinstance(override, bool): 223 if override: 224 over = {} 225 else: 226 return None 227 else: 228 if override: 229 over = override.copy() 230 elif self.edit_inline: 231 over = {} 232 else: 233 return None 234 235 over[self.field.name] = False 236 return self.opts.get_follow(over) 237 238 def __repr__(self): 239 return "<RelatedObject: %s related to %s>" % ( self.name, self.field.name) 240 241 def get_manipulator_fields(self, opts, manipulator, change, follow): 242 # TODO: Remove core fields stuff. 243 if change: 244 meth_name = 'get_%s_count' % self.get_method_name_part() 245 count = getattr(manipulator.original_object, meth_name)() 246 count += self.field.rel.num_extra_on_change 247 if self.field.rel.min_num_in_admin: 248 count = max(count, self.field.rel.min_num_in_admin) 249 if self.field.rel.max_num_in_admin: 250 count = min(count, self.field.rel.max_num_in_admin) 251 else: 252 count = self.field.rel.num_in_admin 253 254 fields = [] 255 for i in range(count): 256 for f in self.opts.fields + self.opts.many_to_many: 257 if follow.get(f.name, False): 258 prefix = '%s.%d.' % (self.var_name, i) 259 fields.extend(f.get_manipulator_fields(self.opts, manipulator, change, name_prefix=prefix, rel=True)) 260 return fields 261 262 def bind(self, field_mapping, original, bound_related_object_class=BoundRelatedObject): 263 return bound_related_object_class(self, field_mapping, original) 264 265 def get_method_name_part(self): 266 # This method encapsulates the logic that decides what name to give a 267 # method that retrieves related many-to-one objects. Usually it just 268 # uses the lower-cased object_name, but if the related object is in 269 # another app, its app_label is appended. 270 # 271 # Examples: 272 # 273 # # Normal case -- a related object in the same app. 274 # # This method returns "choice". 275 # Poll.get_choice_list() 276 # 277 # # A related object in a different app. 278 # # This method returns "lcom_bestofaward". 279 # Place.get_lcom_bestofaward_list() # "lcom_bestofaward" 280 rel_obj_name = self.field.rel.related_name or self.opts.object_name.lower() 281 if self.parent_opts.app_label != self.opts.app_label: 282 rel_obj_name = '%s_%s' % (self.opts.app_label, rel_obj_name) 283 return rel_obj_name 150 284 151 285 class Options: … … 269 403 return 'delete_%s' % self.object_name.lower() 270 404 271 def get_rel_object_method_name(self, rel_opts, rel_field):272 # This method encapsulates the logic that decides what name to give a273 # method that retrieves related many-to-one objects. Usually it just274 # uses the lower-cased object_name, but if the related object is in275 # another app, its app_label is appended.276 #277 # Examples:278 #279 # # Normal case -- a related object in the same app.280 # # This method returns "choice".281 # Poll.get_choice_list()282 #283 # # A related object in a different app.284 # # This method returns "lcom_bestofaward".285 # Place.get_lcom_bestofaward_list() # "lcom_bestofaward"286 rel_obj_name = rel_field.rel.related_name or rel_opts.object_name.lower()287 if self.app_label != rel_opts.app_label:288 rel_obj_name = '%s_%s' % (rel_opts.app_label, rel_obj_name)289 return rel_obj_name290 291 405 def get_all_related_objects(self): 292 406 try: # Try the cache first. … … 299 413 for f in klass._meta.fields: 300 414 if f.rel and self == f.rel.to: 301 rel_objs.append( (klass._meta, f))415 rel_objs.append(RelatedObject(self, klass._meta, f)) 302 416 if self.has_related_links: 303 417 # Manually add RelatedLink objects, which are a special case. … … 313 427 'content_type__python_module_name__exact': self.module_name, 314 428 }) 315 rel_objs.append( (relatedlinks.RelatedLink._meta, link_field))429 rel_objs.append(RelatedObject(self, relatedlinks.RelatedLink._meta, link_field)) 316 430 self._all_related_objects = rel_objs 317 431 return rel_objs 318 432 319 def get_inline_related_objects(self): 320 return [(a, b) for a, b in self.get_all_related_objects() if b.rel.edit_inline] 433 def get_followed_related_objects(self, follow=None): 434 if follow == None: 435 follow = self.get_follow() 436 return [f for f in self.get_all_related_objects() if follow.get(f.name, None)] 437 438 def get_data_holders(self, follow=None): 439 if follow == None: 440 follow = self.get_follow() 441 return [f for f in self.fields + self.many_to_many + self.get_all_related_objects() if follow.get(f.name, None)] 442 443 def get_follow(self, override=None): 444 follow = {} 445 for f in self.fields + self.many_to_many + self.get_all_related_objects(): 446 if override and override.has_key(f.name): 447 child_override = override[f.name] 448 else: 449 child_override = None 450 fol = f.get_follow(child_override) 451 if fol: 452 follow[f.name] = fol 453 return follow 321 454 322 455 def get_all_related_many_to_many_objects(self): … … 328 461 for f in klass._meta.many_to_many: 329 462 if f.rel and self == f.rel.to: 330 rel_objs.append( (klass._meta, f))463 rel_objs.append(RelatedObject(self, klass._meta, f)) 331 464 raise StopIteration 332 465 except StopIteration: … … 346 479 return self._ordered_objects 347 480 348 def has_field_type(self, field_type ):481 def has_field_type(self, field_type, follow=None): 349 482 """ 350 483 Returns True if this object's admin form has at least one of the given 351 484 field_type (e.g. FileField). 352 485 """ 486 # TODO: follow 353 487 if not hasattr(self, '_field_types'): 354 488 self._field_types = {} … … 360 494 raise StopIteration 361 495 # Failing that, check related fields. 362 for rel _obj, rel_field in self.get_inline_related_objects():363 for f in rel _obj.fields:496 for related in self.get_followed_related_objects(follow): 497 for f in related.opts.fields: 364 498 if isinstance(f, field_type): 365 499 raise StopIteration … … 598 732 599 733 for f in opts.fields: 734 #TODO : change this into a virtual function so that user defined fields will be able to add methods to module or class. 600 735 if f.choices: 601 736 # Add "get_thingie_display" method to get human-readable value. … … 721 856 # Replace all relationships to the old class with 722 857 # relationships to the new one. 723 for rel_opts, rel_field in model._meta.get_all_related_objects(): 724 rel_field.rel.to = opts 725 for rel_opts, rel_field in model._meta.get_all_related_many_to_many_objects(): 726 rel_field.rel.to = opts 858 for related in model._meta.get_all_related_objects() + model._meta.get_all_related_many_to_many_objects(): 859 related.field.rel.to = opts 727 860 break 728 729 861 return new_class 730 862 … … 827 959 self._pre_delete() 828 960 cursor = db.db.cursor() 829 for rel _opts, rel_field in opts.get_all_related_objects():830 rel_opts_name = opts.get_rel_object_method_name(rel_opts, rel_field)831 if isinstance(rel _field.rel, OneToOne):961 for related in opts.get_all_related_objects(): 962 rel_opts_name = related.get_method_name_part() 963 if isinstance(related.field.rel, OneToOne): 832 964 try: 833 965 sub_obj = getattr(self, 'get_%s' % rel_opts_name)() … … 839 971 for sub_obj in getattr(self, 'get_%s_list' % rel_opts_name)(): 840 972 sub_obj.delete() 841 for rel _opts, rel_field in opts.get_all_related_many_to_many_objects():973 for related in opts.get_all_related_many_to_many_objects(): 842 974 cursor.execute("DELETE FROM %s WHERE %s=%%s" % \ 843 (db.db.quote_name(rel _field.get_m2m_db_table(rel_opts)),975 (db.db.quote_name(related.field.get_m2m_db_table(related.opts)), 844 976 db.db.quote_name(self._meta.object_name.lower() + '_id')), [getattr(self, opts.pk.attname)]) 845 977 for f in opts.many_to_many: … … 1475 1607 man.__init__ = curry(manipulator_init, opts, add, change) 1476 1608 man.save = curry(manipulator_save, opts, klass, add, change) 1609 man.get_related_objects = curry(manipulator_get_related_objects, opts, klass, add, change) 1610 man.flatten_data = curry(manipulator_flatten_data, opts, klass, add, change) 1477 1611 for field_name_list in opts.unique_together: 1478 1612 setattr(man, 'isUnique%s' % '_'.join(field_name_list), curry(manipulator_validator_unique_together, field_name_list, opts)) … … 1488 1622 return man 1489 1623 1490 def manipulator_init(opts, add, change, self, obj_key=None): 1624 def manipulator_init(opts, add, change, self, obj_key=None, follow=None): 1625 self.follow = opts.get_follow(follow) 1626 1491 1627 if change: 1492 1628 assert obj_key is not None, "ChangeManipulator.__init__() must be passed obj_key parameter." … … 1512 1648 raise 1513 1649 self.fields = [] 1650 1514 1651 for f in opts.fields + opts.many_to_many: 1515 if f.editable and not (f.primary_key and change) and (not f.rel or not f.rel.edit_inline):1652 if self.follow.get(f.name, False): 1516 1653 self.fields.extend(f.get_manipulator_fields(opts, self, change)) 1517 1654 1518 1655 # Add fields for related objects. 1519 for rel_opts, rel_field in opts.get_inline_related_objects(): 1520 if change: 1521 count = getattr(self.original_object, 'get_%s_count' % opts.get_rel_object_method_name(rel_opts, rel_field))() 1522 count += rel_field.rel.num_extra_on_change 1523 if rel_field.rel.min_num_in_admin: 1524 count = max(count, rel_field.rel.min_num_in_admin) 1525 if rel_field.rel.max_num_in_admin: 1526 count = min(count, rel_field.rel.max_num_in_admin) 1527 else: 1528 count = rel_field.rel.num_in_admin 1529 for f in rel_opts.fields + rel_opts.many_to_many: 1530 if f.editable and f != rel_field and (not f.primary_key or (f.primary_key and change)): 1531 for i in range(count): 1532 self.fields.extend(f.get_manipulator_fields(rel_opts, self, change, name_prefix='%s.%d.' % (rel_opts.object_name.lower(), i), rel=True)) 1656 for f in opts.get_all_related_objects(): 1657 if self.follow.get(f.name, False): 1658 fol = self.follow[f.name] 1659 self.fields.extend(f.get_manipulator_fields(opts, self, change, fol)) 1533 1660 1534 1661 # Add field for ordering. … … 1537 1664 1538 1665 def manipulator_save(opts, klass, add, change, self, new_data): 1666 # TODO: big cleanup when core fields go -> use recursive manipulators. 1539 1667 from django.utils.datastructures import DotExpandedDict 1540 1668 params = {} 1541 1669 for f in opts.fields: 1542 # Fields with auto_now_add are another special case; they should keep1543 # their original value in the change stage.1544 if change and getattr(f, 'auto_now_add', False):1545 param s[f.attname] = getattr(self.original_object, f.attname)1670 # Fields with auto_now_add should keep their original value in the change stage. 1671 auto_now_add = change and getattr(f, 'auto_now_add', False) 1672 if self.follow.get(f.name, None) and not auto_now_add: 1673 param = f.get_manipulator_new_data(new_data) 1546 1674 else: 1547 params[f.attname] = f.get_manipulator_new_data(new_data) 1675 if change: 1676 param = getattr(self.original_object, f.attname) 1677 else: 1678 param = f.get_default() 1679 params[f.attname] = param 1680 1548 1681 1549 1682 if change: … … 1568 1701 # Save many-to-many objects. Example: Poll.set_sites() 1569 1702 for f in opts.many_to_many: 1570 if not f.rel.edit_inline: 1571 was_changed = getattr(new_object, 'set_%s' % f.name)(new_data.getlist(f.name)) 1572 if change and was_changed: 1573 self.fields_changed.append(f.verbose_name) 1574 1703 if self.follow.get(f.name, None): 1704 if not f.rel.edit_inline: 1705 was_changed = getattr(new_object, 'set_%s' % f.name)(new_data.getlist(f.name)) 1706 if change and was_changed: 1707 self.fields_changed.append(f.verbose_name) 1708 1709 expanded_data = DotExpandedDict(new_data.data) 1575 1710 # Save many-to-one objects. Example: Add the Choice objects for a Poll. 1576 for rel _opts, rel_field in opts.get_inline_related_objects():1711 for related in opts.get_all_related_objects(): 1577 1712 # Create obj_list, which is a DotExpandedDict such as this: 1578 1713 # [('0', {'id': ['940'], 'choice': ['This is the first choice']}), 1579 1714 # ('1', {'id': ['941'], 'choice': ['This is the second choice']}), 1580 1715 # ('2', {'id': [''], 'choice': ['']})] 1581 obj_list = DotExpandedDict(new_data.data)[rel_opts.object_name.lower()].items() 1582 obj_list.sort(lambda x, y: cmp(int(x[0]), int(y[0]))) 1583 params = {} 1584 1585 # For each related item... 1586 for _, rel_new_data in obj_list: 1587 1588 # Keep track of which core=True fields were provided. 1589 # If all core fields were given, the related object will be saved. 1590 # If none of the core fields were given, the object will be deleted. 1591 # If some, but not all, of the fields were given, the validator would 1592 # have caught that. 1593 all_cores_given, all_cores_blank = True, True 1594 1595 # Get a reference to the old object. We'll use it to compare the 1596 # old to the new, to see which fields have changed. 1597 if change: 1716 child_follow = self.follow.get(related.name, None) 1717 1718 if child_follow: 1719 obj_list = expanded_data[related.var_name].items() 1720 obj_list.sort(lambda x, y: cmp(int(x[0]), int(y[0]))) 1721 params = {} 1722 1723 1724 # For each related item... 1725 for _, rel_new_data in obj_list: 1726 1727 # Keep track of which core=True fields were provided. 1728 # If all core fields were given, the related object will be saved. 1729 # If none of the core fields were given, the object will be deleted. 1730 # If some, but not all, of the fields were given, the validator would 1731 # have caught that. 1732 all_cores_given, all_cores_blank = True, True 1733 1734 # Get a reference to the old object. We'll use it to compare the 1735 # old to the new, to see which fields have changed. 1598 1736 old_rel_obj = None 1599 if rel_new_data[rel_opts.pk.name][0]: 1600 try: 1601 old_rel_obj = getattr(self.original_object, 'get_%s' % opts.get_rel_object_method_name(rel_opts, rel_field))(**{'%s__exact' % rel_opts.pk.name: rel_new_data[rel_opts.pk.attname][0]}) 1602 except ObjectDoesNotExist: 1603 pass 1604 1605 for f in rel_opts.fields: 1606 if f.core and not isinstance(f, FileField) and f.get_manipulator_new_data(rel_new_data, rel=True) in (None, ''): 1607 all_cores_given = False 1608 elif f.core and not isinstance(f, FileField) and f.get_manipulator_new_data(rel_new_data, rel=True) not in (None, ''): 1609 all_cores_blank = False 1610 # If this field isn't editable, give it the same value it had 1611 # previously, according to the given ID. If the ID wasn't 1612 # given, use a default value. FileFields are also a special 1613 # case, because they'll be dealt with later. 1614 if change and (isinstance(f, FileField) or not f.editable): 1615 if rel_new_data.get(rel_opts.pk.attname, False) and rel_new_data[rel_opts.pk.attname][0]: 1616 params[f.attname] = getattr(old_rel_obj, f.attname) 1737 if change: 1738 if rel_new_data[related.opts.pk.name][0]: 1739 try: 1740 old_rel_obj = getattr(self.original_object, 'get_%s' % related.get_method_name_part() )(**{'%s__exact' % related.opts.pk.name: rel_new_data[related.opts.pk.attname][0]}) 1741 except ObjectDoesNotExist: 1742 pass 1743 1744 for f in related.opts.fields: 1745 if f.core and not isinstance(f, FileField) and f.get_manipulator_new_data(rel_new_data, rel=True) in (None, ''): 1746 all_cores_given = False 1747 elif f.core and not isinstance(f, FileField) and f.get_manipulator_new_data(rel_new_data, rel=True) not in (None, ''): 1748 all_cores_blank = False 1749 # If this field isn't editable, give it the same value it had 1750 # previously, according to the given ID. If the ID wasn't 1751 # given, use a default value. FileFields are also a special 1752 # case, because they'll be dealt with later. 1753 1754 if f == related.field: 1755 param = getattr(new_object, related.field.rel.field_name) 1756 elif add and isinstance(f, AutoField): 1757 param = None 1758 elif change and (isinstance(f, FileField) or not child_follow.get(f.name, None)): 1759 if old_rel_obj: 1760 param = getattr(old_rel_obj, f.column) 1761 else: 1762 param = f.get_default() 1617 1763 else: 1618 params[f.attname] = f.get_default() 1619 elif f == rel_field: 1620 params[f.attname] = getattr(new_object, rel_field.rel.field_name) 1621 elif add and isinstance(f, AutoField): 1622 params[f.attname] = None 1623 else: 1624 params[f.attname] = f.get_manipulator_new_data(rel_new_data, rel=True) 1625 # Related links are a special case, because we have to 1626 # manually set the "content_type_id" and "object_id" fields. 1627 if opts.has_related_links and rel_opts.module_name == 'relatedlinks': 1628 contenttypes_mod = get_module('core', 'contenttypes') 1629 params['content_type_id'] = contenttypes_mod.get_object(package__label__exact=opts.app_label, python_module_name__exact=opts.module_name).id 1630 params['object_id'] = new_object.id 1631 1632 # Create the related item. 1633 new_rel_obj = rel_opts.get_model_module().Klass(**params) 1634 1635 # If all the core fields were provided (non-empty), save the item. 1636 if all_cores_given: 1637 new_rel_obj.save() 1638 1639 # Save any uploaded files. 1640 for f in rel_opts.fields: 1641 if isinstance(f, FileField) and rel_new_data.get(f.attname, False): 1642 f.save_file(rel_new_data, new_rel_obj, change and old_rel_obj or None, old_rel_obj is not None, rel=True) 1643 1644 # Calculate whether any fields have changed. 1645 if change: 1646 if not old_rel_obj: # This object didn't exist before. 1647 self.fields_added.append('%s "%r"' % (rel_opts.verbose_name, new_rel_obj)) 1648 else: 1649 for f in rel_opts.fields: 1650 if not f.primary_key and f != rel_field and str(getattr(old_rel_obj, f.attname)) != str(getattr(new_rel_obj, f.attname)): 1651 self.fields_changed.append('%s for %s "%r"' % (f.verbose_name, rel_opts.verbose_name, new_rel_obj)) 1652 1653 # Save many-to-many objects. 1654 for f in rel_opts.many_to_many: 1655 if not f.rel.edit_inline: 1656 was_changed = getattr(new_rel_obj, 'set_%s' % f.name)(rel_new_data[f.attname]) 1657 if change and was_changed: 1658 self.fields_changed.append('%s for %s "%s"' % (f.verbose_name, rel_opts.verbose_name, new_rel_obj)) 1659 1660 # If, in the change stage, all of the core fields were blank and 1661 # the primary key (ID) was provided, delete the item. 1662 if change and all_cores_blank and rel_new_data.has_key(rel_opts.pk.attname) and rel_new_data[rel_opts.pk.attname][0]: 1663 new_rel_obj.delete() 1664 self.fields_deleted.append('%s "%r"' % (rel_opts.verbose_name, old_rel_obj)) 1764 param = f.get_manipulator_new_data(rel_new_data, rel=True) 1765 if param != None: 1766 params[f.attname] = param 1767 1768 1769 # Related links are a special case, because we have to 1770 # manually set the "content_type_id" and "object_id" fields. 1771 if opts.has_related_links and related.opts.module_name == 'relatedlinks': 1772 contenttypes_mod = get_module('core', 'contenttypes') 1773 params['content_type_id'] = contenttypes_mod.get_object(package__label__exact=opts.app_label, python_module_name__exact=opts.module_name).id 1774 params['object_id'] = new_object.id 1775 1776 # Create the related item. 1777 new_rel_obj = related.opts.get_model_module().Klass(**params) 1778 1779 1780 1781 # If all the core fields were provided (non-empty), save the item. 1782 if all_cores_given: 1783 new_rel_obj.save() 1784 1785 # Save any uploaded files. 1786 for f in related.opts.fields: 1787 if child_follow.get(f.name, None): 1788 if isinstance(f, FileField) and rel_new_data.get(f.name, False): 1789 f.save_file(rel_new_data, new_rel_obj, change and old_rel_obj or None, old_rel_obj is not None, rel=True) 1790 1791 # Calculate whether any fields have changed. 1792 if change: 1793 if not old_rel_obj: # This object didn't exist before. 1794 self.fields_added.append('%s "%s"' % (related.opts.verbose_name, new_rel_obj)) 1795 else: 1796 for f in related.opts.fields: 1797 if not f.primary_key and f != related.field and str(getattr(old_rel_obj, f.attname)) != str(getattr(new_rel_obj, f.attname)): 1798 self.fields_changed.append('%s for %s "%s"' % (f.verbose_name, related.opts.verbose_name, new_rel_obj)) 1799 1800 # Save many-to-many objects. 1801 for f in related.opts.many_to_many: 1802 if child_follow.get(f.name, None) and not f.rel.edit_inline: 1803 was_changed = getattr(new_rel_obj, 'set_%s' % f.name)(rel_new_data[f.attname]) 1804 if change and was_changed: 1805 self.fields_changed.append('%s for %s "%s"' % (f.verbose_name, related.opts.verbose_name, new_rel_obj)) 1806 1807 # If, in the change stage, all of the core fields were blank and 1808 # the primary key (ID) was provided, delete the item. 1809 if change and all_cores_blank and old_rel_obj: 1810 new_rel_obj.delete() 1811 self.fields_deleted.append('%s "%s"' % (related.opts.verbose_name, old_rel_obj)) 1812 1665 1813 1666 1814 # Save the order, if applicable. … … 1670 1818 getattr(new_object, 'set_%s_order' % rel_opts.object_name.lower())(order) 1671 1819 return new_object 1820 1821 def manipulator_get_related_objects(opts, klass, add, change, self): 1822 return opts.get_followed_related_objects(self.follow) 1823 1824 def manipulator_flatten_data(opts, klass, add, change, self): 1825 new_data = {} 1826 obj = change and self.original_object or None 1827 for f in opts.get_data_holders(self.follow): 1828 fol = self.follow.get(f.name) 1829 new_data.update(f.flatten_data(fol, obj)) 1830 return new_data 1672 1831 1673 1832 def manipulator_validator_unique_together(field_name_list, opts, self, field_data, all_data): … … 1679 1838 kwargs = {'%s__iexact' % field_name_list[0]: field_data} 1680 1839 for f in field_list[1:]: 1840 # This is really not going to work for fields that have different 1841 # form fields, e.g. DateTime. 1842 # This validation needs to occur after html2python to be effective. 1681 1843 field_val = all_data.get(f.attname, None) 1682 1844 if field_val is None: django/trunk/django/models/__init__.py
r1401 r1434 20 20 # Add "get_thingie", "get_thingie_count" and "get_thingie_list" methods 21 21 # for all related objects. 22 for rel _obj, rel_field in klass._meta.get_all_related_objects():22 for related in klass._meta.get_all_related_objects(): 23 23 # Determine whether this related object is in another app. 24 24 # If it's in another app, the method names will have the app 25 25 # label prepended, and the add_BLAH() method will not be 26 26 # generated. 27 rel_mod = rel _obj.get_model_module()28 rel_obj_name = klass._meta.get_rel_object_method_name(rel_obj, rel_field)29 if isinstance(rel _field.rel, meta.OneToOne):27 rel_mod = related.opts.get_model_module() 28 rel_obj_name = related.get_method_name_part() 29 if isinstance(related.field.rel, meta.OneToOne): 30 30 # Add "get_thingie" methods for one-to-one related objects. 31 31 # EXAMPLE: Place.get_restaurants_restaurant() 32 func = curry(meta.method_get_related, 'get_object', rel_mod, rel _field)33 func.__doc__ = "Returns the associated `%s.%s` object." % (rel _obj.app_label, rel_obj.module_name)32 func = curry(meta.method_get_related, 'get_object', rel_mod, related.field) 33 func.__doc__ = "Returns the associated `%s.%s` object." % (related.opts.app_label, related.opts.module_name) 34 34 setattr(klass, 'get_%s' % rel_obj_name, func) 35 elif isinstance(rel _field.rel, meta.ManyToOne):35 elif isinstance(related.field.rel, meta.ManyToOne): 36 36 # Add "get_thingie" methods for many-to-one related objects. 37 37 # EXAMPLE: Poll.get_choice() 38 func = curry(meta.method_get_related, 'get_object', rel_mod, rel_field) 39 func.__doc__ = "Returns the associated `%s.%s` object matching the given criteria." % (rel_obj.app_label, rel_obj.module_name) 38 func = curry(meta.method_get_related, 'get_object', rel_mod, related.field) 39 func.__doc__ = "Returns the associated `%s.%s` object matching the given criteria." % \ 40 (related.opts.app_label, related.opts.module_name) 40 41 setattr(klass, 'get_%s' % rel_obj_name, func) 41 42 # Add "get_thingie_count" methods for many-to-one related objects. 42 43 # EXAMPLE: Poll.get_choice_count() 43 func = curry(meta.method_get_related, 'get_count', rel_mod, rel_field) 44 func.__doc__ = "Returns the number of associated `%s.%s` objects." % (rel_obj.app_label, rel_obj.module_name) 44 func = curry(meta.method_get_related, 'get_count', rel_mod, related.field) 45 func.__doc__ = "Returns the number of associated `%s.%s` objects." % \ 46 (related.opts.app_label, related.opts.module_name) 45 47 setattr(klass, 'get_%s_count' % rel_obj_name, func) 46 48 # Add "get_thingie_list" methods for many-to-one related objects. 47 49 # EXAMPLE: Poll.get_choice_list() 48 func = curry(meta.method_get_related, 'get_list', rel_mod, rel_field) 49 func.__doc__ = "Returns a list of associated `%s.%s` objects." % (rel_obj.app_label, rel_obj.module_name) 50 func = curry(meta.method_get_related, 'get_list', rel_mod, related.field) 51 func.__doc__ = "Returns a list of associated `%s.%s` objects." % \ 52 (related.opts.app_label, related.opts.module_name) 50 53 setattr(klass, 'get_%s_list' % rel_obj_name, func) 51 54 # Add "add_thingie" methods for many-to-one related objects, 52 55 # but only for related objects that are in the same app. 53 56 # EXAMPLE: Poll.add_choice() 54 if rel _obj.app_label == klass._meta.app_label:55 func = curry(meta.method_add_related, rel _obj, rel_mod, rel_field)57 if related.opts.app_label == klass._meta.app_label: 58 func = curry(meta.method_add_related, related.opts, rel_mod, related.field) 56 59 func.alters_data = True 57 60 setattr(klass, 'add_%s' % rel_obj_name, func) 58 61 del func 59 del rel_obj_name, rel_mod, rel _obj, rel_field # clean up62 del rel_obj_name, rel_mod, related # clean up 60 63 61 64 # Do the same for all related many-to-many objects. 62 for rel _opts, rel_field in klass._meta.get_all_related_many_to_many_objects():63 rel_mod = rel _opts.get_model_module()64 rel_obj_name = klass._meta.get_rel_object_method_name(rel_opts, rel_field)65 setattr(klass, 'get_%s' % rel_obj_name, curry(meta.method_get_related_many_to_many, 'get_object', klass._meta, rel_mod, rel _field))66 setattr(klass, 'get_%s_count' % rel_obj_name, curry(meta.method_get_related_many_to_many, 'get_count', klass._meta, rel_mod, rel _field))67 setattr(klass, 'get_%s_list' % rel_obj_name, curry(meta.method_get_related_many_to_many, 'get_list', klass._meta, rel_mod, rel _field))68 if rel _opts.app_label == klass._meta.app_label:69 func = curry(meta.method_set_related_many_to_many, rel _opts, rel_field)65 for related in klass._meta.get_all_related_many_to_many_objects(): 66 rel_mod = related.opts.get_model_module() 67 rel_obj_name = related.get_method_name_part() 68 setattr(klass, 'get_%s' % rel_obj_name, curry(meta.method_get_related_many_to_many, 'get_object', klass._meta, rel_mod, related.field)) 69 setattr(klass, 'get_%s_count' % rel_obj_name, curry(meta.method_get_related_many_to_many, 'get_count', klass._meta, rel_mod, related.field)) 70 setattr(klass, 'get_%s_list' % rel_obj_name, curry(meta.method_get_related_many_to_many, 'get_list', klass._meta, rel_mod, related.field)) 71 if related.opts.app_label == klass._meta.app_label: 72 func = curry(meta.method_set_related_many_to_many, related.opts, related.field) 70 73 func.alters_data = True 71 setattr(klass, 'set_%s' % rel _opts.module_name, func)74 setattr(klass, 'set_%s' % related.opts.module_name, func) 72 75 del func 73 del rel_obj_name, rel_mod, rel _opts, rel_field # clean up76 del rel_obj_name, rel_mod, related # clean up 74 77 75 78 # Add "set_thingie_order" and "get_thingie_order" methods for objects django/trunk/django/views/generic/create_update.py
r734 r1434 10 10 def create_object(request, app_label, module_name, template_name=None, 11 11 template_loader=template_loader, extra_context={}, 12 post_save_redirect=None, login_required=False ):12 post_save_redirect=None, login_required=False, follow=None): 13 13 """ 14 14 Generic object-creation function. … … 23 23 24 24 mod = models.get_module(app_label, module_name) 25 manipulator = mod.AddManipulator( )25 manipulator = mod.AddManipulator(follow=follow) 26 26 if request.POST: 27 27 # If data was POSTed, we're trying to create a new object … … 30 30 # Check for errors 31 31 errors = manipulator.get_validation_errors(new_data) 32 manipulator.do_html2python(new_data) 32 33 33 34 if not errors: 34 35 # No errors -- this means we can save the data! 35 manipulator.do_html2python(new_data)36 36 new_object = manipulator.save(new_data) 37 37 … … 49 49 else: 50 50 # No POST, so we want a brand new form without any data or errors 51 errors = new_data = {} 51 errors = {} 52 new_data = manipulator.flatten_data() 52 53 53 54 # Create the FormWrapper, template, context, response … … 69 70 slug_field=None, template_name=None, template_loader=template_loader, 70 71 extra_lookup_kwargs={}, extra_context={}, post_save_redirect=None, 71 login_required=False ):72 login_required=False, follow=None): 72 73 """ 73 74 Generic object-update function. … … 99 100 raise Http404("%s.%s does not exist for %s" % (app_label, module_name, lookup_kwargs)) 100 101 101 manipulator = mod.ChangeManipulator(object.id )102 manipulator = mod.ChangeManipulator(object.id, follow=follow) 102 103 103 104 if request.POST: 104 105 new_data = request.POST.copy() 105 106 errors = manipulator.get_validation_errors(new_data) 107 manipulator.do_html2python(new_data) 106 108 if not errors: 107 manipulator.do_html2python(new_data)108 109 manipulator.save(new_data) 109 110 … … 121 122 errors = {} 122 123 # This makes sure the form acurate represents the fields of the place. 123 new_data = object.__dict__124 new_data = manipulator.flatten_data() 124 125 125 126 form = formfields.FormWrapper(manipulator, new_data, errors) django/trunk/setup.py
r1348 r1434 37 37 'templates/admin_doc/*.html', 38 38 'templates/registration/*.html', 39 'templates/widget/*.html', 39 40 'media/css/*.css', 40 41 'media/img/admin/*.gif',
