MERGED NEW-ADMIN BRANCH (except for po/mo files, which will come in a separate commit)

git-svn-id: http://code.djangoproject.com/svn/django/trunk@1434 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Adrian Holovaty 2005-11-25 21:20:09 +00:00
parent 4fe5c9b7ee
commit 9dda4abee1
30 changed files with 2046 additions and 1109 deletions

View File

@ -16,19 +16,19 @@ def validate_class(klass):
assert isinstance(f.rel, meta.ManyToMany), \ assert isinstance(f.rel, meta.ManyToMany), \
"ManyToManyField %s should have 'rel' set to a ManyToMany instance." % f.name "ManyToManyField %s should have 'rel' set to a ManyToMany instance." % f.name
# Inline related objects. # Inline related objects.
for rel_opts, rel_field in opts.get_inline_related_objects(): for related in opts.get_followed_related_objects():
assert len([f for f in rel_opts.fields if f.core]) > 0, \ assert len([f for f in related.opts.fields if f.core]) > 0, \
"At least one field in %s should have core=True, because it's being edited inline by %s." % \ "At least one field in %s should have core=True, because it's being edited inline by %s." % \
(rel_opts.object_name, opts.object_name) (related.opts.object_name, opts.object_name)
# All related objects. # All related objects.
related_apps_seen = [] related_apps_seen = []
for rel_opts, rel_field in opts.get_all_related_objects(): for related in opts.get_all_related_objects():
if rel_opts in related_apps_seen: if related.opts in related_apps_seen:
assert rel_field.rel.related_name is not None, \ assert related.field.rel.related_name is not None, \
"Relationship in field %s.%s needs to set 'related_name' because more than one" \ "Relationship in field %s.%s needs to set 'related_name' because more than one" \
" %s object is referenced in %s." % \ " %s object is referenced in %s." % \
(rel_opts.object_name, rel_field.name, opts.object_name, rel_opts.object_name) (related.opts.object_name, related.field.name, opts.object_name, rel_opts.object_name)
related_apps_seen.append(rel_opts) related_apps_seen.append(related.opts)
# Etc. # Etc.
if opts.admin is not None: if opts.admin is not None:
assert opts.admin.ordering or opts.ordering, \ assert opts.admin.ordering or opts.ordering, \

View File

@ -0,0 +1,152 @@
"""
FilterSpec encapsulates the logic for displaying filters in the Django admin.
Filters are specified in models with the "list_filter" option.
Each filter subclass knows how to display a filter for a field that passes a
certain test -- e.g. being a DateField or ForeignKey.
"""
from django.core import meta
import datetime
class FilterSpec(object):
filter_specs = []
def __init__(self, f, request, params):
self.field = f
self.params = params
def register(cls, test, factory):
cls.filter_specs.append( (test, factory) )
register = classmethod(register)
def create(cls, f, request, params):
for test, factory in cls.filter_specs:
if test(f):
return factory(f, request, params)
create = classmethod(create)
def has_output(self):
return True
def choices(self, cl):
raise NotImplementedError()
def title(self):
return self.field.verbose_name
def output(self, cl):
t = []
if self.has_output():
t.append(_('<h3>By %s:</h3>\n<ul>\n') % self.title())
for choice in self.choices(cl):
t.append('<li%s><a href="%s">%s</a></li>\n' % \
((choice['selected'] and ' class="selected"' or ''),
choice['query_string'] ,
choice['display']))
t.append('</ul>\n\n')
return "".join(t)
class RelatedFilterSpec(FilterSpec):
def __init__(self, f, request, params):
super(RelatedFilterSpec, self).__init__(f, request, params)
if isinstance(f, meta.ManyToManyField):
self.lookup_title = f.rel.to.verbose_name
else:
self.lookup_title = f.verbose_name
self.lookup_kwarg = '%s__%s__exact' % (f.name, f.rel.to.pk.name)
self.lookup_val = request.GET.get(self.lookup_kwarg, None)
self.lookup_choices = f.rel.to.get_model_module().get_list()
def has_output(self):
return len(self.lookup_choices) > 1
def title(self):
return self.lookup_title
def choices(self, cl):
yield {'selected': self.lookup_val is None,
'query_string': cl.get_query_string({}, [self.lookup_kwarg]),
'display': _('All')}
for val in self.lookup_choices:
pk_val = getattr(val, self.field.rel.to.pk.attname)
yield {'selected': self.lookup_val == str(pk_val),
'query_string': cl.get_query_string( {self.lookup_kwarg: pk_val}),
'display': val}
FilterSpec.register(lambda f: bool(f.rel), RelatedFilterSpec)
class ChoicesFilterSpec(FilterSpec):
def __init__(self, f, request, params):
super(ChoicesFilterSpec, self).__init__(f, request, params)
self.lookup_kwarg = '%s__exact' % f.name
self.lookup_val = request.GET.get(self.lookup_kwarg, None)
def choices(self, cl):
yield {'selected': self.lookup_val is None,
'query_string': cl.get_query_string( {}, [self.lookup_kwarg]),
'display': _('All')}
for k, v in self.field.choices:
yield {'selected': str(k) == self.lookup_val,
'query_string': cl.get_query_string( {self.lookup_kwarg: k}),
'display': v}
FilterSpec.register(lambda f: bool(f.choices), ChoicesFilterSpec)
class DateFieldFilterSpec(FilterSpec):
def __init__(self, f, request, params):
super(DateFieldFilterSpec, self).__init__(f, request, params)
self.field_generic = '%s__' % self.field.name
self.date_params = dict([(k, v) for k, v in params.items() if k.startswith(self.field_generic)])
today = datetime.date.today()
one_week_ago = today - datetime.timedelta(days=7)
today_str = isinstance(self.field, meta.DateTimeField) and today.strftime('%Y-%m-%d 23:59:59') or today.strftime('%Y-%m-%d')
self.links = (
(_('Any date'), {}),
(_('Today'), {'%s__year' % self.field.name: str(today.year),
'%s__month' % self.field.name: str(today.month),
'%s__day' % self.field.name: str(today.day)}),
(_('Past 7 days'), {'%s__gte' % self.field.name: one_week_ago.strftime('%Y-%m-%d'),
'%s__lte' % f.name: today_str}),
(_('This month'), {'%s__year' % self.field.name: str(today.year),
'%s__month' % f.name: str(today.month)}),
(_('This year'), {'%s__year' % self.field.name: str(today.year)})
)
def title(self):
return self.field.verbose_name
def choices(self, cl):
for title, param_dict in self.links:
yield {'selected': self.date_params == param_dict,
'query_string': cl.get_query_string( param_dict, self.field_generic),
'display': title}
FilterSpec.register(lambda f: isinstance(f, meta.DateField), DateFieldFilterSpec)
class BooleanFieldFilterSpec(FilterSpec):
def __init__(self, f, request, params):
super(BooleanFieldFilterSpec, self).__init__(f, request, params)
self.lookup_kwarg = '%s__exact' % f.name
self.lookup_kwarg2 = '%s__isnull' % f.name
self.lookup_val = request.GET.get(self.lookup_kwarg, None)
self.lookup_val2 = request.GET.get(self.lookup_kwarg2, None)
def title(self):
return self.field.verbose_name
def choices(self, cl):
for k, v in ((_('All'), None), (_('Yes'), '1'), (_('No'), '0')):
yield {'selected': self.lookup_val == v and not self.lookup_val2,
'query_string': cl.get_query_string( {self.lookup_kwarg: v}, [self.lookup_kwarg2]),
'display': k}
if isinstance(self.field, meta.NullBooleanField):
yield {'selected': self.lookup_val2 == 'True',
'query_string': cl.get_query_string( {self.lookup_kwarg2: 'True'}, [self.lookup_kwarg]),
'display': _('Unknown')}
FilterSpec.register(lambda f: isinstance(f, meta.BooleanField) or isinstance(f, meta.NullBooleanField), BooleanFieldFilterSpec)

View File

