1. Luke Plant
  2. django

Commits

rjwi...@bcc190cf-cafb-0310-a4f2-bffc1f526a37  committed 9dc1a57

Initial checkin of new admin branch. Ticket #535. I've tried to cover the changes below, but may have forgotten some.

M django/conf/urls/admin.py
Modified to allow running the old and new code in parallel. Simply add _old on the end of a change or add form to check against the behaviour of the old admin.
eg http://myadmin/auth/users/1/ -> http://myadmin/auth/users/1_old/

A django/conf/admin_templates/admin_change_form.html
A django/conf/admin_templates/admin_edit_inline_stacked.html
A django/conf/admin_templates/admin_field.html
A django/conf/admin_templates/admin_field_widget.html
A django/conf/admin_templates/admin_edit_inline_tabular.html

These are templates extracted from the admin code that are now used to render the views.

M django/conf/admin_media/js/urlify.js

Change to dashes rather than underscores in slug fields.

M django/core/formfields.py

All of the data conversion from POST to something fields can understand now takes place here.

M django/core/meta/__init__.py

Added InlineRelatedObject and added manipulator methods for data flattening.
Also includes a fix to ordering descending select='' fields.

M django/core/meta/fields.py

Data flattening pushed down into fields.

M django/core/defaulttags.py

Added "include" tag, which is like ssi parsed, but uses normal template resolution rather than absolute paths.

M django/core/validators.py

Allow dashes in slugfields.

A django/templatetags/admin_modify.py

A new set of template tags to provide functionality for the admin.

M django/views/admin/main.py

New view functions for add and change. New helper objects for the admin templates to access ( BoundField, AdminFieldSet)

M tests/runtests.py

Show the details of an error rather than assuming the existance of a database.

  • Participants
  • Parent commits 165d9a6
  • Branches new-admin

Comments (0)

Files changed (15)

File django/conf/admin_media/js/urlify.js

View file
  • Ignore whitespace
     s = s.replace(r, '');
     s = s.replace(/[^\w\s]/g, '');   // remove unneeded chars
     s = s.replace(/^\s+|\s+$/g, ''); // trim leading/trailing spaces
-    s = s.replace(/\s+/g, '_');      // convert spaces to underscores
+    s = s.replace(/\s+/g, '-');      // convert spaces to dashes
     s = s.toLowerCase();             // convert to lowercase
     return s.substring(0, num_chars);// trim to first num_chars chars
-}
+}

File django/conf/admin_templates/admin_change_form.html

View file
  • Ignore whitespace
+{% extends "base_site" %}
+{% load admin_modify %}
+{% load adminmedia %}
+{% block extrahead %}
+ 
+   {% for js in javascript_imports %}
+      {% include_admin_script js %}
+   {% endfor %}
+
+{% endblock %}
+
+{% block coltype %}{{ coltype }}{% endblock %}
+
+{% block bodyclass %}{{app_label}}-{{object_name.lower}} change-form{% endblock %}
+
+{% block breadcrumbs %}{% if not is_popup %}
+<div class="breadcrumbs">
+     <a href="../../../">Home</a> &rsaquo;
+     <a href="../">{{verbose_name_plural|capfirst}}</a> &rsaquo;
+     {% if add %}
+     	Add {{verbose_name}}
+     {% else %}
+        {{original|striptags|truncatewords:"18"}}
+     {% endif %}
+</div>
+{% endif %}
+{% endblock %}
+
+{% block content %}<div id="content-main">
+{% if change %}
+   {% if not is_popup %}
+      <ul class="object-tools"><li><a href="history/" class="historylink">History</a></li>
+      {% if has_absolute_url %}
+         <li><a href="/r/{{ content_type_id }}/{{ object_id }}/" class="viewsitelink">View on site</a></li>
+      {% endif%}
+      </ul>
+   {% endif %}
+{% endif %}
+
+<form {{ form_enc_attrib }} action='{{ form_url }}' method="post">
+
+{% if is_popup %}<input type="hidden" name="_popup" value="1">{% endif %}
+
+{% if save_on_top %}
+   {% submit_row %}
+{% endif %}
+
+{% if form.error_dict %}
+   <p class="errornote">Please correct the error{{ form.error_dict.items|pluralize }} below.</p>
+{% endif %}
+<b>
+</b>
+{% for fieldset in admin_fieldsets %}
+   <fieldset class="module aligned {{ fieldset.classes }}">
+    {% if fieldset.name %}
+   <h2>{{fieldset.name }}</h2> 
+    {% endif %}
+    {% for bound_field_set in fieldset.bound_field_sets %}
+        {% for bound_field in bound_field_set %}
+            {% admin_field_bound bound_field %}
+            {% filter_interface_script_maybe bound_field %} 
+        {% endfor %} 
+    {% endfor %}
+   </fieldset>
+{% endfor %}
+
+{% if change %}
+   {% if ordered_objects %}
+   <fieldset class="module"><h2>Ordering</h2>
+   <div class="form-row{% if form.order_.errors %} error{% endif %} ">
+   {% if form.order_.errors %}{{ form.order_.html_error_list }}{% endif %}
+   <p><label for="id_order_">Order:</label> {{ form.order_ }}</p>
+   </div></fieldset>
+   {% endif %}
+{% endif %}
+
+
+{% for relation in inline_related_objects %}
+    {% edit_inline relation %}
+{% endfor %}
+
+{% submit_row %}
+
+{% if add %}
+   <script type="text/javascript">document.getElementById("id_{{first_field}}").focus();</script>'
+{% endif %}
+
+{% if auto_populated_fields %}
+   <script type="text/javascript">
+   {% auto_populated_field_script auto_populated_fields %} 
+   </script>
+{% endif %}
+
+{% if change %}
+   {% if ordered_objects %}
+      {% if form.order_objects %}<ul id="orderthese">
+          {% for object in form.order_objects %}
+	     <li id="p{% firstof ordered_object_names %}">
+	     <span id="handlep{% firstof ordered_object_names %}">{{ object|truncatewords:"5" }}</span>
+	     </li>
+	  {% endfor%}
+      {% endif %}
+   {% endif %}
+{% endif%}
+</form>
+
+{% endblock %}

File django/conf/admin_templates/admin_edit_inline_stacked.html

View file
  • Ignore whitespace
+<fieldset class="module aligned">
+   {% for fcw in form_field_collection_wrapper_list %} 
+      <h2>{{relation.opts.verbose_name|capfirst }}&nbsp;#{{ forloop.counter }}</h2>
+      {% if fcw.show_url %}{% if fcw.obj.original %}
+      <p><a href="/r/{{ fcw.obj.original.content_type_id }}/{{ fcw.obj.original.id }}/">View on site</a></p>
+      {% endif %}{% endif %}
+      {% for bound_field in fcw.bound_fields %}
+         {% if bound_field.not_in_table %}
+            {% field_widget bound_field %}
+         {% else %}
+            {% admin_field_bound bound_field %}
+         {% endif %}
+      {% endfor %}
+    {%endfor%}
+</fieldset>
+

File django/conf/admin_templates/admin_edit_inline_tabular.html

View file
  • Ignore whitespace
+<fieldset class="module">
+   <h2>{{relation.opts.verbose_name_plural|capfirst}}</h2><table>
+   <thead><tr>
+   {% for fw in field_wrapper_list %}
+      {% if fw.needs_header %}
+         <th{{fw.header_class_attribute}}> {{fw.field.verbose_name|capfirst}}  </th>
+      {% endif %}
+   {% endfor %}
+   {% for fcw in form_field_collection_wrapper_list %} 
+       
+      {% if change %}{% if original_row_needed %}
+         {% if fcw.obj.original %}
+            <tr class="row-label {% cycle row1,row2 %}"><td colspan="{{num_headers}}"><strong>{{ fcw.obj.original }}</strong></tr>
+         {% endif %}
+      {% endif %}{% endif %}
+      {% if fcw.obj.errors %}
+         <tr class="errorlist"><td colspan="{{num_headers}}">
+            {{ fcw.obj.html_combined_error_list }}
+         </tr>
+      {% endif %}
+      <tr class="{% cycle row1,row2 %}">
+      {% for bound_field in fcw.bound_fields %}
+         {% if not bound_field.not_in_table %}
+         <td "{{ bound_field.cell_class_attribute}}"> 
+            {% field_widget bound_field %}
+         </td>
+         {% endif %}
+      {% endfor %}
+      {% if fcw.show_url %}<td>
+         {% if fcw.obj.original %}<a href="/r/{{ fcw.obj.original.content_type_id }}/{{ fcw.obj.original.id }}/">View on site</a>{% endif %} 
+      </td>{% endif %}
+      </tr>
+
+   {% endfor %} </table>
+
+   {% for fcw in form_field_collection_wrapper_list %}
+      {% for bound_field in fcw.bound_fields %}
+         {% if bound_field.not_in_table %}
+            {% field_widget bound_field %}
+         {% endif %}
+      {% endfor %}
+   {% endfor %}
+</fieldset>
+

File django/conf/admin_templates/admin_field.html

View file
  • Ignore whitespace
