1
0
mirror of https://github.com/django/django.git synced 2025-07-05 10:19:20 +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(/[^\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
}

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 += (
# 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)

View File

@ -2,6 +2,7 @@
import sys
import template
import template_loader
class CommentNode(template.Node):
def render(self, context):
@ -223,6 +224,19 @@ class SsiNode(template.Node):
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
@ -600,6 +614,16 @@ def do_ssi(parser, token):
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.
@ -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('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)

View File

@ -22,7 +22,7 @@ class Manipulator:
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"
@ -87,16 +87,12 @@ class Manipulator:
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:
"""
@ -104,25 +100,40 @@ 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 != {}
@ -136,6 +147,7 @@ class FormFieldWrapper:
"Renders the field"
return str(self.formfield.render(self.data))
def __repr__(self):
return '<FormFieldWrapper for "%s">' % self.formfield.field_name
@ -155,6 +167,9 @@ class FormFieldWrapper:
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):
@ -174,9 +189,67 @@ class FormFieldCollection(FormFieldWrapper):
"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.
@ -209,6 +282,39 @@ class FormField:
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 #
####################
@ -237,7 +343,7 @@ class TextField(FormField):
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):
@ -248,7 +354,7 @@ class PasswordField(TextField):
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):
@ -266,7 +372,7 @@ 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):
@ -276,7 +382,7 @@ 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):
@ -289,7 +395,7 @@ class CheckboxField(FormField):
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):
@ -299,18 +405,21 @@ class CheckboxField(FormField):
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):
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
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:
selected_html = ''
if str(value) == str_data:
@ -334,12 +443,14 @@ class NullSelectField(SelectField):
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):
"""
@ -382,9 +493,9 @@ class RadioSelectField(FormField):
'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)
@ -414,7 +525,7 @@ class SelectMultipleField(SelectField):
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:
@ -469,9 +580,9 @@ class CheckboxSelectMultipleField(SelectMultipleField):
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)
@ -490,7 +601,7 @@ class FileUploadField(FormField):
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):

View File

@ -146,6 +146,70 @@ class FieldDoesNotExist(Exception):
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,
@ -317,6 +381,12 @@ class Options:
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 = []
@ -594,6 +664,7 @@ class ModelBase(type):
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)
@ -788,8 +859,14 @@ def method_save(opts, self):
# 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
@ -1332,16 +1409,21 @@ def function_get_sql_clause(opts, **kwargs):
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
@ -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.__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:
@ -1439,20 +1523,20 @@ def manipulator_init(opts, add, change, self, obj_key=None):
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():
@ -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)
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]

View File

@ -16,7 +16,7 @@ BLANK_CHOICE_DASH = [("", "---------")]
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'
@ -155,7 +155,7 @@ class Field(object):
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 ""
@ -163,7 +163,7 @@ class Field(object):
"""
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):
"""
@ -177,28 +177,28 @@ class Field(object):
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()
@ -258,13 +258,39 @@ class Field(object):
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
@ -274,7 +300,7 @@ class AutoField(Field):
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):
@ -330,6 +356,10 @@ class DateField(Field):
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.
@ -359,6 +389,12 @@ class DateTimeField(DateField):
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]
@ -542,6 +578,10 @@ class TimeField(Field):
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:
@ -598,6 +638,18 @@ class ForeignKey(Field):
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)
@ -615,9 +667,12 @@ class ManyToManyField(Field):
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)
@ -638,6 +693,25 @@ class ManyToManyField(Field):
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')
@ -720,6 +794,12 @@ class Admin:
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)]}),)

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]))'
_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)
@ -53,7 +53,7 @@ class CriticalValidationError(Exception):
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):

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.
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
@ -493,23 +493,6 @@ def change_list(request, app_label, module_name):
})
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):
@ -530,6 +513,327 @@ def _get_submit_row_template(opts, app_label, add, change, show_delete, ordered_
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()[:]
@ -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():
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'):
@ -680,7 +985,7 @@ def _get_template(opts, app_label, add=False, change=False, show_delete=False, f
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:
@ -802,7 +1107,7 @@ def add_stage(request, app_label, module_name, show_delete=False, form_url='', p
# 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
@ -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):
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)
@ -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:
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))
@ -915,7 +1221,7 @@ def change_stage(request, app_label, module_name, object_id):
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:
@ -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 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)()]
@ -960,7 +1266,9 @@ def change_stage(request, app_label, module_name, object_id):
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:

View File

@ -100,7 +100,8 @@ class TestRunner:
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)