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

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

Clean up templates a bit, allow overriding edit_inline behaviour with custom templates.

  • django/conf/urls/admin.py

     
    4848
    4949urlpatterns += (
    5050    # Metasystem admin pages
     51    ('^(?P<app_label>[^/]+)/(?P<module_name>[^/]+)/add_new/$', 'django.views.admin.main.add_stage_new'),
     52    ('^(?P<app_label>[^/]+)/(?P<module_name>[^/]+)/(?P<object_id>.+)_new/$', 'django.views.admin.main.change_stage_new'),
    5153    ('^(?P<app_label>[^/]+)/(?P<module_name>[^/]+)/$', 'django.views.admin.main.change_list'),
    5254    ('^(?P<app_label>[^/]+)/(?P<module_name>[^/]+)/add/$', 'django.views.admin.main.add_stage'),
    5355    ('^(?P<app_label>[^/]+)/(?P<module_name>[^/]+)/jsvalidation/$', 'django.views.admin.jsvalidation.jsvalidation'),
  • 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}}"> <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

     
    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 = []
     
    592659            new_mod.get_latest = curry(function_get_latest, opts, new_class, does_not_exist_exception)
    593660
    594661        for f in opts.fields:
     662            if len(f.choices) != 0:
     663                # Add an accessor method to get to the human readable value
     664                func = curry(method_get_display_value, f)
     665                setattr(new_class, 'get_%s_display' % f.name , func)
    595666            if isinstance(f, DateField) or isinstance(f, DateTimeField):
    596667                # Add "get_next_by_thingie" and "get_previous_by_thingie" methods
    597668                # for all DateFields and DateTimeFields that cannot be null.
     
    782853        # If it does already exist, do an UPDATE.
    783854        if cursor.fetchone():
    784855            db_values = [f.get_db_prep_save(f.pre_save(getattr(self, f.column), False)) for f in non_pks]
    785             cursor.execute("UPDATE %s SET %s WHERE %s=%%s" % (opts.db_table,
    786                 ','.join(['%s=%%s' % f.column for f in non_pks]), opts.pk.column),
     856            while 1:
     857                try:
     858                    idx = db_values.index('')
     859                    non_pks[idx:idx+1] = []
     860                    db_values[idx:idx +1] = []
     861                except: break
     862            cursor.execute("UPDATE %s SET %s WHERE %s=%%s" % (opts.db_table,
     863                ','.join(['%s=%%s' % f.column for f in non_pks]), opts.pk.column),
    787864                db_values + [pk_val])
    788865        else:
    789866            record_exists = False
     
    9911068    kwargs['limit'] = 1
    9921069    return get_object_func(**kwargs)
    9931070
     1071# CHOICE-RELATED METHODS ###################
     1072
     1073def method_get_display_value(field, self):
     1074    value = getattr(self, field.name)
     1075    for (v, d) in field.choices:
     1076        if v==value:
     1077            return d
     1078    # Couldn't find it in the list
     1079    return value
     1080
    9941081# FILE-RELATED METHODS #####################
    9951082
    9961083def method_get_file_filename(field, self):
     
    13861473    man.__module__ = MODEL_PREFIX + '.' + opts.module_name # Set this explicitly, as above.
    13871474    man.__init__ = curry(manipulator_init, opts, add, change)
    13881475    man.save = curry(manipulator_save, opts, klass, add, change)
     1476    man.get_inline_related_objects_wrapped = curry(manipulator_get_inline_related_objects_wrapped, opts, klass, add, change)
     1477    man.flatten_data = curry(manipulator_flatten_data, opts, klass, add, change)
    13891478    for field_name_list in opts.unique_together:
    13901479        setattr(man, 'isUnique%s' % '_'.join(field_name_list), curry(manipulator_validator_unique_together, field_name_list, opts))
    13911480    for f in opts.fields:
     
    15821671            getattr(new_object, 'set_%s_order' % rel_opts.object_name.lower())(order)
    15831672    return new_object
    15841673
     1674def manipulator_get_inline_related_objects_wrapped(opts, klass, add, change, self):
     1675    return opts.get_inline_related_objects_wrapped()
     1676       
     1677def manipulator_flatten_data(opts, klass, add, change, self):
     1678     new_data = {}
     1679     obj = change and self.original_object or None
     1680     for f in opts.get_data_holders():
     1681            new_data.update(f.flatten_data(obj))
     1682     return new_data
     1683
    15851684def manipulator_validator_unique_together(field_name_list, opts, self, field_data, all_data):
    15861685    from django.utils.text import get_text_list
    15871686    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
     
    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):
     
    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    def has_errors(self):
     130        return max([ bool( len( self.obj[fw.field.name].errors()))  for fw in self.field_wrappers])
     131       
     132    def html_combined_error_list(self):
     133        return ''.join( [ self.obj[fw.field.name].html_error_list() for fw in self.field_wrappers])
     134
     135
     136   
     137
     138class EditInlineNode(template.Node):
     139    def __init__(self, rel_var):
     140        self.rel_var = rel_var
     141   
     142    def render(self, context):
     143        relation = template.resolve_variable(self.rel_var, context)
     144        add, change = context['add'], context['change']
     145       
     146        context.push()
     147
     148        self.fill_context(relation, add, change, context)
     149       
     150        t = template_loader.get_template(relation.field.rel.edit_inline)
     151       
     152        output = t.render(context)
     153         
     154        context.pop()
     155        return output
     156
     157       
     158    def fill_context(self, relation, add, change, context):
     159        field_wrapper_list = [FieldWrapper(f) for f in relation.editable_fields()]
     160       
     161        var_name = relation.opts.object_name.lower()
     162       
     163        form = template.resolve_variable('form', context)
     164        form_objects = form[relation.opts.module_name]
     165        form_object_wrapper_list = [FormObjectWrapper(o,field_wrapper_list) for o in form_objects]
     166   
     167        context['field_wrapper_list'] = field_wrapper_list
     168        context['form_object_wrapper_list'] = form_object_wrapper_list
     169        context['num_headers'] = len(field_wrapper_list)
     170        context['original_row_needed'] = max([fw.use_raw_id_admin() for fw in field_wrapper_list])
     171        context['name_prefix'] = "%s." % (var_name,)
     172   
     173class FieldLabelNode(template.Node):
     174    def __init__(self, bound_field_var):
     175        self.bound_field_var = bound_field_var
     176       
     177    def render(self, context):
     178        bound_field = template.resolve_variable(self.bound_field_var, context)
     179        class_names = []
     180        if isinstance(bound_field.field, meta.BooleanField):
     181            class_names.append("vCheckboxLabel")
     182        else:
     183            if not bound_field.field.blank:
     184                class_names.append('required')
     185            if not bound_field.first:
     186                class_names.append('inline')
     187       
     188        class_str = class_names and ' class="%s"' % ' '.join(class_names) or ''
     189        return '<label for="%s"%s>%s:</label> ' % (bound_field.element_id, class_str, capfirst(bound_field.field.verbose_name) )
     190
     191class OutputAllNode(template.Node):
     192    def __init__(self, form_fields_var):
     193        self.form_fields_var = form_fields_var
     194   
     195    def render(self, context):
     196        form_fields = template.resolve_variable(self.form_fields_var, context)
     197        return ''.join([str(f) for f in form_fields])
     198
     199class AutoPopulatedFieldScriptNode(template.Node):
     200    def __init__(self, auto_pop_var):
     201        self.auto_pop_var = auto_pop_var
     202
     203    def render(self,context):
     204        auto_pop_fields = template.resolve_variable(self.auto_pop_var, context)
     205        change = context['change']
     206        for field in auto_pop_fields:
     207            t = []
     208            if change:
     209                t.append('document.getElementById("id_%s")._changed = true;' % field.name )
     210            else:
     211                t.append('document.getElementById("id_%s").onchange = function() { this._changed = true; };' % field.name)
     212
     213            add_values = ' + " " + '.join(['document.getElementById("id_%s").value' % g for g in field.prepopulate_from])
     214            for f in field.prepopulate_from:
     215                t.append("""
     216                         document.getElementById("id_%s").onkeyup = function() {
     217                                var e = document.getElementById("id_%s");
     218                                if(e._changed) { e.value = URLify(%s, %s);}
     219                        }
     220                        """ % (f, field.name, add_values, field.maxlength) )
     221
     222        return ''.join(t)
     223
     224class FilterInterfaceScriptMaybeNode(template.Node):
     225    def __init__(self, bound_field_var):
     226       self.bound_field_var = bound_field_var
     227
     228    def render(self, context):
     229        bound_field = template.resolve_variable(self.bound_field_var, context)
     230        f = bound_field.field
     231        if f.rel and isinstance(f.rel, meta.ManyToMany) and f.rel.filter_interface:
     232           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)
     233        else:
     234            return ''
     235
     236     
     237
     238
     239def do_submit_row(parser, token):
     240    return SubmitRowNode()
     241
     242
     243def do_one_arg_tag(node_factory, parser,token):
     244    tokens = token.contents.split()
     245    if len(tokens) != 2:
     246        raise template.TemplateSyntaxError("%s takes 1 argument" % tokens[0])
     247    return node_factory(tokens[1])
     248
     249
     250one_arg_tag_nodes = [
     251    IncludeAdminScriptNode,
     252    AdminFieldBoundNode,
     253    FieldLabelNode,
     254    FieldWidgetNode,
     255    OutputAllNode,
     256    EditInlineNode,
     257    AutoPopulatedFieldScriptNode,
     258    FilterInterfaceScriptMaybeNode,
     259]
     260
     261word = re.compile('[A-Z][a-z]+')
     262for node in one_arg_tag_nodes:
     263    tag_name = '_'.join([ s.lower() for s in word.findall(node.__name__)[:-1] ])
     264
     265    parse_func = curry(do_one_arg_tag, node)
     266    template.register_tag(tag_name, parse_func)
     267
     268#template.register_tag('include_admin_script', do_include_admin_script)
     269template.register_tag('submit_row', do_submit_row )
     270#template.register_tag('admin_field_bound', do_admin_field_bound)
     271#template.register_tag('edit_inline', do_edit_inline)
     272#template.register_tag('auto_populated_field_script', do_auto_populated_field_script)
     273#template.register_tag('field_label', do_field_label)
     274#template.register_tag('field_widget', do_field_widget)
     275#template.register_tag('output_all', do_output_all)
     276#template.register_tag('filter_interface_script_maybe', curry(do_one_arg_tag, FilterInterfaceScriptMaybeNode)  )
  • 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
     
    492492    })
    493493    return HttpResponse(t.render(c))
    494494
    495 def _get_flattened_data(field, val):
    496     """
    497     Returns a dictionary mapping the field's manipulator field names to its
    498     "flattened" string values for the admin view. "val" is an instance of the
    499     field's value.
    500     """
    501     if isinstance(field, meta.DateTimeField):
    502         date_field, time_field = field.get_manipulator_field_names('')
    503         return {date_field: (val is not None and val.strftime("%Y-%m-%d") or ''),
    504                 time_field: (val is not None and val.strftime("%H:%M:%S") or '')}
    505     elif isinstance(field, meta.DateField):
    506         return {field.name: (val is not None and val.strftime("%Y-%m-%d") or '')}
    507     elif isinstance(field, meta.TimeField):
    508         return {field.name: (val is not None and val.strftime("%H:%M:%S") or '')}
    509     else:
    510         return {field.name: val}
    511 
    512495use_raw_id_admin = lambda field: isinstance(field.rel, (meta.ManyToOne, meta.ManyToMany)) and field.rel.raw_id_admin
    513496
    514497def _get_submit_row_template(opts, app_label, add, change, show_delete, ordered_objects):
     
    529512    t.append('</div>\n')
    530513    return t
    531514
     515def get_javascript_imports(opts,auto_populated_fields, ordered_objects, admin_field_objs):
     516# Put in any necessary JavaScript imports.
     517    js = ['js/core.js', 'js/admin/RelatedObjectLookups.js']
     518    if auto_populated_fields:
     519        js.append('js/urlify.js')
     520    if opts.has_field_type(meta.DateTimeField) or opts.has_field_type(meta.TimeField) or opts.has_field_type(meta.DateField):
     521        js.extend(['js/calendar.js', 'js/admin/DateTimeShortcuts.js'])
     522    if ordered_objects:
     523        js.extend(['js/getElementsBySelector.js', 'js/dom-drag.js' , 'js/admin/ordering.js'])
     524    if opts.admin.js:
     525        js.extend(opts.admin.js)
     526    seen_collapse = False
     527    for _, options in admin_field_objs:
     528        if not seen_collapse and 'collapse' in options.get('classes', ''):
     529            seen_collapse = True
     530            js.append('js/admin/CollapsedFieldsets.js' )
     531        try:
     532            for field_list in options['fields']:
     533                for f in field_list:
     534                    if f.rel and isinstance(f, meta.ManyToManyField) and f.rel.filter_interface:
     535                        js.extend(['js/SelectBox.js' , 'js/SelectFilter2.js'])
     536                        raise StopIteration
     537        except StopIteration:
     538            break
     539    return js
     540
     541class BoundField(object):
     542    def __init__(self, field, original, rel, field_mapping):
     543        self.field = field
     544       
     545        self.form_fields = self.resolve_form_fields(field_mapping)
     546       
     547        self.element_id = self.form_fields[0].get_id()
     548        self.has_label_first = not isinstance(self.field, meta.BooleanField)
     549        self.original = original
     550        self.raw_id_admin = use_raw_id_admin(field)
     551        self.rel = rel
     552        self.is_date_time = isinstance(field, meta.DateTimeField)
     553        self.is_file_field = isinstance(field, meta.FileField)
     554        self.needs_add_label = field.rel and isinstance(field.rel, meta.ManyToOne) and field.rel.to.admin
     555        self.not_in_table = isinstance(self.field, meta.AutoField)
     556        self.first = False
     557       
     558        classes = []
     559        if(self.raw_id_admin):
     560            classes.append('nowrap')
     561        if max([bool(f.errors()) for f in self.form_fields]):
     562            classes.append('error')
     563        self.cell_class_attribute = ' '.join(classes)   
     564        self._repr_filled = False
     565   
     566    def resolve_form_fields(self, field_mapping):
     567        return [field_mapping[name] for name in self.field.get_manipulator_field_names('')]
     568
     569   
     570    def as_field_list(self):
     571        return [self.field]
     572
     573    def original_value(self):
     574        return self.original.__dict__[self.field.name]
     575       
     576    def _fetch_existing_repr(self, func_name):
     577        class_dict = self.original.__class__.__dict__
     578        func = class_dict.get(func_name)
     579        return func(self.original)
     580       
     581    def _fill_existing_repr(self):
     582        if self._repr_filled:
     583            return
     584        #HACK
     585        if isinstance(self.field.rel, meta.ManyToOne):
     586             func_name = 'get_%s' % self.field.name
     587             self._repr = self._fetch_existing_repr(func_name)
     588        elif isinstance(self.field.rel, meta.ManyToMany):
     589             func_name = 'get_%s_list' % self.field.name
     590             self._repr =  ",".join(self._fetch_existing_repr(func_name))
     591        self._repr_filled = True
     592             
     593    def existing_repr(self):
     594        self._fill_existing_repr()
     595        return self._repr
     596
     597    def __repr__(self):
     598        return repr(self.__dict__)
     599
     600   
     601
     602
     603class AdminFieldSet(object):
     604    def __init__(self, fieldset_name, options, form, original):
     605         self.name = fieldset_name
     606         self.options = options
     607         self.bound_field_sets = self.get_bound_field_sets(form, original)
     608         self.classes = options.get('classes', '')
     609     
     610    def __repr__(self):
     611        return "Fieldset:(%s,%s)" % (self.name, self.bound_field_sets)
     612
     613    def get_bound_field_sets(self, form, original):
     614        fields = self.options['fields']
     615        bound_field_sets = [ [BoundField(f, original, False, form) for f in field  ] for field in fields]
     616        for set in bound_field_sets:
     617            set[0].first = True
     618
     619        return bound_field_sets
     620
     621def fill_extra_context(opts, app_label, context, add=False, change=False, show_delete=False, form_url=''):
     622    admin_field_objs = opts.admin.get_field_objs(opts)
     623    ordered_objects = opts.get_ordered_objects()[:]
     624    auto_populated_fields = [f for f in opts.fields if f.prepopulate_from]
     625 
     626    javascript_imports = get_javascript_imports(opts,auto_populated_fields, ordered_objects, admin_field_objs);
     627   
     628    if ordered_objects:
     629        coltype = 'colMS'
     630    else:
     631        coltype = 'colM'
     632       
     633    has_absolute_url = hasattr(opts.get_model_module().Klass, 'get_absolute_url')
     634   
     635    form_enc_attrib = opts.has_field_type(meta.FileField) and 'enctype="multipart/form-data" ' or ''
     636
     637    form = context['form']
     638    original = context['original']
     639    admin_fieldsets = [AdminFieldSet(name, options, form, original) for name, options in admin_field_objs]
     640    inline_related_objects = opts.get_inline_related_objects_wrapped()
     641   
     642    ordered_object_names =   ' '.join(['object.%s' % o.pk.name for o in ordered_objects])
     643   
     644    extra_context = {
     645        'add': add,
     646        'change': change,
     647        'admin_field_objs' : admin_field_objs,
     648        'ordered_objects' : ordered_objects,
     649        'auto_populated_fields' : auto_populated_fields,
     650        'javascript_imports' : javascript_imports,
     651        'coltype' : coltype,
     652        'has_absolute_url': has_absolute_url,
     653        'form_enc_attrib': form_enc_attrib,
     654        'form_url' : form_url,
     655        'admin_fieldsets' : admin_fieldsets,
     656        'inline_related_objects': inline_related_objects,
     657        'ordered_object_names' : ordered_object_names,
     658        'content_type_id' : opts.get_content_type_id(),
     659        'save_on_top' : opts.admin.save_on_top,
     660        'verbose_name_plural': opts.verbose_name_plural,
     661        'save_as': opts.admin.save_as,
     662        'app_label': app_label,
     663        'object_name': opts.object_name,
     664        'has_delete_permission' : context['perms'][app_label][opts.get_delete_permission()]
     665    }
     666   
     667    context.update(extra_context)   
     668   
     669   
     670def add_stage_new(request, app_label, module_name, show_delete=False, form_url='', post_url='../', post_url_continue='../%s/', object_id_override=None):
     671    mod, opts = _get_mod_opts(app_label, module_name)
     672    if not request.user.has_perm(app_label + '.' + opts.get_add_permission()):
     673        raise PermissionDenied
     674    manipulator = mod.AddManipulator()
     675    if request.POST:
     676        new_data = request.POST.copy()
     677        if opts.has_field_type(meta.FileField):
     678            new_data.update(request.FILES)
     679        errors = manipulator.get_validation_errors(new_data)
     680        if not errors and not request.POST.has_key("_preview"):
     681            for f in opts.many_to_many:
     682                if f.rel.raw_id_admin:
     683                    new_data.setlist(f.name, new_data[f.name].split(","))
     684            manipulator.do_html2python(new_data)
     685            new_object = manipulator.save(new_data)
     686            pk_value = getattr(new_object, opts.pk.column)
     687            log.log_action(request.user.id, opts.get_content_type_id(), pk_value, repr(new_object), log.ADDITION)
     688            msg = 'The %s "%s" was added successfully.' % (opts.verbose_name, new_object)
     689            # Here, we distinguish between different save types by checking for
     690            # the presence of keys in request.POST.
     691            if request.POST.has_key("_continue"):
     692                request.user.add_message("%s You may edit it again below." % msg)
     693                if request.POST.has_key("_popup"):
     694                    post_url_continue += "?_popup=1"
     695                return HttpResponseRedirect(post_url_continue % pk_value)
     696            if request.POST.has_key("_popup"):
     697                return HttpResponse('<script type="text/javascript">opener.dismissAddAnotherPopup(window, %s, "%s");</script>' % \
     698                    (pk_value, repr(new_object).replace('"', '\\"')))
     699            elif request.POST.has_key("_addanother"):
     700                request.user.add_message("%s You may add another %s below." % (msg, opts.verbose_name))
     701                return HttpResponseRedirect(request.path)
     702            else:
     703                request.user.add_message(msg)
     704                return HttpResponseRedirect(post_url)
     705        if request.POST.has_key("_preview"):
     706            manipulator.do_html2python(new_data)
     707    else:
     708        # Add default data.
     709        new_data = manipulator.flatten_data()
     710       
     711        # Override the defaults with request.GET, if it exists.
     712        new_data.update(request.GET)
     713        errors = {}
     714
     715    # Populate the FormWrapper.
     716    form = formfields.FormWrapper(manipulator, new_data, errors)
     717   
     718    c = Context(request, {
     719        'title': 'Add %s' % opts.verbose_name,
     720        'form': form,
     721        'is_popup': request.REQUEST.has_key('_popup'),
     722    })
     723    if object_id_override is not None:
     724        c['object_id'] = object_id_override
     725   
     726   
     727    fill_extra_context(opts, app_label, c, change=True)
     728   
     729    return render_to_response("admin_change_form", context_instance=c)
     730
     731
     732
     733def change_stage_new(request, app_label, module_name, object_id):
     734    mod, opts = _get_mod_opts(app_label, module_name)
     735    if not request.user.has_perm(app_label + '.' + opts.get_change_permission()):
     736        raise PermissionDenied
     737    if request.POST and request.POST.has_key("_saveasnew"):
     738        return add_stage_new(request, app_label, module_name, form_url='../add/')
     739    try:
     740        manipulator = mod.ChangeManipulator(object_id)
     741    except ObjectDoesNotExist:
     742        raise Http404
     743
     744    inline_related_objects = opts.get_inline_related_objects()
     745    if request.POST:
     746        new_data = request.POST.copy()
     747        if opts.has_field_type(meta.FileField):
     748            new_data.update(request.FILES)
     749
     750        errors = manipulator.get_validation_errors(new_data)
     751        if not errors and not request.POST.has_key("_preview"):
     752            for f in opts.many_to_many:
     753                if f.rel.raw_id_admin:
     754                    new_data.setlist(f.name, new_data[f.name].split(","))
     755            manipulator.do_html2python(new_data)
     756            new_object = manipulator.save(new_data)
     757            pk_value = getattr(new_object, opts.pk.column)
     758
     759            # Construct the change message.
     760            change_message = []
     761            if manipulator.fields_added:
     762                change_message.append('Added %s.' % get_text_list(manipulator.fields_added, 'and'))
     763            if manipulator.fields_changed:
     764                change_message.append('Changed %s.' % get_text_list(manipulator.fields_changed, 'and'))
     765            if manipulator.fields_deleted:
     766                change_message.append('Deleted %s.' % get_text_list(manipulator.fields_deleted, 'and'))
     767            change_message = ' '.join(change_message)
     768            if not change_message:
     769                change_message = 'No fields changed.'
     770
     771            log.log_action(request.user.id, opts.get_content_type_id(), pk_value, repr(new_object), log.CHANGE, change_message)
     772            msg = 'The %s "%s" was changed successfully.' % (opts.verbose_name, new_object)
     773            if request.POST.has_key("_continue"):
     774                request.user.add_message("%s You may edit it again below." % msg)
     775                if request.REQUEST.has_key('_popup'):
     776                    return HttpResponseRedirect(request.path + "?_popup=1")
     777                else:
     778                    return HttpResponseRedirect(request.path)
     779            elif request.POST.has_key("_saveasnew"):
     780                request.user.add_message('The %s "%s" was added successfully. You may edit it again below.' % (opts.verbose_name, new_object))
     781                return HttpResponseRedirect("../%s/" % pk_value)
     782            elif request.POST.has_key("_addanother"):
     783                request.user.add_message("%s You may add another %s below." % (msg, opts.verbose_name))
     784                return HttpResponseRedirect("../add/")
     785            else:
     786                request.user.add_message(msg)
     787                return HttpResponseRedirect("../")
     788        if request.POST.has_key("_preview"):
     789            manipulator.do_html2python(new_data)
     790    else:
     791        # Populate new_data with a "flattened" version of the current data.
     792        new_data = manipulator.flatten_data()
     793       
     794 
     795        # If the object has ordered objects on its admin page, get the existing
     796        # order and flatten it into a comma-separated list of IDs.
     797        id_order_list = []
     798        for rel_obj in opts.get_ordered_objects():
     799            id_order_list.extend(getattr(obj, 'get_%s_order' % rel_obj.object_name.lower())())
     800        if id_order_list:
     801            new_data['order_'] = ','.join(map(str, id_order_list))
     802        errors = {}
     803
     804    # Populate the FormWrapper.
     805    form = formfields.FormWrapper(manipulator, new_data, errors, edit_inline = True)
     806    form.original = manipulator.original_object
     807    form.order_objects = []
     808   
     809    for rel_opts, rel_field in inline_related_objects:
     810        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:
     811            form.order_objects.extend(orig_list)
     812
     813    c = Context(request, {
     814        'title': 'Change %s' % opts.verbose_name,
     815        'form': form,
     816        'object_id': object_id,
     817        'original': manipulator.original_object,
     818        'is_popup' : request.REQUEST.has_key('_popup')
     819    })
     820
     821    fill_extra_context(opts, app_label, c, change=True)
     822   
     823    #t = template_loader.get_template_from_string(raw_template)
     824   
     825    return render_to_response('admin_change_form', context_instance=c);
     826
     827
    532828def _get_template(opts, app_label, add=False, change=False, show_delete=False, form_url=''):
    533829    admin_field_objs = opts.admin.get_field_objs(opts)
    534830    ordered_objects = opts.get_ordered_objects()[:]
     
    679975            else:
    680976                t.append('document.getElementById("id_%s").onchange = function() { this._changed = true; };' % field.name)
    681977            for f in field.prepopulate_from:
    682                 t.append('document.getElementById("id_%s").onkeyup = function() { var e = document.getElementById("id_%s"); if (!e._changed) { e.value = URLify(%s, %s);}};' % \
     978                t.append('document.getElementById("id_%s").onkeyup = function() { var e = document.getElementById("id_%s"); if (e._changed) { e.value = URLify(%s, %s);}};' % \
    683979                    (f, field.name, ' + " " + '.join(['document.getElementById("id_%s").value' % g for g in field.prepopulate_from]), field.maxlength))
    684980        t.append('</script>\n')
    685981    if change and ordered_objects:
     
    8011097        # Add default data.
    8021098        for f in opts.fields:
    8031099            if f.has_default():
    804                 new_data.update(_get_flattened_data(f, f.get_default()))
     1100                new_data.update( f.flatten_data() )
    8051101            # In required many-to-one fields with only one available choice,
    8061102            # select that one available choice. Note: We have to check that
    8071103            # the length of choices is *2*, not 1, because SelectFields always
     
    8361132                if f.editable and f != rel_field and not isinstance(f, meta.AutoField):
    8371133                    for field_name in f.get_manipulator_field_names(''):
    8381134                        full_field_name = '%s.%d.%s' % (var_name, i, field_name)
    839                         collection[field_name] = formfields.FormFieldWrapper(manipulator[full_field_name], new_data.get(full_field_name, ''), errors.get(full_field_name, []))
     1135                        field = manipulator[full_field_name]
     1136                        data = field.extract_data(new_data)
     1137                        collection[field_name] = formfields.FormFieldWrapper(field, data, errors.get(full_field_name, []))
    8401138            wrapper.append(formfields.FormFieldCollection(collection))
    8411139        setattr(form, rel_opts.module_name, wrapper)
    8421140
     
    8481146    if object_id_override is not None:
    8491147        c['object_id'] = object_id_override
    8501148    raw_template = _get_template(opts, app_label, add=True, show_delete=show_delete, form_url=form_url)
    851 #     return HttpResponse(raw_template, mimetype='text/plain')
    8521149    t = template_loader.get_template_from_string(raw_template)
    8531150    return HttpResponse(t.render(c))
    8541151
     
    9141211        new_data = {}
    9151212        obj = manipulator.original_object
    9161213        for f in opts.fields:
    917             new_data.update(_get_flattened_data(f, getattr(obj, f.column)))
     1214            new_data.update(f.flatten_data(obj))
    9181215        for f in opts.many_to_many:
    9191216            get_list_func = getattr(obj, 'get_%s_list' % f.rel.singular)
    9201217            if f.rel.raw_id_admin:
     
    9261223            for i, rel_instance in enumerate(getattr(obj, 'get_%s_list' % opts.get_rel_object_method_name(rel_obj, rel_field))()):
    9271224                for f in rel_obj.fields:
    9281225                    if f.editable and f != rel_field:
    929                         for k, v in _get_flattened_data(f, getattr(rel_instance, f.column)).items():
     1226                        for k, v in f.flatten_data(rel_instance).items():
    9301227                            new_data['%s.%d.%s' % (var_name, i, k)] = v
    9311228                for f in rel_obj.many_to_many:
    9321229                    new_data['%s.%d.%s' % (var_name, i, f.column)] = [j.id for j in getattr(rel_instance, 'get_%s_list' % f.rel.singular)()]
     
    9591256                if f.editable and f != rel_field:
    9601257                    for field_name in f.get_manipulator_field_names(''):
    9611258                        full_field_name = '%s.%d.%s' % (var_name, i, field_name)
    962                         collection[field_name] = formfields.FormFieldWrapper(manipulator[full_field_name], new_data.get(full_field_name, f.get_default()), errors.get(full_field_name, []))
     1259                        field = manipulator[full_field_name]
     1260                        data = field.extract_data(new_data)
     1261                        collection[field_name] = formfields.FormFieldWrapper(field, data, errors.get(full_field_name, []))
    9631262            wrapper.append(formfields.FormFieldCollection(collection))
    9641263        setattr(form, rel_opts.module_name, wrapper)
    9651264        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