@ -0,0 +1,75 @@
{% extends "admin/base_site" %}
{% load i18n %}
{% load admin_modify %}
{% load adminmedia %}
{% block extrahead %}
{% for js in bound_manipulator.javascript_imports %}{% include_admin_script js %}{% endfor %}
{% endblock %}
{% block coltype %}{{ bound_manipulator.coltype }}{% endblock %}
{% block bodyclass %}{{ app_label }}-{{ bound_manipulator.object_name.lower }} change-form{% endblock %}
{% block breadcrumbs %}{% if not is_popup %}
<div class="breadcrumbs">
<a href="../../../">{% trans "Home" %}</a> &rsaquo;
<a href="../">{{ bound_manipulator.verbose_name_plural|capfirst }}</a> &rsaquo;
{% if add %}{% trans "Add" %} {{ bound_manipulator.verbose_name }}{% else %}{{ bound_manipulator.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">{% trans "History" %}</a></li>
{% if bound_manipulator.has_absolute_url %}<li><a href="/r/{{ bound_manipulator.content_type_id }}/{{ object_id }}/" class="viewsitelink">{% trans "View on site" %}</a></li>{% endif%}
</ul>
{% endif %}{% endif %}
<form {{ bound_manipulator.form_enc_attrib }} action='{{ form_url }}' method="post">{% block form_top %}{% endblock %}
{% if is_popup %}<input type="hidden" name="_popup" value="1">{% endif %}
{% if bound_manipulator.save_on_top %}{% submit_row bound_manipulator %}{% endif %}
{% if form.error_dict %}
<p class="errornote">
{% blocktrans count form.error_dict.items|length as counter %}Please correct the error below.{% plural %}Please correct the errors below.{% endblocktrans %}
</p>
{% endif %}
{% for bound_field_set in bound_manipulator.bound_field_sets %}
<fieldset class="module aligned {{ bound_field_set.classes }}">
{% if bound_field_set.name %}<h2>{{ bound_field_set.name }}</h2>{% endif %}
{% for bound_field_line in bound_field_set %}
{% admin_field_line bound_field_line %}
{% for bound_field in bound_field_line %}
{% filter_interface_script_maybe bound_field %}
{% endfor %}
{% endfor %}
</fieldset>
{% endfor %}
{% block after_field_sets %}{% endblock %}
{% if change %}
{% if bound_manipulator.ordered_objects %}
<fieldset class="module"><h2>{% trans "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_">{% trans "Order:" %}</label> {{ form.order_ }}</p>
</div></fieldset>
{% endif %}
{% endif %}
{% for related_object in bound_manipulator.inline_related_objects %}{% edit_inline related_object %}{% endfor %}
{% block after_related_objects %}{% endblock %}
{% submit_row bound_manipulator %}
{% if add %}
<script type="text/javascript">document.getElementById("{{ bound_manipulator.first_form_field_id }}").focus();</script>
{% endif %}
{% if bound_manipulator.auto_populated_fields %}
<script type="text/javascript">
{% auto_populated_field_script bound_manipulator.auto_populated_fields change %}
</script>
{% endif %}
{% if change %}
{% if bound_manipulator.ordered_objects %}
{% if form.order_objects %}<ul id="orderthese">
{% for object in form.order_objects %}
<li id="p{% object_pk bound_manipulator object %}">
<span id="handlep{% object_pk bound_manipulator object %}">{{ object|truncatewords:"5" }}</span>
</li>
{% endfor %}
</ul>{% endif %}
{% endif %}
{% endif %}
</form></div>
{% endblock %}

View File

@ -0,0 +1,20 @@
{% load admin_list %}
{% load i18n %}
{% extends "admin/base_site" %}
{% block bodyclass %}change-list{% endblock %}
{% if not is_popup %}{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">{% trans "Home" %}</a> &rsaquo; {{ cl.opts.verbose_name_plural|capfirst }} </div>{% endblock %}{% endif %}
{% block coltype %}flex{% endblock %}
{% block content %}
<div id="content-main">
{% if has_add_permission %}
<ul class="object-tools"><li><a href="add/{% if is_popup %}?_popup=1{% endif %}" class="addlink">{% blocktrans with cl.opts.verbose_name as name %}Add {{ name }}{% endblocktrans %}</a></li></ul>
{% endif %}
<div class="module{% if cl.has_filters %} filtered{% endif %}" id="changelist">
{% search_form cl %}
{% date_hierarchy cl %}
{% filters cl %}
{% result_list cl %}
{% pagination cl %}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,13 @@
<table cellspacing="0">
<thead>
<tr>
{% for header in result_headers %}<th{{ header.class_attrib }}>
{% if header.sortable %}<a href="{{ header.url }}">{% endif %}
{{ header.text|capfirst }}
{% if header.sortable %}</a>{% endif %}</th>{% endfor %}
</tr>
</thead>
{% for result in results %}
<tr class="{% cycle row1,row2 %}">{% for item in result %}{{ item }}{% endfor %}</tr>
{% endfor %}
</table>

View File

@ -0,0 +1,10 @@
{% if show %}
<div class="xfull">
<ul class="toplinks">
{% if back %}<li class="date-back"><a href="{{ back.link }}">&lsaquo; {{ back.title }}</a></li>{% endif %}
{% for choice in choices %}
<li> {% if choice.link %}<a href="{{ choice.link }}">{% endif %}{{ choice.title }}{% if choice.link %}</a>{% endif %}</li>
{% endfor %}
</ul><br class="clear" />
</div>
{% endif %}

View File

@ -0,0 +1,15 @@
<fieldset class="module aligned">
{% for fcw in bound_related_object.form_field_collection_wrappers %}
<h2>{{ bound_related_object.relation.opts.verbose_name|capfirst }}&nbsp;#{{ forloop.counter }}</h2>
{% if bound_related_object.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.hidden %}
{% field_widget bound_field %}
{% else %}
{% admin_field_line bound_field %}
{% endif %}
{% endfor %}
{% endfor %}
</fieldset>

View File

@ -0,0 +1,42 @@
<fieldset class="module">
<h2>{{ bound_related_object.relation.opts.verbose_name_plural|capfirst }}</h2><table>
<thead><tr>
{% for fw in bound_related_object.field_wrapper_list %}
{% if fw.needs_header %}
<th{{ fw.header_class_attribute }}>{{ fw.field.verbose_name|capfirst }}</th>
{% endif %}
{% endfor %}
{% for fcw in bound_related_object.form_field_collection_wrappers %}
{% 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.hidden %}
<td {{ bound_field.cell_class_attribute }}>
{% field_widget bound_field %}
</td>
{% endif %}
{% endfor %}
{% if bound_related_object.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 bound_related_object.form_field_collection_wrappers %}
{% for bound_field in fcw.bound_fields %}
{% if bound_field.hidden %}
{% field_widget bound_field %}
{% endif %}
{% endfor %}
{% endfor %}
</fieldset>

View File

@ -0,0 +1,21 @@
<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_display %}&nbsp;<strong>{{ bound_field.existing_display|truncatewords:"14" }}</strong>{% endif %}
{% endif %}
{% endif %}
{% if bound_field.field.help_text %}<p class="help">{{ bound_field.field.help_text }}</p>{% endif %}
{% endfor %}
</div>

View File

@ -0,0 +1,7 @@
<h3>{% blocktrans %} By {{ title }} {% endblocktrans %}</h3>
<ul>
{% for choice in choices %}
<li{% if choice.selected %} class="selected"{% endif %}>
<a href="{{ choice.query_string }}">{{ choice.display }}</a></li>
{% endfor %}
</ul>

View File

@ -0,0 +1,5 @@
{% if cl.has_filters %}<div id="changelist-filter">
<h2>Filter</h2>
{% for spec in cl.filter_specs %}
{% filter cl spec %}
{% endfor %}</div>{% endif %}

View File

@ -0,0 +1,9 @@
<p class="paginator">
{% if pagination_required %}
{% for i in page_range %}
{% paginator_number cl i %}
{% endfor %}
{% endif %}
{{ cl.result_count }} {% ifequal cl.result_count 1 %}{{ cl.opts.verbose_name }}{% else %}{{ cl.opts.verbose_name_plural }}{% endifequal %}
{% if show_all_url %}&nbsp;&nbsp;<a href="{{ show_all_url }}" class="showall">Show all</a>{% endif %}
</p>

View File

@ -0,0 +1,14 @@
{% if cl.lookup_opts.admin.search_fields %}
<div id="toolbar"><form id="changelist-search" action="" method="get">
<label><img src="{% admin_media_prefix %}img/admin/icon_searchbox.png" /></label>
<input type="text" size="40" name="{{ search_var }}" value="{{ cl.query|escape }}" id="searchbar" />
<input type="submit" value="Go" />
{% if show_result_count %}
<span class="small quiet">{{ cl.result_count }} result{{ cl.result_count|pluralize }} (<a href="?">{{ cl.full_result_count }} total</a>)</span>
{% endif %}
{% for pair in cl.params.items %}
{% ifnotequal pair.0 search_var %}<input type="hidden" name="{{ pair.0|escape }}" value="{{ pair.1|escape }}"/>{% endifnotequal %}
{% endfor %}
</form></div>
<script type="text/javascript">document.getElementById("searchbar").focus();</script>
{% endif %}

View File

@ -0,0 +1,8 @@
{% load i18n %}
<div class="submit-row">
{% if show_delete_link %}<p class="float-left"><a href="delete/" class="deletelink">{% trans "Delete" %}</a></p>{% endif %}
{% if show_save_as_new %}<input type="submit" value="{% trans 'Save as new' %}" name="_saveasnew" {{ onclick_attrib }}/>{%endif%}
{% if show_save_and_add_another %}<input type="submit" value="{% trans 'Save and add another' %}" name="_addanother" {{ onclick_attrib }} />{% endif %}
{% if show_save_and_continue %}<input type="submit" value="{% trans 'Save and continue editing' %}" name="_continue" {{ onclick_attrib }}/>{% endif %}
{% if show_save %}<input type="submit" value="{% trans 'Save' %}" class="default" {{ onclick_attrib }}/>{% endif %}
</div>

View File

@ -1,30 +1,32 @@
{% extends "admin/base_site" %} {% extends "admin/base_site" %}
{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">Home</a> &rsaquo; <a href="../">Documentation</a> &rsaquo; Bookmarklets</div>{% endblock %} {% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">{% trans "Home" %}</a> &rsaquo; <a href="../">{% trans "Documentation" %}</a> &rsaquo; {% trans "Bookmarklets" %}</div>{% endblock %}
{% block title %}Documentation bookmarklets{% endblock %} {% block title %}{% trans "Documentation bookmarklets" %}{% endblock %}
{% block content %} {% block content %}
{% blocktrans %}
<p class="help">To install bookmarklets, drag the link to your bookmarks <p class="help">To install bookmarklets, drag the link to your bookmarks
toolbar, or right-click the link and add it to your bookmarks. Now you can toolbar, or right-click the link and add it to your bookmarks. Now you can
select the bookmarklet from any page in the site. Note that some of these select the bookmarklet from any page in the site. Note that some of these
bookmarklets require you to be viewing the site from a computer designated bookmarklets require you to be viewing the site from a computer designated
as "internal" (talk to your system administrator if you aren't sure if as "internal" (talk to your system administrator if you aren't sure if
your computer is "internal").</p> your computer is "internal").</p>
{% endblocktrans %}
<div id="content-main"> <div id="content-main">
<h3><a href="javascript:(function(){if(typeof ActiveXObject!='undefined'){x=new ActiveXObject('Microsoft.XMLHTTP')}else if(typeof XMLHttpRequest!='undefined'){x=new XMLHttpRequest()}else{return;}x.open('HEAD',location.href,false);x.send(null);try{view=x.getResponseHeader('x-view');}catch(e){alert('No view found for this page');return;}if(view=="undefined"){alert("No view found for this page");}document.location='{{ admin_url }}/doc/views/'+view+'/';})()">Documentation for this page</a></h3> <h3><a href="javascript:(function(){if(typeof ActiveXObject!='undefined'){x=new ActiveXObject('Microsoft.XMLHTTP')}else if(typeof XMLHttpRequest!='undefined'){x=new XMLHttpRequest()}else{return;}x.open('HEAD',location.href,false);x.send(null);try{view=x.getResponseHeader('x-view');}catch(e){alert('No view found for this page');return;}if(view=="undefined"){alert("No view found for this page");}document.location='{{ admin_url }}/doc/views/'+view+'/';})()">{% trans "Documentation for this page" %}</a></h3>
<p>Jumps you from any page to the documentation for the view that generates that page.</p> <p>{% trans "Jumps you from any page to the documentation for the view that generates that page." %}</p>
<h3><a href="javascript:(function(){if(typeof ActiveXObject!='undefined'){x=new ActiveXObject('Microsoft.XMLHTTP')}else if(typeof XMLHttpRequest!='undefined'){x=new XMLHttpRequest()}else{return;}x.open('GET',location.href,false);x.send(null);try{type=x.getResponseHeader('x-object-type');id=x.getResponseHeader('x-object-id');}catch(e){type='(none)';id='(none)';}d=document;b=d.body;e=d.createElement('div');e.id='xxxhhh';s=e.style;s.position='absolute';s.left='10px';s.top='10px';s.font='10px monospace';s.border='1px black solid';s.padding='4px';s.backgroundColor='#eee';e.appendChild(d.createTextNode('Type: '+type));e.appendChild(d.createElement('br'));e.appendChild(d.createTextNode('ID: '+id));e.appendChild(d.createElement('br'));l=d.createElement('a');l.href='#';l.onclick=function(){b.removeChild(e);};l.appendChild(d.createTextNode('[close]'));l.style.textDecoration='none';e.appendChild(l);b.appendChild(e);})();">Show object ID</a></h3> <h3><a href="javascript:(function(){if(typeof ActiveXObject!='undefined'){x=new ActiveXObject('Microsoft.XMLHTTP')}else if(typeof XMLHttpRequest!='undefined'){x=new XMLHttpRequest()}else{return;}x.open('GET',location.href,false);x.send(null);try{type=x.getResponseHeader('x-object-type');id=x.getResponseHeader('x-object-id');}catch(e){type='(none)';id='(none)';}d=document;b=d.body;e=d.createElement('div');e.id='xxxhhh';s=e.style;s.position='absolute';s.left='10px';s.top='10px';s.font='10px monospace';s.border='1px black solid';s.padding='4px';s.backgroundColor='#eee';e.appendChild(d.createTextNode('Type: '+type));e.appendChild(d.createElement('br'));e.appendChild(d.createTextNode('ID: '+id));e.appendChild(d.createElement('br'));l=d.createElement('a');l.href='#';l.onclick=function(){b.removeChild(e);};l.appendChild(d.createTextNode('[close]'));l.style.textDecoration='none';e.appendChild(l);b.appendChild(e);})();">{% trans "Show object ID" %}</a></h3>
<p>Shows the content-type and unique ID for pages that represent a single object.</p> <p>{% trans "Shows the content-type and unique ID for pages that represent a single object." %}</p>
<h3><a href="javascript:(function(){if(typeof ActiveXObject!='undefined'){var x=new ActiveXObject('Microsoft.XMLHTTP')}else if(typeof XMLHttpRequest!='undefined'){var x=new XMLHttpRequest()}else{return;}x.open('GET',location.href,false);x.send(null);try{var type=x.getResponseHeader('x-object-type');var id=x.getResponseHeader('x-object-id');}catch(e){return;}document.location='{{ admun_url }}/'+type.split('.').join('/')+'/'+id+'/';})()">Edit this object (current window)</a></h3> <h3><a href="javascript:(function(){if(typeof ActiveXObject!='undefined'){var x=new ActiveXObject('Microsoft.XMLHTTP')}else if(typeof XMLHttpRequest!='undefined'){var x=new XMLHttpRequest()}else{return;}x.open('GET',location.href,false);x.send(null);try{var type=x.getResponseHeader('x-object-type');var id=x.getResponseHeader('x-object-id');}catch(e){return;}document.location='{{ admun_url }}/'+type.split('.').join('/')+'/'+id+'/';})()">{% trans "Edit this object (current window)" %}</a></h3>
<p>Jumps to the admin page for pages that represent a single object.</p> <p>{% trans "Jumps to the admin page for pages that represent a single object." %}</p>
<h3><a href="javascript:(function(){if(typeof ActiveXObject!='undefined'){var x=new ActiveXObject('Microsoft.XMLHTTP')}else if(typeof XMLHttpRequest!='undefined'){var x=new XMLHttpRequest()}else{return;}x.open('GET',location.href,false);x.send(null);try{var type=x.getResponseHeader('x-object-type');var id=x.getResponseHeader('x-object-id');}catch(e){return;}window.open('{{ admun_url }}/'+type.split('.').join('/')+'/'+id+'/');})()">Edit this object (new window)</a></h3> <h3><a href="javascript:(function(){if(typeof ActiveXObject!='undefined'){var x=new ActiveXObject('Microsoft.XMLHTTP')}else if(typeof XMLHttpRequest!='undefined'){var x=new XMLHttpRequest()}else{return;}x.open('GET',location.href,false);x.send(null);try{var type=x.getResponseHeader('x-object-type');var id=x.getResponseHeader('x-object-id');}catch(e){return;}window.open('{{ admun_url }}/'+type.split('.').join('/')+'/'+id+'/');})()">{% trans "Edit this object (new window)" %}</a></h3>
<p>As above, but opens the admin page in a new window.</p> <p>{% trans "As above, but opens the admin page in a new window." %}</p>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -0,0 +1,4 @@
<p class="datetime">
Date: {{ bound_field.form_fields.0 }}<br />
Time: {{ bound_field.form_fields.1 }}
</p>

View File

@ -0,0 +1 @@
{% output_all bound_field.form_fields %}

View File

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

View File

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

View File

@ -0,0 +1 @@
{% include "widget/foreign" %}

View File

@ -0,0 +1,278 @@
from django.contrib.admin.views.main import MAX_SHOW_ALL_ALLOWED, DEFAULT_RESULTS_PER_PAGE, ALL_VAR
from django.contrib.admin.views.main import ORDER_VAR, ORDER_TYPE_VAR, PAGE_VAR, SEARCH_VAR
from django.contrib.admin.views.main import IS_POPUP_VAR, EMPTY_CHANGELIST_VALUE, MONTHS
from django.core import meta, template
from django.core.exceptions import ObjectDoesNotExist
from django.core.template.decorators import simple_tag, inclusion_tag
from django.utils import dateformat
from django.utils.html import strip_tags, escape
from django.utils.text import capfirst
from django.utils.translation import get_date_formats
from django.conf.settings import ADMIN_MEDIA_PREFIX
DOT = '.'
#@simple_tag
def paginator_number(cl,i):
if i == DOT:
return '... '
elif i == cl.page_num:
return '<span class="this-page">%d</span> ' % (i+1)
else:
return '<a href="%s"%s>%d</a> ' % (cl.get_query_string({PAGE_VAR: i}), (i == cl.paginator.pages-1 and ' class="end"' or ''), i+1)
paginator_number = simple_tag(paginator_number)
#@inclusion_tag('admin/pagination')
def pagination(cl):
paginator, page_num = cl.paginator, cl.page_num
pagination_required = (not cl.show_all or not cl.can_show_all) and cl.multi_page
if not pagination_required:
page_range = []
else:
ON_EACH_SIDE = 3
ON_ENDS = 2
# If there are 10 or fewer pages, display links to every page.
# Otherwise, do some fancy
if paginator.pages <= 10:
page_range = range(paginator.pages)
else:
# Insert "smart" pagination links, so that there are always ON_ENDS
# links at either end of the list of pages, and there are always
# ON_EACH_SIDE links at either end of the "current page" link.
page_range = []
if page_num > (ON_EACH_SIDE + ON_ENDS):
page_range.extend(range(0, ON_EACH_SIDE - 1))
page_range.append(DOT)
page_range.extend(range(page_num - ON_EACH_SIDE, page_num + 1))
else:
page_range.extend(range(0, page_num + 1))
if page_num < (paginator.pages - ON_EACH_SIDE - ON_ENDS - 1):
page_range.extend(range(page_num + 1, page_num + ON_EACH_SIDE + 1))
page_range.append(DOT)
page_range.extend(range(paginator.pages - ON_ENDS, paginator.pages))
else:
page_range.extend(range(page_num + 1, paginator.pages))
need_show_all_link = cl.can_show_all and not cl.show_all and cl.multi_page
return {
'cl': cl,
'pagination_required': pagination_required,
'show_all_url': need_show_all_link and cl.get_query_string({ALL_VAR: ''}),
'page_range': page_range,
'ALL_VAR': ALL_VAR,
'1': 1,
}
pagination = inclusion_tag('admin/pagination')(pagination)
def result_headers(cl):
lookup_opts = cl.lookup_opts
for i, field_name in enumerate(lookup_opts.admin.list_display):
try:
f = lookup_opts.get_field(field_name)
except meta.FieldDoesNotExist:
# For non-field list_display values, check for the function
# attribute "short_description". If that doesn't exist, fall
# back to the method name. And __repr__ is a special-case.
if field_name == '__repr__':
header = lookup_opts.verbose_name
else:
func = getattr(cl.mod.Klass, field_name) # Let AttributeErrors propogate.
try:
header = func.short_description
except AttributeError:
header = func.__name__.replace('_', ' ')
# Non-field list_display values don't get ordering capability.
yield {"text": header}
else:
if isinstance(f.rel, meta.ManyToOne) and f.null:
yield {"text": f.verbose_name}
else:
th_classes = []
new_order_type = 'asc'
if field_name == cl.order_field:
th_classes.append('sorted %sending' % cl.order_type.lower())
new_order_type = {'asc': 'desc', 'desc': 'asc'}[cl.order_type.lower()]
yield {"text": f.verbose_name,
"sortable": True,
"url": cl.get_query_string({ORDER_VAR: i, ORDER_TYPE_VAR: new_order_type}),
"class_attrib": (th_classes and ' class="%s"' % ' '.join(th_classes) or '')}
def items_for_result(cl, result):
first = True
pk = cl.lookup_opts.pk.attname
for field_name in cl.lookup_opts.admin.list_display:
row_class = ''
try:
f = cl.lookup_opts.get_field(field_name)
except meta.FieldDoesNotExist:
# For non-field list_display values, the value is a method
# name. Execute the method.
try:
func = getattr(result, field_name)
result_repr = str(func())
except AttributeError, ObjectDoesNotExist:
result_repr = EMPTY_CHANGELIST_VALUE
else:
# Strip HTML tags in the resulting text, except if the
# function has an "allow_tags" attribute set to True.
if not getattr(func, 'allow_tags', False):
result_repr = strip_tags(result_repr)
else:
field_val = getattr(result, f.attname)
if isinstance(f.rel, meta.ManyToOne):
if field_val is not None:
result_repr = getattr(result, 'get_%s' % f.name)()
else:
result_repr = EMPTY_CHANGELIST_VALUE
# Dates and times are special: They're formatted in a certain way.
elif isinstance(f, meta.DateField) or isinstance(f, meta.TimeField):
if field_val:
(date_format, datetime_format, time_format) = get_date_formats()
if isinstance(f, meta.DateTimeField):
result_repr = capfirst(dateformat.format(field_val, datetime_format))
elif isinstance(f, meta.TimeField):
result_repr = capfirst(dateformat.time_format(field_val, time_format))
else:
result_repr = capfirst(dateformat.format(field_val, date_format))
else:
result_repr = EMPTY_CHANGELIST_VALUE
row_class = ' class="nowrap"'
# Booleans are special: We use images.
elif isinstance(f, meta.BooleanField) or isinstance(f, meta.NullBooleanField):
BOOLEAN_MAPPING = {True: 'yes', False: 'no', None: 'unknown'}
result_repr = '<img src="%simg/admin/icon-%s.gif" alt="%s" />' % (ADMIN_MEDIA_PREFIX, BOOLEAN_MAPPING[field_val], field_val)
# ImageFields are special: Use a thumbnail.
elif isinstance(f, meta.ImageField):
from django.parts.media.photos import get_thumbnail_url
result_repr = '<img src="%s" alt="%s" title="%s" />' % (get_thumbnail_url(getattr(result, 'get_%s_url' % f.name)(), '120'), field_val, field_val)
# FloatFields are special: Zero-pad the decimals.
elif isinstance(f, meta.FloatField):
if field_val is not None:
result_repr = ('%%.%sf' % f.decimal_places) % field_val
else:
result_repr = EMPTY_CHANGELIST_VALUE
# Fields with choices are special: Use the representation
# of the choice.
elif f.choices:
result_repr = dict(f.choices).get(field_val, EMPTY_CHANGELIST_VALUE)
else:
result_repr = strip_tags(str(field_val))
if result_repr == '':
result_repr = '&nbsp;'
if first: # First column is a special case
first = False
url = cl.url_for_result(result)
result_id = getattr(result, pk)
yield ('<th%s><a href="%s"%s>%s</a></th>' % \
(row_class, url, (cl.is_popup and ' onclick="opener.dismissRelatedLookupPopup(window, %r); return false;"' % result_id or ''), result_repr))
else:
yield ('<td%s>%s</td>' % (row_class, result_repr))
def results(cl):
for res in cl.result_list:
yield list(items_for_result(cl,res))
#@inclusion_tag("admin/change_list_results")
def result_list(cl):
res = list(results(cl))
return {'cl': cl,
'result_headers': list(result_headers(cl)),
'results': list(results(cl))}
result_list = inclusion_tag("admin/change_list_results")(result_list)
#@inclusion_tag("admin/date_hierarchy")
def date_hierarchy(cl):
lookup_opts, params, lookup_params, lookup_mod = \
cl.lookup_opts, cl.params, cl.lookup_params, cl.lookup_mod
if lookup_opts.admin.date_hierarchy:
field_name = lookup_opts.admin.date_hierarchy
year_field = '%s__year' % field_name
month_field = '%s__month' % field_name
day_field = '%s__day' % field_name
field_generic = '%s__' % field_name
year_lookup = params.get(year_field)
month_lookup = params.get(month_field)
day_lookup = params.get(day_field)
def link(d):
return cl.get_query_string(d, [field_generic])
def get_dates(unit, params):
return getattr(lookup_mod, 'get_%s_list' % field_name)(unit, **params)
if year_lookup and month_lookup and day_lookup:
month_name = MONTHS[int(month_lookup)]
return {
'show': True,
'back': {
'link': link({year_field: year_lookup, month_field: month_lookup}),
'title': "%s %s" % (month_name, year_lookup)
},
'choices': [{'title': "%s %s" % (month_name, day_lookup)}]
}
elif year_lookup and month_lookup:
date_lookup_params = lookup_params.copy()
date_lookup_params.update({year_field: year_lookup, month_field: month_lookup})
days = get_dates('day', date_lookup_params)
return {
'show': True,
'back': {
'link': link({year_field: year_lookup}),
'title': year_lookup
},
'choices': [{
'link': link({year_field: year_lookup, month_field: month_lookup, day_field: day.day}),
'title': day.strftime('%B %d')
} for day in days]
}
elif year_lookup:
date_lookup_params = lookup_params.copy()
date_lookup_params.update({year_field: year_lookup})
months = get_dates('month', date_lookup_params)
return {
'show' : True,
'back': {
'link' : link({}),
'title': _('All dates')
},
'choices': [{
'link': link( {year_field: year_lookup, month_field: month.month}),
'title': "%s %s" % (month.strftime('%B') , month.year)
} for month in months]
}
else:
years = get_dates('year', lookup_params)
return {
'show': True,
'choices': [{
'link': link({year_field: year.year}),
'title': year.year
} for year in years ]
}
date_hierarchy = inclusion_tag('admin/date_hierarchy')(date_hierarchy)
#@inclusion_tag('admin/search_form')
def search_form(cl):
return {
'cl': cl,
'show_result_count': cl.result_count != cl.full_result_count and not cl.opts.one_to_one_field,
'search_var': SEARCH_VAR
}
search_form = inclusion_tag('admin/search_form')(search_form)
#@inclusion_tag('admin/filter')
def filter(cl, spec):
return {'title': spec.title(), 'choices' : list(spec.choices(cl))}
filter = inclusion_tag('admin/filter')(filter)
#@inclusion_tag('admin/filters')
def filters(cl):
return {'cl': cl}
filters = inclusion_tag('admin/filters')(filters)

View File

@ -0,0 +1,258 @@
from django.core import template, template_loader, meta
from django.utils.html import escape
from django.utils.text import capfirst
from django.utils.functional import curry
from django.core.template.decorators import simple_tag, inclusion_tag
from django.contrib.admin.views.main import AdminBoundField
from django.core.meta.fields import BoundField, Field
from django.core.meta import BoundRelatedObject, TABULAR, STACKED
from django.conf.settings import ADMIN_MEDIA_PREFIX
import re
word_re = re.compile('[A-Z][a-z]+')
def class_name_to_underscored(name):
return '_'.join([s.lower() for s in word_re.findall(name)[:-1]])
#@simple_tag
def include_admin_script(script_path):
return '<script type="text/javascript" src="%s%s"></script>' % (ADMIN_MEDIA_PREFIX, script_path)
include_admin_script = simple_tag(include_admin_script)
#@inclusion_tag('admin/submit_line', takes_context=True)
def submit_row(context, bound_manipulator):
change = context['change']
add = context['add']
show_delete = context['show_delete']
has_delete_permission = context['has_delete_permission']
is_popup = context['is_popup']
return {
'onclick_attrib': (bound_manipulator.ordered_objects and change
and 'onclick="submitOrderForm();"' or ''),
'show_delete_link': (not is_popup and has_delete_permission
and (change or show_delete)),
'show_save_as_new': not is_popup and change and bound_manipulator.save_as,
'show_save_and_add_another': not is_popup and (not bound_manipulator.save_as or add),
'show_save_and_continue': not is_popup,
'show_save': True
}
submit_row = inclusion_tag('admin/submit_line', takes_context=True)(submit_row)
#@simple_tag
def field_label(bound_field):
class_names = []
if isinstance(bound_field.field, meta.BooleanField):
class_names.append("vCheckboxLabel")
colon = ""
else:
if not bound_field.field.blank:
class_names.append('required')
if not bound_field.first:
class_names.append('inline')
colon = ":"
class_str = class_names and ' class="%s"' % ' '.join(class_names) or ''
return '<label for="%s"%s>%s%s</label> ' % (bound_field.element_id, class_str, \
capfirst(bound_field.field.verbose_name), colon)
field_label = simple_tag(field_label)
class FieldWidgetNode(template.Node):
nodelists = {}
default = None
def __init__(self, bound_field_var):
self.bound_field_var = bound_field_var
def get_nodelist(cls, klass):
if not cls.nodelists.has_key(klass):
try:
field_class_name = klass.__name__
template_name = "widget/%s" % \
class_name_to_underscored(field_class_name)
nodelist = template_loader.get_template(template_name).nodelist
except template.TemplateDoesNotExist:
super_klass = bool(klass.__bases__) and klass.__bases__[0] or None
if super_klass and super_klass != Field:
nodelist = cls.get_nodelist(super_klass)
else:
if not cls.default:
cls.default = template_loader.get_template("widget/default").nodelist
nodelist = cls.default
cls.nodelists[klass] = nodelist
return nodelist
else:
return cls.nodelists[klass]
get_nodelist = classmethod(get_nodelist)
def render(self, context):
bound_field = template.resolve_variable(self.bound_field_var, context)
context.push()
context['bound_field'] = bound_field
output = self.get_nodelist(bound_field.field.__class__).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, field_mapping, fields):
self.field_mapping = field_mapping
self.fields = fields
self.bound_fields = [AdminBoundField(field, self.field_mapping, field_mapping['original'])
for field in self.fields]
class TabularBoundRelatedObject(BoundRelatedObject):
def __init__(self, related_object, field_mapping, original):
super(TabularBoundRelatedObject, self).__init__(related_object, field_mapping, original)
self.field_wrapper_list = [FieldWrapper(field) for field in self.relation.editable_fields()]
fields = self.relation.editable_fields()
self.form_field_collection_wrappers = [FormFieldCollectionWrapper(field_mapping, fields)
for field_mapping in self.field_mappings]
self.original_row_needed = max([fw.use_raw_id_admin() for fw in self.field_wrapper_list])
self.show_url = original and hasattr(self.relation.opts, 'get_absolute_url')
def template_name(self):
return "admin/edit_inline_tabular"
class StackedBoundRelatedObject(BoundRelatedObject):
def __init__(self, related_object, field_mapping, original):
super(StackedBoundRelatedObject, self).__init__(related_object, field_mapping, original)
fields = self.relation.editable_fields()
self.form_field_collection_wrappers = [FormFieldCollectionWrapper(field_mapping ,fields)
for field_mapping in self.field_mappings]
self.show_url = original and hasattr(self.relation.opts, 'get_absolute_url')
def template_name(self):
return "admin/edit_inline_stacked"
bound_related_object_overrides = {
TABULAR: TabularBoundRelatedObject,
STACKED: StackedBoundRelatedObject,
}
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)
context.push()
klass = relation.field.rel.edit_inline
bound_related_object_class = bound_related_object_overrides.get(klass, klass)
original = context.get('original', None)
bound_related_object = relation.bind(context['form'], original, bound_related_object_class)
context['bound_related_object'] = bound_related_object
t = template_loader.get_template(bound_related_object.template_name())
output = t.render(context)
context.pop()
return output
#@simple_tag
def output_all(form_fields):
return ''.join([str(f) for f in form_fields])
output_all = simple_tag(output_all)
#@simple_tag
def auto_populated_field_script(auto_pop_fields, change = False):
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)
auto_populated_field_script = simple_tag(auto_populated_field_script)
#@simple_tag
def filter_interface_script_maybe(bound_field):
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, "%s"); });</script>\n' % (
f.name, f.verbose_name, f.rel.filter_interface-1, ADMIN_MEDIA_PREFIX)
else:
return ''
filter_interface_script_maybe = simple_tag(filter_interface_script_maybe)
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])
def register_one_arg_tag(node):
tag_name = class_name_to_underscored(node.__name__)
parse_func = curry(do_one_arg_tag, node)
template.register_tag(tag_name, parse_func)
one_arg_tag_nodes = (
FieldWidgetNode,
EditInlineNode,
)
for node in one_arg_tag_nodes:
register_one_arg_tag(node)
#@inclusion_tag('admin/field_line', takes_context=True)
def admin_field_line(context, argument_val):
if (isinstance(argument_val, BoundField)):
bound_fields = [argument_val]
else:
bound_fields = [bf for bf in argument_val]
add = context['add']
change = context['change']
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 {
'add': context['add'],
'change': context['change'],
'bound_fields': bound_fields,
'class_names': " ".join(class_names),
}
admin_field_line = inclusion_tag('admin/field_line', takes_context=True)(admin_field_line)
#@simple_tag
def object_pk(bound_manip, ordered_obj):
return bound_manip.get_ordered_object_pk(ordered_obj)
object_pk = simple_tag(object_pk)

