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

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

Makes inline editing a piece of cake outside the admin

  • 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                {% for field in bound_field.form_fields %}
     62                    {% if field.needs_filter_script %}
     63                   <script type="text/javascript">
     64                     addEvent(window, load, function(e){
     65                            SelectFilter.init("id_{{f.name}}", {{ f.verbose_name}}, {{f.filterthing}});
     66                     }
     67                   </script>
     68                {% endif %}
     69            {% endfor %}
     70         {% endfor%}
     71      {% endfor %}
     72   </fieldset>
     73{% endfor %}
     74
     75{% if change %}
     76   {% if ordered_objects %}
     77   <fieldset class="module"><h2>Ordering</h2>
     78   <div class="form-row{% if form.order_.errors %} error{% endif %} ">
     79   {% if form.order_.errors %}{{ form.order_.html_error_list }}{% endif %}
     80   <p><label for="id_order_">Order:</label> {{ form.order_ }}</p>
     81   </div></fieldset>
     82   {% endif %}
     83{% endif %}
     84
     85
     86{% for relation in inline_related_objects %}
     87    {% edit_inline relation %}
     88{% endfor %}
     89
     90{% submit_row %}
     91
     92{% if add %}
     93   <script type="text/javascript">document.getElementById("id_{{first_field}}").focus();</script>'
     94{% endif %}
     95
     96{% if auto_populated_fields %}
     97   <script type="text/javascript">
     98   {% auto_populated_field_script auto_populated_fields %}
     99   </script>
     100{% endif %}
     101
     102{% if change %}
     103   {% if ordered_objects %}
     104      {% if form.order_objects %}<ul id="orderthese">
     105          {% for object in form.order_objects %}
     106             <li id="p{% firstof ordered_object_names %}">
     107             <span id="handlep{% firstof ordered_object_names %}">{{ object|truncatewords:"5" }}</span>
     108             </li>
     109          {% endfor%}
     110      {% endif %}
     111   {% endif %}
     112{% endif%}
     113</form>
     114
     115{% 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.label_name}}" 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.label_name}}"> <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               
     116                data = field.extract_data(self.data)
     117               
     118                return FormFieldWrapper(field, data, self.error_dict.get(field.field_name, []))
     119        if self.edit_inline:
     120            self.fill_inline_collections()
     121            for inline_collection in self._inline_collections:
     122                if inline_collection.name == key:
     123                    return inline_collection
     124
    124125        raise KeyError
    125126
     127    def fill_inline_collections(self):
     128        if not self._inline_collections:
     129            ic = []
     130            i = 0
     131            related_objects = self.manipulator.get_inline_related_objects_wrapped()
     132            for rel_obj in related_objects:
     133                i += 1
     134                data = rel_obj.extract_data(self.data)
     135                inline_collection = InlineObjectCollection(self.manipulator, rel_obj, data, self.error_dict)
     136                ic.append(inline_collection)
     137            self._inline_collections = ic
     138
     139
     140
    126141    def has_errors(self):
    127142        return self.error_dict != {}
    128143
     
    155170        else:
    156171            return ''
    157172
     173
    158174class FormFieldCollection(FormFieldWrapper):
    159175    "A utility class that gives the template access to a dict of FormFieldWrappers"
    160176    def __init__(self, formfield_dict):
     
    177193            errors.extend(field.errors())
    178194        return errors
    179195
     196
     197
     198   
     199
     200class InlineObjectCollection:
     201    "An object that acts like a list of form field collections." 
     202    def __init__(self, parent_manipulator,  rel_obj, data, errors):
     203        self.parent_manipulator = parent_manipulator
     204        self.rel_obj = rel_obj
     205        self.data = data
     206        self.errors = errors
     207        self._collections = None   
     208        self.name = rel_obj.name
     209 
     210    def __len__(self):
     211        self.fill()
     212        return self._collections.__len__()
     213   
     214    def __getitem__(self, k):
     215        self.fill()
     216        return self._collections.__getitem__(k)
     217
     218    def __setitem__(self, k, v):
     219        self.fill()
     220        return self._collections.__setitem__(k,v)
     221
     222    def __delitem__(self, k):
     223        self.fill()
     224        return self._collections.__delitem__(k)
     225
     226    def __iter__(self):
     227        self.fill()
     228        return self._collections.__iter__()
     229
     230    def fill(self):
     231        if self._collections:
     232            return
     233        else:
     234            var_name = self.rel_obj.opts.object_name.lower()
     235            wrapper = []
     236            orig = hasattr(self.parent_manipulator, 'original_object') and self.parent_manipulator.original_object  or None
     237            orig_list = self.rel_obj.get_list(orig)
     238            for i, instance in enumerate(orig_list):
     239                collection = {'original': instance }
     240                for f in self.rel_obj.editable_fields():
     241                        for field_name in f.get_manipulator_field_names(''):
     242                            full_field_name = '%s.%d.%s' % (var_name, i, field_name)
     243                            field = self.parent_manipulator[full_field_name]
     244                            data = field.extract_data(self.data)
     245                            collection[field_name] = FormFieldWrapper(field, data, self.errors.get(full_field_name, []))
     246                wrapper.append(FormFieldCollection(collection))
     247            self._collections = wrapper
     248
    180249class FormField:
    181250    """Abstract class representing a form field.
    182251
     
    209278    def render(self, data):
    210279        raise NotImplementedError
    211280
     281    def get_member_name(self):
     282        return self.field_name
     283
     284    def extract_data(self, data_dict):
     285        if hasattr(self, 'requires_data_list') and hasattr(data_dict, 'getlist'):
     286            data = data_dict.getlist(self.get_member_name())
     287        else:
     288            data = data_dict.get(self.get_member_name(), None)
     289        if data is None:
     290            data = ''
     291        self.data_dict = data_dict 
     292        return data
     293
     294    def convert_post_data(self, new_data):
     295        name = self.get_member_name()
     296        if new_data.has_key(self.field_name):
     297            d = new_data.getlist(self.field_name)
     298            #del new_data[self.field_name]
     299            new_data.setlist(name,
     300                    [self.__class__.html2python(data)
     301                     for data in d])
     302        else:
     303            try:
     304               # individual fields deal with None values themselves
     305               new_data.setlist(name, [self.__class__.html2python(None)])
     306            except EmptyValue:
     307               new_data.setlist(name, [])
     308
    212309####################
    213310# GENERIC WIDGETS  #
    214311####################
     
    319416        output.append('  </select>')
    320417        return '\n'.join(output)
    321418
     419    def get_member_name(self):
     420        return "%s_id" % self.field_name
     421
    322422    def isValidChoice(self, data, form):
    323423        str_data = str(data)
    324424        str_choices = [str(item[0]) for item in self.choices]
  • 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

     
    180180            else:
    181181                if self.radio_admin:
    182182                    field_objs = [formfields.RadioSelectField]
    183                     params['choices'] = self.get_choices(include_blank=self.blank, blank_choice=BLANK_CHOICE_NONE)
    184183                    params['ul_class'] = get_ul_class(self.radio_admin)
    185184                else:
    186185                    if self.null:
    187186                        field_objs = [formfields.NullSelectField]
    188187                    else:
    189188                        field_objs = [formfields.SelectField]
    190                     params['choices'] = self.get_choices()
     189                params['choices'] = self.get_choices_default()
    191190        elif self.choices:
    192191            if self.radio_admin:
    193192                field_objs = [formfields.RadioSelectField]
    194                 params['choices'] = self.get_choices(include_blank=self.blank, blank_choice=BLANK_CHOICE_NONE)
    195193                params['ul_class'] = get_ul_class(self.radio_admin)
    196194            else:
    197195                field_objs = [formfields.SelectField]
    198                 params['choices'] = self.get_choices()
     196             
     197            params['choices'] = self.get_choices_default()
    199198        else:
    200199            field_objs = self.get_manipulator_field_objs()
    201200
     
    255254                val = None
    256255            return val
    257256
     257
    258258    def get_choices(self, include_blank=True, blank_choice=BLANK_CHOICE_DASH):
    259259        "Returns a list of tuples used as SelectField choices for this field."
     260       
    260261        first_choice = include_blank and blank_choice or []
    261262        if self.choices:
    262263            return first_choice + list(self.choices)
    263264        rel_obj = self.rel.to
    264265        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)]
    265266
     267    def get_choices_default(self):
     268        if(self.radio_admin):
     269            return self.get_choices(include_blank=self.blank, blank_choice=BLANK_CHOICE_NONE)
     270        else:
     271            return self.get_choices()
     272
     273    def _get_val_from_obj(self, obj):
     274        if obj:
     275           return getattr(obj, self.column)
     276        else:
     277           return self.get_default()
     278
     279    def flatten_data(self, obj = None):
     280         """
     281             Returns a dictionary mapping the field's manipulator field names to its
     282             "flattened" string values for the admin view. Obj is the instance to extract the
     283             values from.
     284         """
     285         return { self.get_db_column(): self._get_val_from_obj(obj)}
     286       
     287 
    266288class AutoField(Field):
    267289    empty_strings_allowed = False
    268290    def __init__(self, *args, **kwargs):
     
    327349    def get_manipulator_field_objs(self):
    328350        return [formfields.DateField]
    329351
     352    def flatten_data(self, obj = None):
     353        val = self._get_val_from_obj(obj)
     354        return {self.get_db_column(): (val is not None and val.strftime("%Y-%m-%d") or '')}
     355
    330356class DateTimeField(DateField):
    331357    def get_db_prep_save(self, value):
    332358        # Casts dates into string format for entry into database.
     
    356382            return datetime.datetime.combine(d, t)
    357383        return self.get_default()
    358384
     385    def flatten_data(self,obj = None):
     386        val = self._get_val_from_obj(obj)
     387        date_field, time_field = self.get_manipulator_field_names('')
     388        return {date_field: (val is not None and val.strftime("%Y-%m-%d") or ''),
     389                time_field: (val is not None and val.strftime("%H:%M:%S") or '')}
     390
    359391class EmailField(Field):
    360392    def get_manipulator_field_objs(self):
    361393        return [formfields.EmailField]
     
    539571    def get_manipulator_field_objs(self):
    540572        return [formfields.TimeField]
    541573
     574    def flatten_data(self,obj = None):
     575        val = self._get_val_from_obj(obj)
     576        return {self.get_db_column(): (val is not None and val.strftime("%H:%M:%S") or '')}
     577
    542578class URLField(Field):
    543579    def __init__(self, verbose_name=None, name=None, verify_exists=True, **kwargs):
    544580        if verify_exists:
     
    592628    def get_manipulator_field_objs(self):
    593629        return [formfields.IntegerField]
    594630
     631    def flatten_data(self, obj = None):
     632        if not obj:
     633            # In required many-to-one fields with only one available choice,
     634            # select that one available choice. Note: We have to check that
     635            # the length of choices is *2*, not 1, because SelectFields always
     636            # have an initial "blank" value.
     637            if not self.blank and not self.rel.raw_id_admin and self.choices:
     638               choice_list = self.get_choices_default()
     639               if len(choice_list) == 2:
     640                  return { self.name : choice_list[1][0] }
     641        return Field.flatten_data(self, obj)
     642
    595643class ManyToManyField(Field):
    596644    def __init__(self, to, **kwargs):
    597645        kwargs['verbose_name'] = kwargs.get('verbose_name', to._meta.verbose_name_plural)
     
    609657        if self.rel.raw_id_admin:
    610658            return [formfields.CommaSeparatedIntegerField]
    611659        else:
    612             choices = self.get_choices(include_blank=False)
     660            choices = self.get_choices_default()
    613661            return [curry(formfields.SelectMultipleField, size=min(max(len(choices), 5), 15), choices=choices)]
    614662
     663    def get_choices_default(self):
     664        Field.get_choices(self, include_blank=False)
     665
    615666    def get_m2m_db_table(self, original_opts):
    616667        "Returns the name of the many-to-many 'join' table."
    617668        return '%s_%s' % (original_opts.db_table, self.name)
     
    632683                len(badkeys) == 1 and badkeys[0] or tuple(badkeys),
    633684                len(badkeys) == 1 and "is" or "are")
    634685
     686    def flatten_data(self, obj = None):
     687        new_data = {}
     688        if obj:
     689            get_list_func = getattr(obj, 'get_%s_list' % self.rel.singular)
     690            instance_ids = [getattr(instance, self.rel.to.pk.column) for instance in get_list_func()]
     691            if self.rel.raw_id_admin:
     692                 new_data[self.name] = ",".join([str(id) for id in instance_ids])
     693            elif not f.rel.edit_inline:
     694                 new_data[self.name] = instance_ids
     695        else:
     696            # In required many-to-many fields with only one available choice,
     697            # select that one available choice.
     698            if not self.blank and not self.rel.edit_inline and not self.rel.raw_id_admin and self.choices:
     699               choice_list = self.get_choices_default()
     700               if len(choice_list) == 1:
     701                   new_data[self.name] = [choices_list[0][0]]
     702        return new_data
     703
     704
    635705class OneToOneField(IntegerField):
    636706    def __init__(self, to, to_field=None, **kwargs):
    637707        kwargs['verbose_name'] = kwargs.get('verbose_name', 'ID')
     
    714784        Returns self.fields, except with fields as Field objects instead of
    715785        field names. If self.fields is None, defaults to putting every
    716786        non-AutoField field with editable=True in a single fieldset.
     787       
     788        returns a list of lists of name, dict
     789        the dict has attribs 'fields' and maybe 'classes'.
     790        fields is a list of subclasses of Field.
     791
     792        Return value needs to be encapsulated.
    717793        """
    718794        if self.fields is None:
    719795            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
     5
     6
     7from django.views.admin.main import BoundField
     8
     9class IncludeAdminScriptNode(template.Node):
     10      def __init__(self, var):
     11         self.var = var
     12 
     13      def render(self, context):
     14        resolved = template.resolve_variable(self.var, context)
     15        return '<script type="text/javascript" src="%s%s"></script>' % \
     16                (ADMIN_MEDIA_PREFIX, resolved)
     17         
     18class SubmitRowNode(template.Node):
     19      def __init__(self):
     20          pass
     21
     22      def render(self, context):
     23          change = context['change']
     24          add = context['add']
     25          show_delete = context['show_delete']
     26          ordered_objects = context['ordered_objects']
     27          save_as = context['save_as']
     28          has_delete_permission = context['has_delete_permission']
     29          is_popup = context['is_popup']
     30         
     31          t = ['<div class="submit-row">']
     32          onclick_attrib = ordered_objects and change and 'onclick="submitOrderForm();"' or ''
     33         
     34          if not is_popup:
     35                if has_delete_permission and (change or show_delete):
     36                   t.append('<p class="float-left"><a href="delete/" class="deletelink">Delete</a></p>')
     37                if change and save_as:
     38                   t.append('<input type="submit" value="Save as new" name="_saveasnew" %s/>' %  onclick_attrib)
     39                if (not save_as or add):
     40                   t.append('<input type="submit" value="Save and add another" name="_addanother" %s/>' %  onclick_attrib)
     41          t.append('<input type="submit" value="Save and continue editing" name="_continue" %s/>' %  onclick_attrib )
     42          t.append('<input type="submit" value="Save" class="default" %s/>' %  onclick_attrib)
     43          t.append('</div>\n')
     44         
     45          return ''.join(t)
     46
     47
     48
     49
     50class AdminFieldBoundNode(template.Node):
     51    def __init__(self, argument):
     52        self.argument = argument
     53   
     54    def render(self, context):
     55        argument_val = template.resolve_variable(self.argument, context)
     56        if (isinstance(argument_val, list)):
     57            bound_fields = argument_val
     58        else:
     59            bound_fields = [argument_val]
     60        add = context['add']
     61        change = context['change']
     62       
     63        context.push()
     64        context['bound_fields'] = bound_fields
     65        context['class_names'] = " ".join(self.get_class_names(bound_fields))
     66        t = template_loader.get_template("admin_field")
     67        output =  t.render(context)
     68        context.pop()
     69         
     70        return output
     71
     72    def get_class_names(self, bound_fields):
     73
     74        class_names = ['form-row']
     75        for bound_field in bound_fields:
     76            for f in bound_field.form_fields:
     77                if f.errors():
     78                    class_names.append('errors')
     79                    break
     80         
     81        # Assumes BooleanFields won't be stacked next to each other!
     82        if isinstance(bound_fields[0].field, meta.BooleanField):
     83            class_names.append('checkbox-row')
     84         
     85        return class_names
     86       
     87class FieldWidgetNode(template.Node):
     88    def __init__(self, bound_field_var):
     89        self.bound_field_var = bound_field_var
     90
     91    def render(self, context):
     92        bound_field = template.resolve_variable(self.bound_field_var, context)
     93        add = context['add']
     94        change = context['change']
     95       
     96        context.push()
     97        context['bound_field'] = bound_field
     98        t = template_loader.get_template("admin_field_widget")
     99        output =  t.render(context)
     100        context.pop()
     101         
     102        return output
     103
     104       
     105
     106class FieldWrapper(object):
     107    def __init__(self, field ):
     108        self.field = field
     109
     110    def needs_header(self):
     111        return not isinstance(self.field, meta.AutoField)
     112
     113    def header_class_attribute(self):
     114        return self.field.blank and ' class="optional"' or ''
     115
     116    def use_raw_id_admin(self):
     117         return isinstance(self.field.rel, (meta.ManyToOne, meta.ManyToMany)) \
     118                and self.field.rel.raw_id_admin
     119
     120class FormObjectWrapper(object):
     121    def __init__(self, obj, field_wrappers, i, object_name):
     122        self.obj = obj
     123        self.field_wrappers = field_wrappers
     124     
     125        form_prefix = '%s.%s.' % ( object_name, i)
     126     
     127        name_prefix = ''
     128     
     129        self.bound_fields = [ BoundField(0, fw.field, obj['original'], form_prefix,
     130                              name_prefix, True, self.resolve_form_fields(fw.field, name_prefix) ) \
     131                                for fw in self.field_wrappers ]
     132
     133   
     134    def resolve_form_fields(self,field, name_prefix):
     135        return [self.obj[name] for name in field.get_manipulator_field_names(name_prefix)]
     136
     137#HACK
     138    def has_errors(self):
     139        return max([ bool( len( self.obj[fw.field.name].errors() ) )  for fw in self.field_wrappers])
     140       
     141    def html_combined_error_list(self):
     142        return ''.join( [ self.obj[fw.field.name].html_error_list() for fw in self.field_wrappers])
     143
     144   
     145   
     146
     147class EditInlineNode(template.Node):
     148    def __init__(self, rel_var):
     149        self.rel_var = rel_var
     150   
     151    def render(self, context):
     152        relation = template.resolve_variable(self.rel_var, context)
     153        add, change = context['add'], context['change']
     154       
     155        context.push()
     156
     157        self.fill_context(relation, add, change, context)
     158       
     159        if relation.field.rel.edit_inline == meta.TABULAR:
     160            t = template_loader.get_template("admin_edit_inline_tabular")
     161        else:  # meta.STACKED
     162            t = template_loader.get_template("admin_edit_inline_stacked")   
     163       
     164        output = t.render(context)
     165       
     166        context.pop()
     167        return output
     168
     169       
     170    def fill_context(self, relation, add, change, context):
     171        field_wrapper_list = [FieldWrapper(f) for f in relation.editable_fields()]
     172       
     173        var_name = relation.opts.object_name.lower()
     174       
     175        form = template.resolve_variable('form', context)
     176        form_objects = form[relation.opts.module_name]
     177        form_object_wrapper_list = [FormObjectWrapper(o,field_wrapper_list, i, var_name) for i,o in enumerate(form_objects)]
     178   
     179        context['field_wrapper_list'] = field_wrapper_list
     180        context['form_object_wrapper_list'] = form_object_wrapper_list
     181        context['num_headers'] = len(field_wrapper_list)
     182        context['original_row_needed'] = max([fw.use_raw_id_admin() for fw in field_wrapper_list])
     183        context['name_prefix'] = "%s." % (var_name,)
     184   
     185class FieldLabelNode(template.Node):
     186    def __init__(self, bound_field_var):
     187        self.bound_field_var = bound_field_var
     188       
     189    def render(self, context):
     190        bound_field = template.resolve_variable(self.bound_field_var, context)
     191        class_names = []
     192        if isinstance(bound_field.field, meta.BooleanField):
     193            class_names.append("vCheckboxLabel")
     194        else:
     195            if not bound_field.field.blank:
     196                class_names.append('required')
     197            if bound_field.index > 0:
     198                class_names.append('inline')
     199       
     200        class_str = class_names and ' class="%s"' % ' '.join(class_names) or ''
     201        return '<label for="%s"%s>%s:</label> ' % (bound_field.label_name, class_str, capfirst(bound_field.field.verbose_name) )
     202
     203class OutputAllNode(template.Node):
     204    def __init__(self, form_fields_var):
     205        self.form_fields_var = form_fields_var
     206   
     207    def render(self, context):
     208        form_fields = template.resolve_variable(self.form_fields_var, context)
     209        return ''.join([str(f) for f in form_fields])
     210
     211class AutoPopulatedFieldScriptNode(template.Node):
     212    def __init__(self, auto_pop_var):
     213        self.auto_pop_var = auto_pop_var
     214
     215    def render(self,context):
     216        auto_pop_fields = template.resolve_variable(self, context)
     217        change = context['change']
     218        for field in auto_populated_fields:
     219            t = []
     220            if change:
     221                t.append('document.getElementById("id_%s")._changed = true;' % field.name )
     222            else:
     223                t.append('document.getElementById("id_%s").onchange = function() { this._changed = true; };' % field.name)
     224
     225            add_values = ' + " " + '.join(['document.getElementById("id_%s").value' % g for g in field.prepopulate_from])
     226            for f in field.prepopulate_from:
     227                t.append("""
     228                         document.getElementById("id_%s").onkeyup = function() {
     229                                var e = document.getElementById("id_%s");
     230                                if(!e._changed) { e.value = URLify(%s, %s);}
     231                        }
     232                        """ % (f, field.name, add_values, field.maxlength)
     233                )
     234
     235        return ''.join(t)
     236
     237def do_include_admin_script(parser, token):
     238     tokens = token.contents.split()
     239     if len(tokens) != 2:
     240         raise template.TemplateSyntaxError("%s takes 1 argument" % tokens[0])
     241     
     242     return IncludeAdminScriptNode(tokens[1])
     243
     244def do_dummy(parser, token):
     245     return DummyNode(token.contents)
     246
     247def do_submit_row(parser, token):
     248     return SubmitRowNode()
     249
     250def do_admin_field_bound(parser, token):
     251    tokens = token.contents.split()
     252    if len(tokens) != 2:
     253        raise template.TemplateSyntaxError("%s takes 1 argument" % tokens[0])
     254    return AdminFieldBoundNode(tokens[1])
     255
     256
     257def do_field_label(parser, token):
     258    tokens = token.contents.split()
     259    if len(tokens) != 2:
     260        raise template.TemplateSyntaxError("%s takes 1 argument" % tokens[0])
     261    return FieldLabelNode(tokens[1])
     262
     263def do_field_widget(parser, token):
     264    tokens = token.contents.split()
     265    if len(tokens) != 2:
     266        raise template.TemplateSyntaxError("%s takes 1 argument" % tokens[0])
     267    return FieldWidgetNode(tokens[1])
     268
     269def do_output_all(parser, token):
     270    tokens = token.contents.split()
     271    if len(tokens) != 2:
     272        raise template.TemplateSyntaxError("%s takes 1 argument" % tokens[0])
     273    return OutputAllNode(tokens[1])
     274
     275
     276def do_edit_inline(parser, token):
     277    tokens = token.contents.split()
     278    if len(tokens) != 2:
     279        raise template.TemplateSyntaxError("%s takes 1 argument" % tokens[0])
     280    return EditInlineNode(tokens[1])
     281
     282def do_auto_populated_field_script(parser, token):
     283    tokens = token.contents.split()
     284    if len(tokens) != 2:
     285        raise template.TemplateSyntaxError("%s takes 1 argument" % tokens[0])
     286    return AutoPopulatedFieldScriptNode(tokens[1])
     287
     288
     289
     290template.register_tag('include_admin_script', do_include_admin_script)
     291template.register_tag('submit_row', do_submit_row )
     292template.register_tag('admin_field_bound', do_admin_field_bound)
     293template.register_tag('edit_inline', do_edit_inline)
     294template.register_tag('auto_populated_field_script', do_auto_populated_field_script)
     295template.register_tag('field_label', do_field_label)
     296template.register_tag('field_widget', do_field_widget)
     297template.register_tag('output_all', do_output_all)
     298         
     299         
  • 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.models.auth import log
     
    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, index, field, original, form_prefix, name_prefix, rel, form_fields):
     544        self.index = index
     545        self.field = field
     546        self.hack_prefix = 'form.' + form_prefix
     547        self.label_name = 'id_%s%s' % (form_prefix, field.get_manipulator_field_names('')[0])
     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.name_prefix = name_prefix
     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.form_fields = form_fields
     558       
     559        classes = []
     560        if(self.raw_id_admin):
     561            classes.append('nowrap')
     562      #  if(self.field.errors()):
     563      #      classes.append('error')
     564        self.cell_class_attribute = ' '.join(classes)   
     565        self._repr_filled = False
     566       
     567    def as_field_list(self):
     568        return [self.field]
     569
     570    def original_value(self):
     571        return self.original.__dict__[self.field.name]
     572       
     573    def _fetch_existing_repr(self, func_name):
     574        class_dict = self.original.__class__.__dict__
     575        func = class_dict.get(func_name)
     576        return func(self.original)
     577       
     578    def _fill_existing_repr(self):
     579        if self._repr_filled:
     580            return
     581        #HACK
     582        if isinstance(self.field.rel, meta.ManyToOne):
     583             func_name = 'get_%s' % self.field.name
     584             self._repr = self._fetch_existing_repr(func_name)
     585        elif isinstance(self.field.rel, meta.ManyToMany):
     586             func_name = 'get_%s_list' % self.field.name
     587             self._repr =  ",".join(self._fetch_existing_repr(func_name))
     588        self._repr_filled = True
     589             
     590    def existing_repr(self):
     591        self._fill_existing_repr()
     592        return self._repr
     593
     594    def __repr__(self):
     595        return repr(self.__dict__)
     596
     597   
     598
     599
     600class AdminFieldSet(object):
     601     def __init__(self, fieldset_name, options, bound_field_sets):
     602         self.name = fieldset_name
     603         self.options = options
     604         self.bound_field_sets = bound_field_sets
     605         self.classes = options.get('classes', '')
     606# self.fields = options['fields']
     607     
     608     def __repr__(self):
     609        return "Fieldset:(%s,%s)" % (self.name, self.bound_field_sets)
     610
     611
     612def bound_field_sets(opts, context, name_prefix):
     613    original = template.resolve_variable('original', context);
     614    form_prefix = ''
     615    fields = opts['fields']
     616    bound_field_sets = [ [BoundField(i, f, original, form_prefix, name_prefix, False,
     617                                resolve_form_fields(f,name_prefix, context)) for i,f in enumerate(field)  ] for field in fields]
     618
     619    return bound_field_sets
     620
     621def resolve_form_fields(field, name_prefix, context):
     622    return [template.resolve_variable(name, context) for name in field.get_manipulator_field_names(name_prefix)]
     623
     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    admin_fieldsets = [AdminFieldSet(f, o, bound_field_sets(o, context, 'form.')) for f, o in admin_field_objs]
     642    inline_related_objects = opts.get_inline_related_objects_wrapped()
     643   
     644    ordered_object_names =   ' '.join(['object.%s' % o.pk.name for o in ordered_objects])
     645   
     646    extra_context = {
     647        'add': add,
     648        'change': change,
     649        'admin_field_objs' : admin_field_objs,
     650        'ordered_objects' : ordered_objects,
     651        'auto_populated_fields' : auto_populated_fields,
     652        'javascript_imports' : javascript_imports,
     653        'coltype' : coltype,
     654        'has_absolute_url': has_absolute_url,
     655        'form_enc_attrib': form_enc_attrib,
     656        'form_url' : form_url,
     657        'admin_fieldsets' : admin_fieldsets,
     658        'inline_related_objects': inline_related_objects,
     659        'ordered_object_names' : ordered_object_names,
     660        'content_type_id' : opts.get_content_type_id(),
     661        'save_on_top' : opts.admin.save_on_top,
     662        'verbose_name_plural': opts.verbose_name_plural,
     663        'save_as': opts.admin.save_as,
     664        'app_label': app_label,
     665        'object_name': opts.object_name,
     666        'has_delete_permission' : context['perms'][app_label][opts.get_delete_permission()]
     667    }
     668   
     669    context.update(extra_context)   
     670   
     671   
     672def add_stage_new(request, app_label, module_name, show_delete=False, form_url='', post_url='../', post_url_continue='../%s/', object_id_override=None):
     673    mod, opts = _get_mod_opts(app_label, module_name)
     674    if not request.user.has_perm(app_label + '.' + opts.get_add_permission()):
     675        raise PermissionDenied
     676    manipulator = mod.AddManipulator()
     677    if request.POST:
     678        new_data = request.POST.copy()
     679        if opts.has_field_type(meta.FileField):
     680            new_data.update(request.FILES)
     681        errors = manipulator.get_validation_errors(new_data)
     682        if not errors and not request.POST.has_key("_preview"):
     683            for f in opts.many_to_many:
     684                if f.rel.raw_id_admin:
     685                    new_data.setlist(f.name, new_data[f.name].split(","))
     686            manipulator.do_html2python(new_data)
     687            new_object = manipulator.save(new_data)
     688            pk_value = getattr(new_object, opts.pk.column)
     689            log.log_action(request.user.id, opts.get_content_type_id(), pk_value, repr(new_object), log.ADDITION)
     690            msg = 'The %s "%s" was added successfully.' % (opts.verbose_name, new_object)
     691            # Here, we distinguish between different save types by checking for
     692            # the presence of keys in request.POST.
     693            if request.POST.has_key("_continue"):
     694                request.user.add_message("%s You may edit it again below." % msg)
     695                if request.POST.has_key("_popup"):
     696                    post_url_continue += "?_popup=1"
     697                return HttpResponseRedirect(post_url_continue % pk_value)
     698            if request.POST.has_key("_popup"):
     699                return HttpResponse('<script type="text/javascript">opener.dismissAddAnotherPopup(window, %s, "%s");</script>' % \
     700                    (pk_value, repr(new_object).replace('"', '\\"')))
     701            elif request.POST.has_key("_addanother"):
     702                request.user.add_message("%s You may add another %s below." % (msg, opts.verbose_name))
     703                return HttpResponseRedirect(request.path)
     704            else:
     705                request.user.add_message(msg)
     706                return HttpResponseRedirect(post_url)
     707        if request.POST.has_key("_preview"):
     708            manipulator.do_html2python(new_data)
     709    else:
     710        # Add default data.
     711        new_data = manipulator.flatten_data()
     712       
     713        # Override the defaults with request.GET, if it exists.
     714        new_data.update(request.GET)
     715        errors = {}
     716
     717    # Populate the FormWrapper.
     718    form = formfields.FormWrapper(manipulator, new_data, errors)
     719   
     720    c = Context(request, {
     721        'title': 'Add %s' % opts.verbose_name,
     722        "form": form,
     723        "is_popup": request.REQUEST.has_key("_popup"),
     724    })
     725    if object_id_override is not None:
     726        c['object_id'] = object_id_override
     727   
     728   
     729    fill_extra_context(opts, app_label, c, change=True)
     730   
     731    t = template_loader.get_template("admin_change_form");
     732   
     733    return HttpResponse(t.render(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    t = template_loader.get_template("admin_change_form");
     829    return HttpResponse(t.render(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()[:]
     
    8021101        # Add default data.
    8031102        for f in opts.fields:
    8041103            if f.has_default():
    805                 new_data.update(_get_flattened_data(f, f.get_default()))
     1104                new_data.update( f.flatten_data() )
    8061105            # In required many-to-one fields with only one available choice,
    8071106            # select that one available choice. Note: We have to check that
    8081107            # the length of choices is *2*, not 1, because SelectFields always
     
    8371136                if f.editable and f != rel_field and not isinstance(f, meta.AutoField):
    8381137                    for field_name in f.get_manipulator_field_names(''):
    8391138                        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, []))
     1139                        field = manipulator[full_field_name]
     1140                        data = field.extract_data(new_data)
     1141                        collection[field_name] = formfields.FormFieldWrapper(field, data, errors.get(full_field_name, []))
    8411142            wrapper.append(formfields.FormFieldCollection(collection))
    8421143        setattr(form, rel_opts.module_name, wrapper)
    8431144
     
    8491150    if object_id_override is not None:
    8501151        c['object_id'] = object_id_override
    8511152    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')
    8531153    t = template_loader.get_template_from_string(raw_template)
    8541154    return HttpResponse(t.render(c))
    8551155
     
    9151215        new_data = {}
    9161216        obj = manipulator.original_object
    9171217        for f in opts.fields:
    918             new_data.update(_get_flattened_data(f, getattr(obj, f.column)))
     1218            new_data.update(f.flatten_data(obj))
    9191219        for f in opts.many_to_many:
    9201220            get_list_func = getattr(obj, 'get_%s_list' % f.rel.singular)
    9211221            if f.rel.raw_id_admin:
     
    9271227            for i, rel_instance in enumerate(getattr(obj, 'get_%s_list' % opts.get_rel_object_method_name(rel_obj, rel_field))()):
    9281228                for f in rel_obj.fields:
    9291229                    if f.editable and f != rel_field:
    930                         for k, v in _get_flattened_data(f, getattr(rel_instance, f.column)).items():
     1230                        for k, v in f.flatten_data(rel_instance).items():
    9311231                            new_data['%s.%d.%s' % (var_name, i, k)] = v
    9321232                for f in rel_obj.many_to_many:
    9331233                    new_data['%s.%d.%s' % (var_name, i, f.column)] = [j.id for j in getattr(rel_instance, 'get_%s_list' % f.rel.singular)()]
     
    9601260                if f.editable and f != rel_field:
    9611261                    for field_name in f.get_manipulator_field_names(''):
    9621262                        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, []))
     1263                        field = manipulator[full_field_name]
     1264                        data = field.extract_data(new_data)
     1265                        collection[field_name] = formfields.FormFieldWrapper(field, data, errors.get(full_field_name, []))
    9641266            wrapper.append(formfields.FormFieldCollection(collection))
    9651267        setattr(form, rel_opts.module_name, wrapper)
    9661268        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