+<div class="{{ class_names }}">
+   {% for bound_field in bound_fields %}
+      {{ bound_field.html_error_list }} 
+   {% endfor %}
+
+   {% for bound_field in bound_fields %}
+      {% if bound_field.has_label_first %}
+         {% field_label bound_field %}
+      {% endif %}
+      
+      {% field_widget bound_field %}
+
+      {% if not bound_field.has_label_first %}
+         {% field_label bound_field %}
+      {% endif %}
+
+      {% if change %}
+      	 {% if bound_field.field.primary_key %}
+	    {{ bound_field.original_value }} 
+	 {% endif %}
+
+	 {% if bound_field.raw_id_admin %}
+	    {% if bound_field.existing_repr %}
+		&nbsp;<strong>{{ bound_field.existing_repr|truncatewords:"14" }}</strong>
+	    {% endif %}
+	 {% endif %}
+      {% endif %}
+
+      {% if bound_field.field.help_text %}
+	<p class="help">
+           {{bound_field.field.help_text}}
+	</p>
+      {% endif %}
+   {% endfor %}
+
+</div>

File django/conf/admin_templates/admin_field_widget.html

View file
  • Ignore whitespace
+{% if bound_field.is_date_time %}
+   <p class="datetime"> 
+      Date: {{ bound_field.form_fields.0 }}<br />
+      Time: {{ bound_field.form_fields.1 }}
+   </p>
+{% else %}
+    {% if bound_field.is_file_field %}
+        {% if bound_field.original_value %}
+            Currently: <a href="{{ bound_field.original_url }}" > {{ bound_field.original_value }} </a><br />
+            Change: {% output_all bound_field.form_fields %}
+        {% else %}
+            {% output_all bound_field.form_fields %}
+        {% endif %}
+    {% else %}
+        {% output_all bound_field.form_fields %}
+        {% if bound_field.raw_id_admin %}
+            <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>
+        {% else  %}
+            {% if bound_field.needs_add_label %}
+            <a href="../../../{{ bound_field.field.rel.to.app_label }}/{{ bound_field.field.rel.to.module_name }}/add/" class="add-another" id="add_{{ bound_field.element_id}}" onclick="return showAddAnotherPopup(this);"> <img src="{% admin_media_prefix %}img/admin/icon_addlink.gif" width="10" height="10" alt="Add Another"/></a>
+            {% endif %}
+        {% endif %}
+    {% endif %}
+{% endif %}
+    
+
+

File django/conf/urls/admin.py

View file
  • Ignore whitespace
 
 urlpatterns += (
     # Metasystem admin pages
+    ('^(?P<app_label>[^/]+)/(?P<module_name>[^/]+)/add_old/$', 'django.views.admin.main.add_stage'),
+    ('^(?P<app_label>[^/]+)/(?P<module_name>[^/]+)/(?P<object_id>.+)_old/$', 'django.views.admin.main.change_stage'),
     ('^(?P<app_label>[^/]+)/(?P<module_name>[^/]+)/$', 'django.views.admin.main.change_list'),
-    ('^(?P<app_label>[^/]+)/(?P<module_name>[^/]+)/add/$', 'django.views.admin.main.add_stage'),
+    ('^(?P<app_label>[^/]+)/(?P<module_name>[^/]+)/add/$', 'django.views.admin.main.add_stage_new'),
     ('^(?P<app_label>[^/]+)/(?P<module_name>[^/]+)/jsvalidation/$', 'django.views.admin.jsvalidation.jsvalidation'),
     ('^(?P<app_label>[^/]+)/(?P<module_name>[^/]+)/(?P<object_id>.+)/history/$', 'django.views.admin.main.history'),
     ('^(?P<app_label>[^/]+)/(?P<module_name>[^/]+)/(?P<object_id>.+)/delete/$', 'django.views.admin.main.delete_stage'),
-    ('^(?P<app_label>[^/]+)/(?P<module_name>[^/]+)/(?P<object_id>.+)/$', 'django.views.admin.main.change_stage'),
+    ('^(?P<app_label>[^/]+)/(?P<module_name>[^/]+)/(?P<object_id>.+)/$', 'django.views.admin.main.change_stage_new'),
 )
 urlpatterns = patterns('', *urlpatterns)

File django/core/defaulttags.py

View file
  • Ignore whitespace
 
 import sys
 import template
+import template_loader
 
 class CommentNode(template.Node):
     def render(self, context):
                 return '' # Fail silently for invalid included templates.
         return output
 
+class IncludeNode(template.Node):
+    def __init__(self, template_path):
+        self.template_path_var = template_path_var
+
+    def render(self, context):
+         try:
+             template_path = template.resolve(self.template_path_var, context)
+             t = template_loader.get_template(template_path)
+             return t.render(context)
+         except:
+             return '' # Fail silently for invalid included templates.
+
+
 class LoadNode(template.Node):
     def __init__(self, taglib):
         self.taglib = taglib
             raise template.TemplateSyntaxError, "Second (optional) argument to %s tag must be 'parsed'" % bits[0]
     return SsiNode(bits[1], parsed)
 
+def do_include(parser, token):
+    """
+    Loads a template using standard resolution mechanisms, and renders it in the current context.     
+    """
+    bits = token.contents.split()
+    parsed = False
+    if len(bits) != 2:
+        raise template.TemplateSyntaxError, "'include' tag takes one argument: the path to the template to be included"
+    return IncludeNode(bits[1])
+
 def do_load(parser, token):
     """
     Load a custom template tag set.
 template.register_tag('ifnotequal', lambda parser, token: do_ifequal(parser, token, True))
 template.register_tag('if', do_if)
 template.register_tag('ifchanged', do_ifchanged)
+template.register_tag('include', do_include)
 template.register_tag('regroup', do_regroup)
 template.register_tag('ssi', do_ssi)
 template.register_tag('load', do_load)

File django/core/formfields.py

View file
  • Ignore whitespace
         for field in self.fields:
             if field.field_name == field_name:
                 return field
-        raise KeyError, "Field %s not found" % field_name
+        raise KeyError, "Field %s not found\n%s" % (field_name, repr(self.fields)) 
 
     def __delitem__(self, field_name):
         "Deletes the field with the given field name; raises KeyError on failure"
         must happen after validation because html2python functions aren't
         expected to deal with invalid input.
         """
-        for field in self.fields:
-            if new_data.has_key(field.field_name):
-                new_data.setlist(field.field_name,
-                    [field.__class__.html2python(data) for data in new_data.getlist(field.field_name)])
-            else:
-                try:
-                    # individual fields deal with None values themselves
-                    new_data.setlist(field.field_name, [field.__class__.html2python(None)])
-                except EmptyValue:
-                    new_data.setlist(field.field_name, [])
+        """
+	for field in self.fields:
+	"""
+
+	for field in self.fields:
+	    field.convert_post_data(new_data)
 
 class FormWrapper:
     """
     This allows dictionary-style lookups of formfields. It also handles feeding
     prepopulated data and validation error messages to the formfield objects.
     """
-    def __init__(self, manipulator, data, error_dict):
+    def __init__(self, manipulator, data, error_dict, edit_inline = False):
         self.manipulator, self.data = manipulator, data
         self.error_dict = error_dict
+        self._inline_collections = None
+        self.edit_inline = edit_inline
 
     def __repr__(self):
-        return repr(self.data)
+        return repr(self.__dict__)
 
     def __getitem__(self, key):
         for field in self.manipulator.fields:
             if field.field_name == key:
-                if hasattr(field, 'requires_data_list') and hasattr(self.data, 'getlist'):
-                    data = self.data.getlist(field.field_name)
-                else:
-                    data = self.data.get(field.field_name, None)
-                if data is None:
-                    data = ''
-                return FormFieldWrapper(field, data, self.error_dict.get(field.field_name, []))
+                data = field.extract_data(self.data)
+		return FormFieldWrapper(field, data, self.error_dict.get(field.field_name, []))
+        if self.edit_inline:
+            self.fill_inline_collections() 
+            for inline_collection in self._inline_collections:
+                if inline_collection.name == key:
+                    return inline_collection
+
         raise KeyError
 
+    def fill_inline_collections(self): 
+        if not self._inline_collections:
+            ic = []
+            related_objects = self.manipulator.get_inline_related_objects_wrapped()
+            for rel_obj in related_objects:
+                data = rel_obj.extract_data(self.data)
+                inline_collection = InlineObjectCollection(self.manipulator, rel_obj, data, self.error_dict)
+                ic.append(inline_collection)
+            self._inline_collections = ic
+
+
+
     def has_errors(self):
         return self.error_dict != {}
 
     def __str__(self):
         "Renders the field"
         return str(self.formfield.render(self.data))
+            
 
     def __repr__(self):
         return '<FormFieldWrapper for "%s">' % self.formfield.field_name
         else:
             return ''
 
+    def get_id(self):
+        return  self.formfield.get_id()
+
 class FormFieldCollection(FormFieldWrapper):
     "A utility class that gives the template access to a dict of FormFieldWrappers"
     def __init__(self, formfield_dict):
         "Returns list of all errors in this collection's formfields"
         errors = []
         for field in self.formfield_dict.values():
-            errors.extend(field.errors())
+            if(hasattr(field, 'errors') ):
+                errors.extend(field.errors())
         return errors
 
