Django

Code

Changeset 1434

Show
Ignore:
Timestamp:
11/25/05 15:20:09 (4 years ago)
Author:
adrian
Message:

MERGED NEW-ADMIN BRANCH (except for po/mo files, which will come in a separate commit)

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • django/trunk/django/bin/validate.py

    r1420 r1434  
    1717                "ManyToManyField %s should have 'rel' set to a ManyToMany instance." % f.name 
    1818    # 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, \ 
    2121            "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) 
    2323    # All related objects. 
    2424    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, \ 
    2828                "Relationship in field %s.%s needs to set 'related_name' because more than one" \ 
    2929                " %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) 
    3232    # Etc. 
    3333    if opts.admin is not None: 
  • django/trunk/django/contrib/admin/templates/admin_doc/bookmarklets.html

    r948 r1434  
    11{% extends "admin/base_site" %} 
    22 
    3 {% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">Home</a> &rsaquo; <a href="../">Documentation</a> &rsaquo; Bookmarklets</div>{% endblock %} 
     3{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">{% trans "Home" %}</a> &rsaquo; <a href="../">{% trans "Documentation" %}</a> &rsaquo; {% trans "Bookmarklets" %}</div>{% endblock %} 
    44 
    5 {% block title %}Documentation bookmarklets{% endblock %} 
     5{% block title %}{% trans "Documentation bookmarklets" %}{% endblock %} 
    66 
    77{% block content %} 
    88 
     9{% blocktrans %} 
    910<p class="help">To install bookmarklets, drag the link to your bookmarks 
    1011toolbar, or right-click the link and add it to your bookmarks. Now you can 
     
    1314as "internal" (talk to your system administrator if you aren't sure if 
    1415your computer is "internal").</p> 
     16{% endblocktrans %} 
    1517 
    1618<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> 
    1921 
    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> 
    2224 
    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> 
    2527 
    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> 
    2830</div> 
    2931 
  • 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. 
    32from django.contrib.admin.views.decorators import staff_member_required 
    4 from django.core import formfields, meta 
     3from django.contrib.admin.filterspecs import FilterSpec 
     4from django.core import formfields, meta, template 
    55from django.core.template import loader 
     6from django.core.meta.fields import BoundField, BoundFieldLine, BoundFieldSet 
    67from django.core.exceptions import Http404, ObjectDoesNotExist, PermissionDenied 
    78from django.core.extensions import DjangoContext as Context 
    89from django.core.extensions import get_object_or_404, render_to_response 
     10from django.core.paginator import ObjectPaginator, InvalidPage 
     11from django.conf.settings import ADMIN_MEDIA_PREFIX 
    912from django.models.admin import log 
    1013from django.utils.html import strip_tags 
    1114from django.utils.httpwrappers import HttpResponse, HttpResponseRedirect 
    1215from 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 
     16from django.utils import dateformat 
     17from django.utils.dates import MONTHS 
     18from django.utils.html import escape 
    1519import 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. 
     23MAX_SHOW_ALL_ALLOWED = 200 
     24 
     25DEFAULT_RESULTS_PER_PAGE = 100 
     26 
     27ALL_VAR = 'all' 
     28ORDER_VAR = 'o' 
     29ORDER_TYPE_VAR = 'ot' 
     30PAGE_VAR = 'p' 
     31SEARCH_VAR = 'q' 
     32IS_POPUP_VAR = 'pop' 
    1633 
    1734# Text to display within changelist table cells if the value is blank. 
     
    2946    return mod, opts 
    3047 
    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&amp;last_name=smith' 
    35     >>> get_query_string({'first_name': 'adrian', 'last_name': 'smith'}, {'first_name': 'john'}) 
    36     '?first_name=john&amp;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): 
     48def index(request): 
     49    return render_to_response('admin/index', {'title': _('Site administration')}, context_instance=Context(request)) 
     50index = staff_member_required(index) 
     51 
     52class IncorrectLookupParameters(Exception): 
     53    pass 
     54 
     55class 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: 
    4488                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 '?' + '&amp;'.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 '?' + '&amp;'.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. 
    106102        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: 
    107142            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): 
    138169            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 
    185195        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> &rsaquo; %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">&lsaquo; %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">&lsaquo; %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">&lsaquo; 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 
    326199        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: 
    380210                try: 
    381211                    f = lookup_opts.get_field(field_name) 
    382212                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 
    395214                else: 
    396                     field_val = getattr(result, f.attname) 
    397                     # Foreign-key fields are special: Use the repr of the 
    398                     # related object. 
    399215                    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 = '&nbsp;' 
    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 &rsaquo; ') 
    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('&nbsp;&nbsp;<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 
     233def 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 
    499239    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 
    502243    }) 
    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) 
    504247change_list = staff_member_required(change_list) 
    505248 
    506 def _get_flattened_data(field, val): 
    507     """ 
    508     Returns a dictionary mapping the field's manipulator field names to its 
    509     "flattened" string values for the admin view. "val" is an instance of the 
    510     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} 
    522249 
    523250use_raw_id_admin = lambda field: isinstance(field.rel, (meta.ManyToOne, meta.ManyToMany)) and field.rel.raw_id_admin 
    524251 
    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 
     253def 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'] 
    552256    if auto_populated_fields: 
    553         javascript_imports.append('%sjs/urlify.js' % ADMIN_MEDIA_PREFIX
     257        js.append('js/urlify.js'
    554258    if opts.has_field_type(meta.DateTimeField) or opts.has_field_type(meta.TimeField) or opts.has_field_type(meta.DateField): 
    555         javascript_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']) 
    556260    if ordered_objects: 
    557         javascript_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']) 
    558262    if opts.admin.js: 
    559         javascript_imports.extend(opts.admin.js) 
     263        js.extend(opts.admin.js) 
    560264    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
    563267            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: 
    566271            try: 
    567                 for f in field_list
     272                for f in field_line
    568273                    if f.rel and isinstance(f, meta.ManyToManyField) and f.rel.filter_interface: 
    569                         javascript_imports.extend(['%sjs/SelectBox.js' % ADMIN_MEDIA_PREFIX, '%sjs/SelectFilter2.js' % ADMIN_MEDIA_PREFIX]) 
     274                        js.extend(['js/SelectBox.js' , 'js/SelectFilter2.js']) 
    570275                        raise StopIteration 
    571276            except StopIteration: 
    572277                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> &rsaquo; <a href="../">%s</a> &rsaquo; %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 %%}&nbsp;<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 
     281class 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 
     330class 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 
     337class AdminBoundFieldSet(BoundFieldSet): 
     338    def __init__(self, field_set, field_mapping, original): 
     339        super(AdminBoundFieldSet, self).__init__(field_set, field_mapping, original, AdminBoundFieldLine) 
     340 
     341class 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 
     349class 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 
     379def 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 
     393def 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) 
    772396 
    773397def add_stage(request, app_label, module_name, show_delete=False, form_url='', post_url='../', post_url_continue='../%s/', object_id_override=None): 
     
    781405            new_data.update(request.FILES) 
    782406        errors = manipulator.get_validation_errors(new_data) 
     407        manipulator.do_html2python(new_data) 
     408 
    783409        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) 
    788410            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
    792414            # Here, we distinguish between different save types by checking for 
    793415            # the presence of keys in request.POST. 
    794416            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.")
    796418                if request.POST.has_key("_popup"): 
    797419                    post_url_continue += "?_popup=1" 
     
    801423                    (pk_value, repr(new_object).replace('"', '\\"'))) 
    802424            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)) 
    804426                return HttpResponseRedirect(request.path) 
    805427            else: 
    806428                request.user.add_message(msg) 
    807429                return HttpResponseRedirect(post_url) 
    808         if request.POST.has_key("_preview"): 
    809             manipulator.do_html2python(new_data) 
    810430    else: 
    811         new_data = {} 
    812431        # 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 
    835434        # Override the defaults with request.GET, if it exists. 
    836435        new_data.update(request.GET) 
     
    838437 
    839438    # 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) 
    853440 
    854441    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, 
    858446    }) 
    859447    if object_id_override is not None: 
    860448        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) 
    865451add_stage = staff_member_required(add_stage) 
     452 
     453def 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) 
    866467 
    867468def change_stage(request, app_label, module_name, object_id): 
     
    876477        raise Http404 
    877478 
    878     inline_related_objects = opts.get_inline_related_objects() 
    879479    if request.POST: 
    880480        new_data = request.POST.copy() 
     
    883483 
    884484        errors = manipulator.get_validation_errors(new_data) 
     485 
     486        manipulator.do_html2python(new_data) 
    885487        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) 
    890488            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) 
    907492            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.")
    909494                if request.REQUEST.has_key('_popup'): 
    910495                    return HttpResponseRedirect(request.path + "?_popup=1") 
     
    912497                    return HttpResponseRedirect(request.path) 
    913498            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}
    915500                return HttpResponseRedirect("../%s/" % pk_value) 
    916501            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)) 
    918503                return HttpResponseRedirect("../add/") 
    919504            else: 
    920505                request.user.add_message(msg) 
    921506                return HttpResponseRedirect("../") 
    922         if request.POST.has_key("_preview"): 
    923             manipulator.do_html2python(new_data) 
    924507    else: 
    925508        # 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... 
    946512        # If the object has ordered objects on its admin page, get the existing 
    947513        # order and flatten it into a comma-separated list of IDs. 
     514 
    948515        id_order_list = [] 
    949516        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())()) 
    951518        if id_order_list: 
    952519            new_data['order_'] = ','.join(map(str, id_order_list)) 
     
    954521 
    955522    # Populate the FormWrapper. 
    956     form = formfields.FormWrapper(manipulator, new_data, errors
     523    form = formfields.FormWrapper(manipulator, new_data, errors, edit_inline = True
    957524    form.original = manipulator.original_object 
    958525    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() 
    978534            form.order_objects.extend(orig_list) 
    979535 
    980536    c = Context(request, { 
    981         'title': 'Change %s' % opts.verbose_name, 
    982         "form": form, 
     537        'title': _('Change %s') % opts.verbose_name, 
     538        'form': form, 
    983539        'object_id': object_id, 
    984540        'original': manipulator.original_object, 
    985         'is_popup' : request.REQUEST.has_key('_popup'), 
     541        'is_popup' : request.REQUEST.has_key('_popup') 
    986542    }) 
    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) 
    992545 
    993546def _nest_help(obj, depth, val): 
     
    1003556        return # Avoid recursing too deep. 
    1004557    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: 
    1007560            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): 
    1011564            try: 
    1012565                sub_obj = getattr(obj, 'get_%s' % rel_opts_name)() 
     
    1015568            else: 
    1016569                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()) 
    1018571                    if not user.has_perm(p): 
    1019                         perms_needed.add(rel_opts.verbose_name) 
     572                        perms_needed.add(related.opts.verbose_name) 
    1020573                        # We don't care about populating deleted_objects now. 
    1021574                        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: 
    1023576                    # Don't display link to edit, because it either has no 
    1024577                    # 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), []]) 
    1026579                else: 
    1027580                    # 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) 
    1032585        else: 
    1033586            has_related_objs = False 
    1034587            for sub_obj in getattr(obj, 'get_%s_list' % rel_opts_name)(): 
    1035588                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: 
    1037590                    # Don't display link to edit, because it either has no 
    1038591                    # 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))), []]) 
    1040593                else: 
    1041594                    # Display a link to the admin page. 
    1042595                    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) 
    1045598            # If there were related objects, and the user doesn't have 
    1046599            # 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()) 
    1049602                if not user.has_perm(p): 
    1050603                    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: 
    1053606            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(
    1056609        has_related_objs = False 
    1057610        for sub_obj in getattr(obj, 'get_%s_list' % rel_opts_name)(): 
    1058611            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: 
    1060613                # Don't display link to edit, because it either has no 
    1061614                # 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))}, []]) 
    1064617            else: 
    1065618                # 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)))), []]) 
    1068623        # If there were related objects, and the user doesn't have 
    1069624        # 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()) 
    1072627            if not user.has_perm(p): 
    1073                 perms_needed.add(rel_opts.verbose_name) 
     628                perms_needed.add(related.opts.verbose_name) 
    1074629 
    1075630def delete_stage(request, app_label, module_name, object_id): 
     
    1082637    # Populate deleted_objects, a data structure of all related objects that 
    1083638    # 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))), []] 
    1085640    perms_needed = sets.Set() 
    1086641    _get_deleted_objects(deleted_objects, perms_needed, request.user, obj, opts, 1) 
     
    1089644        if perms_needed: 
    1090645            raise PermissionDenied 
    1091         obj_repr = repr(obj) 
     646        obj_display = str(obj) 
    1092647        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}
    1095650        return HttpResponseRedirect("../../") 
    1096651    return render_to_response('admin/delete_confirmation', { 
    1097         "title": "Are you sure?"
     652        "title": _("Are you sure?")
    1098653        "object_name": opts.verbose_name, 
    1099654        "object": obj, 
     
    1110665    obj = get_object_or_404(mod, pk=object_id) 
    1111666    return render_to_response('admin/object_history', { 
    1112         'title': 'Change history: %r' % obj, 
     667        'title': _('Change history: %s') % obj, 
    1113668        'action_list': action_list, 
    1114669        'module_name': capfirst(opts.verbose_name_plural), 
  • django/trunk/django/core/formfields.py

    r1422 r1434  
    9191        """ 
    9292        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) 
    10294 
    10395class FormWrapper: 
     
    10799    prepopulated data and validation error messages to the formfield objects. 
    108100    """ 
    109     def __init__(self, manipulator, data, error_dict): 
     101    def __init__(self, manipulator, data, error_dict, edit_inline=True): 
    110102        self.manipulator, self.data = manipulator, data 
    111103        self.error_dict = error_dict 
     104        self._inline_collections = None 
     105        self.edit_inline = edit_inline 
    112106 
    113107    def __repr__(self): 
    114         return repr(self.data
     108        return repr(self.__dict__
    115109 
    116110    def __getitem__(self, key): 
    117111        for field in self.manipulator.fields: 
    118112            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) 
    125114                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 
    127131 
    128132    def has_errors(self): 
     
    167171            return '' 
    168172 
     173    def get_id(self): 
     174        return self.formfield.get_id() 
     175 
    169176class FormFieldCollection(FormFieldWrapper): 
    170177    "A utility class that gives the template access to a dict of FormFieldWrappers" 
     
    186193        errors = [] 
    187194        for field in self.formfield_dict.values(): 
    188             errors.extend(field.errors()) 
     195            if hasattr(field, 'errors'): 
     196                errors.extend(field.errors()) 
    189197        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 
     205class 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 
    190254 
    191255class FormField: 
     
    221285        raise NotImplementedError 
    222286 
     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 
    223318    def get_id(self): 
    224319        "Returns the HTML 'id' attribute for this form field." 
     
    314409 
    315410class 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): 
    317412        self.field_name = field_name 
    318413        # choices is a list of (value, human-readable key) tuples because order matters 
    319414        self.choices, self.size, self.is_required = choices, size, is_required 
    320415        self.validator_list = [self.isValidChoice] + validator_list 
     416        if member_name != None: 
     417            self.member_name = member_name 
    321418 
    322419    def render(self, data): 
     
    348445 
    349446class 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): 
    351448        self.field_name = field_name 
    352449        # choices is a list of (value, human-readable key) tuples because order matters 
     
    354451        self.validator_list = [self.isValidChoice] + validator_list 
    355452        self.ul_class = ul_class 
     453        if member_name != None: 
     454            self.member_name = member_name 
    356455 
    357456    def render(self, data): 
     
    484583            field_name = '%s%s' % (self.field_name, value) 
    485584            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)) 
    488587        output.append('</ul>') 
    489588        return '\n'.join(output) 
     
    529628 
    530629class 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): 
    532631        validator_list = [self.isInteger] + validator_list 
     632        if member_name is not None: 
     633            self.member_name = member_name 
    533634        TextField.__init__(self, field_name, length, maxlength, is_required, validator_list) 
    534635 
     
    785886            raise validators.CriticalValidationError, e.messages 
    786887 
     888class RawIdAdminField(CommaSeparatedIntegerField): 
     889    def html2python(data): 
     890        return data.split(','); 
     891    html2python = classmethod(html2python) 
     892 
    787893class XMLLargeTextField(LargeTextField): 
    788894    """ 
  • django/trunk/django/core/management.py

    r1429 r1434  
    671671 
    672672            # 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(): 
    674674                try: 
    675                     for f in rel_opts.fields: 
     675                    for f in related.opts.fields: 
    676676                        if f.core: 
    677677                            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)) 
    679679                except StopIteration: 
    680680                    pass 
  • django/trunk/django/core/meta/fields.py

    r1423 r1434  
    6060    raise validators.ValidationError, _("%(optname)s with this %(fieldname)s already exists.") % {'optname': capfirst(opts.verbose_name), 'fieldname': f.verbose_name} 
    6161 
     62class 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) 
    6280 
    6381# A guide to Field parameters: 
     
    186204                return self.default.__get_value__() 
    187205            return self.default 
    188         if self.null: 
     206        if not self.empty_strings_allowed or self.null: 
    189207            return None 
    190208        return "" 
     
    208226            params['maxlength'] = self.maxlength 
    209227        if isinstance(self.rel, ManyToOne): 
     228            params['member_name'] = name_prefix + self.attname 
    210229            if self.rel.raw_id_admin: 
    211230                field_objs = self.get_manipulator_field_objs() 
     
    214233                if self.radio_admin: 
    215234                    field_objs = [formfields.RadioSelectField] 
    216                     params['choices'] = self.get_choices(include_blank=self.blank, blank_choice=BLANK_CHOICE_NONE) 
    217235                    params['ul_class'] = get_ul_class(self.radio_admin) 
    218236                else: 
     
    221239                    else: 
    222240                        field_objs = [formfields.SelectField] 
    223                     params['choices'] = self.get_choices() 
     241                params['choices'] = self.get_choices_default() 
    224242        elif self.choices: 
    225243            if self.radio_admin: 
    226244                field_objs = [formfields.RadioSelectField] 
    227                 params['choices'] = self.get_choices(include_blank=self.blank, blank_choice=BLANK_CHOICE_NONE) 
    228245                params['ul_class'] = get_ul_class(self.radio_admin) 
    229246            else: 
    230247                field_objs = [formfields.SelectField] 
    231                 params['choices'] = self.get_choices() 
     248 
     249            params['choices'] = self.get_choices_default() 
    232250        else: 
    233251            field_objs = self.get_manipulator_field_objs() 
     
    295313            return first_choice + list(self.choices) 
    296314        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) 
    298346 
    299347class AutoField(Field): 
     
    336384    def __init__(self, verbose_name=None, name=None, auto_now=False, auto_now_add=False, **kwargs): 
    337385        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... 
    338387        if auto_now or auto_now_add: 
    339388            kwargs['editable'] = False 
     389            kwargs['blank'] = True 
    340390        Field.__init__(self, verbose_name, name, **kwargs) 
    341391 
     
    351401            return datetime.datetime.now() 
    352402        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 
    353410 
    354411    def get_db_prep_save(self, value): 
     
    360417    def get_manipulator_field_objs(self): 
    361418        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 '')} 
    362423 
    363424class DateTimeField(DateField): 
     
    389450            return datetime.datetime.combine(d, t) 
    390451        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 '')} 
    391458 
    392459class EmailField(Field): 
     
    588655        return [formfields.TimeField] 
    589656 
     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 
    590661class URLField(Field): 
    591662    def __init__(self, verbose_name=None, name=None, verify_exists=True, **kwargs): 
     
    647718        else: 
    648719            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) 
    649738 
    650739class ManyToManyField(Field): 
     
    663752    def get_manipulator_field_objs(self): 
    664753        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(
    668757            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) 
    669761 
    670762    def get_m2m_db_table(self, original_opts): 
     
    689781            } 
    690782 
     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 
    691802class OneToOneField(IntegerField): 
    692803    def __init__(self, to, to_field=None, **kwargs): 
     
    754865        self.raw_id_admin = raw_id_admin 
    755866 
     867class 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 
     878class 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 
     895class 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 
     908class 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 
    756927class Admin: 
    757928    def __init__(self, fields=None, js=None, list_display=None, list_filter=None, date_hierarchy=None, 
     
    767938        self.list_select_related = list_select_related 
    768939 
    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): 
    775941        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                }),) 
    777945        else: 
    778946            field_struct = self.fields 
    779947        new_fieldset_list = [] 
    780948        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)) 
    791954        return new_fieldset_list 
  • django/trunk/django/core/meta/__init__.py

    r1326 r1434  
    148148class BadKeywordArguments(Exception): 
    149149    pass 
     150 
     151class 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 
     162class 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 
    150284 
    151285class Options: 
     
    269403        return 'delete_%s' % self.object_name.lower() 
    270404 
    271     def get_rel_object_method_name(self, rel_opts, rel_field): 
    272         # This method encapsulates the logic that decides what name to give a 
    273         # method that retrieves related many-to-one objects. Usually it just 
    274         # uses the lower-cased object_name, but if the related object is in 
    275         # 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_name 
    290  
    291405    def get_all_related_objects(self): 
    292406        try: # Try the cache first. 
     
    299413                    for f in klass._meta.fields: 
    300414                        if f.rel and self == f.rel.to: 
    301                             rel_objs.append((klass._meta, f)) 
     415                            rel_objs.append(RelatedObject(self, klass._meta, f)) 
    302416            if self.has_related_links: 
    303417                # Manually add RelatedLink objects, which are a special case. 
     
    313427                        'content_type__python_module_name__exact': self.module_name, 
    314428                    }) 
    315                 rel_objs.append((relatedlinks.RelatedLink._meta, link_field)) 
     429                rel_objs.append(RelatedObject(self, relatedlinks.RelatedLink._meta, link_field)) 
    316430            self._all_related_objects = rel_objs 
    317431            return rel_objs 
    318432 
    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 
    321454 
    322455    def get_all_related_many_to_many_objects(self): 
     
    328461                    for f in klass._meta.many_to_many: 
    329462                        if f.rel and self == f.rel.to: 
    330                             rel_objs.append((klass._meta, f)) 
     463                            rel_objs.append(RelatedObject(self, klass._meta, f)) 
    331464                            raise StopIteration 
    332465                except StopIteration: 
     
    346479        return self._ordered_objects 
    347480 
    348     def has_field_type(self, field_type): 
     481    def has_field_type(self, field_type, follow=None): 
    349482        """ 
    350483        Returns True if this object's admin form has at least one of the given 
    351484        field_type (e.g. FileField). 
    352485        """ 
     486        # TODO: follow 
    353487        if not hasattr(self, '_field_types'): 
    354488            self._field_types = {} 
     
    360494                        raise StopIteration 
    361495                # 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: 
    364498                        if isinstance(f, field_type): 
    365499                            raise StopIteration 
     
    598732 
    599733        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. 
    600735            if f.choices: 
    601736                # Add "get_thingie_display" method to get human-readable value. 
     
    721856                    # Replace all relationships to the old class with 
    722857                    # 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 
    727860                    break 
    728  
    729861        return new_class 
    730862 
     
    827959        self._pre_delete() 
    828960    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): 
    832964            try: 
    833965                sub_obj = getattr(self, 'get_%s' % rel_opts_name)() 
     
    839971            for sub_obj in getattr(self, 'get_%s_list' % rel_opts_name)(): 
    840972                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(): 
    842974        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)), 
    844976            db.db.quote_name(self._meta.object_name.lower() + '_id')), [getattr(self, opts.pk.attname)]) 
    845977    for f in opts.many_to_many: 
     
    14751607    man.__init__ = curry(manipulator_init, opts, add, change) 
    14761608    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) 
    14771611    for field_name_list in opts.unique_together: 
    14781612        setattr(man, 'isUnique%s' % '_'.join(field_name_list), curry(manipulator_validator_unique_together, field_name_list, opts)) 
     
    14881622    return man 
    14891623 
    1490 def manipulator_init(opts, add, change, self, obj_key=None): 
     1624def manipulator_init(opts, add, change, self, obj_key=None, follow=None): 
     1625    self.follow = opts.get_follow(follow) 
     1626 
    14911627    if change: 
    14921628        assert obj_key is not None, "ChangeManipulator.__init__() must be passed obj_key parameter." 
     
    15121648                raise 
    15131649    self.fields = [] 
     1650 
    15141651    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): 
    15161653            self.fields.extend(f.get_manipulator_fields(opts, self, change)) 
    15171654 
    15181655    # 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)) 
    15331660 
    15341661    # Add field for ordering. 
     
    15371664 
    15381665def manipulator_save(opts, klass, add, change, self, new_data): 
     1666    # TODO: big cleanup when core fields go -> use recursive manipulators. 
    15391667    from django.utils.datastructures import DotExpandedDict 
    15401668    params = {} 
    15411669    for f in opts.fields: 
    1542         # Fields with auto_now_add are another special case; they should keep 
    1543         # their original value in the change stage. 
    1544         if change and getattr(f, 'auto_now_add', False)
    1545             params[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
    15461674        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 
    15481681 
    15491682    if change: 
     
    15681701    # Save many-to-many objects. Example: Poll.set_sites() 
    15691702    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) 
    15751710    # 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(): 
    15771712        # Create obj_list, which is a DotExpandedDict such as this: 
    15781713        # [('0', {'id': ['940'], 'choice': ['This is the first choice']}), 
    15791714        #  ('1', {'id': ['941'], 'choice': ['This is the second choice']}), 
    15801715        #  ('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. 
    15981736                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() 
    16171763                    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 
    16651813 
    16661814    # Save the order, if applicable. 
     
    16701818            getattr(new_object, 'set_%s_order' % rel_opts.object_name.lower())(order) 
    16711819    return new_object 
     1820 
     1821def manipulator_get_related_objects(opts, klass, add, change, self): 
     1822    return opts.get_followed_related_objects(self.follow) 
     1823 
     1824def 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 
    16721831 
    16731832def manipulator_validator_unique_together(field_name_list, opts, self, field_data, all_data): 
     
    16791838        kwargs = {'%s__iexact' % field_name_list[0]: field_data} 
    16801839    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. 
    16811843        field_val = all_data.get(f.attname, None) 
    16821844        if field_val is None: 
  • django/trunk/django/models/__init__.py

    r1401 r1434  
    2020        # Add "get_thingie", "get_thingie_count" and "get_thingie_list" methods 
    2121        # 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(): 
    2323            # Determine whether this related object is in another app. 
    2424            # If it's in another app, the method names will have the app 
    2525            # label prepended, and the add_BLAH() method will not be 
    2626            # 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): 
    3030                # Add "get_thingie" methods for one-to-one related objects. 
    3131                # 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) 
    3434                setattr(klass, 'get_%s' % rel_obj_name, func) 
    35             elif isinstance(rel_field.rel, meta.ManyToOne): 
     35            elif isinstance(related.field.rel, meta.ManyToOne): 
    3636                # Add "get_thingie" methods for many-to-one related objects. 
    3737                # 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) 
    4041                setattr(klass, 'get_%s' % rel_obj_name, func) 
    4142                # Add "get_thingie_count" methods for many-to-one related objects. 
    4243                # 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) 
    4547                setattr(klass, 'get_%s_count' % rel_obj_name, func) 
    4648                # Add "get_thingie_list" methods for many-to-one related objects. 
    4749                # 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) 
    5053                setattr(klass, 'get_%s_list' % rel_obj_name, func) 
    5154                # Add "add_thingie" methods for many-to-one related objects, 
    5255                # but only for related objects that are in the same app. 
    5356                # 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) 
    5659                    func.alters_data = True 
    5760                    setattr(klass, 'add_%s' % rel_obj_name, func) 
    5861                del func 
    59             del rel_obj_name, rel_mod, rel_obj, rel_field # clean up 
     62            del rel_obj_name, rel_mod, related # clean up 
    6063 
    6164        # 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) 
    7073                func.alters_data = True 
    71                 setattr(klass, 'set_%s' % rel_opts.module_name, func) 
     74                setattr(klass, 'set_%s' % related.opts.module_name, func) 
    7275                del func 
    73             del rel_obj_name, rel_mod, rel_opts, rel_field # clean up 
     76            del rel_obj_name, rel_mod, related # clean up 
    7477 
    7578        # Add "set_thingie_order" and "get_thingie_order" methods for objects 
  • django/trunk/django/views/generic/create_update.py

    r734 r1434  
    1010def create_object(request, app_label, module_name, template_name=None,  
    1111                 template_loader=template_loader, extra_context={},  
    12                  post_save_redirect=None, login_required=False): 
     12                 post_save_redirect=None, login_required=False, follow=None): 
    1313    """ 
    1414    Generic object-creation function. 
     
    2323         
    2424    mod = models.get_module(app_label, module_name) 
    25     manipulator = mod.AddManipulator(
     25    manipulator = mod.AddManipulator(follow=follow
    2626    if request.POST: 
    2727        # If data was POSTed, we're trying to create a new object 
     
    3030        # Check for errors 
    3131        errors = manipulator.get_validation_errors(new_data) 
     32        manipulator.do_html2python(new_data) 
    3233         
    3334        if not errors: 
    3435            # No errors -- this means we can save the data! 
    35             manipulator.do_html2python(new_data) 
    3636            new_object = manipulator.save(new_data) 
    3737             
     
    4949    else: 
    5050        # 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() 
    5253     
    5354    # Create the FormWrapper, template, context, response 
     
    6970                  slug_field=None, template_name=None, template_loader=template_loader, 
    7071                  extra_lookup_kwargs={}, extra_context={}, post_save_redirect=None,  
    71                   login_required=False): 
     72                  login_required=False, follow=None): 
    7273    """ 
    7374    Generic object-update function. 
     
    99100        raise Http404("%s.%s does not exist for %s" % (app_label, module_name, lookup_kwargs)) 
    100101     
    101     manipulator = mod.ChangeManipulator(object.id
     102    manipulator = mod.ChangeManipulator(object.id, follow=follow
    102103     
    103104    if request.POST: 
    104105        new_data = request.POST.copy() 
    105106        errors = manipulator.get_validation_errors(new_data) 
     107        manipulator.do_html2python(new_data) 
    106108        if not errors: 
    107             manipulator.do_html2python(new_data) 
    108109            manipulator.save(new_data) 
    109110             
     
    121122        errors = {} 
    122123        # This makes sure the form acurate represents the fields of the place. 
    123         new_data = object.__dict__ 
     124        new_data = manipulator.flatten_data() 
    124125     
    125126    form = formfields.FormWrapper(manipulator, new_data, errors) 
  • django/trunk/setup.py

    r1348 r1434  
    3737                                 'templates/admin_doc/*.html', 
    3838                                 'templates/registration/*.html', 
     39                                 'templates/widget/*.html', 
    3940                                 'media/css/*.css', 
    4041                                 'media/img/admin/*.gif',