File diff suppressed because it is too large Load Diff

View File

@ -90,15 +90,7 @@ class Manipulator:
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): field.convert_post_data(new_data)
new_data.setlist(field.field_name,
[field.__class__.html2python(data) for data in new_data.getlist(field.field_name)])
else:
try:
# individual fields deal with None values themselves
new_data.setlist(field.field_name, [field.__class__.html2python(None)])
except EmptyValue:
new_data.setlist(field.field_name, [])
class FormWrapper: class FormWrapper:
""" """
@ -106,24 +98,36 @@ 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=True):
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, []))
raise KeyError 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, "Could not find Formfield or InlineObjectCollection named %r" % key
def fill_inline_collections(self):
if not self._inline_collections:
ic = []
related_objects = self.manipulator.get_related_objects()
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 != {}
@ -166,6 +170,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):
@ -185,9 +192,66 @@ 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, [])
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.
@ -220,6 +284,37 @@ 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 = ''
return data
def convert_post_data(self, new_data):
name = self.get_member_name()
if new_data.has_key(self.field_name):
d = new_data.getlist(self.field_name)
try:
converted_data = [self.__class__.html2python(data) for data in d]
except ValueError:
converted_data = d
new_data.setlist(name, converted_data)
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): def get_id(self):
"Returns the HTML 'id' attribute for this form field." "Returns the HTML 'id' attribute for this form field."
return FORM_FIELD_ID_PREFIX + self.field_name return FORM_FIELD_ID_PREFIX + self.field_name
@ -313,11 +408,13 @@ class CheckboxField(FormField):
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">' % \ output = ['<select id="%s" class="v%s%s" name="%s" size="%s">' % \
@ -347,12 +444,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):
""" """
@ -483,8 +582,8 @@ class CheckboxSelectMultipleField(SelectMultipleField):
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" class="v%s" name="%s"%s /> <label for="%s">%s</label></li>' % \ output.append('<li><input type="checkbox" id="%s" class="v%s" name="%s"%s /> <label for="%s">%s</label></li>' % \
(self.get_id(), self.__class__.__name__, field_name, checked_html, (self.get_id() + value , self.__class__.__name__, field_name, checked_html,
self.get_id(), choice)) self.get_id() + value, choice))
output.append('</ul>') output.append('</ul>')
return '\n'.join(output) return '\n'.join(output)
@ -528,8 +627,10 @@ class ImageUploadField(FileUploadField):
#################### ####################
class IntegerField(TextField): class IntegerField(TextField):
def __init__(self, field_name, length=10, maxlength=None, is_required=False, validator_list=[]): def __init__(self, field_name, length=10, maxlength=None, is_required=False, validator_list=[], member_name=None):
validator_list = [self.isInteger] + validator_list validator_list = [self.isInteger] + validator_list
if member_name is not None:
self.member_name = member_name
TextField.__init__(self, field_name, length, maxlength, is_required, validator_list) TextField.__init__(self, field_name, length, maxlength, is_required, validator_list)
def isInteger(self, field_data, all_data): def isInteger(self, field_data, all_data):
@ -784,6 +885,11 @@ class CommaSeparatedIntegerField(TextField):
except validators.ValidationError, e: except validators.ValidationError, e:
raise validators.CriticalValidationError, e.messages raise validators.CriticalValidationError, e.messages
class RawIdAdminField(CommaSeparatedIntegerField):
def html2python(data):
return data.split(',');
html2python = classmethod(html2python)
class XMLLargeTextField(LargeTextField): class XMLLargeTextField(LargeTextField):
""" """
A LargeTextField with an XML validator. The schema_path argument is the A LargeTextField with an XML validator. The schema_path argument is the