+    def has_errors(self):
+        return bool(len(self.errors())) 
+        
+    def html_combined_error_list(self):
+        return ''.join( [ field.html_error_list() for field in self.formfield_dict.values() if hasattr(field, 'errors')])
+
+class InlineObjectCollection:
+    "An object that acts like a list of form field collections."  
+    def __init__(self, parent_manipulator,  rel_obj, data, errors):
+        self.parent_manipulator = parent_manipulator 
+        self.rel_obj = rel_obj
+        self.data = data
+        self.errors = errors
+        self._collections = None   
+        self.name = rel_obj.name
+ 
+    def __len__(self):
+        self.fill() 
+        return self._collections.__len__()
+    
+    def __getitem__(self, k):
+        self.fill()
+        return self._collections.__getitem__(k)
+
+    def __setitem__(self, k, v):
+        self.fill()
+        return self._collections.__setitem__(k,v)
+
+    def __delitem__(self, k):
+        self.fill()
+        return self._collections.__delitem__(k)
+
+    def __iter__(self):
+        self.fill()
+        return self._collections.__iter__()
+
+    def fill(self):
+        if self._collections:
+            return 
+        else:
+            var_name = self.rel_obj.opts.object_name.lower()
+            wrapper = []
+            orig = hasattr(self.parent_manipulator, 'original_object') and self.parent_manipulator.original_object  or None 
+            orig_list = self.rel_obj.get_list(orig)
+            for i, instance in enumerate(orig_list):
+                collection = {'original': instance }
+                for f in self.rel_obj.editable_fields():
+                        for field_name in f.get_manipulator_field_names(''):
+                            full_field_name = '%s.%d.%s' % (var_name, i, field_name)
+                            field = self.parent_manipulator[full_field_name]
+                            data = field.extract_data(self.data)
+                            errors = self.errors.get(full_field_name, [])
+#                           if(errors):raise full_field_name + " " + repr(errors)
+                            collection[field_name] = FormFieldWrapper(field, data, errors)
+                wrapper.append(FormFieldCollection(collection))
+            self._collections = wrapper 
+
 class FormField:
     """Abstract class representing a form field.
 
     def render(self, data):
         raise NotImplementedError
 
+    def get_member_name(self):
+        if hasattr(self, 'member_name'):
+            return self.member_name
+    	else:
+            return self.field_name
+
+    def extract_data(self, data_dict):
+	if hasattr(self, 'requires_data_list') and hasattr(data_dict, 'getlist'):
+            data = data_dict.getlist(self.get_member_name())
+        else:
+            data = data_dict.get(self.get_member_name(), None)
+        if data is None:
+            data = ''
+     	self.data_dict = data_dict  
+	return data
+
+    def convert_post_data(self, new_data):
+    	name = self.get_member_name()
+        if new_data.has_key(name):
+	    d = new_data.getlist(name)
+	    #del new_data[self.field_name]
+            new_data.setlist(name,
+                    [self.__class__.html2python(data) 
+	    	     for data in d])
+        else:
+            try:
+               # individual fields deal with None values themselves
+               new_data.setlist(name, [self.__class__.html2python(None)])
+            except EmptyValue:
+               new_data.setlist(name, [])
+
+    def get_id(self):
+        return  FORM_FIELD_ID_PREFIX + self.field_name  
 ####################
 # GENERIC WIDGETS  #
 ####################
         if isinstance(data, unicode):
             data = data.encode('utf-8')
         return '<input type="text" id="%s" class="v%s%s" name="%s" size="%s" value="%s" %s/>' % \
-            (FORM_FIELD_ID_PREFIX + self.field_name, self.__class__.__name__, self.is_required and ' required' or '',
+            (self.get_id(), self.__class__.__name__, self.is_required and ' required' or '',
             self.field_name, self.length, escape(data), maxlength)
 
     def html2python(data):
     def render(self, data):
         # value is always blank because we never want to redisplay it
         return '<input type="password" id="%s" class="v%s%s" name="%s" value="" />' % \
-            (FORM_FIELD_ID_PREFIX + self.field_name, self.__class__.__name__, self.is_required and ' required' or '',
+            (self.get_id(), self.__class__.__name__, self.is_required and ' required' or '',
             self.field_name)
 
 class LargeTextField(TextField):
         if isinstance(data, unicode):
             data = data.encode('utf-8')
         return '<textarea id="%s" class="v%s%s" name="%s" rows="%s" cols="%s">%s</textarea>' % \
-            (FORM_FIELD_ID_PREFIX + self.field_name, self.__class__.__name__, self.is_required and ' required' or '',
+            (self.get_id(), self.__class__.__name__, self.is_required and ' required' or '',
             self.field_name, self.rows, self.cols, escape(data))
 
 class HiddenField(FormField):
 
     def render(self, data):
         return '<input type="hidden" id="%s" name="%s" value="%s" />' % \
-            (FORM_FIELD_ID_PREFIX + self.field_name, self.field_name, escape(data))
+            (self.get_id(), self.field_name, escape(data))
 
 class CheckboxField(FormField):
     def __init__(self, field_name, checked_by_default=False):
         if data or (data is '' and self.checked_by_default):
             checked_html = ' checked="checked"'
         return '<input type="checkbox" id="%s" class="v%s" name="%s"%s />' % \
-            (FORM_FIELD_ID_PREFIX + self.field_name, self.__class__.__name__,
+            (self.get_id(), self.__class__.__name__,
             self.field_name, checked_html)
 
     def html2python(data):
         return False
     html2python = staticmethod(html2python)
 
+
 class SelectField(FormField):
-    def __init__(self, field_name, choices=[], size=1, is_required=False, validator_list=[]):
+    def __init__(self, field_name, choices=[], size=1, is_required=False, validator_list=[], member_name=None):
         self.field_name = field_name
         # choices is a list of (value, human-readable key) tuples because order matters
         self.choices, self.size, self.is_required = choices, size, is_required
         self.validator_list = [self.isValidChoice] + validator_list
+        if member_name != None:
+            self.member_name = member_name
 
     def render(self, data):
+        str_data = str(data) # normalize to string
         output = ['<select id="%s" class="v%s%s" name="%s" size="%s">' % \
-            (FORM_FIELD_ID_PREFIX + self.field_name, self.__class__.__name__, self.is_required and ' required' or '',
-            self.field_name, self.size)]
-        str_data = str(data) # normalize to string
+            (self.get_id(), self.__class__.__name__, 
+             self.is_required and ' required' or '', self.field_name, self.size)]
         for value, display_name in self.choices:
             selected_html = ''
             if str(value) == str_data:
     html2python = staticmethod(html2python)
 
 class RadioSelectField(FormField):
-    def __init__(self, field_name, choices=[], ul_class='', is_required=False, validator_list=[]):
+    def __init__(self, field_name, choices=[], ul_class='', is_required=False, validator_list=[], member_name=None):
         self.field_name = field_name
         # choices is a list of (value, human-readable key) tuples because order matters
         self.choices, self.is_required = choices, is_required
         self.validator_list = [self.isValidChoice] + validator_list
         self.ul_class = ul_class
+        if member_name != None:
+            self.member_name = member_name
 
     def render(self, data):
         """
                 'value': value,
                 'name': display_name,
                 'field': '<input type="radio" id="%s" name="%s" value="%s"%s/>' % \
-                    (FORM_FIELD_ID_PREFIX + self.field_name + '_' + str(i), self.field_name, value, selected_html),
+                    (self.get_id() + '_' + str(i), self.field_name, value, selected_html),
                 'label': '<label for="%s">%s</label>' % \
