1
0
mirror of https://github.com/django/django.git synced 2025-07-05 18:29:11 +00:00

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.


git-svn-id: http://code.djangoproject.com/svn/django/branches/new-admin@740 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Robert Wittams 2005-09-30 12:16:43 +00:00
parent 404147c385
commit d0ba57ee90
15 changed files with 1209 additions and 98 deletions

View File

@ -10,7 +10,7 @@ function URLify(s, num_chars) {
s = s.replace(r, ''); s = s.replace(r, '');
s = s.replace(/[^\w\s]/g, ''); // remove unneeded chars s = s.replace(/[^\w\s]/g, ''); // remove unneeded chars
s = s.replace(/^\s+|\s+$/g, ''); // trim leading/trailing spaces 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 s = s.toLowerCase(); // convert to lowercase
return s.substring(0, num_chars);// trim to first num_chars chars return s.substring(0, num_chars);// trim to first num_chars chars
} }

View File

@ -0,0 +1,107 @@
{% 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 %}

View File

@ -0,0 +1,16 @@
<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>

View File

@ -0,0 +1,44 @@
<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>

View File

@ -0,0 +1,36 @@
<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>

View File

@ -0,0 +1,27 @@
{% 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 %}

View File

@ -48,11 +48,13 @@ if 'ellington.media' in INSTALLED_APPS:
urlpatterns += ( urlpatterns += (
# Metasystem admin pages # 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>[^/]+)/$', '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>[^/]+)/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>.+)/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>.+)/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) urlpatterns = patterns('', *urlpatterns)

View File

@ -2,6 +2,7 @@
import sys import sys
import template import template
import template_loader
class CommentNode(template.Node): class CommentNode(template.Node):
def render(self, context): def render(self, context):
@ -223,6 +224,19 @@ class SsiNode(template.Node):
return '' # Fail silently for invalid included templates. return '' # Fail silently for invalid included templates.
return output 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): class LoadNode(template.Node):
def __init__(self, taglib): def __init__(self, taglib):
self.taglib = taglib self.taglib = taglib
@ -600,6 +614,16 @@ def do_ssi(parser, token):
raise template.TemplateSyntaxError, "Second (optional) argument to %s tag must be 'parsed'" % bits[0] raise template.TemplateSyntaxError, "Second (optional) argument to %s tag must be 'parsed'" % bits[0]
return SsiNode(bits[1], parsed) 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): def do_load(parser, token):
""" """
Load a custom template tag set. Load a custom template tag set.
@ -755,6 +779,7 @@ template.register_tag('ifequal', lambda parser, token: do_ifequal(parser, token,
template.register_tag('ifnotequal', lambda parser, token: do_ifequal(parser, token, True)) template.register_tag('ifnotequal', lambda parser, token: do_ifequal(parser, token, True))
template.register_tag('if', do_if) template.register_tag('if', do_if)
template.register_tag('ifchanged', do_ifchanged) template.register_tag('ifchanged', do_ifchanged)
template.register_tag('include', do_include)
template.register_tag('regroup', do_regroup) template.register_tag('regroup', do_regroup)
template.register_tag('ssi', do_ssi) template.register_tag('ssi', do_ssi)
template.register_tag('load', do_load) template.register_tag('load', do_load)

View File

@ -22,7 +22,7 @@ class Manipulator:
for field in self.fields: for field in self.fields:
if field.field_name == field_name: if field.field_name == field_name:
return field 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): def __delitem__(self, field_name):
"Deletes the field with the given field name; raises KeyError on failure" "Deletes the field with the given field name; raises KeyError on failure"
@ -87,16 +87,12 @@ class Manipulator:
must happen after validation because html2python functions aren't must happen after validation because html2python functions aren't
expected to deal with invalid input. expected to deal with invalid input.
""" """
"""
for field in self.fields: 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)]) for field in self.fields:
else: field.convert_post_data(new_data)
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, [])
class FormWrapper: class FormWrapper:
""" """
@ -104,25 +100,40 @@ class FormWrapper:
This allows dictionary-style lookups of formfields. It also handles feeding This allows dictionary-style lookups of formfields. It also handles feeding
prepopulated data and validation error messages to the formfield objects. 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.manipulator, self.data = manipulator, data
self.error_dict = error_dict self.error_dict = error_dict
self._inline_collections = None
self.edit_inline = edit_inline
def __repr__(self): def __repr__(self):
return repr(self.data) return repr(self.__dict__)
def __getitem__(self, key): def __getitem__(self, key):
for field in self.manipulator.fields: for field in self.manipulator.fields:
if field.field_name == key: if field.field_name == key:
if hasattr(field, 'requires_data_list') and hasattr(self.data, 'getlist'): data = field.extract_data(self.data)
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, [])) 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 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): def has_errors(self):
return self.error_dict != {} return self.error_dict != {}
@ -136,6 +147,7 @@ class FormFieldWrapper:
"Renders the field" "Renders the field"
return str(self.formfield.render(self.data)) return str(self.formfield.render(self.data))
def __repr__(self): def __repr__(self):
return '<FormFieldWrapper for "%s">' % self.formfield.field_name return '<FormFieldWrapper for "%s">' % self.formfield.field_name
@ -155,6 +167,9 @@ class FormFieldWrapper:
else: else:
return '' return ''
def get_id(self):
return self.formfield.get_id()
class FormFieldCollection(FormFieldWrapper): class FormFieldCollection(FormFieldWrapper):
"A utility class that gives the template access to a dict of FormFieldWrappers" "A utility class that gives the template access to a dict of FormFieldWrappers"
def __init__(self, formfield_dict): def __init__(self, formfield_dict):
@ -174,9 +189,67 @@ class FormFieldCollection(FormFieldWrapper):
"Returns list of all errors in this collection's formfields" "Returns list of all errors in this collection's formfields"
errors = [] errors = []
for field in self.formfield_dict.values(): for field in self.formfield_dict.values():
if(hasattr(field, 'errors') ):
errors.extend(field.errors()) errors.extend(field.errors())
return 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: class FormField:
"""Abstract class representing a form field. """Abstract class representing a form field.
@ -209,6 +282,39 @@ class FormField:
def render(self, data): def render(self, data):
raise NotImplementedError 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 # # GENERIC WIDGETS #
#################### ####################
@ -237,7 +343,7 @@ class TextField(FormField):
if isinstance(data, unicode): if isinstance(data, unicode):
data = data.encode('utf-8') data = data.encode('utf-8')
return '<input type="text" id="%s" class="v%s%s" name="%s" size="%s" value="%s" %s/>' % \ 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) self.field_name, self.length, escape(data), maxlength)
def html2python(data): def html2python(data):
@ -248,7 +354,7 @@ class PasswordField(TextField):
def render(self, data): def render(self, data):
# value is always blank because we never want to redisplay it # value is always blank because we never want to redisplay it
return '<input type="password" id="%s" class="v%s%s" name="%s" value="" />' % \ 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) self.field_name)
class LargeTextField(TextField): class LargeTextField(TextField):
@ -266,7 +372,7 @@ class LargeTextField(TextField):
if isinstance(data, unicode): if isinstance(data, unicode):
data = data.encode('utf-8') data = data.encode('utf-8')
return '<textarea id="%s" class="v%s%s" name="%s" rows="%s" cols="%s">%s</textarea>' % \ 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)) self.field_name, self.rows, self.cols, escape(data))
class HiddenField(FormField): class HiddenField(FormField):
@ -276,7 +382,7 @@ class HiddenField(FormField):
def render(self, data): def render(self, data):
return '<input type="hidden" id="%s" name="%s" value="%s" />' % \ 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): class CheckboxField(FormField):
def __init__(self, field_name, checked_by_default=False): def __init__(self, field_name, checked_by_default=False):
@ -289,7 +395,7 @@ class CheckboxField(FormField):
if data or (data is '' and self.checked_by_default): if data or (data is '' and self.checked_by_default):
checked_html = ' checked="checked"' checked_html = ' checked="checked"'
return '<input type="checkbox" id="%s" class="v%s" name="%s"%s />' % \ 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) self.field_name, checked_html)
def html2python(data): def html2python(data):
@ -299,18 +405,21 @@ class CheckboxField(FormField):
return False return False
html2python = staticmethod(html2python) html2python = staticmethod(html2python)
class SelectField(FormField): 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 self.field_name = field_name
# choices is a list of (value, human-readable key) tuples because order matters # 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.choices, self.size, self.is_required = choices, size, is_required
self.validator_list = [self.isValidChoice] + validator_list self.validator_list = [self.isValidChoice] + validator_list
if member_name != None:
self.member_name = member_name
def render(self, data): def render(self, data):
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 str_data = str(data) # normalize to string
output = ['<select id="%s" class="v%s%s" name="%s" size="%s">' % \
(self.get_id(), self.__class__.__name__,
self.is_required and ' required' or '', self.field_name, self.size)]
for value, display_name in self.choices: for value, display_name in self.choices:
selected_html = '' selected_html = ''
if str(value) == str_data: if str(value) == str_data:
@ -334,12 +443,14 @@ class NullSelectField(SelectField):
html2python = staticmethod(html2python) html2python = staticmethod(html2python)
class RadioSelectField(FormField): 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 self.field_name = field_name
# choices is a list of (value, human-readable key) tuples because order matters # choices is a list of (value, human-readable key) tuples because order matters
self.choices, self.is_required = choices, is_required self.choices, self.is_required = choices, is_required
self.validator_list = [self.isValidChoice] + validator_list self.validator_list = [self.isValidChoice] + validator_list
self.ul_class = ul_class self.ul_class = ul_class
if member_name != None:
self.member_name = member_name
def render(self, data): def render(self, data):
""" """
@ -382,9 +493,9 @@ class RadioSelectField(FormField):
'value': value, 'value': value,
'name': display_name, 'name': display_name,
'field': '<input type="radio" id="%s" name="%s" value="%s"%s/>' % \ '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>' % \ '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) return RadioFieldRenderer(datalist, self.ul_class)
@ -414,7 +525,7 @@ class SelectMultipleField(SelectField):
requires_data_list = True requires_data_list = True
def render(self, data): def render(self, data):
output = ['<select id="%s" class="v%s%s" name="%s" size="%s" multiple="multiple">' % \ 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)] self.field_name, self.size)]
str_data_list = map(str, data) # normalize to strings str_data_list = map(str, data) # normalize to strings
for value, choice in self.choices: for value, choice in self.choices:
@ -469,9 +580,9 @@ class CheckboxSelectMultipleField(SelectMultipleField):
if str(value) in str_data_list: if str(value) in str_data_list:
checked_html = ' checked="checked"' checked_html = ' checked="checked"'
field_name = '%s%s' % (self.field_name, value) 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>' % \ output.append('<li><input type="checkbox" id="%s" class="v%s" name="%s"%s /> <label for="%s">%s</label></li>' % \
(FORM_FIELD_ID_PREFIX, field_name, self.__class__.__name__, field_name, checked_html, (get_id() + value , self.__class__.__name__, field_name, checked_html,
FORM_FIELD_ID_PREFIX, field_name, choice)) get_id() + value, choice))
output.append('</ul>') output.append('</ul>')
return '\n'.join(output) return '\n'.join(output)
@ -490,7 +601,7 @@ class FileUploadField(FormField):
def render(self, data): def render(self, data):
return '<input type="file" id="%s" class="v%s" name="%s" />' % \ 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) self.field_name)
def html2python(data): def html2python(data):

View File

@ -146,6 +146,70 @@ class FieldDoesNotExist(Exception):
class BadKeywordArguments(Exception): class BadKeywordArguments(Exception):
pass 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: class Options:
def __init__(self, module_name='', verbose_name='', verbose_name_plural='', db_table='', 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, fields=None, ordering=None, unique_together=None, admin=None, has_related_links=False,
@ -317,6 +381,12 @@ class Options:
def get_inline_related_objects(self): def get_inline_related_objects(self):
return [(a, b) for a, b in self.get_all_related_objects() if b.rel.edit_inline] 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): def get_all_related_many_to_many_objects(self):
module_list = get_installed_model_modules() module_list = get_installed_model_modules()
rel_objs = [] rel_objs = []
@ -594,6 +664,7 @@ class ModelBase(type):
new_mod.get_latest = curry(function_get_latest, opts, new_class, does_not_exist_exception) new_mod.get_latest = curry(function_get_latest, opts, new_class, does_not_exist_exception)
for f in opts.fields: 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: if f.choices:
# Add "get_thingie_display" method to get human-readable value. # Add "get_thingie_display" method to get human-readable value.
func = curry(method_get_display_value, f) func = curry(method_get_display_value, f)
@ -788,6 +859,12 @@ def method_save(opts, self):
# If it does already exist, do an UPDATE. # If it does already exist, do an UPDATE.
if cursor.fetchone(): if cursor.fetchone():
db_values = [f.get_db_prep_save(f.pre_save(getattr(self, f.column), False)) for f in non_pks] db_values = [f.get_db_prep_save(f.pre_save(getattr(self, f.column), False)) for f in non_pks]
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, 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), ','.join(['%s=%%s' % f.column for f in non_pks]), opts.pk.column),
db_values + [pk_val]) db_values + [pk_val])
@ -1332,16 +1409,21 @@ def function_get_sql_clause(opts, **kwargs):
if f == '?': # Special case. if f == '?': # Special case.
order_by.append(db.get_random_function_sql()) order_by.append(db.get_random_function_sql())
else: 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, # Use the database table as a column prefix if it wasn't given,
# and if the requested column isn't a custom SELECT. # 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 + '.' table_prefix = opts.db_table + '.'
else: else:
table_prefix = '' table_prefix = ''
if f.startswith('-'):
order_by.append('%s%s DESC' % (table_prefix, orderfield2column(f[1:], opts))) order_by.append('%s%s %s' % (table_prefix, orderfield2column(col_name, opts), order))
else:
order_by.append('%s%s ASC' % (table_prefix, orderfield2column(f, opts)))
order_by = ", ".join(order_by) order_by = ", ".join(order_by)
# LIMIT and OFFSET clauses # LIMIT and OFFSET clauses
@ -1397,6 +1479,8 @@ def get_manipulator(opts, klass, extra_methods, add=False, change=False):
man.__module__ = MODEL_PREFIX + '.' + opts.module_name # Set this explicitly, as above. man.__module__ = MODEL_PREFIX + '.' + opts.module_name # Set this explicitly, as above.
man.__init__ = curry(manipulator_init, opts, add, change) man.__init__ = curry(manipulator_init, opts, add, change)
man.save = curry(manipulator_save, opts, klass, 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: 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)) setattr(man, 'isUnique%s' % '_'.join(field_name_list), curry(manipulator_validator_unique_together, field_name_list, opts))
for f in opts.fields: for f in opts.fields:
@ -1439,20 +1523,20 @@ def manipulator_init(opts, add, change, self, obj_key=None):
self.fields.extend(f.get_manipulator_fields(opts, self, change)) self.fields.extend(f.get_manipulator_fields(opts, self, change))
# Add fields for related objects. # 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: if change:
count = getattr(self.original_object, 'get_%s_count' % opts.get_rel_object_method_name(rel_opts, rel_field))() count = getattr(self.original_object, 'get_%s_count' % opts.get_rel_object_method_name(obj.opts, obj.field))()
count += rel_field.rel.num_extra_on_change count += obj.field.rel.num_extra_on_change
if rel_field.rel.min_num_in_admin: if obj.field.rel.min_num_in_admin:
count = max(count, rel_field.rel.min_num_in_admin) count = max(count, obj.field.rel.min_num_in_admin)
if rel_field.rel.max_num_in_admin: if obj.field.rel.max_num_in_admin:
count = min(count, rel_field.rel.max_num_in_admin) count = min(count, obj.field.rel.max_num_in_admin)
else: else:
count = rel_field.rel.num_in_admin count = obj.field.rel.num_in_admin
for f in rel_opts.fields + rel_opts.many_to_many: for f in obj.opts.fields + obj.opts.many_to_many:
if f.editable and f != rel_field and (not f.primary_key or (f.primary_key and change)): if f.editable and f != obj.field :
for i in range(count): 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. # Add field for ordering.
if change and opts.get_ordered_objects(): if change and opts.get_ordered_objects():
@ -1593,6 +1677,16 @@ def manipulator_save(opts, klass, add, change, self, new_data):
getattr(new_object, 'set_%s_order' % rel_opts.object_name.lower())(order) getattr(new_object, 'set_%s_order' % rel_opts.object_name.lower())(order)
return new_object 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): def manipulator_validator_unique_together(field_name_list, opts, self, field_data, all_data):
from django.utils.text import get_text_list from django.utils.text import get_text_list
field_list = [opts.get_field(field_name) for field_name in field_name_list] field_list = [opts.get_field(field_name) for field_name in field_name_list]

View File

@ -16,7 +16,7 @@ BLANK_CHOICE_DASH = [("", "---------")]
BLANK_CHOICE_NONE = [("", "None")] BLANK_CHOICE_NONE = [("", "None")]
# Values for Relation.edit_inline. # Values for Relation.edit_inline.
TABULAR, STACKED = 1, 2 TABULAR, STACKED = "admin_edit_inline_tabular", "admin_edit_inline_stacked"
RECURSIVE_RELATIONSHIP_CONSTANT = 'self' RECURSIVE_RELATIONSHIP_CONSTANT = 'self'
@ -155,7 +155,7 @@ class Field(object):
if hasattr(self.default, '__get_value__'): if hasattr(self.default, '__get_value__'):
return self.default.__get_value__() return self.default.__get_value__()
return self.default return self.default
if self.null: if not self.empty_strings_allowed or self.null:
return None return None
return "" return ""
@ -163,7 +163,7 @@ class Field(object):
""" """
Returns a list of field names that this object adds to the manipulator. 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): def get_manipulator_fields(self, opts, manipulator, change, name_prefix='', rel=False):
""" """
@ -177,28 +177,28 @@ class Field(object):
if self.maxlength and not self.choices: # Don't give SelectFields a maxlength parameter. if self.maxlength and not self.choices: # Don't give SelectFields a maxlength parameter.
params['maxlength'] = self.maxlength params['maxlength'] = self.maxlength
if isinstance(self.rel, ManyToOne): if isinstance(self.rel, ManyToOne):
params['member_name'] = name_prefix + self.get_db_column()
if self.rel.raw_id_admin: if self.rel.raw_id_admin:
field_objs = self.get_manipulator_field_objs() field_objs = self.get_manipulator_field_objs()
params['validator_list'].append(curry(manipulator_valid_rel_key, self, manipulator)) params['validator_list'].append(curry(manipulator_valid_rel_key, self, manipulator))
else: else:
if self.radio_admin: if self.radio_admin:
field_objs = [formfields.RadioSelectField] 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) params['ul_class'] = get_ul_class(self.radio_admin)
else: else:
if self.null: if self.null:
field_objs = [formfields.NullSelectField] field_objs = [formfields.NullSelectField]
else: else:
field_objs = [formfields.SelectField] field_objs = [formfields.SelectField]
params['choices'] = self.get_choices() params['choices'] = self.get_choices_default()
elif self.choices: elif self.choices:
if self.radio_admin: if self.radio_admin:
field_objs = [formfields.RadioSelectField] 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) params['ul_class'] = get_ul_class(self.radio_admin)
else: else:
field_objs = [formfields.SelectField] field_objs = [formfields.SelectField]
params['choices'] = self.get_choices()
params['choices'] = self.get_choices_default()
else: else:
field_objs = self.get_manipulator_field_objs() field_objs = self.get_manipulator_field_objs()
@ -258,13 +258,39 @@ class Field(object):
val = None val = None
return val return val
def get_choices(self, include_blank=True, blank_choice=BLANK_CHOICE_DASH): def get_choices(self, include_blank=True, blank_choice=BLANK_CHOICE_DASH):
"Returns a list of tuples used as SelectField choices for this field." "Returns a list of tuples used as SelectField choices for this field."
first_choice = include_blank and blank_choice or [] first_choice = include_blank and blank_choice or []
if self.choices: if self.choices:
return first_choice + list(self.choices) return first_choice + list(self.choices)
rel_obj = self.rel.to 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): class AutoField(Field):
empty_strings_allowed = False empty_strings_allowed = False
@ -274,7 +300,7 @@ class AutoField(Field):
def get_manipulator_fields(self, opts, manipulator, change, name_prefix='', rel=False): def get_manipulator_fields(self, opts, manipulator, change, name_prefix='', rel=False):
if not rel: 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) return Field.get_manipulator_fields(self, opts, manipulator, change, name_prefix, rel)
def get_manipulator_field_objs(self): def get_manipulator_field_objs(self):
@ -330,6 +356,10 @@ class DateField(Field):
def get_manipulator_field_objs(self): def get_manipulator_field_objs(self):
return [formfields.DateField] 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): class DateTimeField(DateField):
def get_db_prep_save(self, value): def get_db_prep_save(self, value):
# Casts dates into string format for entry into database. # Casts dates into string format for entry into database.
@ -359,6 +389,12 @@ class DateTimeField(DateField):
return datetime.datetime.combine(d, t) return datetime.datetime.combine(d, t)
return self.get_default() 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): class EmailField(Field):
def get_manipulator_field_objs(self): def get_manipulator_field_objs(self):
return [formfields.EmailField] return [formfields.EmailField]
@ -542,6 +578,10 @@ class TimeField(Field):
def get_manipulator_field_objs(self): def get_manipulator_field_objs(self):
return [formfields.TimeField] 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): class URLField(Field):
def __init__(self, verbose_name=None, name=None, verify_exists=True, **kwargs): def __init__(self, verbose_name=None, name=None, verify_exists=True, **kwargs):
if verify_exists: if verify_exists:
@ -598,6 +638,18 @@ class ForeignKey(Field):
def get_manipulator_field_objs(self): def get_manipulator_field_objs(self):
return [formfields.IntegerField] 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): class ManyToManyField(Field):
def __init__(self, to, **kwargs): def __init__(self, to, **kwargs):
kwargs['verbose_name'] = kwargs.get('verbose_name', to._meta.verbose_name_plural) kwargs['verbose_name'] = kwargs.get('verbose_name', to._meta.verbose_name_plural)
@ -615,9 +667,12 @@ class ManyToManyField(Field):
if self.rel.raw_id_admin: if self.rel.raw_id_admin:
return [formfields.CommaSeparatedIntegerField] return [formfields.CommaSeparatedIntegerField]
else: 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)] 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): def get_m2m_db_table(self, original_opts):
"Returns the name of the many-to-many 'join' table." "Returns the name of the many-to-many 'join' table."
return '%s_%s' % (original_opts.db_table, self.name) return '%s_%s' % (original_opts.db_table, self.name)
@ -638,6 +693,25 @@ class ManyToManyField(Field):
len(badkeys) == 1 and badkeys[0] or tuple(badkeys), len(badkeys) == 1 and badkeys[0] or tuple(badkeys),
len(badkeys) == 1 and "is" or "are") 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): class OneToOneField(IntegerField):
def __init__(self, to, to_field=None, **kwargs): def __init__(self, to, to_field=None, **kwargs):
kwargs['verbose_name'] = kwargs.get('verbose_name', 'ID') kwargs['verbose_name'] = kwargs.get('verbose_name', 'ID')
@ -720,6 +794,12 @@ class Admin:
Returns self.fields, except with fields as Field objects instead of Returns self.fields, except with fields as Field objects instead of
field names. If self.fields is None, defaults to putting every field names. If self.fields is None, defaults to putting every
non-AutoField field with editable=True in a single fieldset. 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: 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)]}),) field_struct = ((None, {'fields': [f.name for f in opts.fields + opts.many_to_many if f.editable and not isinstance(f, AutoField)]}),)

View File

@ -12,7 +12,7 @@ import re
_datere = r'\d{4}-((?:0?[1-9])|(?:1[0-2]))-((?:0?[1-9])|(?:[12][0-9])|(?:3[0-1]))' _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])?' _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/]+$') alnumurl_re = re.compile(r'^[\w/]+$')
ansi_date_re = re.compile('^%s$' % _datere) ansi_date_re = re.compile('^%s$' % _datere)
ansi_time_re = re.compile('^%s$' % _timere) ansi_time_re = re.compile('^%s$' % _timere)
@ -53,7 +53,7 @@ class CriticalValidationError(Exception):
def isAlphaNumeric(field_data, all_data): def isAlphaNumeric(field_data, all_data):
if not alnum_re.search(field_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): def isAlphaNumericURL(field_data, all_data):
if not alnumurl_re.search(field_data): if not alnumurl_re.search(field_data):

View File

@ -0,0 +1,260 @@
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 )

View File

@ -1,6 +1,6 @@
# Generic admin views, with admin templates created dynamically at runtime. # 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.exceptions import Http404, ObjectDoesNotExist, PermissionDenied
from django.core.extensions import DjangoContext as Context from django.core.extensions import DjangoContext as Context
from django.core.extensions import get_object_or_404, render_to_response from django.core.extensions import get_object_or_404, render_to_response
@ -493,23 +493,6 @@ def change_list(request, app_label, module_name):
}) })
return HttpResponse(t.render(c)) 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 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): def _get_submit_row_template(opts, app_label, add, change, show_delete, ordered_objects):
@ -530,6 +513,327 @@ def _get_submit_row_template(opts, app_label, add, change, show_delete, ordered_
t.append('</div>\n') t.append('</div>\n')
return t 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=''): def _get_template(opts, app_label, add=False, change=False, show_delete=False, form_url=''):
admin_field_objs = opts.admin.get_field_objs(opts) admin_field_objs = opts.admin.get_field_objs(opts)
ordered_objects = opts.get_ordered_objects()[:] ordered_objects = opts.get_ordered_objects()[:]
@ -608,6 +912,7 @@ def _get_template(opts, app_label, add=False, change=False, show_delete=False, f
for rel_obj, rel_field in opts.get_inline_related_objects(): for rel_obj, rel_field in opts.get_inline_related_objects():
var_name = rel_obj.object_name.lower() 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] 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 '')) t.append('<fieldset class="module%s">\n' % ((rel_field.rel.edit_inline != meta.TABULAR) and ' aligned' or ''))
view_on_site = '' view_on_site = ''
if change and hasattr(rel_obj, 'get_absolute_url'): if change and hasattr(rel_obj, 'get_absolute_url'):
@ -680,7 +985,7 @@ def _get_template(opts, app_label, add=False, change=False, show_delete=False, f
else: else:
t.append('document.getElementById("id_%s").onchange = function() { this._changed = true; };' % field.name) t.append('document.getElementById("id_%s").onchange = function() { this._changed = true; };' % field.name)
for f 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);}};' % \ 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)) (f, field.name, ' + " " + '.join(['document.getElementById("id_%s").value' % g for g in field.prepopulate_from]), field.maxlength))
t.append('</script>\n') t.append('</script>\n')
if change and ordered_objects: if change and ordered_objects:
@ -802,7 +1107,7 @@ def add_stage(request, app_label, module_name, show_delete=False, form_url='', p
# Add default data. # Add default data.
for f in opts.fields: for f in opts.fields:
if f.has_default(): 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, # In required many-to-one fields with only one available choice,
# select that one available choice. Note: We have to check that # select that one available choice. Note: We have to check that
# the length of choices is *2*, not 1, because SelectFields always # the length of choices is *2*, not 1, because SelectFields always
@ -837,7 +1142,9 @@ def add_stage(request, app_label, module_name, show_delete=False, form_url='', p
if f.editable and f != rel_field and not isinstance(f, meta.AutoField): if f.editable and f != rel_field and not isinstance(f, meta.AutoField):
for field_name in f.get_manipulator_field_names(''): for field_name in f.get_manipulator_field_names(''):
full_field_name = '%s.%d.%s' % (var_name, i, field_name) 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)) wrapper.append(formfields.FormFieldCollection(collection))
setattr(form, rel_opts.module_name, wrapper) setattr(form, rel_opts.module_name, wrapper)
@ -849,7 +1156,6 @@ def add_stage(request, app_label, module_name, show_delete=False, form_url='', p
if object_id_override is not None: if object_id_override is not None:
c['object_id'] = object_id_override c['object_id'] = object_id_override
raw_template = _get_template(opts, app_label, add=True, show_delete=show_delete, form_url=form_url) 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) t = template_loader.get_template_from_string(raw_template)
return HttpResponse(t.render(c)) return HttpResponse(t.render(c))
@ -915,7 +1221,7 @@ def change_stage(request, app_label, module_name, object_id):
new_data = {} new_data = {}
obj = manipulator.original_object obj = manipulator.original_object
for f in opts.fields: 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: for f in opts.many_to_many:
get_list_func = getattr(obj, 'get_%s_list' % f.rel.singular) get_list_func = getattr(obj, 'get_%s_list' % f.rel.singular)
if f.rel.raw_id_admin: if f.rel.raw_id_admin:
@ -927,7 +1233,7 @@ def change_stage(request, app_label, module_name, object_id):
for i, rel_instance in enumerate(getattr(obj, 'get_%s_list' % opts.get_rel_object_method_name(rel_obj, rel_field))()): 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: for f in rel_obj.fields:
if f.editable and f != rel_field: 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 new_data['%s.%d.%s' % (var_name, i, k)] = v
for f in rel_obj.many_to_many: 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)()] new_data['%s.%d.%s' % (var_name, i, f.column)] = [j.id for j in getattr(rel_instance, 'get_%s_list' % f.rel.singular)()]
@ -960,7 +1266,9 @@ def change_stage(request, app_label, module_name, object_id):
if f.editable and f != rel_field: if f.editable and f != rel_field:
for field_name in f.get_manipulator_field_names(''): for field_name in f.get_manipulator_field_names(''):
full_field_name = '%s.%d.%s' % (var_name, i, field_name) 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)) wrapper.append(formfields.FormFieldCollection(collection))
setattr(form, rel_opts.module_name, wrapper) 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: 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:

View File

@ -100,7 +100,8 @@ class TestRunner:
self.output(1, "Creating test database") self.output(1, "Creating test database")
try: try:
cursor.execute("CREATE DATABASE %s" % TEST_DATABASE_NAME) 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) confirm = raw_input("The test database, %s, already exists. Type 'yes' to delete it, or 'no' to cancel: " % TEST_DATABASE_NAME)
if confirm == 'yes': if confirm == 'yes':
cursor.execute("DROP DATABASE %s" % TEST_DATABASE_NAME) cursor.execute("DROP DATABASE %s" % TEST_DATABASE_NAME)