View File

@ -670,12 +670,12 @@ def get_validation_errors(outfile):
e.add(opts, '"ordering" refers to "%s", a field that doesn\'t exist.' % field_name) e.add(opts, '"ordering" refers to "%s", a field that doesn\'t exist.' % field_name)
# Check core=True, if needed. # Check core=True, if needed.
for rel_opts, rel_field in opts.get_inline_related_objects(): for related in opts.get_followed_related_objects():
try: try:
for f in rel_opts.fields: for f in related.opts.fields:
if f.core: if f.core:
raise StopIteration raise StopIteration
e.add(rel_opts, "At least one field in %s should have core=True, because it's being edited inline by %s.%s." % (rel_opts.object_name, opts.module_name, opts.object_name)) e.add(related.opts, "At least one field in %s should have core=True, because it's being edited inline by %s.%s." % (related.opts.object_name, opts.module_name, opts.object_name))
except StopIteration: except StopIteration:
pass pass

View File

@ -148,6 +148,140 @@ class FieldDoesNotExist(Exception):
class BadKeywordArguments(Exception): class BadKeywordArguments(Exception):
pass pass
class BoundRelatedObject(object):
def __init__(self, related_object, field_mapping, original):
self.relation = related_object
self.field_mappings = field_mapping[related_object.opts.module_name]
def template_name(self):
raise NotImplementedError
def __repr__(self):
return repr(self.__dict__)
class RelatedObject(object):
def __init__(self, parent_opts, opts, field):
self.parent_opts = parent_opts
self.opts = opts
self.field = field
self.edit_inline = field.rel.edit_inline
self.name = opts.module_name
self.var_name = opts.object_name.lower()
def flatten_data(self, follow, obj=None):
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:
# TODO: Fix for recursive manipulators.
fol = follow.get(f.name, None)
if fol:
field_data = f.flatten_data(fol, rel_instance)
for name, value in field_data.items():
instance_data['%s.%d.%s' % (self.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,
i.e. 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.get_method_name_part()
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):
"Get the fields in this class that should be edited inline."
return [f for f in self.opts.fields + self.opts.many_to_many if f.editable and f != self.field]
def get_follow(self, override=None):
if isinstance(override, bool):
if override:
over = {}
else:
return None
else:
if override:
over = override.copy()
elif self.edit_inline:
over = {}
else:
return None
over[self.field.name] = False
return self.opts.get_follow(over)
def __repr__(self):
return "<RelatedObject: %s related to %s>" % ( self.name, self.field.name)
def get_manipulator_fields(self, opts, manipulator, change, follow):
# TODO: Remove core fields stuff.
if change:
meth_name = 'get_%s_count' % self.get_method_name_part()
count = getattr(manipulator.original_object, meth_name)()
count += 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)
else:
count = self.field.rel.num_in_admin
fields = []
for i in range(count):
for f in self.opts.fields + self.opts.many_to_many:
if follow.get(f.name, False):
prefix = '%s.%d.' % (self.var_name, i)
fields.extend(f.get_manipulator_fields(self.opts, manipulator, change, name_prefix=prefix, rel=True))
return fields
def bind(self, field_mapping, original, bound_related_object_class=BoundRelatedObject):
return bound_related_object_class(self, field_mapping, original)
def get_method_name_part(self):
# This method encapsulates the logic that decides what name to give a
# method that retrieves related many-to-one objects. Usually it just
# uses the lower-cased object_name, but if the related object is in
# another app, its app_label is appended.
#
# Examples:
#
# # Normal case -- a related object in the same app.
# # This method returns "choice".
# Poll.get_choice_list()
#
# # A related object in a different app.
# # This method returns "lcom_bestofaward".
# Place.get_lcom_bestofaward_list() # "lcom_bestofaward"
rel_obj_name = self.field.rel.related_name or self.opts.object_name.lower()
if self.parent_opts.app_label != self.opts.app_label:
rel_obj_name = '%s_%s' % (self.opts.app_label, rel_obj_name)
return rel_obj_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,
@ -268,26 +402,6 @@ class Options:
def get_delete_permission(self): def get_delete_permission(self):
return 'delete_%s' % self.object_name.lower() return 'delete_%s' % self.object_name.lower()
def get_rel_object_method_name(self, rel_opts, rel_field):
# This method encapsulates the logic that decides what name to give a
# method that retrieves related many-to-one objects. Usually it just
# uses the lower-cased object_name, but if the related object is in
# another app, its app_label is appended.
#
# Examples:
#
# # Normal case -- a related object in the same app.
# # This method returns "choice".
# Poll.get_choice_list()
#
# # A related object in a different app.
# # This method returns "lcom_bestofaward".
# Place.get_lcom_bestofaward_list() # "lcom_bestofaward"
rel_obj_name = rel_field.rel.related_name or rel_opts.object_name.lower()
if self.app_label != rel_opts.app_label:
rel_obj_name = '%s_%s' % (rel_opts.app_label, rel_obj_name)
return rel_obj_name
def get_all_related_objects(self): def get_all_related_objects(self):
try: # Try the cache first. try: # Try the cache first.
return self._all_related_objects return self._all_related_objects
@ -298,7 +412,7 @@ class Options:
for klass in mod._MODELS: for klass in mod._MODELS:
for f in klass._meta.fields: for f in klass._meta.fields:
if f.rel and self == f.rel.to: if f.rel and self == f.rel.to:
rel_objs.append((klass._meta, f)) rel_objs.append(RelatedObject(self, klass._meta, f))
if self.has_related_links: if self.has_related_links:
# Manually add RelatedLink objects, which are a special case. # Manually add RelatedLink objects, which are a special case.
relatedlinks = get_module('relatedlinks', 'relatedlinks') relatedlinks = get_module('relatedlinks', 'relatedlinks')
@ -312,12 +426,31 @@ class Options:
'content_type__package__label__exact': self.app_label, 'content_type__package__label__exact': self.app_label,
'content_type__python_module_name__exact': self.module_name, 'content_type__python_module_name__exact': self.module_name,
}) })
rel_objs.append((relatedlinks.RelatedLink._meta, link_field)) rel_objs.append(RelatedObject(self, relatedlinks.RelatedLink._meta, link_field))
self._all_related_objects = rel_objs self._all_related_objects = rel_objs
return rel_objs return rel_objs
def get_inline_related_objects(self): def get_followed_related_objects(self, follow=None):
return [(a, b) for a, b in self.get_all_related_objects() if b.rel.edit_inline] if follow == None:
follow = self.get_follow()
return [f for f in self.get_all_related_objects() if follow.get(f.name, None)]
def get_data_holders(self, follow=None):
if follow == None:
follow = self.get_follow()
return [f for f in self.fields + self.many_to_many + self.get_all_related_objects() if follow.get(f.name, None)]
def get_follow(self, override=None):
follow = {}
for f in self.fields + self.many_to_many + self.get_all_related_objects():
if override and override.has_key(f.name):
child_override = override[f.name]
else:
child_override = None
fol = f.get_follow(child_override)
if fol:
follow[f.name] = fol
return follow
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()
@ -327,7 +460,7 @@ class Options:
try: try:
for f in klass._meta.many_to_many: for f in klass._meta.many_to_many:
if f.rel and self == f.rel.to: if f.rel and self == f.rel.to:
rel_objs.append((klass._meta, f)) rel_objs.append(RelatedObject(self, klass._meta, f))
raise StopIteration raise StopIteration
except StopIteration: except StopIteration:
continue continue
@ -345,11 +478,12 @@ class Options:
self._ordered_objects = objects self._ordered_objects = objects
return self._ordered_objects return self._ordered_objects
def has_field_type(self, field_type): def has_field_type(self, field_type, follow=None):
""" """
Returns True if this object's admin form has at least one of the given Returns True if this object's admin form has at least one of the given
field_type (e.g. FileField). field_type (e.g. FileField).
""" """
# TODO: follow
if not hasattr(self, '_field_types'): if not hasattr(self, '_field_types'):
self._field_types = {} self._field_types = {}
if not self._field_types.has_key(field_type): if not self._field_types.has_key(field_type):
@ -359,8 +493,8 @@ class Options:
if isinstance(f, field_type): if isinstance(f, field_type):
raise StopIteration raise StopIteration
# Failing that, check related fields. # Failing that, check related fields.
for rel_obj, rel_field in self.get_inline_related_objects(): for related in self.get_followed_related_objects(follow):
for f in rel_obj.fields: for f in related.opts.fields:
if isinstance(f, field_type): if isinstance(f, field_type):
raise StopIteration raise StopIteration
except StopIteration: except StopIteration:
@ -597,6 +731,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)
@ -720,12 +855,9 @@ class ModelBase(type):
old_app._MODELS[i] = new_class old_app._MODELS[i] = new_class
# Replace all relationships to the old class with # Replace all relationships to the old class with
# relationships to the new one. # relationships to the new one.
for rel_opts, rel_field in model._meta.get_all_related_objects(): for related in model._meta.get_all_related_objects() + model._meta.get_all_related_many_to_many_objects():
rel_field.rel.to = opts related.field.rel.to = opts
for rel_opts, rel_field in model._meta.get_all_related_many_to_many_objects():
rel_field.rel.to = opts
break break
return new_class return new_class
class Model: class Model:
@ -826,9 +958,9 @@ def method_delete(opts, self):
if hasattr(self, '_pre_delete'): if hasattr(self, '_pre_delete'):
self._pre_delete() self._pre_delete()
cursor = db.db.cursor() cursor = db.db.cursor()
for rel_opts, rel_field in opts.get_all_related_objects(): for related in opts.get_all_related_objects():
rel_opts_name = opts.get_rel_object_method_name(rel_opts, rel_field) rel_opts_name = related.get_method_name_part()
if isinstance(rel_field.rel, OneToOne): if isinstance(related.field.rel, OneToOne):
try: try:
sub_obj = getattr(self, 'get_%s' % rel_opts_name)() sub_obj = getattr(self, 'get_%s' % rel_opts_name)()
except ObjectDoesNotExist: except ObjectDoesNotExist:
@ -838,9 +970,9 @@ def method_delete(opts, self):
else: else:
for sub_obj in getattr(self, 'get_%s_list' % rel_opts_name)(): for sub_obj in getattr(self, 'get_%s_list' % rel_opts_name)():
sub_obj.delete() sub_obj.delete()
for rel_opts, rel_field in opts.get_all_related_many_to_many_objects(): for related in opts.get_all_related_many_to_many_objects():
cursor.execute("DELETE FROM %s WHERE %s=%%s" % \ cursor.execute("DELETE FROM %s WHERE %s=%%s" % \
(db.db.quote_name(rel_field.get_m2m_db_table(rel_opts)), (db.db.quote_name(related.field.get_m2m_db_table(related.opts)),
db.db.quote_name(self._meta.object_name.lower() + '_id')), [getattr(self, opts.pk.attname)]) db.db.quote_name(self._meta.object_name.lower() + '_id')), [getattr(self, opts.pk.attname)])
for f in opts.many_to_many: for f in opts.many_to_many:
cursor.execute("DELETE FROM %s WHERE %s=%%s" % \ cursor.execute("DELETE FROM %s WHERE %s=%%s" % \
@ -1474,6 +1606,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_related_objects = curry(manipulator_get_related_objects, 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:
@ -1487,7 +1621,9 @@ def get_manipulator(opts, klass, extra_methods, add=False, change=False):
setattr(man, k, v) setattr(man, k, v)
return man return man
def manipulator_init(opts, add, change, self, obj_key=None): def manipulator_init(opts, add, change, self, obj_key=None, follow=None):
self.follow = opts.get_follow(follow)
if change: if change:
assert obj_key is not None, "ChangeManipulator.__init__() must be passed obj_key parameter." assert obj_key is not None, "ChangeManipulator.__init__() must be passed obj_key parameter."
self.obj_key = obj_key self.obj_key = obj_key
@ -1511,40 +1647,37 @@ def manipulator_init(opts, add, change, self, obj_key=None):
else: else:
raise raise
self.fields = [] self.fields = []
for f in opts.fields + opts.many_to_many: for f in opts.fields + opts.many_to_many:
if f.editable and not (f.primary_key and change) and (not f.rel or not f.rel.edit_inline): if self.follow.get(f.name, False):
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 f in opts.get_all_related_objects():
if change: if self.follow.get(f.name, False):
count = getattr(self.original_object, 'get_%s_count' % opts.get_rel_object_method_name(rel_opts, rel_field))() fol = self.follow[f.name]
count += rel_field.rel.num_extra_on_change self.fields.extend(f.get_manipulator_fields(opts, self, change, fol))
if rel_field.rel.min_num_in_admin:
count = max(count, rel_field.rel.min_num_in_admin)
if rel_field.rel.max_num_in_admin:
count = min(count, rel_field.rel.max_num_in_admin)
else:
count = rel_field.rel.num_in_admin
for f in rel_opts.fields + rel_opts.many_to_many:
if f.editable and f != rel_field and (not f.primary_key or (f.primary_key and change)):
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))
# Add field for ordering. # Add field for ordering.
if change and opts.get_ordered_objects(): if change and opts.get_ordered_objects():
self.fields.append(formfields.CommaSeparatedIntegerField(field_name="order_")) self.fields.append(formfields.CommaSeparatedIntegerField(field_name="order_"))
def manipulator_save(opts, klass, add, change, self, new_data): def manipulator_save(opts, klass, add, change, self, new_data):
# TODO: big cleanup when core fields go -> use recursive manipulators.
from django.utils.datastructures import DotExpandedDict from django.utils.datastructures import DotExpandedDict
params = {} params = {}
for f in opts.fields: for f in opts.fields:
# Fields with auto_now_add are another special case; they should keep # Fields with auto_now_add should keep their original value in the change stage.
# their original value in the change stage. auto_now_add = change and getattr(f, 'auto_now_add', False)
if change and getattr(f, 'auto_now_add', False): if self.follow.get(f.name, None) and not auto_now_add:
params[f.attname] = getattr(self.original_object, f.attname) param = f.get_manipulator_new_data(new_data)
else: else:
params[f.attname] = f.get_manipulator_new_data(new_data) if change:
param = getattr(self.original_object, f.attname)
else:
param = f.get_default()
params[f.attname] = param
if change: if change:
params[opts.pk.attname] = self.obj_key params[opts.pk.attname] = self.obj_key
@ -1567,21 +1700,27 @@ def manipulator_save(opts, klass, add, change, self, new_data):
# Save many-to-many objects. Example: Poll.set_sites() # Save many-to-many objects. Example: Poll.set_sites()
for f in opts.many_to_many: for f in opts.many_to_many:
if self.follow.get(f.name, None):
if not f.rel.edit_inline: if not f.rel.edit_inline:
was_changed = getattr(new_object, 'set_%s' % f.name)(new_data.getlist(f.name)) was_changed = getattr(new_object, 'set_%s' % f.name)(new_data.getlist(f.name))
if change and was_changed: if change and was_changed:
self.fields_changed.append(f.verbose_name) self.fields_changed.append(f.verbose_name)
expanded_data = DotExpandedDict(new_data.data)
# Save many-to-one objects. Example: Add the Choice objects for a Poll. # Save many-to-one objects. Example: Add the Choice objects for a Poll.
for rel_opts, rel_field in opts.get_inline_related_objects(): for related in opts.get_all_related_objects():
# Create obj_list, which is a DotExpandedDict such as this: # Create obj_list, which is a DotExpandedDict such as this:
# [('0', {'id': ['940'], 'choice': ['This is the first choice']}), # [('0', {'id': ['940'], 'choice': ['This is the first choice']}),
# ('1', {'id': ['941'], 'choice': ['This is the second choice']}), # ('1', {'id': ['941'], 'choice': ['This is the second choice']}),
# ('2', {'id': [''], 'choice': ['']})] # ('2', {'id': [''], 'choice': ['']})]
obj_list = DotExpandedDict(new_data.data)[rel_opts.object_name.lower()].items() child_follow = self.follow.get(related.name, None)
if child_follow:
obj_list = expanded_data[related.var_name].items()
obj_list.sort(lambda x, y: cmp(int(x[0]), int(y[0]))) obj_list.sort(lambda x, y: cmp(int(x[0]), int(y[0])))
params = {} params = {}
# For each related item... # For each related item...
for _, rel_new_data in obj_list: for _, rel_new_data in obj_list:
@ -1594,15 +1733,15 @@ def manipulator_save(opts, klass, add, change, self, new_data):
# Get a reference to the old object. We'll use it to compare the # Get a reference to the old object. We'll use it to compare the
# old to the new, to see which fields have changed. # old to the new, to see which fields have changed.
if change:
old_rel_obj = None old_rel_obj = None
if rel_new_data[rel_opts.pk.name][0]: if change:
if rel_new_data[related.opts.pk.name][0]:
try: try:
old_rel_obj = getattr(self.original_object, 'get_%s' % opts.get_rel_object_method_name(rel_opts, rel_field))(**{'%s__exact' % rel_opts.pk.name: rel_new_data[rel_opts.pk.attname][0]}) old_rel_obj = getattr(self.original_object, 'get_%s' % related.get_method_name_part() )(**{'%s__exact' % related.opts.pk.name: rel_new_data[related.opts.pk.attname][0]})
except ObjectDoesNotExist: except ObjectDoesNotExist:
pass pass
for f in rel_opts.fields: for f in related.opts.fields:
if f.core and not isinstance(f, FileField) and f.get_manipulator_new_data(rel_new_data, rel=True) in (None, ''): if f.core and not isinstance(f, FileField) and f.get_manipulator_new_data(rel_new_data, rel=True) in (None, ''):
all_cores_given = False all_cores_given = False
elif f.core and not isinstance(f, FileField) and f.get_manipulator_new_data(rel_new_data, rel=True) not in (None, ''): elif f.core and not isinstance(f, FileField) and f.get_manipulator_new_data(rel_new_data, rel=True) not in (None, ''):
@ -1611,57 +1750,66 @@ def manipulator_save(opts, klass, add, change, self, new_data):
# previously, according to the given ID. If the ID wasn't # previously, according to the given ID. If the ID wasn't
# given, use a default value. FileFields are also a special # given, use a default value. FileFields are also a special
# case, because they'll be dealt with later. # case, because they'll be dealt with later.
if change and (isinstance(f, FileField) or not f.editable):
if rel_new_data.get(rel_opts.pk.attname, False) and rel_new_data[rel_opts.pk.attname][0]: if f == related.field:
params[f.attname] = getattr(old_rel_obj, f.attname) param = getattr(new_object, related.field.rel.field_name)
else:
params[f.attname] = f.get_default()
elif f == rel_field:
params[f.attname] = getattr(new_object, rel_field.rel.field_name)
elif add and isinstance(f, AutoField): elif add and isinstance(f, AutoField):
params[f.attname] = None param = None
elif change and (isinstance(f, FileField) or not child_follow.get(f.name, None)):
if old_rel_obj:
param = getattr(old_rel_obj, f.column)
else: else:
params[f.attname] = f.get_manipulator_new_data(rel_new_data, rel=True) param = f.get_default()
else:
param = f.get_manipulator_new_data(rel_new_data, rel=True)
if param != None:
params[f.attname] = param
# Related links are a special case, because we have to # Related links are a special case, because we have to
# manually set the "content_type_id" and "object_id" fields. # manually set the "content_type_id" and "object_id" fields.
if opts.has_related_links and rel_opts.module_name == 'relatedlinks': if opts.has_related_links and related.opts.module_name == 'relatedlinks':
contenttypes_mod = get_module('core', 'contenttypes') contenttypes_mod = get_module('core', 'contenttypes')
params['content_type_id'] = contenttypes_mod.get_object(package__label__exact=opts.app_label, python_module_name__exact=opts.module_name).id params['content_type_id'] = contenttypes_mod.get_object(package__label__exact=opts.app_label, python_module_name__exact=opts.module_name).id
params['object_id'] = new_object.id params['object_id'] = new_object.id
# Create the related item. # Create the related item.
new_rel_obj = rel_opts.get_model_module().Klass(**params) new_rel_obj = related.opts.get_model_module().Klass(**params)
# If all the core fields were provided (non-empty), save the item. # If all the core fields were provided (non-empty), save the item.
if all_cores_given: if all_cores_given:
new_rel_obj.save() new_rel_obj.save()
# Save any uploaded files. # Save any uploaded files.
for f in rel_opts.fields: for f in related.opts.fields:
if isinstance(f, FileField) and rel_new_data.get(f.attname, False): if child_follow.get(f.name, None):
if isinstance(f, FileField) and rel_new_data.get(f.name, False):
f.save_file(rel_new_data, new_rel_obj, change and old_rel_obj or None, old_rel_obj is not None, rel=True) f.save_file(rel_new_data, new_rel_obj, change and old_rel_obj or None, old_rel_obj is not None, rel=True)
# Calculate whether any fields have changed. # Calculate whether any fields have changed.
if change: if change:
if not old_rel_obj: # This object didn't exist before. if not old_rel_obj: # This object didn't exist before.
self.fields_added.append('%s "%r"' % (rel_opts.verbose_name, new_rel_obj)) self.fields_added.append('%s "%s"' % (related.opts.verbose_name, new_rel_obj))
else: else:
for f in rel_opts.fields: for f in related.opts.fields:
if not f.primary_key and f != rel_field and str(getattr(old_rel_obj, f.attname)) != str(getattr(new_rel_obj, f.attname)): if not f.primary_key and f != related.field and str(getattr(old_rel_obj, f.attname)) != str(getattr(new_rel_obj, f.attname)):
self.fields_changed.append('%s for %s "%r"' % (f.verbose_name, rel_opts.verbose_name, new_rel_obj)) self.fields_changed.append('%s for %s "%s"' % (f.verbose_name, related.opts.verbose_name, new_rel_obj))
# Save many-to-many objects. # Save many-to-many objects.
for f in rel_opts.many_to_many: for f in related.opts.many_to_many:
if not f.rel.edit_inline: if child_follow.get(f.name, None) and not f.rel.edit_inline:
was_changed = getattr(new_rel_obj, 'set_%s' % f.name)(rel_new_data[f.attname]) was_changed = getattr(new_rel_obj, 'set_%s' % f.name)(rel_new_data[f.attname])
if change and was_changed: if change and was_changed:
self.fields_changed.append('%s for %s "%s"' % (f.verbose_name, rel_opts.verbose_name, new_rel_obj)) self.fields_changed.append('%s for %s "%s"' % (f.verbose_name, related.opts.verbose_name, new_rel_obj))
# If, in the change stage, all of the core fields were blank and # If, in the change stage, all of the core fields were blank and
# the primary key (ID) was provided, delete the item. # the primary key (ID) was provided, delete the item.
if change and all_cores_blank and rel_new_data.has_key(rel_opts.pk.attname) and rel_new_data[rel_opts.pk.attname][0]: if change and all_cores_blank and old_rel_obj:
new_rel_obj.delete() new_rel_obj.delete()
self.fields_deleted.append('%s "%r"' % (rel_opts.verbose_name, old_rel_obj)) self.fields_deleted.append('%s "%s"' % (related.opts.verbose_name, old_rel_obj))
# Save the order, if applicable. # Save the order, if applicable.
if change and opts.get_ordered_objects(): if change and opts.get_ordered_objects():
@ -1670,6 +1818,17 @@ 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_related_objects(opts, klass, add, change, self):
return opts.get_followed_related_objects(self.follow)
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(self.follow):
fol = self.follow.get(f.name)
new_data.update(f.flatten_data(fol, 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]
@ -1678,6 +1837,9 @@ def manipulator_validator_unique_together(field_name_list, opts, self, field_dat
else: else:
kwargs = {'%s__iexact' % field_name_list[0]: field_data} kwargs = {'%s__iexact' % field_name_list[0]: field_data}
for f in field_list[1:]: for f in field_list[1:]:
# This is really not going to work for fields that have different
# form fields, e.g. DateTime.
# This validation needs to occur after html2python to be effective.
field_val = all_data.get(f.attname, None) field_val = all_data.get(f.attname, None)
if field_val is None: if field_val is None:
# This will be caught by another validator, assuming the field # This will be caught by another validator, assuming the field

View File

@ -59,6 +59,24 @@ def manipulator_validator_unique(f, opts, self, field_data, all_data):
return return
raise validators.ValidationError, _("%(optname)s with this %(fieldname)s already exists.") % {'optname': capfirst(opts.verbose_name), 'fieldname': f.verbose_name} raise validators.ValidationError, _("%(optname)s with this %(fieldname)s already exists.") % {'optname': capfirst(opts.verbose_name), 'fieldname': f.verbose_name}
class BoundField(object):
def __init__(self, field, field_mapping, original):
self.field = field
self.original = original
self.form_fields = self.resolve_form_fields(field_mapping)
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):
if self.original:
return self.original.__dict__[self.field.column]
def __repr__(self):
return "BoundField:(%s, %s)" % (self.field.name, self.form_fields)
# A guide to Field parameters: # A guide to Field parameters:
# #
@ -185,7 +203,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 ""
@ -207,28 +225,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.attname
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()
@ -294,7 +312,37 @@ class Field(object):
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.attname), repr(x)) for x in rel_obj.get_model_module().get_list(**self.rel.limit_choices_to)] return first_choice + [(getattr(x, rel_obj.pk.attname), str(x))
for x in rel_obj.get_model_module().get_list(**self.rel.limit_choices_to)]
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.attname)
else:
return self.get_default()
def flatten_data(self, follow, 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.attname: self._get_val_from_obj(obj)}
def get_follow(self, override=None):
if override != None:
return override
else:
return self.editable
def bind(self, fieldmapping, original, bound_field_class=BoundField):
return bound_field_class(self, fieldmapping, original)
class AutoField(Field): class AutoField(Field):
empty_strings_allowed = False empty_strings_allowed = False
@ -335,8 +383,10 @@ class DateField(Field):
empty_strings_allowed = False empty_strings_allowed = False
def __init__(self, verbose_name=None, name=None, auto_now=False, auto_now_add=False, **kwargs): def __init__(self, verbose_name=None, name=None, auto_now=False, auto_now_add=False, **kwargs):
self.auto_now, self.auto_now_add = auto_now, auto_now_add self.auto_now, self.auto_now_add = auto_now, auto_now_add
#HACKs : auto_now_add/auto_now should be done as a default or a pre_save...
if auto_now or auto_now_add: if auto_now or auto_now_add:
kwargs['editable'] = False kwargs['editable'] = False
kwargs['blank'] = True
Field.__init__(self, verbose_name, name, **kwargs) Field.__init__(self, verbose_name, name, **kwargs)
def get_db_prep_lookup(self, lookup_type, value): def get_db_prep_lookup(self, lookup_type, value):
@ -351,6 +401,13 @@ class DateField(Field):
return datetime.datetime.now() return datetime.datetime.now()
return value return value
# Needed because of horrible auto_now[_add] behaviour wrt. editable
def get_follow(self, override=None):
if override != None:
return override
else:
return self.editable or self.auto_now or self.auto_now_add
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.
if value is not None: if value is not None:
@ -360,6 +417,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, follow, obj = None):
val = self._get_val_from_obj(obj)
return {self.attname: (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.
@ -389,6 +450,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,follow, 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 __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
kwargs['maxlength'] = 75 kwargs['maxlength'] = 75
@ -587,6 +654,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,follow, obj = None):
val = self._get_val_from_obj(obj)
return {self.attname: (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:
@ -647,6 +718,24 @@ class ForeignKey(Field):
else: else:
return [formfields.IntegerField] return [formfields.IntegerField]
def get_db_prep_save(self,value):
if value == '' or value == None:
return None
else:
return int(value)
def flatten_data(self, follow, 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.attname : choice_list[1][0] }
return Field.flatten_data(self, follow, 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)
@ -662,11 +751,14 @@ class ManyToManyField(Field):
def get_manipulator_field_objs(self): def get_manipulator_field_objs(self):
if self.rel.raw_id_admin: if self.rel.raw_id_admin:
return [formfields.CommaSeparatedIntegerField] return [formfields.RawIdAdminField]
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)
@ -688,6 +780,25 @@ class ManyToManyField(Field):
'value': len(badkeys) == 1 and badkeys[0] or tuple(badkeys), 'value': len(badkeys) == 1 and badkeys[0] or tuple(badkeys),
} }
def flatten_data(self, follow, 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.attname) for instance in get_list_func()]
if self.rel.raw_id_admin:
new_data[self.name] = ",".join([str(id) for id in instance_ids])
else:
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:
choices_list = self.get_choices_default()
if len(choices_list) == 1:
print self.name, choices_list[0][0]
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')
@ -753,6 +864,66 @@ class OneToOne(ManyToOne):
self.lookup_overrides = lookup_overrides or {} self.lookup_overrides = lookup_overrides or {}
self.raw_id_admin = raw_id_admin self.raw_id_admin = raw_id_admin
class BoundFieldLine(object):
def __init__(self, field_line, field_mapping, original, bound_field_class=BoundField):
self.bound_fields = [field.bind(field_mapping, original, bound_field_class) for field in field_line]
def __iter__(self):
for bound_field in self.bound_fields:
yield bound_field
def __len__(self):
return len(self.bound_fields)
class FieldLine(object):
def __init__(self, field_locator_func, linespec):
if isinstance(linespec, basestring):
self.fields = [field_locator_func(linespec)]
else:
self.fields = [field_locator_func(field_name) for field_name in linespec]
def bind(self, field_mapping, original, bound_field_line_class=BoundFieldLine):
return bound_field_line_class(self, field_mapping, original)
def __iter__(self):
for field in self.fields:
yield field
def __len__(self):
return len(self.fields)
class BoundFieldSet(object):
def __init__(self, field_set, field_mapping, original, bound_field_line_class=BoundFieldLine):
self.name = field_set.name
self.classes = field_set.classes
self.bound_field_lines = [field_line.bind(field_mapping,original, bound_field_line_class) for field_line in field_set]
def __iter__(self):
for bound_field_line in self.bound_field_lines:
yield bound_field_line
def __len__(self):
return len(self.bound_field_lines)
class FieldSet(object):
def __init__(self, name, classes, field_locator_func, line_specs):
self.name = name
self.field_lines = [FieldLine(field_locator_func, line_spec) for line_spec in line_specs]
self.classes = classes
def __repr__(self):
return "FieldSet:(%s,%s)" % (self.name, self.field_lines)
def bind(self, field_mapping, original, bound_field_set_class=BoundFieldSet):
return bound_field_set_class(self, field_mapping, original)
def __iter__(self):
for field_line in self.field_lines:
yield field_line
def __len__(self):
return len(self.field_lines)
class Admin: class Admin:
def __init__(self, fields=None, js=None, list_display=None, list_filter=None, date_hierarchy=None, def __init__(self, fields=None, js=None, list_display=None, list_filter=None, date_hierarchy=None,
save_as=False, ordering=None, search_fields=None, save_on_top=False, list_select_related=False): save_as=False, ordering=None, search_fields=None, save_on_top=False, list_select_related=False):
@ -766,26 +937,18 @@ class Admin:
self.save_on_top = save_on_top self.save_on_top = save_on_top
self.list_select_related = list_select_related self.list_select_related = list_select_related
def get_field_objs(self, opts): def get_field_sets(self, opts):
"""
Returns self.fields, except with fields as Field objects instead of
field names. If self.fields is None, defaults to putting every
non-AutoField field with editable=True in a single fieldset.
"""
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)]
}),)
else: else:
field_struct = self.fields field_struct = self.fields
new_fieldset_list = [] new_fieldset_list = []
for fieldset in field_struct: for fieldset in field_struct:
new_fieldset = [fieldset[0], {}] name = fieldset[0]
new_fieldset[1].update(fieldset[1]) fs_options = fieldset[1]
admin_fields = [] classes = fs_options.get('classes', ())
for field_name_or_list in fieldset[1]['fields']: line_specs = fs_options['fields']
if isinstance(field_name_or_list, basestring): new_fieldset_list.append(FieldSet(name, classes, opts.get_field, line_specs))
admin_fields.append([opts.get_field(field_name_or_list)])
else:
admin_fields.append([opts.get_field(field_name) for field_name in field_name_or_list])
new_fieldset[1]['fields'] = admin_fields
new_fieldset_list.append(new_fieldset)
return new_fieldset_list return new_fieldset_list