-                    (FORM_FIELD_ID_PREFIX + self.field_name + '_' + str(i), display_name),
+                    (self.get_id() + '_' + str(i), display_name),
             })
         return RadioFieldRenderer(datalist, self.ul_class)
 
     requires_data_list = True
     def render(self, data):
         output = ['<select id="%s" class="v%s%s" name="%s" size="%s" multiple="multiple">' % \
-            (FORM_FIELD_ID_PREFIX + self.field_name, self.__class__.__name__, self.is_required and ' required' or '',
+            (self.get_id(), self.__class__.__name__, self.is_required and ' required' or '',
             self.field_name, self.size)]
         str_data_list = map(str, data) # normalize to strings
         for value, choice in self.choices:
             if str(value) in str_data_list:
                 checked_html = ' checked="checked"'
             field_name = '%s%s' % (self.field_name, value)
-            output.append('<li><input type="checkbox" id="%s%s" class="v%s" name="%s"%s /> <label for="%s%s">%s</label></li>' % \
-                (FORM_FIELD_ID_PREFIX, field_name, self.__class__.__name__, field_name, checked_html,
-                FORM_FIELD_ID_PREFIX, field_name, choice))
+            output.append('<li><input type="checkbox" id="%s" class="v%s" name="%s"%s /> <label for="%s">%s</label></li>' % \
+                (get_id() + value , self.__class__.__name__, field_name, checked_html,
+                get_id() + value, choice))
         output.append('</ul>')
         return '\n'.join(output)
 
 
     def render(self, data):
         return '<input type="file" id="%s" class="v%s" name="%s" />' % \
-            (FORM_FIELD_ID_PREFIX + self.field_name, self.__class__.__name__,
+            (self.get_id(), self.__class__.__name__,
             self.field_name)
 
     def html2python(data):

File django/core/meta/__init__.py

View file
  • Ignore whitespace
 class BadKeywordArguments(Exception):
     pass
 
+
+class InlineRelatedObject(object):
+    def __init__(self,parent_opts, opts, field):
+        self.parent_opts = parent_opts
+        self.opts = opts
+        self.field = field
+        self.name = opts.module_name
+
+    def flatten_data(self,obj = None):
+        var_name = self.opts.object_name.lower()
+        new_data = {}
+        rel_instances = self.get_list(obj)
+
+        for i, rel_instance in enumerate(rel_instances):
+            instance_data = {} 
+            for f in self.opts.fields + self.opts.many_to_many:
+                field_data = f.flatten_data(rel_instance)
+                #if hasattr(f, 'editable') and f.editable and f != self.field:
+                for name, value in field_data.items():
+                    instance_data['%s.%d.%s' % (var_name, i, name)] = value
+            new_data.update(instance_data)             
+    
+        return new_data        
+
+    def extract_data(self, data):
+        "Pull out the data meant for inline objects of this class, ie anything starting with our module name"
+        return data # TODO  
+    
+    def get_list(self, parent_instance = None):
+        "Get the list of this type of object from an instance of the parent class"
+        if parent_instance != None:
+            func_name = 'get_%s_list' % self.parent_opts.get_rel_object_method_name(self.opts, self.field)
+            func = getattr(parent_instance, func_name)
+            list = func()
+            
+            count = len(list) + self.field.rel.num_extra_on_change
+            if self.field.rel.min_num_in_admin:
+               count = max(count, self.field.rel.min_num_in_admin)
+            if self.field.rel.max_num_in_admin:
+               count = min(count, self.field.rel.max_num_in_admin)
+       
+            change = count - len(list) 
+            if change > 0:
+                return list + [None for _ in range(change)]
+            if change < 0:
+                return list[:change]
+            else: # Just right
+                return list
+        else:
+            return [None for _ in range(self.field.rel.num_in_admin)]
+
+    
+    def editable_fields(self, wrapping_func = lambda x: x):
+        """Get the fields in this class that should be edited inline.
+        Pass a callable, eg a class, as the second argument to wrap the fields.
+        This can be useful to add extra attributes for use in templates."""
+        
+        return [wrapping_func(f) for f in self.opts.fields + self.opts.many_to_many if f.editable and f != self.field ]
+        
+    def __repr__(self):
+        return "<InlineRelatedObject: %s related to %s>" % ( self.name, self.field.name)      
+
+        
+
 class Options:
     def __init__(self, module_name='', verbose_name='', verbose_name_plural='', db_table='',
         fields=None, ordering=None, unique_together=None, admin=None, has_related_links=False,
     def get_inline_related_objects(self):
         return [(a, b) for a, b in self.get_all_related_objects() if b.rel.edit_inline]
 
+    def get_inline_related_objects_wrapped(self):
+        return [InlineRelatedObject(self, opts, field) for opts, field in self.get_all_related_objects() if field.rel.edit_inline]
+
+    def get_data_holders(self):
+        return self.fields + self.many_to_many + self.get_inline_related_objects_wrapped()
+
     def get_all_related_many_to_many_objects(self):
         module_list = get_installed_model_modules()
         rel_objs = []
             new_mod.get_latest = curry(function_get_latest, opts, new_class, does_not_exist_exception)
 
         for f in opts.fields:
+            #TODO : change this into a virtual function so that user defined fields will be able to add methods to module or class. 
             if f.choices:
                 # Add "get_thingie_display" method to get human-readable value.
                 func = curry(method_get_display_value, f)
         # If it does already exist, do an UPDATE.
         if cursor.fetchone():
             db_values = [f.get_db_prep_save(f.pre_save(getattr(self, f.column), False)) for f in non_pks]
-            cursor.execute("UPDATE %s SET %s WHERE %s=%%s" % (opts.db_table,
-                ','.join(['%s=%%s' % f.column for f in non_pks]), opts.pk.column),
+	    while 1:
+	    	try:
+		    idx = db_values.index('')
+		    non_pks[idx:idx+1] = []
+		    db_values[idx:idx +1] = []
+                except: break
+	    cursor.execute("UPDATE %s SET %s WHERE %s=%%s" % (opts.db_table, 
+	        ','.join(['%s=%%s' % f.column for f in non_pks]), opts.pk.column),
                 db_values + [pk_val])
         else:
             record_exists = False
         if f == '?': # Special case.
             order_by.append(db.get_random_function_sql())
         else:
+            if f.startswith('-'):
+                col_name = f[1:]
+                order = "DESC"
+            else:
+                col_name = f
+                order = "ASC"
             # Use the database table as a column prefix if it wasn't given,
             # and if the requested column isn't a custom SELECT.
-            if "." not in f and f not in [k[0] for k in kwargs.get('select', [])]:
+            if "." not in col_name and col_name not in [k[0] for k in kwargs.get('select', [])]:
                 table_prefix = opts.db_table + '.'
             else:
                 table_prefix = ''
-            if f.startswith('-'):
-                order_by.append('%s%s DESC' % (table_prefix, orderfield2column(f[1:], opts)))
-            else:
-                order_by.append('%s%s ASC' % (table_prefix, orderfield2column(f, opts)))
+            
+            order_by.append('%s%s %s' % (table_prefix, orderfield2column(col_name, opts), order))
+    
     order_by = ", ".join(order_by)
 
     # LIMIT and OFFSET clauses
     man.__module__ = MODEL_PREFIX + '.' + opts.module_name # Set this explicitly, as above.
     man.__init__ = curry(manipulator_init, opts, add, change)
     man.save = curry(manipulator_save, opts, klass, add, change)
+    man.get_inline_related_objects_wrapped = curry(manipulator_get_inline_related_objects_wrapped, opts, klass, add, change)
+    man.flatten_data = curry(manipulator_flatten_data, opts, klass, add, change)
     for field_name_list in opts.unique_together:
         setattr(man, 'isUnique%s' % '_'.join(field_name_list), curry(manipulator_validator_unique_together, field_name_list, opts))
     for f in opts.fields:
             self.fields.extend(f.get_manipulator_fields(opts, self, change))
 
     # Add fields for related objects.
-    for rel_opts, rel_field in opts.get_inline_related_objects():
+    for obj in opts.get_inline_related_objects_wrapped():
         if change:
-            count = getattr(self.original_object, 'get_%s_count' % opts.get_rel_object_method_name(rel_opts, rel_field))()
-            count += rel_field.rel.num_extra_on_change
-            if rel_field.rel.min_num_in_admin:
-                count = max(count, rel_field.rel.min_num_in_admin)
-            if rel_field.rel.max_num_in_admin:
-                count = min(count, rel_field.rel.max_num_in_admin)
+            count = getattr(self.original_object, 'get_%s_count' % opts.get_rel_object_method_name(obj.opts, obj.field))()
+            count += obj.field.rel.num_extra_on_change
+            if obj.field.rel.min_num_in_admin:
+                count = max(count, obj.field.rel.min_num_in_admin)
+            if obj.field.rel.max_num_in_admin:
+                count = min(count, obj.field.rel.max_num_in_admin)
         else:
-            count = rel_field.rel.num_in_admin
-        for f in rel_opts.fields + rel_opts.many_to_many:
-            if f.editable and f != rel_field and (not f.primary_key or (f.primary_key and change)):
+            count = obj.field.rel.num_in_admin
+        for f in obj.opts.fields + obj.opts.many_to_many:
+            if f.editable and f != obj.field :
                 for i in range(count):
-                    self.fields.extend(f.get_manipulator_fields(rel_opts, self, change, name_prefix='%s.%d.' % (rel_opts.object_name.lower(), i), rel=True))
+                    self.fields.extend(f.get_manipulator_fields(obj.opts, self, change, name_prefix='%s.%d.' % (obj.opts.object_name.lower(), i), rel=True))
 
     # Add field for ordering.
     if change and opts.get_ordered_objects():
             getattr(new_object, 'set_%s_order' % rel_opts.object_name.lower())(order)
     return new_object
 
+def manipulator_get_inline_related_objects_wrapped(opts, klass, add, change, self):
+    return opts.get_inline_related_objects_wrapped() 
+        
+def manipulator_flatten_data(opts, klass, add, change, self):
+     new_data = {}
+     obj = change and self.original_object or None
+     for f in opts.get_data_holders():
+            new_data.update(f.flatten_data(obj))
+     return new_data
+
 def manipulator_validator_unique_together(field_name_list, opts, self, field_data, all_data):
     from django.utils.text import get_text_list
     field_list = [opts.get_field(field_name) for field_name in field_name_list]

File django/core/meta/fields.py

View file
  • Ignore whitespace
 BLANK_CHOICE_NONE = [("", "None")]
 
 # Values for Relation.edit_inline.
-TABULAR, STACKED = 1, 2
+TABULAR, STACKED = "admin_edit_inline_tabular", "admin_edit_inline_stacked"
 
 RECURSIVE_RELATIONSHIP_CONSTANT = 'self'
 
             if hasattr(self.default, '__get_value__'):
                 return self.default.__get_value__()
             return self.default
-        if self.null:
+        if not self.empty_strings_allowed or self.null:
             return None
         return ""
 
         """
         Returns a list of field names that this object adds to the manipulator.
         """
-        return [name_prefix + self.name]
+        return [name_prefix + self.column]
 
     def get_manipulator_fields(self, opts, manipulator, change, name_prefix='', rel=False):
         """
         if self.maxlength and not self.choices: # Don't give SelectFields a maxlength parameter.
             params['maxlength'] = self.maxlength
         if isinstance(self.rel, ManyToOne):
+            params['member_name'] = name_prefix + self.get_db_column()
             if self.rel.raw_id_admin:
                 field_objs = self.get_manipulator_field_objs()
                 params['validator_list'].append(curry(manipulator_valid_rel_key, self, manipulator))
             else:
                 if self.radio_admin:
                     field_objs = [formfields.RadioSelectField]
-                    params['choices'] = self.get_choices(include_blank=self.blank, blank_choice=BLANK_CHOICE_NONE)
                     params['ul_class'] = get_ul_class(self.radio_admin)
                 else:
                     if self.null:
                         field_objs = [formfields.NullSelectField]
                     else:
                         field_objs = [formfields.SelectField]
-                    params['choices'] = self.get_choices()
+                params['choices'] = self.get_choices_default()
         elif self.choices:
             if self.radio_admin:
                 field_objs = [formfields.RadioSelectField]
-                params['choices'] = self.get_choices(include_blank=self.blank, blank_choice=BLANK_CHOICE_NONE)
                 params['ul_class'] = get_ul_class(self.radio_admin)
             else:
                 field_objs = [formfields.SelectField]
-                params['choices'] = self.get_choices()
+             
+            params['choices'] = self.get_choices_default()
         else:
             field_objs = self.get_manipulator_field_objs()
 
                 val = None
             return val
 
+
     def get_choices(self, include_blank=True, blank_choice=BLANK_CHOICE_DASH):
         "Returns a list of tuples used as SelectField choices for this field."
+       
         first_choice = include_blank and blank_choice or []
         if self.choices:
             return first_choice + list(self.choices)
         rel_obj = self.rel.to
-        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)]
+        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)]
+ 
+        return choices
 
+
+    def get_choices_default(self):
+        if(self.radio_admin):
+            return self.get_choices(include_blank=self.blank, blank_choice=BLANK_CHOICE_NONE)
+        else:
+            return self.get_choices()
+
+    def _get_val_from_obj(self, obj):
+        if obj:
+           return getattr(obj, self.column) 
+        else: 
+           return self.get_default()
+
+    def flatten_data(self, obj = None):
+         """
+	     Returns a dictionary mapping the field's manipulator field names to its
+	     "flattened" string values for the admin view. Obj is the instance to extract the 
+             values from. 
+	 """
+	 return { self.get_db_column(): self._get_val_from_obj(obj)}
+    	
+ 
 class AutoField(Field):
     empty_strings_allowed = False
     def __init__(self, *args, **kwargs):
 
     def get_manipulator_fields(self, opts, manipulator, change, name_prefix='', rel=False):
         if not rel:
-            return [] # Don't add a FormField unless it's in a related context.
+            return [] # Don't add a FormField unless it's in a related change context.
         return Field.get_manipulator_fields(self, opts, manipulator, change, name_prefix, rel)
 
     def get_manipulator_field_objs(self):
     def get_manipulator_field_objs(self):
         return [formfields.DateField]
 
+    def flatten_data(self, obj = None):
+    	val = self._get_val_from_obj(obj)
+        return {self.get_db_column(): (val is not None and val.strftime("%Y-%m-%d") or '')}
+
 class DateTimeField(DateField):
     def get_db_prep_save(self, value):
         # Casts dates into string format for entry into database.
             return datetime.datetime.combine(d, t)
         return self.get_default()
 
+    def flatten_data(self,obj = None):
+        val = self._get_val_from_obj(obj) 
+    	date_field, time_field = self.get_manipulator_field_names('')
+	return {date_field: (val is not None and val.strftime("%Y-%m-%d") or ''),
+	        time_field: (val is not None and val.strftime("%H:%M:%S") or '')}
+
 class EmailField(Field):
     def get_manipulator_field_objs(self):
         return [formfields.EmailField]
     def get_manipulator_field_objs(self):
         return [formfields.TimeField]
 
+    def flatten_data(self,obj = None):
+        val = self._get_val_from_obj(obj) 
+    	return {self.get_db_column(): (val is not None and val.strftime("%H:%M:%S") or '')} 
+
 class URLField(Field):
     def __init__(self, verbose_name=None, name=None, verify_exists=True, **kwargs):
         if verify_exists:
     def get_manipulator_field_objs(self):
         return [formfields.IntegerField]
 
+    def flatten_data(self, obj = None):
+        if not obj: 
+            # In required many-to-one fields with only one available choice,
+            # select that one available choice. Note: We have to check that
+            # the length of choices is *2*, not 1, because SelectFields always
+            # have an initial "blank" value.
+            if not self.blank and not self.rel.raw_id_admin and self.choices:
+               choice_list = self.get_choices_default()
+               if len(choice_list) == 2:
+                  return { self.name : choice_list[1][0] }
+        return Field.flatten_data(self, obj)
+
 class ManyToManyField(Field):
     def __init__(self, to, **kwargs):
         kwargs['verbose_name'] = kwargs.get('verbose_name', to._meta.verbose_name_plural)
         if self.rel.raw_id_admin:
             return [formfields.CommaSeparatedIntegerField]
         else:
-            choices = self.get_choices(include_blank=False)
+            choices = self.get_choices_default()
             return [curry(formfields.SelectMultipleField, size=min(max(len(choices), 5), 15), choices=choices)]
 
+    def get_choices_default(self):
+        return Field.get_choices(self, include_blank=False)
+
     def get_m2m_db_table(self, original_opts):
         "Returns the name of the many-to-many 'join' table."
         return '%s_%s' % (original_opts.db_table, self.name)
                 len(badkeys) == 1 and badkeys[0] or tuple(badkeys),
                 len(badkeys) == 1 and "is" or "are")
 
+    def flatten_data(self, obj = None):
+        new_data = {} 
+        if obj:
+            get_list_func = getattr(obj, 'get_%s_list' % self.rel.singular)
+            instance_ids = [getattr(instance, self.rel.to.pk.column) for instance in get_list_func()]
+            if self.rel.raw_id_admin:
+                 new_data[self.name] = ",".join([str(id) for id in instance_ids])
+            elif not self.rel.edit_inline:
+                 new_data[self.name] = instance_ids 
+        else:
+            # In required many-to-many fields with only one available choice,
+            # select that one available choice.
+            if not self.blank and not self.rel.edit_inline and not self.rel.raw_id_admin and self.choices:
+               choice_list = self.get_choices_default()
+               if len(choice_list) == 1:
+                   new_data[self.name] = [choices_list[0][0]]
+        return new_data
+
+
 class OneToOneField(IntegerField):
     def __init__(self, to, to_field=None, **kwargs):
         kwargs['verbose_name'] = kwargs.get('verbose_name', 'ID')
         Returns self.fields, except with fields as Field objects instead of
         field names. If self.fields is None, defaults to putting every
         non-AutoField field with editable=True in a single fieldset.
+        
+        returns a list of lists of name, dict 
+        the dict has attribs 'fields' and maybe 'classes'. 
+        fields is a list of subclasses of Field. 
+
+        Return value needs to be encapsulated.
         """
         if self.fields is None:
             field_struct = ((None, {'fields': [f.name for f in opts.fields + opts.many_to_many if f.editable and not isinstance(f, AutoField)]}),)

File django/core/validators.py

View file
  • Ignore whitespace
 
 _datere = r'\d{4}-((?:0?[1-9])|(?:1[0-2]))-((?:0?[1-9])|(?:[12][0-9])|(?:3[0-1]))'
 _timere = r'(?:[01]?[0-9]|2[0-3]):[0-5][0-9](?::[0-5][0-9])?'
-alnum_re = re.compile(r'^\w+$')
+alnum_re = re.compile(r'^[\w-]+$')
 alnumurl_re = re.compile(r'^[\w/]+$')
 ansi_date_re = re.compile('^%s$' % _datere)
 ansi_time_re = re.compile('^%s$' % _timere)
 
 def isAlphaNumeric(field_data, all_data):
     if not alnum_re.search(field_data):
-        raise ValidationError, "This value must contain only letters, numbers and underscores."
+        raise ValidationError, "This value must contain only letters, numbers, dashes and underscores."
 
 def isAlphaNumericURL(field_data, all_data):
     if not alnumurl_re.search(field_data):

File django/templatetags/admin_modify.py

View file
  • Ignore whitespace
+from django.core import template, template_loader, meta
+from django.conf.settings import ADMIN_MEDIA_PREFIX
+from django.utils.text import capfirst
+from django.utils.html import escape
+from django.utils.functional import curry
+
+from django.views.admin.main import AdminBoundField
+import re
+
+class IncludeAdminScriptNode(template.Node):
+      def __init__(self, var):
+      	 self.var = var
+ 
+      def render(self, context):
+        resolved = template.resolve_variable(self.var, context)
+     	return '<script type="text/javascript" src="%s%s"></script>' % \
+		(ADMIN_MEDIA_PREFIX, resolved)
+          
+class SubmitRowNode(template.Node):
+      def __init__(self):
+          pass
+
+      def render(self, context):
+          change = context['change']
+	  add = context['add']
+	  show_delete = context['show_delete']
+	  ordered_objects = context['ordered_objects']
+ 	  save_as = context['save_as']
+	  has_delete_permission = context['has_delete_permission']
+          is_popup = context['is_popup']
+	  
+	  t = ['<div class="submit-row">']
+	  onclick_attrib = ordered_objects and change and 'onclick="submitOrderForm();"' or '' 
+	  
+	  if not is_popup:
+	  	if has_delete_permission and (change or show_delete):
+	      	   t.append('<p class="float-left"><a href="delete/" class="deletelink">Delete</a></p>')
+	        if change and save_as:
+		   t.append('<input type="submit" value="Save as new" name="_saveasnew" %s/>' %  onclick_attrib)
+                if (not save_as or add):
+		   t.append('<input type="submit" value="Save and add another" name="_addanother" %s/>' %  onclick_attrib)
+	  t.append('<input type="submit" value="Save and continue editing" name="_continue" %s/>' %  onclick_attrib )
+	  t.append('<input type="submit" value="Save" class="default" %s/>' %  onclick_attrib)
+	  t.append('</div>\n')
+	 
+	  return ''.join(t)
+
+
+
+
+class AdminFieldBoundNode(template.Node):
+    def __init__(self, argument):
+        self.argument = argument
+    
+    def render(self, context):
+        argument_val = template.resolve_variable(self.argument, context)
+        if (isinstance(argument_val, list)):
+            bound_fields = argument_val 
+        else:
+            bound_fields = [argument_val]
+        add = context['add']
+        change = context['change']
+        
+        context.push()
+        context['bound_fields'] = bound_fields
+        context['class_names'] = " ".join(self.get_class_names(bound_fields))
+        t = template_loader.get_template("admin_field")
+        output =  t.render(context)
+        context.pop()
+          
+        return output
+
+    def get_class_names(self, bound_fields):
+
+	class_names = ['form-row']
+	for bound_field in bound_fields: 
+            for f in bound_field.form_fields:
+                if f.errors():
+                    class_names.append('errors')
+                    break
+	  
+	# Assumes BooleanFields won't be stacked next to each other!
+	if isinstance(bound_fields[0].field, meta.BooleanField):
+	    class_names.append('checkbox-row')
+	  
+        return class_names
+       
+class FieldWidgetNode(template.Node):
+    def __init__(self, bound_field_var):
+        self.bound_field_var = bound_field_var
+
+    def render(self, context):
+        bound_field = template.resolve_variable(self.bound_field_var, context)
+        add = context['add']
+        change = context['change']
+        
+        context.push()
+        context['bound_field'] = bound_field
+        t = template_loader.get_template("admin_field_widget")
+        output =  t.render(context)
+        context.pop()
+          
+        return output
+
+        
+
+class FieldWrapper(object):
+    def __init__(self, field ):
+        self.field = field
+
+    def needs_header(self):
+        return not isinstance(self.field, meta.AutoField)
+
+    def header_class_attribute(self):
+        return self.field.blank and ' class="optional"' or ''
+
+    def use_raw_id_admin(self):
+         return isinstance(self.field.rel, (meta.ManyToOne, meta.ManyToMany)) \
+                and self.field.rel.raw_id_admin
+
+class FormFieldCollectionWrapper(object):
+    def __init__(self, obj, fields):
+        self.obj = obj
+        self.fields = fields
+        self.bound_fields = [ AdminBoundField(field, obj['original'],  True, self.obj) for field in self.fields ]
+
+    def showurl(self):
+        return False
+
+class EditInlineNode(template.Node):
+    def __init__(self, rel_var):
+        self.rel_var = rel_var
+    
+    def render(self, context):
+        relation = template.resolve_variable(self.rel_var, context)
+        add, change = context['add'], context['change']
+        
+        context.push()
+
+        self.fill_context(relation, add, change, context)
+        
+        t = template_loader.get_template(relation.field.rel.edit_inline)
+        
+        output = t.render(context)
+         
+        context.pop()
+        return output
+
+       
+    def fill_context(self, relation, add, change, context):
+        field_wrapper_list = relation.editable_fields(FieldWrapper)
+
+        var_name = relation.opts.object_name.lower()
+        
+        form = template.resolve_variable('form', context)
+        form_field_collections = form[relation.opts.module_name]
+        fields = relation.editable_fields()
+        form_field_collection_wrapper_list = [FormFieldCollectionWrapper(o,fields) for o in form_field_collections] 
+   
+        context['field_wrapper_list'] = field_wrapper_list
+        context['form_field_collection_wrapper_list'] = form_field_collection_wrapper_list 
+        context['num_headers'] = len(field_wrapper_list)
+        context['original_row_needed'] = max([fw.use_raw_id_admin() for fw in field_wrapper_list]) 
+#        context['name_prefix'] = "%s." % (var_name,)
+   
+class FieldLabelNode(template.Node):
+    def __init__(self, bound_field_var):
+        self.bound_field_var = bound_field_var
+        
+    def render(self, context):
+        bound_field = template.resolve_variable(self.bound_field_var, context)
+        class_names = []
+        if isinstance(bound_field.field, meta.BooleanField):
+            class_names.append("vCheckboxLabel")
+        else:
+            if not bound_field.field.blank:
+                class_names.append('required')
+            if not bound_field.first:
+                class_names.append('inline')
+        
+        class_str = class_names and ' class="%s"' % ' '.join(class_names) or ''
+        return '<label for="%s"%s>%s:</label> ' % (bound_field.element_id, class_str, capfirst(bound_field.field.verbose_name) )
+
+class OutputAllNode(template.Node):
+    def __init__(self, form_fields_var):
+        self.form_fields_var = form_fields_var
+    
+    def render(self, context):
+        form_fields = template.resolve_variable(self.form_fields_var, context)
+        return ''.join([str(f) for f in form_fields])
+
+class AutoPopulatedFieldScriptNode(template.Node):
+    def __init__(self, auto_pop_var):
+        self.auto_pop_var = auto_pop_var
+
+    def render(self,context):
+        auto_pop_fields = template.resolve_variable(self.auto_pop_var, context)
+        change = context['change']
+        for field in auto_pop_fields:
+            t = []
+            if change:
+                t.append('document.getElementById("id_%s")._changed = true;' % field.name )
+            else: 
+                t.append('document.getElementById("id_%s").onchange = function() { this._changed = true; };' % field.name)
+
+            add_values = ' + " " + '.join(['document.getElementById("id_%s").value' % g for g in field.prepopulate_from])
+            for f in field.prepopulate_from:
+                t.append('document.getElementById("id_%s").onkeyup = function() { var e = document.getElementById("id_%s"); if(e._changed) { e.value = URLify(%s, %s);} } ' % (f, field.name, add_values, field.maxlength) )
+
+        return ''.join(t)
+
+class FilterInterfaceScriptMaybeNode(template.Node):
+    def __init__(self, bound_field_var):
+       self.bound_field_var = bound_field_var
+
+    def render(self, context):
+        bound_field = template.resolve_variable(self.bound_field_var, context)
+        f = bound_field.field 
+        if f.rel and isinstance(f.rel, meta.ManyToMany) and f.rel.filter_interface:
+           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) 
+        else: 
+            return ''
+
+     
+
+
+def do_submit_row(parser, token):
+    return SubmitRowNode()
+
+
+def do_one_arg_tag(node_factory, parser,token):
+    tokens = token.contents.split()
+    if len(tokens) != 2:
+        raise template.TemplateSyntaxError("%s takes 1 argument" % tokens[0])
+    return node_factory(tokens[1]) 
+
+
+one_arg_tag_nodes = [
+    IncludeAdminScriptNode,
+    AdminFieldBoundNode,
+    FieldLabelNode,
+    FieldWidgetNode, 
+    OutputAllNode,
+    EditInlineNode, 
+    AutoPopulatedFieldScriptNode,
+    FilterInterfaceScriptMaybeNode,
+]
+
+word = re.compile('[A-Z][a-z]+')
+def register_one_arg_tag(node):
+    tag_name = '_'.join([ s.lower() for s in word.findall(node.__name__)[:-1] ])
+    parse_func = curry(do_one_arg_tag, node)
+    template.register_tag(tag_name, parse_func)
+
+ 
+
+for node in one_arg_tag_nodes:
+    register_one_arg_tag(node)    
+
+template.register_tag('submit_row', do_submit_row )

File django/views/admin/main.py

View file
  • Ignore whitespace
 # Generic admin views, with admin templates created dynamically at runtime.
 
-from django.core import formfields, meta, template_loader
+from django.core import formfields, meta, template_loader, template
 from django.core.exceptions import Http404, ObjectDoesNotExist, PermissionDenied
 from django.core.extensions import DjangoContext as Context
 from django.core.extensions import get_object_or_404, render_to_response
     })
     return HttpResponse(t.render(c))
 
-def _get_flattened_data(field, val):
-    """
-    Returns a dictionary mapping the field's manipulator field names to its
-    "flattened" string values for the admin view. "val" is an instance of the
-    field's value.
-    """
-    if isinstance(field, meta.DateTimeField):
-        date_field, time_field = field.get_manipulator_field_names('')
-        return {date_field: (val is not None and val.strftime("%Y-%m-%d") or ''),
-                time_field: (val is not None and val.strftime("%H:%M:%S") or '')}
-    elif isinstance(field, meta.DateField):
-        return {field.name: (val is not None and val.strftime("%Y-%m-%d") or '')}
-    elif isinstance(field, meta.TimeField):
-        return {field.name: (val is not None and val.strftime("%H:%M:%S") or '')}
-    else:
-        return {field.name: val}
-
 use_raw_id_admin = lambda field: isinstance(field.rel, (meta.ManyToOne, meta.ManyToMany)) and field.rel.raw_id_admin
 
 def _get_submit_row_template(opts, app_label, add, change, show_delete, ordered_objects):
     t.append('</div>\n')
     return t
 
+def get_javascript_imports(opts,auto_populated_fields, ordered_objects, admin_field_objs):
+# Put in any necessary JavaScript imports.
+    js = ['js/core.js', 'js/admin/RelatedObjectLookups.js']
+    if auto_populated_fields:
+        js.append('js/urlify.js')
+    if opts.has_field_type(meta.DateTimeField) or opts.has_field_type(meta.TimeField) or opts.has_field_type(meta.DateField):
+        js.extend(['js/calendar.js', 'js/admin/DateTimeShortcuts.js'])
+    if ordered_objects:
+        js.extend(['js/getElementsBySelector.js', 'js/dom-drag.js' , 'js/admin/ordering.js'])
+    if opts.admin.js:
+        js.extend(opts.admin.js)
+    seen_collapse = False
+    for _, options in admin_field_objs:
+        if not seen_collapse and 'collapse' in options.get('classes', ''):
+            seen_collapse = True
+            js.append('js/admin/CollapsedFieldsets.js' )
+        try:
+            for field_list in options['fields']:
+                for f in field_list:
+                    if f.rel and isinstance(f, meta.ManyToManyField) and f.rel.filter_interface:
+                        js.extend(['js/SelectBox.js' , 'js/SelectFilter2.js'])
+                        raise StopIteration
+        except StopIteration:
+            break
+    return js
+
+class BoundField(object):
+    def __init__(self, field, original, rel, field_mapping):
+        self.field = field
+        self.form_fields = self.resolve_form_fields(field_mapping)
+        self.original = original
+        self.rel = rel
+
+    def resolve_form_fields(self, field_mapping):
+        return [field_mapping[name] for name in self.field.get_manipulator_field_names('')]
+
+    def as_field_list(self):
+        return [self.field]
+
+    def original_value(self):
+        return self.original.__dict__[self.field.name] 
+ 
+class AdminBoundField(BoundField):
+    def __init__(self, field, original, rel, field_mapping):
+        super(AdminBoundField, self).__init__(field,original, rel, field_mapping) 	
+
+        self.element_id = self.form_fields[0].get_id() 
+        self.has_label_first = not isinstance(self.field, meta.BooleanField)
+        self.raw_id_admin = use_raw_id_admin(field)
+        self.is_date_time = isinstance(field, meta.DateTimeField)
+        self.is_file_field = isinstance(field, meta.FileField)
+        self.needs_add_label = field.rel and isinstance(field.rel, meta.ManyToOne) or isinstance(field.rel, meta.ManyToMany) and field.rel.to.admin
+        self.not_in_table = isinstance(self.field, meta.AutoField)
+        self.first = True
+        
+        classes = []
+        if(self.raw_id_admin): 
+            classes.append('nowrap')
+        if max([bool(f.errors()) for f in self.form_fields]):
+            classes.append('error')
+        self.cell_class_attribute = ' '.join(classes)   
+        self._repr_filled = False
+    
+
+       
+    def _fetch_existing_repr(self, func_name):
+        class_dict = self.original.__class__.__dict__
+        func = class_dict.get(func_name)
+        return func(self.original)
+        
+    def _fill_existing_repr(self):
+        if self._repr_filled: 
+            return
+        #HACK
+        if isinstance(self.field.rel, meta.ManyToOne):
+             func_name = 'get_%s' % self.field.name
+             self._repr = self._fetch_existing_repr(func_name)
+	elif isinstance(self.field.rel, meta.ManyToMany):
+	     func_name = 'get_%s_list' % self.field.name 
+             self._repr =  ",".join(self._fetch_existing_repr(func_name))
+        self._repr_filled = True
+             
+    def existing_repr(self):
+        self._fill_existing_repr()
+        return self._repr
+
+    def __repr__(self):
+        return repr(self.__dict__)
+
+    def html_error_list(self):
+        return " ".join([form_field.html_error_list() for form_field in self.form_fields if form_field.errors])        
+
+
+class AdminFieldSet(object):
+    def __init__(self, fieldset_name, options, form, original):
+         self.name = fieldset_name
+	 self.options = options
+         self.bound_field_sets = self.get_bound_field_sets(form, original)
+         self.classes = options.get('classes', '')
+     
+    def __repr__(self):
+     	return "Fieldset:(%s,%s)" % (self.name, self.bound_field_sets)
+
+    def get_bound_field_sets(self, form, original):
+        fields = self.options['fields']
+        bound_field_sets = [ [AdminBoundField(f, original, False, form) for f in field  ] for field in fields]
+        for set in bound_field_sets:
+            first = True 
+            for bound_field in set:
+                bound_field.first = first
+                first = False
+
+        return bound_field_sets
+
+def fill_extra_context(opts, app_label, context, add=False, change=False, show_delete=False, form_url=''):
+    admin_field_objs = opts.admin.get_field_objs(opts)
+    ordered_objects = opts.get_ordered_objects()[:]
+    auto_populated_fields = [f for f in opts.fields if f.prepopulate_from]
+ 
+    javascript_imports = get_javascript_imports(opts,auto_populated_fields, ordered_objects, admin_field_objs);
+    
+    if ordered_objects:
+        coltype = 'colMS'
+    else:
+        coltype = 'colM'
+	
+    has_absolute_url = hasattr(opts.get_model_module().Klass, 'get_absolute_url')
+    
+    form_enc_attrib = opts.has_field_type(meta.FileField) and 'enctype="multipart/form-data" ' or ''
+
+    form = context['form']
+    original = context['original']
+    admin_fieldsets = [AdminFieldSet(name, options, form, original) for name, options in admin_field_objs] 
+    inline_related_objects = opts.get_inline_related_objects_wrapped()
+    
+    ordered_object_names =   ' '.join(['object.%s' % o.pk.name for o in ordered_objects])
+   
+    extra_context = {
+        'add': add, 
+        'change': change, 
+        'admin_field_objs' : admin_field_objs, 
+        'ordered_objects' : ordered_objects, 
+        'auto_populated_fields' : auto_populated_fields,
+        'javascript_imports' : javascript_imports, 
+        'coltype' : coltype, 
+        'has_absolute_url': has_absolute_url, 
+        'form_enc_attrib': form_enc_attrib,
+        'form_url' : form_url, 
+        'admin_fieldsets' : admin_fieldsets, 
+        'inline_related_objects': inline_related_objects,
+        'ordered_object_names' : ordered_object_names, 
+        'content_type_id' : opts.get_content_type_id(),
+        'save_on_top' : opts.admin.save_on_top,
+        'verbose_name_plural': opts.verbose_name_plural, 
+        'save_as': opts.admin.save_as, 
+        'app_label': app_label,
+        'object_name': opts.object_name,
+        'has_delete_permission' : context['perms'][app_label][opts.get_delete_permission()]
+    }
+    
+    context.update(extra_context)   
+   
+   
+def add_stage_new(request, app_label, module_name, show_delete=False, form_url='', post_url='../', post_url_continue='../%s/', object_id_override=None):
+    mod, opts = _get_mod_opts(app_label, module_name)
+    if not request.user.has_perm(app_label + '.' + opts.get_add_permission()):
+        raise PermissionDenied
+    manipulator = mod.AddManipulator()
+    if request.POST:
+        new_data = request.POST.copy()
+        if opts.has_field_type(meta.FileField):
+            new_data.update(request.FILES)
+        errors = manipulator.get_validation_errors(new_data)
+        if not errors and not request.POST.has_key("_preview"):
+            for f in opts.many_to_many:
+                if f.rel.raw_id_admin:
+                    new_data.setlist(f.name, new_data[f.name].split(","))
+            manipulator.do_html2python(new_data)
+            new_object = manipulator.save(new_data)
+            pk_value = getattr(new_object, opts.pk.column)
+            log.log_action(request.user.id, opts.get_content_type_id(), pk_value, repr(new_object), log.ADDITION)
+            msg = 'The %s "%s" was added successfully.' % (opts.verbose_name, new_object)
+            # Here, we distinguish between different save types by checking for
+            # the presence of keys in request.POST.
+            if request.POST.has_key("_continue"):
+                request.user.add_message("%s You may edit it again below." % msg)
+                if request.POST.has_key("_popup"):
+                    post_url_continue += "?_popup=1"
+                return HttpResponseRedirect(post_url_continue % pk_value)
+            if request.POST.has_key("_popup"):
+                return HttpResponse('<script type="text/javascript">opener.dismissAddAnotherPopup(window, %s, "%s");</script>' % \
+                    (pk_value, repr(new_object).replace('"', '\\"')))
+            elif request.POST.has_key("_addanother"):
+                request.user.add_message("%s You may add another %s below." % (msg, opts.verbose_name))
+                return HttpResponseRedirect(request.path)
+            else:
+                request.user.add_message(msg)
+                return HttpResponseRedirect(post_url)
+        if request.POST.has_key("_preview"):
+            manipulator.do_html2python(new_data)
+    else:
+        # Add default data.
+        new_data = manipulator.flatten_data()
+        
+        # Override the defaults with request.GET, if it exists.
+        new_data.update(request.GET)
+        errors = {}
+
+    # Populate the FormWrapper.
+    form = formfields.FormWrapper(manipulator, new_data, errors, edit_inline=True)
+    
+    c = Context(request, {
+        'title': 'Add %s' % opts.verbose_name,
+        'form': form,
+        'is_popup': request.REQUEST.has_key('_popup'),
+    })
+    if object_id_override is not None:
+        c['object_id'] = object_id_override
+    
+    
+    fill_extra_context(opts, app_label, c, change=True)
+   
+    return render_to_response("admin_change_form", context_instance=c) 
+
+
+
+def change_stage_new(request, app_label, module_name, object_id):
+    mod, opts = _get_mod_opts(app_label, module_name)
+    if not request.user.has_perm(app_label + '.' + opts.get_change_permission()):
+        raise PermissionDenied
+    if request.POST and request.POST.has_key("_saveasnew"):
+        return add_stage_new(request, app_label, module_name, form_url='../add/')
+    try:
+        manipulator = mod.ChangeManipulator(object_id)
+    except ObjectDoesNotExist:
+        raise Http404
+
+    inline_related_objects = opts.get_inline_related_objects()
+    if request.POST:
+        new_data = request.POST.copy()
+        if opts.has_field_type(meta.FileField):
+            new_data.update(request.FILES)
+
+        errors = manipulator.get_validation_errors(new_data)
+        if not errors and not request.POST.has_key("_preview"):
+            for f in opts.many_to_many:
+                if f.rel.raw_id_admin:
+                    new_data.setlist(f.name, new_data[f.name].split(","))
+            manipulator.do_html2python(new_data)
+            new_object = manipulator.save(new_data)
+            pk_value = getattr(new_object, opts.pk.column)
+
+            # Construct the change message.
+            change_message = []
+            if manipulator.fields_added:
+                change_message.append('Added %s.' % get_text_list(manipulator.fields_added, 'and'))
+            if manipulator.fields_changed:
+                change_message.append('Changed %s.' % get_text_list(manipulator.fields_changed, 'and'))
+            if manipulator.fields_deleted:
+                change_message.append('Deleted %s.' % get_text_list(manipulator.fields_deleted, 'and'))
+            change_message = ' '.join(change_message)
+            if not change_message:
+                change_message = 'No fields changed.'
+
+            log.log_action(request.user.id, opts.get_content_type_id(), pk_value, repr(new_object), log.CHANGE, change_message)
+            msg = 'The %s "%s" was changed successfully.' % (opts.verbose_name, new_object)
+            if request.POST.has_key("_continue"):
+                request.user.add_message("%s You may edit it again below." % msg)
+                if request.REQUEST.has_key('_popup'):
+                    return HttpResponseRedirect(request.path + "?_popup=1")
+                else:
+                    return HttpResponseRedirect(request.path)
+            elif request.POST.has_key("_saveasnew"):
+                request.user.add_message('The %s "%s" was added successfully. You may edit it again below.' % (opts.verbose_name, new_object))
+                return HttpResponseRedirect("../%s/" % pk_value)
+            elif request.POST.has_key("_addanother"):
+                request.user.add_message("%s You may add another %s below." % (msg, opts.verbose_name))
+                return HttpResponseRedirect("../add/")
+            else:
+                request.user.add_message(msg)
+                return HttpResponseRedirect("../")
+        if request.POST.has_key("_preview"):
+            manipulator.do_html2python(new_data)
+    else:
+        # Populate new_data with a "flattened" version of the current data.
+        new_data = manipulator.flatten_data()
+       
+ 
+        # If the object has ordered objects on its admin page, get the existing
+        # order and flatten it into a comma-separated list of IDs.
+        id_order_list = []
+        for rel_obj in opts.get_ordered_objects():
+            id_order_list.extend(getattr(obj, 'get_%s_order' % rel_obj.object_name.lower())())
+        if id_order_list:
+            new_data['order_'] = ','.join(map(str, id_order_list))
+        errors = {}
+
+    # Populate the FormWrapper.
+    form = formfields.FormWrapper(manipulator, new_data, errors, edit_inline = True)
+    form.original = manipulator.original_object
+    form.order_objects = []
+    
+    for rel_opts, rel_field in inline_related_objects:
+        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:
+            form.order_objects.extend(orig_list)
+
+    c = Context(request, {
+        'title': 'Change %s' % opts.verbose_name,
+        'form': form,
+        'object_id': object_id,
+        'original': manipulator.original_object,
+        'is_popup' : request.REQUEST.has_key('_popup')
+    })
+
+    fill_extra_context(opts, app_label, c, change=True)
+    
+    #t = template_loader.get_template_from_string(raw_template)
+    
+    return render_to_response('admin_change_form', context_instance=c);
+
+
 def _get_template(opts, app_label, add=False, change=False, show_delete=False, form_url=''):
     admin_field_objs = opts.admin.get_field_objs(opts)
     ordered_objects = opts.get_ordered_objects()[:]
     for rel_obj, rel_field in opts.get_inline_related_objects():
         var_name = rel_obj.object_name.lower()
         field_list = [f for f in rel_obj.fields + rel_obj.many_to_many if f.editable and f != rel_field]
+
         t.append('<fieldset class="module%s">\n' % ((rel_field.rel.edit_inline != meta.TABULAR) and ' aligned' or ''))
         view_on_site = ''
         if change and hasattr(rel_obj, 'get_absolute_url'):
             else:
                 t.append('document.getElementById("id_%s").onchange = function() { this._changed = true; };' % field.name)
             for f in field.prepopulate_from:
-                t.append('document.getElementById("id_%s").onkeyup = function() { var e = document.getElementById("id_%s"); if (!e._changed) { e.value = URLify(%s, %s);}};' % \
+                t.append('document.getElementById("id_%s").onkeyup = function() { var e = document.getElementById("id_%s"); if (e._changed) { e.value = URLify(%s, %s);}};' % \
                     (f, field.name, ' + " " + '.join(['document.getElementById("id_%s").value' % g for g in field.prepopulate_from]), field.maxlength))
         t.append('</script>\n')
     if change and ordered_objects:
         # Add default data.
         for f in opts.fields:
             if f.has_default():
-                new_data.update(_get_flattened_data(f, f.get_default()))
+                new_data.update( f.flatten_data() )
             # In required many-to-one fields with only one available choice,
             # select that one available choice. Note: We have to check that
             # the length of choices is *2*, not 1, because SelectFields always
                 if f.editable and f != rel_field and not isinstance(f, meta.AutoField):
                     for field_name in f.get_manipulator_field_names(''):
                         full_field_name = '%s.%d.%s' % (var_name, i, field_name)
-                        collection[field_name] = formfields.FormFieldWrapper(manipulator[full_field_name], new_data.get(full_field_name, ''), errors.get(full_field_name, []))
+			field = manipulator[full_field_name]
+			data = field.extract_data(new_data)
+                        collection[field_name] = formfields.FormFieldWrapper(field, data, errors.get(full_field_name, []))
             wrapper.append(formfields.FormFieldCollection(collection))
         setattr(form, rel_opts.module_name, wrapper)
 
     if object_id_override is not None:
         c['object_id'] = object_id_override
     raw_template = _get_template(opts, app_label, add=True, show_delete=show_delete, form_url=form_url)
-#     return HttpResponse(raw_template, mimetype='text/plain')
     t = template_loader.get_template_from_string(raw_template)
     return HttpResponse(t.render(c))
 
         new_data = {}
         obj = manipulator.original_object
         for f in opts.fields:
-            new_data.update(_get_flattened_data(f, getattr(obj, f.column)))
+            new_data.update(f.flatten_data(obj))
         for f in opts.many_to_many:
             get_list_func = getattr(obj, 'get_%s_list' % f.rel.singular)
             if f.rel.raw_id_admin:
             for i, rel_instance in enumerate(getattr(obj, 'get_%s_list' % opts.get_rel_object_method_name(rel_obj, rel_field))()):
                 for f in rel_obj.fields:
                     if f.editable and f != rel_field:
-                        for k, v in _get_flattened_data(f, getattr(rel_instance, f.column)).items():
+                        for k, v in f.flatten_data(rel_instance).items():
                             new_data['%s.%d.%s' % (var_name, i, k)] = v
                 for f in rel_obj.many_to_many:
                     new_data['%s.%d.%s' % (var_name, i, f.column)] = [j.id for j in getattr(rel_instance, 'get_%s_list' % f.rel.singular)()]
                 if f.editable and f != rel_field:
                     for field_name in f.get_manipulator_field_names(''):
                         full_field_name = '%s.%d.%s' % (var_name, i, field_name)
-                        collection[field_name] = formfields.FormFieldWrapper(manipulator[full_field_name], new_data.get(full_field_name, f.get_default()), errors.get(full_field_name, []))
+			field = manipulator[full_field_name]
+			data = field.extract_data(new_data)
+                        collection[field_name] = formfields.FormFieldWrapper(field, data, errors.get(full_field_name, []))
             wrapper.append(formfields.FormFieldCollection(collection))
         setattr(form, rel_opts.module_name, wrapper)
         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:

File tests/runtests.py

View file
  • Ignore whitespace
             self.output(1, "Creating test database")
             try:
                 cursor.execute("CREATE DATABASE %s" % TEST_DATABASE_NAME)
-            except:
+            except Exception, e:
+                self.output(0, "There was an error creating the test database:%s " % str(e))
                 confirm = raw_input("The test database, %s, already exists. Type 'yes' to delete it, or 'no' to cancel: " % TEST_DATABASE_NAME)
                 if confirm == 'yes':
                     cursor.execute("DROP DATABASE %s" % TEST_DATABASE_NAME)