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:
parent
404147c385
commit
d0ba57ee90
2
django/conf/admin_media/js/urlify.js
vendored
2
django/conf/admin_media/js/urlify.js
vendored
@ -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
|
||||||
}
|
}
|
107
django/conf/admin_templates/admin_change_form.html
Normal file
107
django/conf/admin_templates/admin_change_form.html
Normal 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> ›
|
||||||
|
<a href="../">{{verbose_name_plural|capfirst}}</a> ›
|
||||||
|
{% 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 %}
|
16
django/conf/admin_templates/admin_edit_inline_stacked.html
Normal file
16
django/conf/admin_templates/admin_edit_inline_stacked.html
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<fieldset class="module aligned">
|
||||||
|
{% for fcw in form_field_collection_wrapper_list %}
|
||||||
|
<h2>{{relation.opts.verbose_name|capfirst }} #{{ 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>
|
||||||
|
|
44
django/conf/admin_templates/admin_edit_inline_tabular.html
Normal file
44
django/conf/admin_templates/admin_edit_inline_tabular.html
Normal 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>
|
||||||
|
|
36
django/conf/admin_templates/admin_field.html
Normal file
36
django/conf/admin_templates/admin_field.html
Normal 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 %}
|
||||||
|
<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>
|
27
django/conf/admin_templates/admin_field_widget.html
Normal file
27
django/conf/admin_templates/admin_field_widget.html
Normal 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 %}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
@ -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):
|
||||||
|
@ -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]
|
||||||
|
@ -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)]}),)
|
||||||
|
@ -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):
|
||||||
|
260
django/templatetags/admin_modify.py
Normal file
260
django/templatetags/admin_modify.py
Normal 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 )
|
@ -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:
|
||||||
|
@ -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)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user