View File

@ -19,58 +19,61 @@ for mod in modules:
# Add "get_thingie", "get_thingie_count" and "get_thingie_list" methods # Add "get_thingie", "get_thingie_count" and "get_thingie_list" methods
# for all related objects. # for all related objects.
for rel_obj, rel_field in klass._meta.get_all_related_objects(): for related in klass._meta.get_all_related_objects():
# Determine whether this related object is in another app. # Determine whether this related object is in another app.
# If it's in another app, the method names will have the app # If it's in another app, the method names will have the app
# label prepended, and the add_BLAH() method will not be # label prepended, and the add_BLAH() method will not be
# generated. # generated.
rel_mod = rel_obj.get_model_module() rel_mod = related.opts.get_model_module()
rel_obj_name = klass._meta.get_rel_object_method_name(rel_obj, rel_field) rel_obj_name = related.get_method_name_part()
if isinstance(rel_field.rel, meta.OneToOne): if isinstance(related.field.rel, meta.OneToOne):
# Add "get_thingie" methods for one-to-one related objects. # Add "get_thingie" methods for one-to-one related objects.
# EXAMPLE: Place.get_restaurants_restaurant() # EXAMPLE: Place.get_restaurants_restaurant()
func = curry(meta.method_get_related, 'get_object', rel_mod, rel_field) func = curry(meta.method_get_related, 'get_object', rel_mod, related.field)
func.__doc__ = "Returns the associated `%s.%s` object." % (rel_obj.app_label, rel_obj.module_name) func.__doc__ = "Returns the associated `%s.%s` object." % (related.opts.app_label, related.opts.module_name)
setattr(klass, 'get_%s' % rel_obj_name, func) setattr(klass, 'get_%s' % rel_obj_name, func)
elif isinstance(rel_field.rel, meta.ManyToOne): elif isinstance(related.field.rel, meta.ManyToOne):
# Add "get_thingie" methods for many-to-one related objects. # Add "get_thingie" methods for many-to-one related objects.
# EXAMPLE: Poll.get_choice() # EXAMPLE: Poll.get_choice()
func = curry(meta.method_get_related, 'get_object', rel_mod, rel_field) func = curry(meta.method_get_related, 'get_object', rel_mod, related.field)
func.__doc__ = "Returns the associated `%s.%s` object matching the given criteria." % (rel_obj.app_label, rel_obj.module_name) func.__doc__ = "Returns the associated `%s.%s` object matching the given criteria." % \
(related.opts.app_label, related.opts.module_name)
setattr(klass, 'get_%s' % rel_obj_name, func) setattr(klass, 'get_%s' % rel_obj_name, func)
# Add "get_thingie_count" methods for many-to-one related objects. # Add "get_thingie_count" methods for many-to-one related objects.
# EXAMPLE: Poll.get_choice_count() # EXAMPLE: Poll.get_choice_count()
func = curry(meta.method_get_related, 'get_count', rel_mod, rel_field) func = curry(meta.method_get_related, 'get_count', rel_mod, related.field)
func.__doc__ = "Returns the number of associated `%s.%s` objects." % (rel_obj.app_label, rel_obj.module_name) func.__doc__ = "Returns the number of associated `%s.%s` objects." % \
(related.opts.app_label, related.opts.module_name)
setattr(klass, 'get_%s_count' % rel_obj_name, func) setattr(klass, 'get_%s_count' % rel_obj_name, func)
# Add "get_thingie_list" methods for many-to-one related objects. # Add "get_thingie_list" methods for many-to-one related objects.
# EXAMPLE: Poll.get_choice_list() # EXAMPLE: Poll.get_choice_list()
func = curry(meta.method_get_related, 'get_list', rel_mod, rel_field) func = curry(meta.method_get_related, 'get_list', rel_mod, related.field)
func.__doc__ = "Returns a list of associated `%s.%s` objects." % (rel_obj.app_label, rel_obj.module_name) func.__doc__ = "Returns a list of associated `%s.%s` objects." % \
(related.opts.app_label, related.opts.module_name)
setattr(klass, 'get_%s_list' % rel_obj_name, func) setattr(klass, 'get_%s_list' % rel_obj_name, func)
# Add "add_thingie" methods for many-to-one related objects, # Add "add_thingie" methods for many-to-one related objects,
# but only for related objects that are in the same app. # but only for related objects that are in the same app.
# EXAMPLE: Poll.add_choice() # EXAMPLE: Poll.add_choice()
if rel_obj.app_label == klass._meta.app_label: if related.opts.app_label == klass._meta.app_label:
func = curry(meta.method_add_related, rel_obj, rel_mod, rel_field) func = curry(meta.method_add_related, related.opts, rel_mod, related.field)
func.alters_data = True func.alters_data = True
setattr(klass, 'add_%s' % rel_obj_name, func) setattr(klass, 'add_%s' % rel_obj_name, func)
del func del func
del rel_obj_name, rel_mod, rel_obj, rel_field # clean up del rel_obj_name, rel_mod, related # clean up
# Do the same for all related many-to-many objects. # Do the same for all related many-to-many objects.
for rel_opts, rel_field in klass._meta.get_all_related_many_to_many_objects(): for related in klass._meta.get_all_related_many_to_many_objects():
rel_mod = rel_opts.get_model_module() rel_mod = related.opts.get_model_module()
rel_obj_name = klass._meta.get_rel_object_method_name(rel_opts, rel_field) rel_obj_name = related.get_method_name_part()
setattr(klass, 'get_%s' % rel_obj_name, curry(meta.method_get_related_many_to_many, 'get_object', klass._meta, rel_mod, rel_field)) setattr(klass, 'get_%s' % rel_obj_name, curry(meta.method_get_related_many_to_many, 'get_object', klass._meta, rel_mod, related.field))
setattr(klass, 'get_%s_count' % rel_obj_name, curry(meta.method_get_related_many_to_many, 'get_count', klass._meta, rel_mod, rel_field)) setattr(klass, 'get_%s_count' % rel_obj_name, curry(meta.method_get_related_many_to_many, 'get_count', klass._meta, rel_mod, related.field))
setattr(klass, 'get_%s_list' % rel_obj_name, curry(meta.method_get_related_many_to_many, 'get_list', klass._meta, rel_mod, rel_field)) setattr(klass, 'get_%s_list' % rel_obj_name, curry(meta.method_get_related_many_to_many, 'get_list', klass._meta, rel_mod, related.field))
if rel_opts.app_label == klass._meta.app_label: if related.opts.app_label == klass._meta.app_label:
func = curry(meta.method_set_related_many_to_many, rel_opts, rel_field) func = curry(meta.method_set_related_many_to_many, related.opts, related.field)
func.alters_data = True func.alters_data = True
setattr(klass, 'set_%s' % rel_opts.module_name, func) setattr(klass, 'set_%s' % related.opts.module_name, func)
del func del func
del rel_obj_name, rel_mod, rel_opts, rel_field # clean up del rel_obj_name, rel_mod, related # clean up
# Add "set_thingie_order" and "get_thingie_order" methods for objects # Add "set_thingie_order" and "get_thingie_order" methods for objects
# that are ordered with respect to this. # that are ordered with respect to this.

