Ticket #535: django-admin-refactor-6.patch

File django-admin-refactor-6.patch, 75.8 KB (added by robert@…, 19 years ago)

Updated patch

  • django/conf/urls/admin.py

     
    4848
    4949urlpatterns += (
    5050    # Metasystem admin pages
     51    ('^(?P<app_label>[^/]+)/(?P<module_name>[^/]+)/add_old/$', 'django.views.admin.main.add_stage'),
     52    ('^(?P<app_label>[^/]+)/(?P<module_name>[^/]+)/(?P<object_id>.+)_old/$', 'django.views.admin.main.change_stage'),
    5153    ('^(?P<app_label>[^/]+)/(?P<module_name>[^/]+)/$', 'django.views.admin.main.change_list'),
    52     ('^(?P<app_label>[^/]+)/(?P<module_name>[^/]+)/add/$', 'django.views.admin.main.add_stage'),
     54    ('^(?P<app_label>[^/]+)/(?P<module_name>[^/]+)/add/$', 'django.views.admin.main.add_stage_new'),
    5355    ('^(?P<app_label>[^/]+)/(?P<module_name>[^/]+)/jsvalidation/$', 'django.views.admin.jsvalidation.jsvalidation'),
    5456    ('^(?P<app_label>[^/]+)/(?P<module_name>[^/]+)/(?P<object_id>.+)/history/$', 'django.views.admin.main.history'),
    5557    ('^(?P<app_label>[^/]+)/(?P<module_name>[^/]+)/(?P<object_id>.+)/delete/$', 'django.views.admin.main.delete_stage'),
    56     ('^(?P<app_label>[^/]+)/(?P<module_name>[^/]+)/(?P<object_id>.+)/$', 'django.views.admin.main.change_stage'),
     58    ('^(?P<app_label>[^/]+)/(?P<module_name>[^/]+)/(?P<object_id>.+)/$', 'django.views.admin.main.change_stage_new'),
    5759)
    5860urlpatterns = patterns('', *urlpatterns)
  • django/conf/admin_templates/admin_change_form.html

     
     1{% extends "base_site" %}
     2{% load admin_modify %}
     3{% load adminmedia %}
     4{% block extrahead %}
     5 
     6   {% for js in javascript_imports %}
     7      {% include_admin_script js %}
     8   {% endfor %}
     9
     10{% endblock %}
     11
     12{% block coltype %}{{ coltype }}{% endblock %}
     13
     14{% block bodyclass %}{{app_label}}-{{object_name.lower}} change-form{% endblock %}
     15
     16{% block breadcrumbs %}{% if not is_popup %}
     17<div class="breadcrumbs">
     18     <a href="../../../">Home</a> &rsaquo;
     19     <a href="../">{{verbose_name_plural|capfirst}}</a> &rsaquo;
     20     {% if add %}
     21        Add {{verbose_name}}
     22     {% else %}
     23        {{original|striptags|truncatewords:"18"}}
     24     {% endif %}
     25</div>
     26{% endif %}
     27{% endblock %}
     28
     29{% block content %}<div id="content-main">
     30{% if change %}
     31   {% if not is_popup %}
     32      <ul class="object-tools"><li><a href="history/" class="historylink">History</a></li>
     33      {% if has_absolute_url %}
     34         <li><a href="/r/{{ content_type_id }}/{{ object_id }}/" class="viewsitelink">View on site</a></li>
     35      {% endif%}
     36      </ul>
     37   {% endif %}
     38{% endif %}
     39
     40<form {{ form_enc_attrib }} action='{{ form_url }}' method="post">
     41
     42{% if is_popup %}<input type="hidden" name="_popup" value="1">{% endif %}
     43
     44{% if save_on_top %}
     45   {% submit_row %}
     46{% endif %}
     47
     48{% if form.error_dict %}
     49   <p class="errornote">Please correct the error{{ form.error_dict.items|pluralize }} below.</p>
     50{% endif %}
     51<b>
     52</b>
     53{% for fieldset in admin_fieldsets %}
     54   <fieldset class="module aligned {{ fieldset.classes }}">
     55    {% if fieldset.name %}
     56   <h2>{{fieldset.name }}</h2>
     57    {% endif %}
     58    {% for bound_field_set in fieldset.bound_field_sets %}
     59        {% for bound_field in bound_field_set %}
     60            {% admin_field_bound bound_field %}
     61            {% filter_interface_script_maybe bound_field %}
     62        {% endfor %}
     63    {% endfor %}
     64   </fieldset>
     65{% endfor %}
     66
     67{% if change %}
     68   {% if ordered_objects %}
     69   <fieldset class="module"><h2>Ordering</h2>
     70   <div class="form-row{% if form.order_.errors %} error{% endif %} ">
     71   {% if form.order_.errors %}{{ form.order_.html_error_list }}{% endif %}
     72   <p><label for="id_order_">Order:</label> {{ form.order_ }}</p>
     73   </div></fieldset>
     74   {% endif %}
     75{% endif %}
     76
     77
     78{% for relation in inline_related_objects %}
     79    {% edit_inline relation %}
     80{% endfor %}
     81
     82{% submit_row %}
     83
     84{% if add %}
     85   <script type="text/javascript">document.getElementById("id_{{first_field}}").focus();</script>'
     86{% endif %}
     87
     88{% if auto_populated_fields %}
     89   <script type="text/javascript">
     90   {% auto_populated_field_script auto_populated_fields %}
     91   </script>
     92{% endif %}
     93
     94{% if change %}
     95   {% if ordered_objects %}
     96      {% if form.order_objects %}<ul id="orderthese">
     97          {% for object in form.order_objects %}
     98             <li id="p{% firstof ordered_object_names %}">
     99             <span id="handlep{% firstof ordered_object_names %}">{{ object|truncatewords:"5" }}</span>
     100             </li>
     101          {% endfor%}
     102      {% endif %}
     103   {% endif %}
     104{% endif%}
     105</form>
     106
     107{% endblock %}
  • django/conf/admin_templates/admin_edit_inline_stacked.html

     
     1<fieldset class="module aligned">
     2   {% for ow in form_object_wrapper_list %}
     3      <h2>{{relation.opts.verbose_name|capfirst }}&nbsp;#{{ forloop.counter }}</h2>
     4      {% if ow.show_url %}{% if ow.obj.original %}
     5      <p><a href="/r/{{ ow.obj.original.content_type_id }}/{{ ow.obj.original.id }}/">View on site</a></p>
     6      {% endif %}{% endif %}
     7      {% for bound_field in ow.bound_fields %}
     8         {% if bound_field.not_in_table %}
     9            {% field_widget bound_field %}
     10         {% else %}
     11            {% admin_field_bound bound_field %}
     12         {% endif %}
     13      {% endfor %}
     14    {%endfor%}
     15</fieldset>
     16
  • django/conf/admin_templates/admin_field.html

     
     1<div class="{{ class_names }}">
     2   {% for bound_field in bound_fields %}
     3      {% if bound_field.field.errors %}
     4         {{ bound_field.field.html_error_list }}
     5      {% endif %}
     6   {% endfor %}
     7
     8   {% for bound_field in bound_fields %}
     9      {% if bound_field.has_label_first %}
     10         {% field_label bound_field %}
     11      {% endif %}
     12     
     13      {% field_widget bound_field %}
     14
     15      {% if not bound_field.has_label_first %}
     16         {% field_label bound_field %}
     17      {% endif %}
     18
     19      {% if change %}
     20         {% if bound_field.field.primary_key %}
     21            {{ bound_field.original_value }}
     22         {% endif %}
     23
     24         {% if bound_field.raw_id_admin %}
     25            {% if bound_field.existing_repr %}
     26                &nbsp;<strong>{{ bound_field.existing_repr|truncatewords:"14" }}</strong>
     27            {% endif %}
     28         {% endif %}
     29      {% endif %}
     30
     31      {% if bound_field.field.help_text %}
     32        <p class="help">
     33           {{bound_field.field.help_text}}
     34        </p>
     35      {% endif %}
     36   {% endfor %}
     37
     38</div>
  • django/conf/admin_templates/admin_field_widget.html

     
     1{% if bound_field.is_date_time %}
     2   <p class="datetime">
     3      Date: {{ bound_field.form_fields.0 }}<br />
     4      Time: {{ bound_field.form_fields.1 }}
     5   </p>
     6{% else %}
     7    {% if bound_field.is_file_field %}
     8        {% if bound_field.original_value %}
     9            Currently: <a href="{{ bound_field.original_url }}" > {{ bound_field.original_value }} </a><br />
     10            Change: {% output_all bound_field.form_fields %}
     11        {% else %}
     12            {% output_all bound_field.form_fields %}
     13        {% endif %}
     14    {% else %}
     15        {% output_all bound_field.form_fields %}
     16        {% if bound_field.raw_id_admin %}
     17            <a href="../../../{{ bound_field.field.rel.to.app_label }}/{{ bound_field.field.rel.to.module_name }}/" class="related-lookup" id="lookup_{{bound_field.element_id}}" onclick="return showRelatedObjectLookupPopup(this);"> <img src="{% admin_media_prefix %}img/admin/selector-search.gif" width="16" height="16" alt="Lookup"></a>
     18        {% else  %}
     19            {% if bound_field.needs_add_label %}
     20            <a href="../../../{{ bound_field.field.rel.to.app_label }}/{{ bound_field.field.rel.to.module_name }}/add/" class="add-another" id="add_{{ bound_field.element_id}}" onclick="return showAddAnotherPopup(this);"> <img src="{% admin_media_prefix %}img/admin/icon_addlink.gif" width="10" height="10" alt="Add Another"/></a>
     21            {% endif %}
     22        {% endif %}
     23    {% endif %}
     24{% endif %}
     25   
     26
     27
  • django/conf/admin_templates/admin_edit_inline_tabular.html

     
     1<fieldset class="module">
     2   <h2>{{relation.opts.verbose_name_plural|capfirst}}</h2><table>
     3   <thead><tr>
     4   {% for fw in field_wrapper_list %}
     5      {% if fw.needs_header %}
     6         <th{{fw.header_class_attribute}}> {{fw.field.verbose_name|capfirst}}  </th>
     7      {% endif %}
     8   {% endfor %}
     9   {% for ow in form_object_wrapper_list %}
     10     
     11      {% if change %}{% if original_row_needed %}
     12         {% if ow.obj.original %}
     13            <tr class="row-label {% cycle row1,row2 %}"><td colspan="{{num_headers}}"><strong>{{ ow.obj.original }}</strong></tr>
     14         {% endif %}
     15      {% endif %}{% endif %}
     16      {% if ow.has_errors %}
     17         <tr class="errorlist"><td colspan="{{num_headers}}">
     18            {{ ow.html_combined_error_list }}
     19         </tr>
     20      {% endif %}
     21      <tr class="{% cycle row1,row2 %}">
     22      {% for bound_field in ow.bound_fields %}
     23         {% if not bound_field.not_in_table %}
     24         <td "{{ bound_field.cell_class_attribute}}">
     25            {% field_widget bound_field %}
     26         </td>
     27         {% endif %}
     28      {% endfor %}
     29      {% if ow.show_url %}<td>
     30         {% if ow.obj.original %}<a href="/r/{{ ow.obj.original.content_type_id }}/{{ ow.obj.original.id }}/">View on site</a>{% endif %}
     31      </td>{% endif %}
     32      </tr>
     33
     34   {% endfor %} </table>
     35
     36   {% for ow in form_object_wrapper_list %}
     37      {% for bound_field in ow.bound_fields %}
     38         {% if bound_field.not_in_table %}
     39            {% field_widget bound_field %}
     40         {% endif %}
     41      {% endfor %}
     42   {% endfor %}
     43</fieldset>
     44
  • django/conf/settings.py

     
    2626    if not me.SETTINGS_MODULE: # If it's set but is an empty string.
    2727        raise KeyError
    2828except KeyError:
    29     raise EnvironmentError, "Environment variable %s is undefined." % ENVIRONMENT_VARIABLE
     29    raise EnvironmentError, "Environment variable %s is undefined." % (ENVIRONMENT_VARIABLE)
    3030
    3131try:
    3232    mod = __import__(me.SETTINGS_MODULE, '', '', [''])
  • django/core/formfields.py

     
    2222        for field in self.fields:
    2323            if field.field_name == field_name:
    2424                return field
    25         raise KeyError, "Field %s not found" % field_name
     25        raise KeyError, "Field %s not found\n%s" % (field_name, repr(self.fields))
    2626
    2727    def __delitem__(self, field_name):
    2828        "Deletes the field with the given field name; raises KeyError on failure"
     
    8787        must happen after validation because html2python functions aren't
    8888        expected to deal with invalid input.
    8989        """
    90         for field in self.fields:
    91             if new_data.has_key(field.field_name):
    92                 new_data.setlist(field.field_name,
    93                     [field.__class__.html2python(data) for data in new_data.getlist(field.field_name)])
    94             else:
    95                 try:
    96                     # individual fields deal with None values themselves
    97                     new_data.setlist(field.field_name, [field.__class__.html2python(None)])
    98                 except EmptyValue:
    99                     new_data.setlist(field.field_name, [])
     90        """
     91        for field in self.fields:
     92        """
    10093
     94        for field in self.fields:
     95            field.convert_post_data(new_data)
     96
    10197class FormWrapper:
    10298    """
    10399    A wrapper linking a Manipulator to the template system.
    104100    This allows dictionary-style lookups of formfields. It also handles feeding
    105101    prepopulated data and validation error messages to the formfield objects.
    106102    """
    107     def __init__(self, manipulator, data, error_dict):
     103    def __init__(self, manipulator, data, error_dict, edit_inline = False):
    108104        self.manipulator, self.data = manipulator, data
    109105        self.error_dict = error_dict
     106        self._inline_collections = None
     107        self.edit_inline = edit_inline
    110108
    111109    def __repr__(self):
    112         return repr(self.data)
     110        return repr(self.__dict__)
    113111
    114112    def __getitem__(self, key):
    115113        for field in self.manipulator.fields:
    116114            if field.field_name == key:
    117                 if hasattr(field, 'requires_data_list') and hasattr(self.data, 'getlist'):
    118                     data = self.data.getlist(field.field_name)
    119                 else:
    120                     data = self.data.get(field.field_name, None)
    121                 if data is None:
    122                     data = ''
    123                 return FormFieldWrapper(field, data, self.error_dict.get(field.field_name, []))
     115                data = field.extract_data(self.data)
     116                return FormFieldWrapper(field, data, self.error_dict.get(field.field_name, []))
     117        if self.edit_inline:
     118            self.fill_inline_collections()
     119            for inline_collection in self._inline_collections:
     120                if inline_collection.name == key:
     121                    return inline_collection
     122
    124123        raise KeyError
    125124
     125    def fill_inline_collections(self):
     126        if not self._inline_collections:
     127            ic = []
     128            related_objects = self.manipulator.get_inline_related_objects_wrapped()
     129            for rel_obj in related_objects:
     130                data = rel_obj.extract_data(self.data)
     131                inline_collection = InlineObjectCollection(self.manipulator, rel_obj, data, self.error_dict)
     132                ic.append(inline_collection)
     133            self._inline_collections = ic
     134
     135
     136
    126137    def has_errors(self):
    127138        return self.error_dict != {}
    128139
     
    135146    def __str__(self):
    136147        "Renders the field"
    137148        return str(self.formfield.render(self.data))
     149           
    138150
    139151    def __repr__(self):
    140152        return '<FormFieldWrapper for "%s">' % self.formfield.field_name
     
    155167        else:
    156168            return ''
    157169
     170    def get_id(self):
     171        return  self.formfield.get_id()
     172
    158173class FormFieldCollection(FormFieldWrapper):
    159174    "A utility class that gives the template access to a dict of FormFieldWrappers"
    160175    def __init__(self, formfield_dict):
     
    177192            errors.extend(field.errors())
    178193        return errors
    179194
     195
     196
     197   
     198
     199class InlineObjectCollection:
     200    "An object that acts like a list of form field collections." 
     201    def __init__(self, parent_manipulator,  rel_obj, data, errors):
     202        self.parent_manipulator = parent_manipulator
     203        self.rel_obj = rel_obj
     204        self.data = data
     205        self.errors = errors
     206        self._collections = None   
     207        self.name = rel_obj.name
     208 
     209    def __len__(self):
     210        self.fill()
     211        return self._collections.__len__()
     212   
     213    def __getitem__(self, k):
     214        self.fill()
     215        return self._collections.__getitem__(k)
     216
     217    def __setitem__(self, k, v):
     218        self.fill()
     219        return self._collections.__setitem__(k,v)
     220
     221    def __delitem__(self, k):
     222        self.fill()
     223        return self._collections.__delitem__(k)
     224
     225    def __iter__(self):
     226        self.fill()
     227        return self._collections.__iter__()
     228
     229    def fill(self):
     230        if self._collections:
     231            return
     232        else:
     233            var_name = self.rel_obj.opts.object_name.lower()
     234            wrapper = []
     235            orig = hasattr(self.parent_manipulator, 'original_object') and self.parent_manipulator.original_object  or None
     236            orig_list = self.rel_obj.get_list(orig)
     237            for i, instance in enumerate(orig_list):
     238                collection = {'original': instance }
     239                for f in self.rel_obj.editable_fields():
     240                        for field_name in f.get_manipulator_field_names(''):
     241                            full_field_name = '%s.%d.%s' % (var_name, i, field_name)
     242                            field = self.parent_manipulator[full_field_name]
     243                            data = field.extract_data(self.data)
     244                            collection[field_name] = FormFieldWrapper(field, data, self.errors.get(full_field_name, []))
     245                wrapper.append(FormFieldCollection(collection))
     246            self._collections = wrapper
     247
    180248class FormField:
    181249    """Abstract class representing a form field.
    182250
     
    209277    def render(self, data):
    210278        raise NotImplementedError
    211279
     280    def get_member_name(self):
     281        if hasattr(self, 'member_name'):
     282            return self.member_name
     283        else:
     284            return self.field_name
     285
     286    def extract_data(self, data_dict):
     287        if hasattr(self, 'requires_data_list') and hasattr(data_dict, 'getlist'):
     288            data = data_dict.getlist(self.get_member_name())
     289        else:
     290            data = data_dict.get(self.get_member_name(), None)
     291        if data is None:
     292            data = ''
     293        self.data_dict = data_dict 
     294        return data
     295
     296    def convert_post_data(self, new_data):
     297        name = self.get_member_name()
     298        if new_data.has_key(self.field_name):
     299            d = new_data.getlist(self.field_name)
     300            #del new_data[self.field_name]
     301            new_data.setlist(name,
     302                    [self.__class__.html2python(data)
     303                     for data in d])
     304        else:
     305            try:
     306               # individual fields deal with None values themselves
     307               new_data.setlist(name, [self.__class__.html2python(None)])
     308            except EmptyValue:
     309               new_data.setlist(name, [])
     310
     311    def get_id(self):
     312        return  FORM_FIELD_ID_PREFIX + self.field_name 
    212313####################
    213314# GENERIC WIDGETS  #
    214315####################
     
    237338        if isinstance(data, unicode):
    238339            data = data.encode('utf-8')
    239340        return '<input type="text" id="%s" class="v%s%s" name="%s" size="%s" value="%s" %s/>' % \
    240             (FORM_FIELD_ID_PREFIX + self.field_name, self.__class__.__name__, self.is_required and ' required' or '',
     341            (self.get_id(), self.__class__.__name__, self.is_required and ' required' or '',
    241342            self.field_name, self.length, escape(data), maxlength)
    242343
    243344    def html2python(data):
     
    248349    def render(self, data):
    249350        # value is always blank because we never want to redisplay it
    250351        return '<input type="password" id="%s" class="v%s%s" name="%s" value="" />' % \
    251             (FORM_FIELD_ID_PREFIX + self.field_name, self.__class__.__name__, self.is_required and ' required' or '',
     352            (self.get_id(), self.__class__.__name__, self.is_required and ' required' or '',
    252353            self.field_name)
    253354
    254355class LargeTextField(TextField):
     
    266367        if isinstance(data, unicode):
    267368            data = data.encode('utf-8')
    268369        return '<textarea id="%s" class="v%s%s" name="%s" rows="%s" cols="%s">%s</textarea>' % \
    269             (FORM_FIELD_ID_PREFIX + self.field_name, self.__class__.__name__, self.is_required and ' required' or '',
     370            (self.get_id(), self.__class__.__name__, self.is_required and ' required' or '',
    270371            self.field_name, self.rows, self.cols, escape(data))
    271372
    272373class HiddenField(FormField):
     
    276377
    277378    def render(self, data):
    278379        return '<input type="hidden" id="%s" name="%s" value="%s" />' % \
    279             (FORM_FIELD_ID_PREFIX + self.field_name, self.field_name, escape(data))
     380            (self.get_id(), self.field_name, escape(data))
    280381
    281382class CheckboxField(FormField):
    282383    def __init__(self, field_name, checked_by_default=False):
     
    289390        if data or (data is '' and self.checked_by_default):
    290391            checked_html = ' checked="checked"'
    291392        return '<input type="checkbox" id="%s" class="v%s" name="%s"%s />' % \
    292             (FORM_FIELD_ID_PREFIX + self.field_name, self.__class__.__name__,
     393            (self.get_id(), self.__class__.__name__,
    293394            self.field_name, checked_html)
    294395
    295396    def html2python(data):
     
    299400        return False
    300401    html2python = staticmethod(html2python)
    301402
     403
    302404class SelectField(FormField):
    303     def __init__(self, field_name, choices=[], size=1, is_required=False, validator_list=[]):
     405    def __init__(self, field_name, choices=[], size=1, is_required=False, validator_list=[], member_name=None):
    304406        self.field_name = field_name
    305407        # choices is a list of (value, human-readable key) tuples because order matters
    306408        self.choices, self.size, self.is_required = choices, size, is_required
    307409        self.validator_list = [self.isValidChoice] + validator_list
     410        if member_name != None:
     411            self.member_name = member_name
    308412
    309413    def render(self, data):
     414        str_data = str(data) # normalize to string
    310415        output = ['<select id="%s" class="v%s%s" name="%s" size="%s">' % \
    311             (FORM_FIELD_ID_PREFIX + self.field_name, self.__class__.__name__, self.is_required and ' required' or '',
    312             self.field_name, self.size)]
    313         str_data = str(data) # normalize to string
     416            (self.get_id(), self.__class__.__name__,
     417             self.is_required and ' required' or '', self.field_name, self.size)]
    314418        for value, display_name in self.choices:
    315419            selected_html = ''
    316420            if str(value) == str_data:
     
    334438    html2python = staticmethod(html2python)
    335439
    336440class RadioSelectField(FormField):
    337     def __init__(self, field_name, choices=[], ul_class='', is_required=False, validator_list=[]):
     441    def __init__(self, field_name, choices=[], ul_class='', is_required=False, validator_list=[], member_name=None):
    338442        self.field_name = field_name
    339443        # choices is a list of (value, human-readable key) tuples because order matters
    340444        self.choices, self.is_required = choices, is_required
    341445        self.validator_list = [self.isValidChoice] + validator_list
    342446        self.ul_class = ul_class
     447        if member_name != None:
     448            self.member_name = member_name
    343449
    344450    def render(self, data):
    345451        """
     
    382488                'value': value,
    383489                'name': display_name,
    384490                'field': '<input type="radio" id="%s" name="%s" value="%s"%s/>' % \
    385                     (FORM_FIELD_ID_PREFIX + self.field_name + '_' + str(i), self.field_name, value, selected_html),
     491                    (self.get_id() + '_' + str(i), self.field_name, value, selected_html),
    386492                'label': '<label for="%s">%s</label>' % \
    387                     (FORM_FIELD_ID_PREFIX + self.field_name + '_' + str(i), display_name),
     493                    (self.get_id() + '_' + str(i), display_name),
    388494            })
    389495        return RadioFieldRenderer(datalist, self.ul_class)
    390496
     
    414520    requires_data_list = True
    415521    def render(self, data):
    416522        output = ['<select id="%s" class="v%s%s" name="%s" size="%s" multiple="multiple">' % \
    417             (FORM_FIELD_ID_PREFIX + self.field_name, self.__class__.__name__, self.is_required and ' required' or '',
     523            (self.get_id(), self.__class__.__name__, self.is_required and ' required' or '',
    418524            self.field_name, self.size)]
    419525        str_data_list = map(str, data) # normalize to strings
    420526        for value, choice in self.choices:
     
    469575            if str(value) in str_data_list:
    470576                checked_html = ' checked="checked"'
    471577            field_name = '%s%s' % (self.field_name, value)
    472             output.append('<li><input type="checkbox" id="%s%s" class="v%s" name="%s"%s /> <label for="%s%s">%s</label></li>' % \
    473                 (FORM_FIELD_ID_PREFIX, field_name, self.__class__.__name__, field_name, checked_html,
    474                 FORM_FIELD_ID_PREFIX, field_name, choice))
     578            output.append('<li><input type="checkbox" id="%s" class="v%s" name="%s"%s /> <label for="%s">%s</label></li>' % \
     579                (get_id() + value , self.__class__.__name__, field_name, checked_html,
     580                get_id() + value, choice))
    475581        output.append('</ul>')
    476582        return '\n'.join(output)
    477583
     
    490596
    491597    def render(self, data):
    492598        return '<input type="file" id="%s" class="v%s" name="%s" />' % \
    493             (FORM_FIELD_ID_PREFIX + self.field_name, self.__class__.__name__,
     599            (self.get_id(), self.__class__.__name__,
    494600            self.field_name)
    495601
    496602    def html2python(data):
  • django/core/meta/__init__.py

     
    146146class BadKeywordArguments(Exception):
    147147    pass
    148148
     149
     150class InlineRelatedObject(object):
     151    def __init__(self,parent_opts, opts, field):
     152        self.parent_opts = parent_opts
     153        self.opts = opts
     154        self.field = field
     155        self.name = opts.module_name
     156
     157    def flatten_data(self,obj = None):
     158        var_name = self.opts.object_name.lower()
     159        new_data = {}
     160        rel_instances = self.get_list(obj)
     161
     162        for i, rel_instance in enumerate(rel_instances):
     163            instance_data = {}
     164            for f in self.opts.fields + self.opts.many_to_many:
     165                field_data = f.flatten_data(rel_instance)
     166                #if hasattr(f, 'editable') and f.editable and f != self.field:
     167                for name, value in field_data.items():
     168                    instance_data['%s.%d.%s' % (var_name, i, name)] = value
     169            new_data.update(instance_data)             
     170   
     171        return new_data       
     172
     173    def extract_data(self, data):
     174        "Pull out the data meant for inline objects of this class, ie anything starting with our module name"
     175        return data # TODO 
     176   
     177    def get_list(self, parent_instance = None):
     178        "Get the list of this type of object from an instance of the parent class"
     179        if parent_instance != None:
     180            func_name = 'get_%s_list' % self.parent_opts.get_rel_object_method_name(self.opts, self.field)
     181            func = getattr(parent_instance, func_name)
     182            list = func()
     183           
     184            count = len(list) + self.field.rel.num_extra_on_change
     185            if self.field.rel.min_num_in_admin:
     186               count = max(count, self.field.rel.min_num_in_admin)
     187            if self.field.rel.max_num_in_admin:
     188               count = min(count, self.field.rel.max_num_in_admin)
     189       
     190            change = count - len(list)
     191            if change > 0:
     192                return list + [None for _ in range(change)]
     193            if change < 0:
     194                return list[:change]
     195            else: # Just right
     196                return list
     197        else:
     198            return [None for _ in range(self.field.rel.num_in_admin)]
     199
     200   
     201    def editable_fields(self):
     202        "Get the fields in this class that should be edited inline"
     203        return [f for f in self.opts.fields + self.opts.many_to_many if f.editable and f != self.field ]
     204       
     205    def __repr__(self):
     206        return "<InlineRelatedObject: %s related to %s>" % ( self.name, self.field.name)     
     207
     208
     209
    149210class Options:
    150211    def __init__(self, module_name='', verbose_name='', verbose_name_plural='', db_table='',
    151212        fields=None, ordering=None, unique_together=None, admin=None, has_related_links=False,
     
    317378    def get_inline_related_objects(self):
    318379        return [(a, b) for a, b in self.get_all_related_objects() if b.rel.edit_inline]
    319380
     381    def get_inline_related_objects_wrapped(self):
     382        return [InlineRelatedObject(self, opts, field) for opts, field in self.get_all_related_objects() if field.rel.edit_inline]
     383
     384    def get_data_holders(self):
     385        return self.fields + self.many_to_many + self.get_inline_related_objects_wrapped()
     386
    320387    def get_all_related_many_to_many_objects(self):
    321388        module_list = get_installed_model_modules()
    322389        rel_objs = []
     
    594661            new_mod.get_latest = curry(function_get_latest, opts, new_class, does_not_exist_exception)
    595662
    596663        for f in opts.fields:
     664            #TODO : change this into a virtual function so that user defined fields will be able to add methods to module or class.
    597665            if f.choices:
    598666                # Add "get_thingie_display" method to get human-readable value.
    599667                func = curry(method_get_display_value, f)
     
    788856        # If it does already exist, do an UPDATE.
    789857        if cursor.fetchone():
    790858            db_values = [f.get_db_prep_save(f.pre_save(getattr(self, f.column), False)) for f in non_pks]
    791             cursor.execute("UPDATE %s SET %s WHERE %s=%%s" % (opts.db_table,
    792                 ','.join(['%s=%%s' % f.column for f in non_pks]), opts.pk.column),
     859            while 1:
     860                try:
     861                    idx = db_values.index('')
     862                    non_pks[idx:idx+1] = []
     863                    db_values[idx:idx +1] = []
     864                except: break
     865            cursor.execute("UPDATE %s SET %s WHERE %s=%%s" % (opts.db_table,
     866                ','.join(['%s=%%s' % f.column for f in non_pks]), opts.pk.column),
    793867                db_values + [pk_val])
    794868        else:
    795869            record_exists = False
     
    13321406        if f == '?': # Special case.
    13331407            order_by.append(db.get_random_function_sql())
    13341408        else:
     1409            if f.startswith('-'):
     1410                col_name = f[1:]
     1411                order = "DESC"
     1412            else:
     1413                col_name = f
     1414                order = "ASC"
    13351415            # Use the database table as a column prefix if it wasn't given,
    13361416            # and if the requested column isn't a custom SELECT.
    1337             if "." not in f and f not in [k[0] for k in kwargs.get('select', [])]:
     1417            if "." not in col_name and col_name not in [k[0] for k in kwargs.get('select', [])]:
    13381418                table_prefix = opts.db_table + '.'
    13391419            else:
    13401420                table_prefix = ''
    1341             if f.startswith('-'):
    1342                 order_by.append('%s%s DESC' % (table_prefix, orderfield2column(f[1:], opts)))
    1343             else:
    1344                 order_by.append('%s%s ASC' % (table_prefix, orderfield2column(f, opts)))
     1421           
     1422            order_by.append('%s%s %s' % (table_prefix, orderfield2column(col_name, opts), order))
     1423   
    13451424    order_by = ", ".join(order_by)
    13461425
    13471426    # LIMIT and OFFSET clauses
     
    13971476    man.__module__ = MODEL_PREFIX + '.' + opts.module_name # Set this explicitly, as above.
    13981477    man.__init__ = curry(manipulator_init, opts, add, change)
    13991478    man.save = curry(manipulator_save, opts, klass, add, change)
     1479    man.get_inline_related_objects_wrapped = curry(manipulator_get_inline_related_objects_wrapped, opts, klass, add, change)
     1480    man.flatten_data = curry(manipulator_flatten_data, opts, klass, add, change)
    14001481    for field_name_list in opts.unique_together:
    14011482        setattr(man, 'isUnique%s' % '_'.join(field_name_list), curry(manipulator_validator_unique_together, field_name_list, opts))
    14021483    for f in opts.fields:
     
    14391520            self.fields.extend(f.get_manipulator_fields(opts, self, change))
    14401521
    14411522    # Add fields for related objects.
    1442     for rel_opts, rel_field in opts.get_inline_related_objects():
     1523    for obj in opts.get_inline_related_objects_wrapped():
    14431524        if change:
    1444             count = getattr(self.original_object, 'get_%s_count' % opts.get_rel_object_method_name(rel_opts, rel_field))()
    1445             count += rel_field.rel.num_extra_on_change
    1446             if rel_field.rel.min_num_in_admin:
    1447                 count = max(count, rel_field.rel.min_num_in_admin)
    1448             if rel_field.rel.max_num_in_admin:
    1449                 count = min(count, rel_field.rel.max_num_in_admin)
     1525            count = getattr(self.original_object, 'get_%s_count' % opts.get_rel_object_method_name(obj.opts, obj.field))()
     1526            count += obj.field.rel.num_extra_on_change
     1527            if obj.field.rel.min_num_in_admin:
     1528                count = max(count, obj.field.rel.min_num_in_admin)
     1529            if obj.field.rel.max_num_in_admin:
     1530                count = min(count, obj.field.rel.max_num_in_admin)
    14501531        else:
    1451             count = rel_field.rel.num_in_admin
    1452         for f in rel_opts.fields + rel_opts.many_to_many:
    1453             if f.editable and f != rel_field and (not f.primary_key or (f.primary_key and change)):
     1532            count = obj.field.rel.num_in_admin
     1533        for f in obj.opts.fields + obj.opts.many_to_many:
     1534            if f.editable and f != obj.field :
    14541535                for i in range(count):
    1455                     self.fields.extend(f.get_manipulator_fields(rel_opts, self, change, name_prefix='%s.%d.' % (rel_opts.object_name.lower(), i), rel=True))
     1536                    self.fields.extend(f.get_manipulator_fields(obj.opts, self, change, name_prefix='%s.%d.' % (obj.opts.object_name.lower(), i), rel=True))
    14561537
    14571538    # Add field for ordering.
    14581539    if change and opts.get_ordered_objects():
     
    15931674            getattr(new_object, 'set_%s_order' % rel_opts.object_name.lower())(order)
    15941675    return new_object
    15951676
     1677def manipulator_get_inline_related_objects_wrapped(opts, klass, add, change, self):
     1678    return opts.get_inline_related_objects_wrapped()
     1679       
     1680def manipulator_flatten_data(opts, klass, add, change, self):
     1681     new_data = {}
     1682     obj = change and self.original_object or None
     1683     for f in opts.get_data_holders():
     1684            new_data.update(f.flatten_data(obj))
     1685     return new_data
     1686
    15961687def manipulator_validator_unique_together(field_name_list, opts, self, field_data, all_data):
    15971688    from django.utils.text import get_text_list
    15981689    field_list = [opts.get_field(field_name) for field_name in field_name_list]
  • django/core/meta/fields.py

     
    1616BLANK_CHOICE_NONE = [("", "None")]
    1717
    1818# Values for Relation.edit_inline.
    19 TABULAR, STACKED = 1, 2
     19TABULAR, STACKED = "admin_edit_inline_tabular", "admin_edit_inline_stacked"
    2020
    2121RECURSIVE_RELATIONSHIP_CONSTANT = 'self'
    2222
     
    152152            if hasattr(self.default, '__get_value__'):
    153153                return self.default.__get_value__()
    154154            return self.default
    155         if self.null:
     155        if not self.empty_strings_allowed or self.null:
    156156            return None
    157157        return ""
    158158
     
    174174        if self.maxlength and not self.choices: # Don't give SelectFields a maxlength parameter.
    175175            params['maxlength'] = self.maxlength
    176176        if isinstance(self.rel, ManyToOne):
     177            params['member_name'] = name_prefix + self.get_db_column()
    177178            if self.rel.raw_id_admin:
    178179                field_objs = self.get_manipulator_field_objs()
    179180                params['validator_list'].append(curry(manipulator_valid_rel_key, self, manipulator))
    180181            else:
    181182                if self.radio_admin:
    182183                    field_objs = [formfields.RadioSelectField]
    183                     params['choices'] = self.get_choices(include_blank=self.blank, blank_choice=BLANK_CHOICE_NONE)
    184184                    params['ul_class'] = get_ul_class(self.radio_admin)
    185185                else:
    186186                    if self.null:
    187187                        field_objs = [formfields.NullSelectField]
    188188                    else:
    189189                        field_objs = [formfields.SelectField]
    190                     params['choices'] = self.get_choices()
     190                params['choices'] = self.get_choices_default()
    191191        elif self.choices:
    192192            if self.radio_admin:
    193193                field_objs = [formfields.RadioSelectField]
    194                 params['choices'] = self.get_choices(include_blank=self.blank, blank_choice=BLANK_CHOICE_NONE)
    195194                params['ul_class'] = get_ul_class(self.radio_admin)
    196195            else:
    197196                field_objs = [formfields.SelectField]
    198                 params['choices'] = self.get_choices()
     197             
     198            params['choices'] = self.get_choices_default()
    199199        else:
    200200            field_objs = self.get_manipulator_field_objs()
    201201
     
    255255                val = None
    256256            return val
    257257
     258
    258259    def get_choices(self, include_blank=True, blank_choice=BLANK_CHOICE_DASH):
    259260        "Returns a list of tuples used as SelectField choices for this field."
     261       
    260262        first_choice = include_blank and blank_choice or []
    261263        if self.choices:
    262264            return first_choice + list(self.choices)
    263265        rel_obj = self.rel.to
    264         return first_choice + [(getattr(x, rel_obj.pk.column), repr(x)) for x in rel_obj.get_model_module().get_list(**self.rel.limit_choices_to)]
     266        choices = first_choice + [(getattr(x, rel_obj.pk.column), repr(x)) for x in rel_obj.get_model_module().get_list(**self.rel.limit_choices_to)]
     267 
     268        return choices
    265269
     270
     271    def get_choices_default(self):
     272        if(self.radio_admin):
     273            return self.get_choices(include_blank=self.blank, blank_choice=BLANK_CHOICE_NONE)
     274        else:
     275            return self.get_choices()
     276
     277    def _get_val_from_obj(self, obj):
     278        if obj:
     279           return getattr(obj, self.column)
     280        else:
     281           return self.get_default()
     282
     283    def flatten_data(self, obj = None):
     284         """
     285             Returns a dictionary mapping the field's manipulator field names to its
     286             "flattened" string values for the admin view. Obj is the instance to extract the
     287             values from.
     288         """
     289         return { self.get_db_column(): self._get_val_from_obj(obj)}
     290       
     291 
    266292class AutoField(Field):
    267293    empty_strings_allowed = False
    268294    def __init__(self, *args, **kwargs):
     
    271297
    272298    def get_manipulator_fields(self, opts, manipulator, change, name_prefix='', rel=False):
    273299        if not rel:
    274             return [] # Don't add a FormField unless it's in a related context.
     300            return [] # Don't add a FormField unless it's in a related change context.
    275301        return Field.get_manipulator_fields(self, opts, manipulator, change, name_prefix, rel)
    276302
    277303    def get_manipulator_field_objs(self):
     
    327353    def get_manipulator_field_objs(self):
    328354        return [formfields.DateField]
    329355
     356    def flatten_data(self, obj = None):
     357        val = self._get_val_from_obj(obj)
     358        return {self.get_db_column(): (val is not None and val.strftime("%Y-%m-%d") or '')}
     359
    330360class DateTimeField(DateField):
    331361    def get_db_prep_save(self, value):
    332362        # Casts dates into string format for entry into database.
     
    356386            return datetime.datetime.combine(d, t)
    357387        return self.get_default()
    358388
     389    def flatten_data(self,obj = None):
     390        val = self._get_val_from_obj(obj)
     391        date_field, time_field = self.get_manipulator_field_names('')
     392        return {date_field: (val is not None and val.strftime("%Y-%m-%d") or ''),
     393                time_field: (val is not None and val.strftime("%H:%M:%S") or '')}
     394
    359395class EmailField(Field):
    360396    def get_manipulator_field_objs(self):
    361397        return [formfields.EmailField]
     
    539575    def get_manipulator_field_objs(self):
    540576        return [formfields.TimeField]
    541577
     578    def flatten_data(self,obj = None):
     579        val = self._get_val_from_obj(obj)
     580        return {self.get_db_column(): (val is not None and val.strftime("%H:%M:%S") or '')}
     581
    542582class URLField(Field):
    543583    def __init__(self, verbose_name=None, name=None, verify_exists=True, **kwargs):
    544584        if verify_exists:
     
    592632    def get_manipulator_field_objs(self):
    593633        return [formfields.IntegerField]
    594634
     635    def flatten_data(self, obj = None):
     636        if not obj:
     637            # In required many-to-one fields with only one available choice,
     638            # select that one available choice. Note: We have to check that
     639            # the length of choices is *2*, not 1, because SelectFields always
     640            # have an initial "blank" value.
     641            if not self.blank and not self.rel.raw_id_admin and self.choices:
     642               choice_list = self.get_choices_default()
     643               if len(choice_list) == 2:
     644                  return { self.name : choice_list[1][0] }
     645        return Field.flatten_data(self, obj)
     646
    595647class ManyToManyField(Field):
    596648    def __init__(self, to, **kwargs):
    597649        kwargs['verbose_name'] = kwargs.get('verbose_name', to._meta.verbose_name_plural)
     
    609661        if self.rel.raw_id_admin:
    610662            return [formfields.CommaSeparatedIntegerField]
    611663        else:
    612             choices = self.get_choices(include_blank=False)
     664            choices = self.get_choices_default()
    613665            return [curry(formfields.SelectMultipleField, size=min(max(len(choices), 5), 15), choices=choices)]
    614666
     667    def get_choices_default(self):
     668        return Field.get_choices(self, include_blank=False)
     669
    615670    def get_m2m_db_table(self, original_opts):
    616671        "Returns the name of the many-to-many 'join' table."
    617672        return '%s_%s' % (original_opts.db_table, self.name)
     
    632687                len(badkeys) == 1 and badkeys[0] or tuple(badkeys),
    633688                len(badkeys) == 1 and "is" or "are")
    634689
     690    def flatten_data(self, obj = None):
     691        new_data = {}
     692        if obj:
     693            get_list_func = getattr(obj, 'get_%s_list' % self.rel.singular)
     694            instance_ids = [getattr(instance, self.rel.to.pk.column) for instance in get_list_func()]
     695            if self.rel.raw_id_admin:
     696                 new_data[self.name] = ",".join([str(id) for id in instance_ids])
     697            elif not self.rel.edit_inline:
     698                 new_data[self.name] = instance_ids
     699        else:
     700            # In required many-to-many fields with only one available choice,
     701            # select that one available choice.
     702            if not self.blank and not self.rel.edit_inline and not self.rel.raw_id_admin and self.choices:
     703               choice_list = self.get_choices_default()
     704               if len(choice_list) == 1:
     705                   new_data[self.name] = [choices_list[0][0]]
     706        return new_data
     707
     708
    635709class OneToOneField(IntegerField):
    636710    def __init__(self, to, to_field=None, **kwargs):
    637711        kwargs['verbose_name'] = kwargs.get('verbose_name', 'ID')
     
    714788        Returns self.fields, except with fields as Field objects instead of
    715789        field names. If self.fields is None, defaults to putting every
    716790        non-AutoField field with editable=True in a single fieldset.
     791       
     792        returns a list of lists of name, dict
     793        the dict has attribs 'fields' and maybe 'classes'.
     794        fields is a list of subclasses of Field.
     795
     796        Return value needs to be encapsulated.
    717797        """
    718798        if self.fields is None:
    719799            field_struct = ((None, {'fields': [f.name for f in opts.fields + opts.many_to_many if f.editable and not isinstance(f, AutoField)]}),)
  • django/core/defaulttags.py

     
    22
    33import sys
    44import template
     5import template_loader
    56
    67class CommentNode(template.Node):
    78    def render(self, context):
     
    223224                return '' # Fail silently for invalid included templates.
    224225        return output
    225226
     227class IncludeNode(template.Node):
     228    def __init__(self, template_path):
     229        self.template_path = template_path
     230
     231    def render(self, context):
     232         try:
     233             t = template_loader.get_template(self.template_path)
     234             return t.render(context)
     235         except:
     236             return '' # Fail silently for invalid included templates.
     237
     238
    226239class LoadNode(template.Node):
    227240    def __init__(self, taglib):
    228241        self.taglib = taglib
     
    600613            raise template.TemplateSyntaxError, "Second (optional) argument to %s tag must be 'parsed'" % bits[0]
    601614    return SsiNode(bits[1], parsed)
    602615
     616def do_include(parser, token):
     617    """
     618    Loads a template using standard resolution mechanisms, and renders it in the current context.     
     619    """
     620    bits = token.contents.split()
     621    parsed = False
     622    if len(bits) != 2:
     623        raise template.TemplateSyntaxError, "'include' tag takes one argument: the path to the template to be included"
     624    return IncludeNode(bits[1])
     625
    603626def do_load(parser, token):
    604627    """
    605628    Load a custom template tag set.
     
    755778template.register_tag('ifnotequal', lambda parser, token: do_ifequal(parser, token, True))
    756779template.register_tag('if', do_if)
    757780template.register_tag('ifchanged', do_ifchanged)
     781template.register_tag('include', do_include)
    758782template.register_tag('regroup', do_regroup)
    759783template.register_tag('ssi', do_ssi)
    760784template.register_tag('load', do_load)
  • django/templatetags/admin_modify.py

     
     1from django.core import template, template_loader, meta
     2from django.conf.settings import ADMIN_MEDIA_PREFIX
     3from django.utils.text import capfirst
     4from django.utils.html import escape
     5from django.utils.functional import curry
     6
     7from django.views.admin.main import BoundField
     8import re
     9
     10class IncludeAdminScriptNode(template.Node):
     11      def __init__(self, var):
     12         self.var = var
     13 
     14      def render(self, context):
     15        resolved = template.resolve_variable(self.var, context)
     16        return '<script type="text/javascript" src="%s%s"></script>' % \
     17                (ADMIN_MEDIA_PREFIX, resolved)
     18         
     19class SubmitRowNode(template.Node):
     20      def __init__(self):
     21          pass
     22
     23      def render(self, context):
     24          change = context['change']
     25          add = context['add']
     26          show_delete = context['show_delete']
     27          ordered_objects = context['ordered_objects']
     28          save_as = context['save_as']
     29          has_delete_permission = context['has_delete_permission']
     30          is_popup = context['is_popup']
     31         
     32          t = ['<div class="submit-row">']
     33          onclick_attrib = ordered_objects and change and 'onclick="submitOrderForm();"' or ''
     34         
     35          if not is_popup:
     36                if has_delete_permission and (change or show_delete):
     37                   t.append('<p class="float-left"><a href="delete/" class="deletelink">Delete</a></p>')
     38                if change and save_as:
     39                   t.append('<input type="submit" value="Save as new" name="_saveasnew" %s/>' %  onclick_attrib)
     40                if (not save_as or add):
     41                   t.append('<input type="submit" value="Save and add another" name="_addanother" %s/>' %  onclick_attrib)
     42          t.append('<input type="submit" value="Save and continue editing" name="_continue" %s/>' %  onclick_attrib )
     43          t.append('<input type="submit" value="Save" class="default" %s/>' %  onclick_attrib)
     44          t.append('</div>\n')
     45         
     46          return ''.join(t)
     47
     48
     49
     50
     51class AdminFieldBoundNode(template.Node):
     52    def __init__(self, argument):
     53        self.argument = argument
     54   
     55    def render(self, context):
     56        argument_val = template.resolve_variable(self.argument, context)
     57        if (isinstance(argument_val, list)):
     58            bound_fields = argument_val
     59        else:
     60            bound_fields = [argument_val]
     61        add = context['add']
     62        change = context['change']
     63       
     64        context.push()
     65        context['bound_fields'] = bound_fields
     66        context['class_names'] = " ".join(self.get_class_names(bound_fields))
     67        t = template_loader.get_template("admin_field")
     68        output =  t.render(context)
     69        context.pop()
     70         
     71        return output
     72
     73    def get_class_names(self, bound_fields):
     74
     75        class_names = ['form-row']
     76        for bound_field in bound_fields:
     77            for f in bound_field.form_fields:
     78                if f.errors():
     79                    class_names.append('errors')
     80                    break
     81         
     82        # Assumes BooleanFields won't be stacked next to each other!
     83        if isinstance(bound_fields[0].field, meta.BooleanField):
     84            class_names.append('checkbox-row')
     85         
     86        return class_names
     87       
     88class FieldWidgetNode(template.Node):
     89    def __init__(self, bound_field_var):
     90        self.bound_field_var = bound_field_var
     91
     92    def render(self, context):
     93        bound_field = template.resolve_variable(self.bound_field_var, context)
     94        add = context['add']
     95        change = context['change']
     96       
     97        context.push()
     98        context['bound_field'] = bound_field
     99        t = template_loader.get_template("admin_field_widget")
     100        output =  t.render(context)
     101        context.pop()
     102         
     103        return output
     104
     105       
     106
     107class FieldWrapper(object):
     108    def __init__(self, field ):
     109        self.field = field
     110
     111    def needs_header(self):
     112        return not isinstance(self.field, meta.AutoField)
     113
     114    def header_class_attribute(self):
     115        return self.field.blank and ' class="optional"' or ''
     116
     117    def use_raw_id_admin(self):
     118         return isinstance(self.field.rel, (meta.ManyToOne, meta.ManyToMany)) \
     119                and self.field.rel.raw_id_admin
     120
     121class FormObjectWrapper(object):
     122    def __init__(self, obj, field_wrappers):
     123        self.obj = obj
     124        self.field_wrappers = field_wrappers
     125     
     126        self.bound_fields = [ BoundField(fw.field, obj['original'],  True, self.obj) for fw in self.field_wrappers ]
     127               
     128
     129
     130    def has_errors(self):
     131        return max([ bool( len( self.obj[fw.field.name].errors()))  for fw in self.field_wrappers])
     132       
     133    def html_combined_error_list(self):
     134        return ''.join( [ self.obj[fw.field.name].html_error_list() for fw in self.field_wrappers])
     135
     136
     137   
     138
     139class EditInlineNode(template.Node):
     140    def __init__(self, rel_var):
     141        self.rel_var = rel_var
     142   
     143    def render(self, context):
     144        relation = template.resolve_variable(self.rel_var, context)
     145        add, change = context['add'], context['change']
     146       
     147        context.push()
     148
     149        self.fill_context(relation, add, change, context)
     150       
     151        t = template_loader.get_template(relation.field.rel.edit_inline)
     152       
     153        output = t.render(context)
     154         
     155        context.pop()
     156        return output
     157
     158       
     159    def fill_context(self, relation, add, change, context):
     160        field_wrapper_list = [FieldWrapper(f) for f in relation.editable_fields()]
     161       
     162        var_name = relation.opts.object_name.lower()
     163       
     164        form = template.resolve_variable('form', context)
     165        form_objects = form[relation.opts.module_name]
     166        form_object_wrapper_list = [FormObjectWrapper(o,field_wrapper_list) for o in form_objects]
     167   
     168        context['field_wrapper_list'] = field_wrapper_list
     169        context['form_object_wrapper_list'] = form_object_wrapper_list
     170        context['num_headers'] = len(field_wrapper_list)
     171        context['original_row_needed'] = max([fw.use_raw_id_admin() for fw in field_wrapper_list])
     172        context['name_prefix'] = "%s." % (var_name,)
     173   
     174class FieldLabelNode(template.Node):
     175    def __init__(self, bound_field_var):
     176        self.bound_field_var = bound_field_var
     177       
     178    def render(self, context):
     179        bound_field = template.resolve_variable(self.bound_field_var, context)
     180        class_names = []
     181        if isinstance(bound_field.field, meta.BooleanField):
     182            class_names.append("vCheckboxLabel")
     183        else:
     184            if not bound_field.field.blank:
     185                class_names.append('required')
     186            if not bound_field.first:
     187                class_names.append('inline')
     188       
     189        class_str = class_names and ' class="%s"' % ' '.join(class_names) or ''
     190        return '<label for="%s"%s>%s:</label> ' % (bound_field.element_id, class_str, capfirst(bound_field.field.verbose_name) )
     191
     192class OutputAllNode(template.Node):
     193    def __init__(self, form_fields_var):
     194        self.form_fields_var = form_fields_var
     195   
     196    def render(self, context):
     197        form_fields = template.resolve_variable(self.form_fields_var, context)
     198        return ''.join([str(f) for f in form_fields])
     199
     200class AutoPopulatedFieldScriptNode(template.Node):
     201    def __init__(self, auto_pop_var):
     202        self.auto_pop_var = auto_pop_var
     203
     204    def render(self,context):
     205        auto_pop_fields = template.resolve_variable(self.auto_pop_var, context)
     206        change = context['change']
     207        for field in auto_pop_fields:
     208            t = []
     209            if change:
     210                t.append('document.getElementById("id_%s")._changed = true;' % field.name )
     211            else:
     212                t.append('document.getElementById("id_%s").onchange = function() { this._changed = true; };' % field.name)
     213
     214            add_values = ' + " " + '.join(['document.getElementById("id_%s").value' % g for g in field.prepopulate_from])
     215            for f in field.prepopulate_from:
     216                t.append("""
     217                         document.getElementById("id_%s").onkeyup = function() {
     218                                var e = document.getElementById("id_%s");
     219                                if(e._changed) { e.value = URLify(%s, %s);}
     220                        }
     221                        """ % (f, field.name, add_values, field.maxlength) )
     222
     223        return ''.join(t)
     224
     225class FilterInterfaceScriptMaybeNode(template.Node):
     226    def __init__(self, bound_field_var):
     227       self.bound_field_var = bound_field_var
     228
     229    def render(self, context):
     230        bound_field = template.resolve_variable(self.bound_field_var, context)
     231        f = bound_field.field
     232        if f.rel and isinstance(f.rel, meta.ManyToMany) and f.rel.filter_interface:
     233           return '<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)
     234        else:
     235            return ''
     236
     237     
     238
     239
     240def do_submit_row(parser, token):
     241    return SubmitRowNode()
     242
     243
     244def do_one_arg_tag(node_factory, parser,token):
     245    tokens = token.contents.split()
     246    if len(tokens) != 2:
     247        raise template.TemplateSyntaxError("%s takes 1 argument" % tokens[0])
     248    return node_factory(tokens[1])
     249
     250
     251one_arg_tag_nodes = [
     252    IncludeAdminScriptNode,
     253    AdminFieldBoundNode,
     254    FieldLabelNode,
     255    FieldWidgetNode,
     256    OutputAllNode,
     257    EditInlineNode,
     258    AutoPopulatedFieldScriptNode,
     259    FilterInterfaceScriptMaybeNode,
     260]
     261
     262word = re.compile('[A-Z][a-z]+')
     263def register_one_arg_tag(node):
     264    tag_name = '_'.join([ s.lower() for s in word.findall(node.__name__)[:-1] ])
     265    parse_func = curry(do_one_arg_tag, node)
     266    template.register_tag(tag_name, parse_func)
     267
     268 
     269
     270for node in one_arg_tag_nodes:
     271    register_one_arg_tag(node)   
     272
     273template.register_tag('submit_row', do_submit_row )
  • django/views/admin/main.py

     
    11# Generic admin views, with admin templates created dynamically at runtime.
    22
    3 from django.core import formfields, meta, template_loader
     3from django.core import formfields, meta, template_loader, template
    44from django.core.exceptions import Http404, ObjectDoesNotExist, PermissionDenied
    55from django.core.extensions import DjangoContext as Context
    66from django.core.extensions import get_object_or_404, render_to_response
     
    493493    })
    494494    return HttpResponse(t.render(c))
    495495
    496 def _get_flattened_data(field, val):
    497     """
    498     Returns a dictionary mapping the field's manipulator field names to its
    499     "flattened" string values for the admin view. "val" is an instance of the
    500     field's value.
    501     """
    502     if isinstance(field, meta.DateTimeField):
    503         date_field, time_field = field.get_manipulator_field_names('')
    504         return {date_field: (val is not None and val.strftime("%Y-%m-%d") or ''),
    505                 time_field: (val is not None and val.strftime("%H:%M:%S") or '')}
    506     elif isinstance(field, meta.DateField):
    507         return {field.name: (val is not None and val.strftime("%Y-%m-%d") or '')}
    508     elif isinstance(field, meta.TimeField):
    509         return {field.name: (val is not None and val.strftime("%H:%M:%S") or '')}
    510     else:
    511         return {field.name: val}
    512 
    513496use_raw_id_admin = lambda field: isinstance(field.rel, (meta.ManyToOne, meta.ManyToMany)) and field.rel.raw_id_admin
    514497
    515498def _get_submit_row_template(opts, app_label, add, change, show_delete, ordered_objects):
     
    530513    t.append('</div>\n')
    531514    return t
    532515
     516def get_javascript_imports(opts,auto_populated_fields, ordered_objects, admin_field_objs):
     517# Put in any necessary JavaScript imports.
     518    js = ['js/core.js', 'js/admin/RelatedObjectLookups.js']
     519    if auto_populated_fields:
     520        js.append('js/urlify.js')
     521    if opts.has_field_type(meta.DateTimeField) or opts.has_field_type(meta.TimeField) or opts.has_field_type(meta.DateField):
     522        js.extend(['js/calendar.js', 'js/admin/DateTimeShortcuts.js'])
     523    if ordered_objects:
     524        js.extend(['js/getElementsBySelector.js', 'js/dom-drag.js' , 'js/admin/ordering.js'])
     525    if opts.admin.js:
     526        js.extend(opts.admin.js)
     527    seen_collapse = False
     528    for _, options in admin_field_objs:
     529        if not seen_collapse and 'collapse' in options.get('classes', ''):
     530            seen_collapse = True
     531            js.append('js/admin/CollapsedFieldsets.js' )
     532        try:
     533            for field_list in options['fields']:
     534                for f in field_list:
     535                    if f.rel and isinstance(f, meta.ManyToManyField) and f.rel.filter_interface:
     536                        js.extend(['js/SelectBox.js' , 'js/SelectFilter2.js'])
     537                        raise StopIteration
     538        except StopIteration:
     539            break
     540    return js
     541
     542class BoundField(object):
     543    def __init__(self, field, original, rel, field_mapping):
     544        self.field = field
     545       
     546        self.form_fields = self.resolve_form_fields(field_mapping)
     547       
     548        self.element_id = self.form_fields[0].get_id()
     549        self.has_label_first = not isinstance(self.field, meta.BooleanField)
     550        self.original = original
     551        self.raw_id_admin = use_raw_id_admin(field)
     552        self.rel = rel
     553        self.is_date_time = isinstance(field, meta.DateTimeField)
     554        self.is_file_field = isinstance(field, meta.FileField)
     555        self.needs_add_label = field.rel and isinstance(field.rel, meta.ManyToOne) and field.rel.to.admin
     556        self.not_in_table = isinstance(self.field, meta.AutoField)
     557        self.first = True
     558       
     559        classes = []
     560        if(self.raw_id_admin):
     561            classes.append('nowrap')
     562        if max([bool(f.errors()) for f in self.form_fields]):
     563            classes.append('error')
     564        self.cell_class_attribute = ' '.join(classes)   
     565        self._repr_filled = False
     566   
     567    def resolve_form_fields(self, field_mapping):
     568        return [field_mapping[name] for name in self.field.get_manipulator_field_names('')]
     569
     570   
     571    def as_field_list(self):
     572        return [self.field]
     573
     574    def original_value(self):
     575        return self.original.__dict__[self.field.name]
     576       
     577    def _fetch_existing_repr(self, func_name):
     578        class_dict = self.original.__class__.__dict__
     579        func = class_dict.get(func_name)
     580        return func(self.original)
     581       
     582    def _fill_existing_repr(self):
     583        if self._repr_filled:
     584            return
     585        #HACK
     586        if isinstance(self.field.rel, meta.ManyToOne):
     587             func_name = 'get_%s' % self.field.name
     588             self._repr = self._fetch_existing_repr(func_name)
     589        elif isinstance(self.field.rel, meta.ManyToMany):
     590             func_name = 'get_%s_list' % self.field.name
     591             self._repr =  ",".join(self._fetch_existing_repr(func_name))
     592        self._repr_filled = True
     593             
     594    def existing_repr(self):
     595        self._fill_existing_repr()
     596        return self._repr
     597
     598    def __repr__(self):
     599        return repr(self.__dict__)
     600
     601   
     602
     603
     604class AdminFieldSet(object):
     605    def __init__(self, fieldset_name, options, form, original):
     606         self.name = fieldset_name
     607         self.options = options
     608         self.bound_field_sets = self.get_bound_field_sets(form, original)
     609         self.classes = options.get('classes', '')
     610     
     611    def __repr__(self):
     612        return "Fieldset:(%s,%s)" % (self.name, self.bound_field_sets)
     613
     614    def get_bound_field_sets(self, form, original):
     615        fields = self.options['fields']
     616        bound_field_sets = [ [BoundField(f, original, False, form) for f in field  ] for field in fields]
     617        for set in bound_field_sets:
     618            first = True
     619            for bound_field in set:
     620                bound_field.first = first
     621                first = False
     622
     623        return bound_field_sets
     624
     625def fill_extra_context(opts, app_label, context, add=False, change=False, show_delete=False, form_url=''):
     626    admin_field_objs = opts.admin.get_field_objs(opts)
     627    ordered_objects = opts.get_ordered_objects()[:]
     628    auto_populated_fields = [f for f in opts.fields if f.prepopulate_from]
     629 
     630    javascript_imports = get_javascript_imports(opts,auto_populated_fields, ordered_objects, admin_field_objs);
     631   
     632    if ordered_objects:
     633        coltype = 'colMS'
     634    else:
     635        coltype = 'colM'
     636       
     637    has_absolute_url = hasattr(opts.get_model_module().Klass, 'get_absolute_url')
     638   
     639    form_enc_attrib = opts.has_field_type(meta.FileField) and 'enctype="multipart/form-data" ' or ''
     640
     641    form = context['form']
     642    original = context['original']
     643    admin_fieldsets = [AdminFieldSet(name, options, form, original) for name, options in admin_field_objs]
     644    inline_related_objects = opts.get_inline_related_objects_wrapped()
     645   
     646    ordered_object_names =   ' '.join(['object.%s' % o.pk.name for o in ordered_objects])
     647   
     648    extra_context = {
     649        'add': add,
     650        'change': change,
     651        'admin_field_objs' : admin_field_objs,
     652        'ordered_objects' : ordered_objects,
     653        'auto_populated_fields' : auto_populated_fields,
     654        'javascript_imports' : javascript_imports,
     655        'coltype' : coltype,
     656        'has_absolute_url': has_absolute_url,
     657        'form_enc_attrib': form_enc_attrib,
     658        'form_url' : form_url,
     659        'admin_fieldsets' : admin_fieldsets,
     660        'inline_related_objects': inline_related_objects,
     661        'ordered_object_names' : ordered_object_names,
     662        'content_type_id' : opts.get_content_type_id(),
     663        'save_on_top' : opts.admin.save_on_top,
     664        'verbose_name_plural': opts.verbose_name_plural,
     665        'save_as': opts.admin.save_as,
     666        'app_label': app_label,
     667        'object_name': opts.object_name,
     668        'has_delete_permission' : context['perms'][app_label][opts.get_delete_permission()]
     669    }
     670   
     671    context.update(extra_context)   
     672   
     673   
     674def add_stage_new(request, app_label, module_name, show_delete=False, form_url='', post_url='../', post_url_continue='../%s/', object_id_override=None):
     675    mod, opts = _get_mod_opts(app_label, module_name)
     676    if not request.user.has_perm(app_label + '.' + opts.get_add_permission()):
     677        raise PermissionDenied
     678    manipulator = mod.AddManipulator()
     679    if request.POST:
     680        new_data = request.POST.copy()
     681        if opts.has_field_type(meta.FileField):
     682            new_data.update(request.FILES)
     683        errors = manipulator.get_validation_errors(new_data)
     684        if not errors and not request.POST.has_key("_preview"):
     685            for f in opts.many_to_many:
     686                if f.rel.raw_id_admin:
     687                    new_data.setlist(f.name, new_data[f.name].split(","))
     688            manipulator.do_html2python(new_data)
     689            new_object = manipulator.save(new_data)
     690            pk_value = getattr(new_object, opts.pk.column)
     691            log.log_action(request.user.id, opts.get_content_type_id(), pk_value, repr(new_object), log.ADDITION)
     692            msg = 'The %s "%s" was added successfully.' % (opts.verbose_name, new_object)
     693            # Here, we distinguish between different save types by checking for
     694            # the presence of keys in request.POST.
     695            if request.POST.has_key("_continue"):
     696                request.user.add_message("%s You may edit it again below." % msg)
     697                if request.POST.has_key("_popup"):
     698                    post_url_continue += "?_popup=1"
     699                return HttpResponseRedirect(post_url_continue % pk_value)
     700            if request.POST.has_key("_popup"):
     701                return HttpResponse('<script type="text/javascript">opener.dismissAddAnotherPopup(window, %s, "%s");</script>' % \
     702                    (pk_value, repr(new_object).replace('"', '\\"')))
     703            elif request.POST.has_key("_addanother"):
     704                request.user.add_message("%s You may add another %s below." % (msg, opts.verbose_name))
     705                return HttpResponseRedirect(request.path)
     706            else:
     707                request.user.add_message(msg)
     708                return HttpResponseRedirect(post_url)
     709        if request.POST.has_key("_preview"):
     710            manipulator.do_html2python(new_data)
     711    else:
     712        # Add default data.
     713        new_data = manipulator.flatten_data()
     714       
     715        # Override the defaults with request.GET, if it exists.
     716        new_data.update(request.GET)
     717        errors = {}
     718
     719    # Populate the FormWrapper.
     720    form = formfields.FormWrapper(manipulator, new_data, errors, edit_inline=True)
     721   
     722    c = Context(request, {
     723        'title': 'Add %s' % opts.verbose_name,
     724        'form': form,
     725        'is_popup': request.REQUEST.has_key('_popup'),
     726    })
     727    if object_id_override is not None:
     728        c['object_id'] = object_id_override
     729   
     730   
     731    fill_extra_context(opts, app_label, c, change=True)
     732   
     733    return render_to_response("admin_change_form", context_instance=c)
     734
     735
     736
     737def change_stage_new(request, app_label, module_name, object_id):
     738    mod, opts = _get_mod_opts(app_label, module_name)
     739    if not request.user.has_perm(app_label + '.' + opts.get_change_permission()):
     740        raise PermissionDenied
     741    if request.POST and request.POST.has_key("_saveasnew"):
     742        return add_stage_new(request, app_label, module_name, form_url='../add/')
     743    try:
     744        manipulator = mod.ChangeManipulator(object_id)
     745    except ObjectDoesNotExist:
     746        raise Http404
     747
     748    inline_related_objects = opts.get_inline_related_objects()
     749    if request.POST:
     750        new_data = request.POST.copy()
     751        if opts.has_field_type(meta.FileField):
     752            new_data.update(request.FILES)
     753
     754        errors = manipulator.get_validation_errors(new_data)
     755        if not errors and not request.POST.has_key("_preview"):
     756            for f in opts.many_to_many:
     757                if f.rel.raw_id_admin:
     758                    new_data.setlist(f.name, new_data[f.name].split(","))
     759            manipulator.do_html2python(new_data)
     760            new_object = manipulator.save(new_data)
     761            pk_value = getattr(new_object, opts.pk.column)
     762
     763            # Construct the change message.
     764            change_message = []
     765            if manipulator.fields_added:
     766                change_message.append('Added %s.' % get_text_list(manipulator.fields_added, 'and'))
     767            if manipulator.fields_changed:
     768                change_message.append('Changed %s.' % get_text_list(manipulator.fields_changed, 'and'))
     769            if manipulator.fields_deleted:
     770                change_message.append('Deleted %s.' % get_text_list(manipulator.fields_deleted, 'and'))
     771            change_message = ' '.join(change_message)
     772            if not change_message:
     773                change_message = 'No fields changed.'
     774
     775            log.log_action(request.user.id, opts.get_content_type_id(), pk_value, repr(new_object), log.CHANGE, change_message)
     776            msg = 'The %s "%s" was changed successfully.' % (opts.verbose_name, new_object)
     777            if request.POST.has_key("_continue"):
     778                request.user.add_message("%s You may edit it again below." % msg)
     779                if request.REQUEST.has_key('_popup'):
     780                    return HttpResponseRedirect(request.path + "?_popup=1")
     781                else:
     782                    return HttpResponseRedirect(request.path)
     783            elif request.POST.has_key("_saveasnew"):
     784                request.user.add_message('The %s "%s" was added successfully. You may edit it again below.' % (opts.verbose_name, new_object))
     785                return HttpResponseRedirect("../%s/" % pk_value)
     786            elif request.POST.has_key("_addanother"):
     787                request.user.add_message("%s You may add another %s below." % (msg, opts.verbose_name))
     788                return HttpResponseRedirect("../add/")
     789            else:
     790                request.user.add_message(msg)
     791                return HttpResponseRedirect("../")
     792        if request.POST.has_key("_preview"):
     793            manipulator.do_html2python(new_data)
     794    else:
     795        # Populate new_data with a "flattened" version of the current data.
     796        new_data = manipulator.flatten_data()
     797       
     798 
     799        # If the object has ordered objects on its admin page, get the existing
     800        # order and flatten it into a comma-separated list of IDs.
     801        id_order_list = []
     802        for rel_obj in opts.get_ordered_objects():
     803            id_order_list.extend(getattr(obj, 'get_%s_order' % rel_obj.object_name.lower())())
     804        if id_order_list:
     805            new_data['order_'] = ','.join(map(str, id_order_list))
     806        errors = {}
     807
     808    # Populate the FormWrapper.
     809    form = formfields.FormWrapper(manipulator, new_data, errors, edit_inline = True)
     810    form.original = manipulator.original_object
     811    form.order_objects = []
     812   
     813    for rel_opts, rel_field in inline_related_objects:
     814        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:
     815            form.order_objects.extend(orig_list)
     816
     817    c = Context(request, {
     818        'title': 'Change %s' % opts.verbose_name,
     819        'form': form,
     820        'object_id': object_id,
     821        'original': manipulator.original_object,
     822        'is_popup' : request.REQUEST.has_key('_popup')
     823    })
     824
     825    fill_extra_context(opts, app_label, c, change=True)
     826   
     827    #t = template_loader.get_template_from_string(raw_template)
     828   
     829    return render_to_response('admin_change_form', context_instance=c);
     830
     831
    533832def _get_template(opts, app_label, add=False, change=False, show_delete=False, form_url=''):
    534833    admin_field_objs = opts.admin.get_field_objs(opts)
    535834    ordered_objects = opts.get_ordered_objects()[:]
     
    608907    for rel_obj, rel_field in opts.get_inline_related_objects():
    609908        var_name = rel_obj.object_name.lower()
    610909        field_list = [f for f in rel_obj.fields + rel_obj.many_to_many if f.editable and f != rel_field]
     910
    611911        t.append('<fieldset class="module%s">\n' % ((rel_field.rel.edit_inline != meta.TABULAR) and ' aligned' or ''))
    612912        view_on_site = ''
    613913        if change and hasattr(rel_obj, 'get_absolute_url'):
     
    680980            else:
    681981                t.append('document.getElementById("id_%s").onchange = function() { this._changed = true; };' % field.name)
    682982            for f in field.prepopulate_from:
    683                 t.append('document.getElementById("id_%s").onkeyup = function() { var e = document.getElementById("id_%s"); if (!e._changed) { e.value = URLify(%s, %s);}};' % \
     983                t.append('document.getElementById("id_%s").onkeyup = function() { var e = document.getElementById("id_%s"); if (e._changed) { e.value = URLify(%s, %s);}};' % \
    684984                    (f, field.name, ' + " " + '.join(['document.getElementById("id_%s").value' % g for g in field.prepopulate_from]), field.maxlength))
    685985        t.append('</script>\n')
    686986    if change and ordered_objects:
     
    8021102        # Add default data.
    8031103        for f in opts.fields:
    8041104            if f.has_default():
    805                 new_data.update(_get_flattened_data(f, f.get_default()))
     1105                new_data.update( f.flatten_data() )
    8061106            # In required many-to-one fields with only one available choice,
    8071107            # select that one available choice. Note: We have to check that
    8081108            # the length of choices is *2*, not 1, because SelectFields always
     
    8371137                if f.editable and f != rel_field and not isinstance(f, meta.AutoField):
    8381138                    for field_name in f.get_manipulator_field_names(''):
    8391139                        full_field_name = '%s.%d.%s' % (var_name, i, field_name)
    840                         collection[field_name] = formfields.FormFieldWrapper(manipulator[full_field_name], new_data.get(full_field_name, ''), errors.get(full_field_name, []))
     1140                        field = manipulator[full_field_name]
     1141                        data = field.extract_data(new_data)
     1142                        collection[field_name] = formfields.FormFieldWrapper(field, data, errors.get(full_field_name, []))
    8411143            wrapper.append(formfields.FormFieldCollection(collection))
    8421144        setattr(form, rel_opts.module_name, wrapper)
    8431145
     
    8491151    if object_id_override is not None:
    8501152        c['object_id'] = object_id_override
    8511153    raw_template = _get_template(opts, app_label, add=True, show_delete=show_delete, form_url=form_url)
    852 #     return HttpResponse(raw_template, mimetype='text/plain')
    8531154    t = template_loader.get_template_from_string(raw_template)
    8541155    return HttpResponse(t.render(c))
    8551156
     
    9151216        new_data = {}
    9161217        obj = manipulator.original_object
    9171218        for f in opts.fields:
    918             new_data.update(_get_flattened_data(f, getattr(obj, f.column)))
     1219            new_data.update(f.flatten_data(obj))
    9191220        for f in opts.many_to_many:
    9201221            get_list_func = getattr(obj, 'get_%s_list' % f.rel.singular)
    9211222            if f.rel.raw_id_admin:
     
    9271228            for i, rel_instance in enumerate(getattr(obj, 'get_%s_list' % opts.get_rel_object_method_name(rel_obj, rel_field))()):
    9281229                for f in rel_obj.fields:
    9291230                    if f.editable and f != rel_field:
    930                         for k, v in _get_flattened_data(f, getattr(rel_instance, f.column)).items():
     1231                        for k, v in f.flatten_data(rel_instance).items():
    9311232                            new_data['%s.%d.%s' % (var_name, i, k)] = v
    9321233                for f in rel_obj.many_to_many:
    9331234                    new_data['%s.%d.%s' % (var_name, i, f.column)] = [j.id for j in getattr(rel_instance, 'get_%s_list' % f.rel.singular)()]
     
    9601261                if f.editable and f != rel_field:
    9611262                    for field_name in f.get_manipulator_field_names(''):
    9621263                        full_field_name = '%s.%d.%s' % (var_name, i, field_name)
    963                         collection[field_name] = formfields.FormFieldWrapper(manipulator[full_field_name], new_data.get(full_field_name, f.get_default()), errors.get(full_field_name, []))
     1264                        field = manipulator[full_field_name]
     1265                        data = field.extract_data(new_data)
     1266                        collection[field_name] = formfields.FormFieldWrapper(field, data, errors.get(full_field_name, []))
    9641267            wrapper.append(formfields.FormFieldCollection(collection))
    9651268        setattr(form, rel_opts.module_name, wrapper)
    9661269        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:
  • tests/runtests.py

     
    100100            self.output(1, "Creating test database")
    101101            try:
    102102                cursor.execute("CREATE DATABASE %s" % TEST_DATABASE_NAME)
    103             except:
     103            except Exception, e:
     104                self.output(0, "There was an error creating the test database:%s " % str(e))
    104105                confirm = raw_input("The test database, %s, already exists. Type 'yes' to delete it, or 'no' to cancel: " % TEST_DATABASE_NAME)
    105106                if confirm == 'yes':
    106107                    cursor.execute("DROP DATABASE %s" % TEST_DATABASE_NAME)
Back to Top