View File

@ -9,7 +9,7 @@ from django.core.exceptions import Http404, ObjectDoesNotExist, ImproperlyConfig
def create_object(request, app_label, module_name, template_name=None, def create_object(request, app_label, module_name, template_name=None,
template_loader=template_loader, extra_context={}, template_loader=template_loader, extra_context={},
post_save_redirect=None, login_required=False): post_save_redirect=None, login_required=False, follow=None):
""" """
Generic object-creation function. Generic object-creation function.
@ -22,17 +22,17 @@ def create_object(request, app_label, module_name, template_name=None,
return redirect_to_login(request.path) return redirect_to_login(request.path)
mod = models.get_module(app_label, module_name) mod = models.get_module(app_label, module_name)
manipulator = mod.AddManipulator() manipulator = mod.AddManipulator(follow=follow)
if request.POST: if request.POST:
# If data was POSTed, we're trying to create a new object # If data was POSTed, we're trying to create a new object
new_data = request.POST.copy() new_data = request.POST.copy()
# Check for errors # Check for errors
errors = manipulator.get_validation_errors(new_data) errors = manipulator.get_validation_errors(new_data)
manipulator.do_html2python(new_data)
if not errors: if not errors:
# No errors -- this means we can save the data! # No errors -- this means we can save the data!
manipulator.do_html2python(new_data)
new_object = manipulator.save(new_data) new_object = manipulator.save(new_data)
if not request.user.is_anonymous(): if not request.user.is_anonymous():
@ -48,7 +48,8 @@ def create_object(request, app_label, module_name, template_name=None,
raise ImproperlyConfigured("No URL to redirect to from generic create view.") raise ImproperlyConfigured("No URL to redirect to from generic create view.")
else: else:
# No POST, so we want a brand new form without any data or errors # No POST, so we want a brand new form without any data or errors
errors = new_data = {} errors = {}
new_data = manipulator.flatten_data()
# Create the FormWrapper, template, context, response # Create the FormWrapper, template, context, response
form = formfields.FormWrapper(manipulator, new_data, errors) form = formfields.FormWrapper(manipulator, new_data, errors)
@ -68,7 +69,7 @@ def create_object(request, app_label, module_name, template_name=None,
def update_object(request, app_label, module_name, object_id=None, slug=None, def update_object(request, app_label, module_name, object_id=None, slug=None,
slug_field=None, template_name=None, template_loader=template_loader, slug_field=None, template_name=None, template_loader=template_loader,
extra_lookup_kwargs={}, extra_context={}, post_save_redirect=None, extra_lookup_kwargs={}, extra_context={}, post_save_redirect=None,
login_required=False): login_required=False, follow=None):
""" """
Generic object-update function. Generic object-update function.
@ -98,13 +99,13 @@ def update_object(request, app_label, module_name, object_id=None, slug=None,
except ObjectDoesNotExist: except ObjectDoesNotExist:
raise Http404("%s.%s does not exist for %s" % (app_label, module_name, lookup_kwargs)) raise Http404("%s.%s does not exist for %s" % (app_label, module_name, lookup_kwargs))
manipulator = mod.ChangeManipulator(object.id) manipulator = mod.ChangeManipulator(object.id, follow=follow)
if request.POST: if request.POST:
new_data = request.POST.copy() new_data = request.POST.copy()
errors = manipulator.get_validation_errors(new_data) errors = manipulator.get_validation_errors(new_data)
if not errors:
manipulator.do_html2python(new_data) manipulator.do_html2python(new_data)
if not errors:
manipulator.save(new_data) manipulator.save(new_data)
if not request.user.is_anonymous(): if not request.user.is_anonymous():
@ -120,7 +121,7 @@ def update_object(request, app_label, module_name, object_id=None, slug=None,
else: else:
errors = {} errors = {}
# This makes sure the form acurate represents the fields of the place. # This makes sure the form acurate represents the fields of the place.
new_data = object.__dict__ new_data = manipulator.flatten_data()
form = formfields.FormWrapper(manipulator, new_data, errors) form = formfields.FormWrapper(manipulator, new_data, errors)
if not template_name: if not template_name:

View File

@ -36,6 +36,7 @@ setup(
'django.contrib.admin': ['templates/admin/*.html', 'django.contrib.admin': ['templates/admin/*.html',
'templates/admin_doc/*.html', 'templates/admin_doc/*.html',
'templates/registration/*.html', 'templates/registration/*.html',
'templates/widget/*.html',
'media/css/*.css', 'media/css/*.css',
'media/img/admin/*.gif', 'media/img/admin/*.gif',
'media/img/admin/*.png', 'media/img/admin/*.png',