1
0
mirror of https://github.com/django/django.git synced 2025-07-05 10:19:20 +00:00

Merged to 1021.

Initial split up of change lists to tags and templates. Needs work. 

Rationalised related object methods. 

Various bug fixes. 

A lot of changes here, so please let me know of any issues. 
Also I would appreciate examples of models with funky list views, 
eg searchs, pagination, filtering etc. 



git-svn-id: http://code.djangoproject.com/svn/django/branches/new-admin@1023 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Robert Wittams 2005-10-30 14:18:28 +00:00
commit 5e5f77f59b
32 changed files with 993 additions and 603 deletions

View File

@ -11,6 +11,7 @@ ACTION_MAPPING = {
'init': management.init,
'inspectdb': management.inspectdb,
'install': management.install,
'installperms': management.installperms,
'runserver': management.runserver,
'sql': management.get_sql_create,
'sqlall': management.get_sql_all,
@ -24,7 +25,7 @@ ACTION_MAPPING = {
'validate': management.validate,
}
NO_SQL_TRANSACTION = ('adminindex', 'createcachetable', 'dbcheck', 'install', 'sqlindexes')
NO_SQL_TRANSACTION = ('adminindex', 'createcachetable', 'dbcheck', 'install', 'installperms', 'sqlindexes')
def get_usage():
"""

View File

@ -65,8 +65,8 @@ TEMPLATE_FILE_EXTENSION = '.html'
# See the comments in django/core/template/loader.py for interface
# documentation.
TEMPLATE_LOADERS = (
# 'django.core.template.loaders.app_directories.load_template_source',
'django.core.template.loaders.filesystem.load_template_source',
'django.core.template.loaders.app_directories.load_template_source',
# 'django.core.template.loaders.eggs.load_template_source',
)

View File

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

View File

@ -2,28 +2,28 @@
{% load admin_modify %}
{% load adminmedia %}
{% block extrahead %}
{% for js in javascript_imports %}{% include_admin_script js %}{% endfor %}
{% for js in bound_manipulator.javascript_imports %}{% include_admin_script js %}{% endfor %}
{% endblock %}
{% block coltype %}{{ coltype }}{% endblock %}
{% block bodyclass %}{{app_label}}-{{object_name.lower}} change-form{% 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="../../../">Home</a> &rsaquo;
<a href="../">{{verbose_name_plural|capfirst}}</a> &rsaquo;
{% if add %}Add {{verbose_name}}{% else %}{{original|striptags|truncatewords:"18"}}{% endif %}
<a href="../">{{bound_manipulator.verbose_name_plural|capfirst}}</a> &rsaquo;
{% if add %}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">History</a></li>
{% if has_absolute_url %}<li><a href="/r/{{ content_type_id }}/{{ object_id }}/" class="viewsitelink">View on site</a></li>{% endif%}
{% if has_absolute_url %}<li><a href="/r/{{ bound_manipulator.content_type_id }}/{{ object_id }}/" class="viewsitelink">View on site</a></li>{% endif%}
</ul>
{% endif %}{% endif %}
<form {{ form_enc_attrib }} action='{{ form_url }}' method="post">{% block form_top %}{%endblock%}
<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 save_on_top %}{% submit_row %}{% endif %}
{% if bound_manipulator.save_on_top %}{% submit_row %}{% endif %}
{% if form.error_dict %}<p class="errornote">Please correct the error{{ form.error_dict.items|pluralize }} below.</p>{% endif %}
{% for bound_field_set in bound_field_sets %}
{% 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 %}
@ -36,7 +36,7 @@
{% endfor %}
{% block after_field_sets %}{% endblock %}
{% if change %}
{% if ordered_objects %}
{% if bound_manipulator.ordered_objects %}
<fieldset class="module"><h2>Ordering</h2>
<div class="form-row{% if form.order_.errors %} error{% endif %} ">
{% if form.order_.errors %}{{ form.order_.html_error_list }}{% endif %}
@ -45,23 +45,23 @@
{% endif %}
{% endif %}
{% for related_object in inline_related_objects %}{% edit_inline related_object %}{% endfor %}
{% for related_object in bound_manipulator.inline_related_objects %}{% edit_inline related_object %}{% endfor %}
{% block after_related_objects%}{%endblock%}
{% submit_row %}
{% if add %}
<script type="text/javascript">document.getElementById("{{first_form_field_id}}").focus();</script>
<script type="text/javascript">document.getElementById("{{bound_manipulator.first_form_field_id}}").focus();</script>
{% endif %}
{% if auto_populated_fields %}
{% if bound_manipulator.auto_populated_fields %}
<script type="text/javascript">
{% auto_populated_field_script auto_populated_fields change %}
{% auto_populated_field_script bound_manipulator.auto_populated_fields change %}
</script>
{% endif %}
{% if change %}
{% if ordered_objects %}
{% if bound_manipulator.ordered_objects %}
{% if form.order_objects %}<ul id="orderthese">
{% for object in form.order_objects %}
<li id="p{% firstof ordered_object_names %}">
<span id="handlep{% firstof ordered_object_names %}">{{ object|truncatewords:"5" }}</span>
<li id="p{% firstof bound_manipulator.ordered_object_names %}">
<span id="handlep{% firstof bound_manipulator.ordered_object_names %}">{{ object|truncatewords:"5" }}</span>
</li>
{% endfor%}
{% endif %}

View File

@ -0,0 +1,19 @@
{% load admin_list %}
{% extends "admin/base_site" %}
{% block bodyclass %}change-list{% endblock %}
{% if not is_popup %}{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">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">Add {{cl.opts.verbose_name}}</a></li></ul>\n'
{% 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,5 @@
{% if cl.has_filters %}<div id="changelist-filter">
<h2>Filter</h2>
{% for spec in cl.filter_specs %}
{% output_filter_spec cl spec %}
{% endfor %}</div>{% endif %}

View File

@ -0,0 +1,13 @@
<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 need_show_all_link %}&nbsp;&nbsp;<a href="{% query_string cl override ALL_VAR:'' %}" 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}}s 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

@ -7,9 +7,9 @@
{{message}}
</pre>
{%ifnotequal bottom "0"%}
.<br/>.<br/>.<br/>
{%endifnotequal%}
{%if top%}
...
{%endif%}
<pre class="source">
@ -17,8 +17,8 @@
{% else %}{{source_line.0|rjust:"5"}}:{{ source_line.1 }}
{% endifequal %}{% endfor %}
</pre>
{%ifnotequal top total%}
.<br/>.<br/>.<br/>
{%ifnotequal bottom total%}
...
{%endifnotequal%}
</div>

View File

@ -0,0 +1,280 @@
from django.core.template.decorators import simple_tag, inclusion_tag
from django.contrib.admin.views.main import MAX_SHOW_ALL_ALLOWED, DEFAULT_RESULTS_PER_PAGE, ALL_VAR, \
ORDER_VAR, ORDER_TYPE_VAR, PAGE_VAR , SEARCH_VAR , IS_POPUP_VAR, EMPTY_CHANGELIST_VALUE
from django.core import meta
from django.utils.text import capfirst
from django.utils.html import strip_tags, escape
from django.core.exceptions import ObjectDoesNotExist
from django.conf.settings import ADMIN_MEDIA_PREFIX
from django.core import template
DOT = '.'
class QueryStringNode(template.Node):
def __init__(self, cl_var, override_vars, remove_vars):
self.cl_var, self.override_vars, self.remove_vars = cl_var, override_vars, remove_vars
def render(self, context):
def res(var):
return template.resolve_variable(var, context)
cl = res(self.cl_var)
overrides = dict([ (res(k), res(v)) for k,v in self.override_vars ])
remove = [res(v) for v in self.remove_vars]
return cl.get_query_string(overrides, remove)
def do_query_string(parser, token):
bits = token.contents.split()[1:]
in_override = False
in_remove = False
override_vars = []
remove_vars = []
cl_var = bits.pop(0)
for word in bits:
if in_override:
if word == 'remove':
in_remove = True
in_override = False
else:
override_vars.append(word.split(':'))
elif in_remove:
remove_vars.append(word)
else:
if word == 'override':
in_override = True
elif word == 'remove':
remove = True
return QueryStringNode(cl_var, override_vars, remove_vars)
template.register_tag('query_string', do_query_string)
#@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 == 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))
return {'cl': cl,
'pagination_required': pagination_required,
'need_show_all_link': cl.can_show_all and not cl.show_all and cl.multi_page,
'page_range': page_range,
'ALL_VAR': ALL_VAR,
'1': 1
}
pagination = inclusion_tag('admin/pagination')(pagination)
#@simple_tag
def result_list(cl):
result_list, lookup_opts, mod, order_field, order_type, params, is_popup, opts = \
cl.result_list, cl.lookup_opts, cl.mod, cl.order_field, cl.order_type, cl.params, cl.is_popup, cl.opts
raw_template = []
if result_list:
# Table headers.
raw_template.append('<table cellspacing="0">\n<thead>\n<tr>\n')
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(mod.Klass, field_name) # Let AttributeErrors propogate.
try:
header = func.short_description
except AttributeError:
header = func.__name__
# Non-field list_display values don't get ordering capability.
raw_template.append('<th>%s</th>' % capfirst(header))
else:
if isinstance(f.rel, meta.ManyToOne) and f.null:
raw_template.append('<th>%s</th>' % capfirst(f.verbose_name))
else:
th_classes = []
new_order_type = 'asc'
if field_name == order_field:
th_classes.append('sorted %sending' % order_type.lower())
new_order_type = {'asc': 'desc', 'desc': 'asc'}[order_type.lower()]
raw_template.append('<th%s><a href="%s">%s</a></th>' % \
((th_classes and ' class="%s"' % ' '.join(th_classes) or ''),
cl.get_query_string( {ORDER_VAR: i, ORDER_TYPE_VAR: new_order_type}),
capfirst(f.verbose_name)))
raw_template.append('</tr>\n</thead>\n')
# Result rows.
pk = lookup_opts.pk.name
for i, result in enumerate(result_list):
raw_template.append('<tr class="row%s">\n' % (i % 2 + 1))
for j, field_name in enumerate(lookup_opts.admin.list_display):
row_class = ''
try:
f = 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:
result_repr = strip_tags(str(getattr(result, field_name)()))
except ObjectDoesNotExist:
result_repr = EMPTY_CHANGELIST_VALUE
else:
field_val = getattr(result, f.column)
# Foreign-key fields are special: Use the repr of the
# related object.
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 are special: They're formatted in a certain way.
elif isinstance(f, meta.DateField):
if field_val:
if isinstance(f, meta.DateTimeField):
result_repr = dateformat.format(field_val, 'N j, Y, P')
else:
result_repr = dateformat.format(field_val, 'N j, Y')
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))
# Some browsers don't like empty "<td></td>"s.
if result_repr == '':
result_repr = '&nbsp;'
if j == 0: # First column is a special case
result_id = getattr(result, pk)
raw_template.append('<th%s><a href="%s/"%s>%s</a></th>' % \
(row_class, result_id, (is_popup and ' onclick="opener.dismissRelatedLookupPopup(window, %r); return false;"' % result_id or ''), result_repr))
else:
raw_template.append('<td%s>%s</td>' % (row_class, result_repr))
raw_template.append('</tr>\n')
del result_list # to free memory
raw_template.append('</table>\n')
else:
raw_template.append('<p>No %s matched your search criteria.</p>' % opts.verbose_name_plural)
return ''.join(raw_template)
result_list = simple_tag(result_list)
#@simple_tag
def date_hierarchy(cl):
lookup_opts, params, lookup_params, lookup_mod = \
cl.lookup_opts, cl.params, cl.lookup_params, cl.lookup_mod
raw_template = []
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)
raw_template.append('<div class="xfull">\n<ul class="toplinks">\n')
if year_lookup and month_lookup and day_lookup:
raw_template.append('<li class="date-back"><a href="%s">&lsaquo; %s %s </a></li>' % \
(cl.get_query_string( {year_field: year_lookup, month_field: month_lookup}, [field_generic]), MONTHS[int(month_lookup)], year_lookup))
raw_template.append('<li>%s %s</li>' % (MONTHS[int(month_lookup)], day_lookup))
elif year_lookup and month_lookup:
raw_template.append('<li class="date-back"><a href="%s">&lsaquo; %s</a></li>' % \
(cl.get_query_string( {year_field: year_lookup}, [field_generic]), year_lookup))
date_lookup_params = lookup_params.copy()
date_lookup_params.update({year_field: year_lookup, month_field: month_lookup})
for day in getattr(lookup_mod, 'get_%s_list' % field_name)('day', **date_lookup_params):
raw_template.append('<li><a href="%s">%s</a></li>' % \
(cl.get_query_string({year_field: year_lookup, month_field: month_lookup, day_field: day.day}, [field_generic]), day.strftime('%B %d')))
elif year_lookup:
raw_template.append('<li class="date-back"><a href="%s">&lsaquo; All dates</a></li>' % \
cl.get_query_string( {}, [year_field]))
date_lookup_params = lookup_params.copy()
date_lookup_params.update({year_field: year_lookup})
for month in getattr(lookup_mod, 'get_%s_list' % field_name)('month', **date_lookup_params):
raw_template.append('<li><a href="%s">%s %s</a></li>' % \
(cl.get_query_string( {year_field: year_lookup, month_field: month.month}, [field_generic]), month.strftime('%B'), month.year))
else:
for year in getattr(lookup_mod, 'get_%s_list' % field_name)('year', **lookup_params):
raw_template.append('<li><a href="%s">%s</a></li>\n' % \
(cl.get_query_string( {year_field: year.year}, [field_generic]), year.year))
raw_template.append('</ul><br class="clear" />\n</div>\n')
return ''.join(raw_template)
date_hierarchy = simple_tag(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)
#@simple_tag
def output_filter_spec(cl, spec):
return spec.output(cl)
output_filter_spec = simple_tag(output_filter_spec)
#@inclusion_tag('admin/filters')
def filters(cl):
return {'cl': cl}
filters = inclusion_tag('admin/filters')(filters)

View File

@ -288,9 +288,9 @@ DATA_TYPE_MAPPING = {
def get_readable_field_data_type(field):
# ForeignKey is a special case. Use the field type of the relation.
if field.__class__.__name__ == 'ForeignKey':
if field.get_internal_type() == 'ForeignKey':
field = field.rel.get_related_field()
return DATA_TYPE_MAPPING[field.__class__.__name__] % field.__dict__
return DATA_TYPE_MAPPING[field.get_internal_type()] % field.__dict__
def extract_views_from_urlpatterns(urlpatterns, base=''):
"""

File diff suppressed because it is too large Load Diff

View File

@ -13,7 +13,9 @@ settings.CACHE_BACKEND and use that to create and load a cache object.
The CACHE_BACKEND setting is a quasi-URI; examples are:
memcached://127.0.0.1:11211/ A memcached backend; the server is running
on localhost port 11211.
on localhost port 11211. You can use
multiple memcached servers by separating
them with semicolons.
db://tablename/ A database backend in a table named
"tablename". This table should be created
@ -134,7 +136,7 @@ else:
"Memcached cache backend."
def __init__(self, server, params):
_Cache.__init__(self, params)
self._cache = memcache.Client([server])
self._cache = memcache.Client(server.split(';'))
def get(self, key, default=None):
val = self._cache.get(key)

View File

@ -315,6 +315,7 @@ class FormField:
####################
class TextField(FormField):
input_type = "text"
def __init__(self, field_name, length=30, maxlength=None, is_required=False, validator_list=[]):
self.field_name = field_name
self.length, self.maxlength = length, maxlength
@ -878,8 +879,10 @@ class CommaSeparatedIntegerField(TextField):
except validators.ValidationError, e:
raise validators.CriticalValidationError, e.messages
class RawIdAdminField(CommaSeparatedIntegerField):
def html2python(data):
return data.split(',');
html2python = classmethod(html2python)
class XMLLargeTextField(LargeTextField):
"""

View File

@ -48,6 +48,9 @@ class BaseHandler:
from django.core.mail import mail_admins
from django.conf.settings import DEBUG, INTERNAL_IPS, ROOT_URLCONF
# Reset query list per request.
db.db.queries = []
# Apply request middleware
for middleware_method in self._request_middleware:
response = middleware_method(request)

View File

@ -345,6 +345,27 @@ The full error: %s\n""" % \
install.help_doc = "Executes ``sqlall`` for the given model module name(s) in the current database."
install.args = APP_ARGS
def installperms(mod):
"Installs any permissions for the given model, if needed."
from django.models.auth import permissions
from django.models.core import packages
num_added = 0
package = packages.get_object(pk=mod._MODELS[0]._meta.app_label)
for klass in mod._MODELS:
opts = klass._meta
for codename, name in _get_all_permissions(opts):
try:
permissions.get_object(name__exact=name, codename__exact=codename, package__label__exact=package.label)
except permissions.PermissionDoesNotExist:
p = permissions.Permission(name=name, package=package, codename=codename)
p.save()
print "Added permission '%r'." % p
num_added += 1
if not num_added:
print "No permissions were added, because all necessary permissions were already installed."
installperms.help_doc = "Installs any permissions for the given model module name(s), if needed."
installperms.args = APP_ARGS
def _start_helper(app_or_project, name, directory, other_name=''):
other = {'project': 'app', 'app': 'project'}[app_or_project]
if not _is_valid_dir_name(name):

View File

@ -190,7 +190,7 @@ class RelatedObject(object):
def get_list(self, parent_instance = None):
"Get the list of this type of object from an instance of the parent class"
if parent_instance != None:
func_name = 'get_%s_list' % self.parent_opts.get_rel_object_method_name(self.opts, self.field)
func_name = 'get_%s_list' % self.get_method_name_part()
func = getattr(parent_instance, func_name)
list = func()
@ -239,8 +239,9 @@ class RelatedObject(object):
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.parent_opts.get_rel_object_method_name(self.opts, self.field)
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:
@ -263,6 +264,9 @@ class RelatedObject(object):
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):
return self.parent_opts.get_rel_object_method_name(self.opts, self.field)
class Options:
def __init__(self, module_name='', verbose_name='', verbose_name_plural='', db_table='',
fields=None, ordering=None, unique_together=None, admin=None, has_related_links=False,
@ -461,7 +465,7 @@ class Options:
try:
for f in klass._meta.many_to_many:
if f.rel and self == f.rel.to:
rel_objs.append((klass._meta, f))
rel_objs.append(RelatedObject(self, klass._meta, f))
raise StopIteration
except StopIteration:
continue
@ -856,10 +860,9 @@ class ModelBase(type):
old_app._MODELS[i] = new_class
# Replace all relationships to the old class with
# relationships to the new one.
for related 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():
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
return new_class
@ -958,7 +961,7 @@ def method_delete(opts, self):
self._pre_delete()
cursor = db.db.cursor()
for related in opts.get_all_related_objects():
rel_opts_name = opts.get_rel_object_method_name(related.opts, related.field)
rel_opts_name = related.get_method_name_part()
if isinstance(related.field.rel, OneToOne):
try:
sub_obj = getattr(self, 'get_%s' % rel_opts_name)()
@ -969,8 +972,8 @@ def method_delete(opts, self):
else:
for sub_obj in getattr(self, 'get_%s_list' % rel_opts_name)():
sub_obj.delete()
for rel_opts, rel_field in opts.get_all_related_many_to_many_objects():
cursor.execute("DELETE FROM %s WHERE %s_id=%%s" % (rel_field.get_m2m_db_table(rel_opts),
for related in opts.get_all_related_many_to_many_objects():
cursor.execute("DELETE FROM %s WHERE %s_id=%%s" % (related.field.get_m2m_db_table(related.opts),
self._meta.object_name.lower()), [getattr(self, opts.pk.column)])
for f in opts.many_to_many:
cursor.execute("DELETE FROM %s WHERE %s_id=%%s" % (f.get_m2m_db_table(opts), self._meta.object_name.lower()),
@ -1671,7 +1674,7 @@ def manipulator_save(opts, klass, add, change, self, new_data):
if change:
if rel_new_data[related.opts.pk.name][0]:
try:
old_rel_obj = getattr(self.original_object, 'get_%s' % opts.get_rel_object_method_name(related.opts, related.field))(**{'%s__exact' % related.opts.pk.name: rel_new_data[related.opts.pk.name][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.name][0]})
except ObjectDoesNotExist:
pass

View File

@ -706,7 +706,7 @@ class ManyToManyField(Field):
def get_manipulator_field_objs(self):
if self.rel.raw_id_admin:
return [formfields.CommaSeparatedIntegerField]
return [formfields.RawIdAdminField]
else:
choices = self.get_choices_default()
return [curry(formfields.SelectMultipleField, size=min(max(len(choices), 5), 15), choices=choices)]
@ -741,7 +741,7 @@ class ManyToManyField(Field):
instance_ids = [getattr(instance, self.rel.to.pk.column) for instance in get_list_func()]
if self.rel.raw_id_admin:
new_data[self.name] = ",".join([str(id) for id in instance_ids])
elif not self.rel.edit_inline:
else:
new_data[self.name] = instance_ids
else:
# In required many-to-many fields with only one available choice,

View File

@ -395,7 +395,7 @@ class DebugParser(Parser):
(command, (origin,line)) = self.command_stack.pop()
msg = "Unclosed tag '%s' starting at %s, line %d. Looking for one of: %s " % \
(command, origin, line, ', '.join(parse_until) )
raise self.error( token.source, msg)
raise self.error( (origin,line), msg)
def compile_function_error(self, token, e):
if not hasattr(e, 'source'):

View File

@ -24,15 +24,15 @@ def fix_ampersands(value, _):
def floatformat(text, _):
"""
Displays a floating point number as 34.2 (with one decimal place) - but
Displays a floating point number as 34.2 (with one decimal place) -- but
only if there's a point to be displayed
"""
from math import modf
if not text:
return ''
if modf(float(text))[0] < 0.1:
return text
return "%.1f" % float(text)
f = float(text)
m = f - int(f)
if m:
return '%.1f' % f
else:
return '%d' % int(f)
def linenumbers(value, _):
"Displays text with line numbers"
@ -175,7 +175,7 @@ def removetags(value, tags):
"Removes a space separated list of [X]HTML tags from the output"
tags = [re.escape(tag) for tag in tags.split()]
tags_re = '(%s)' % '|'.join(tags)
starttag_re = re.compile('<%s(>|(\s+[^>]*>))' % tags_re)
starttag_re = re.compile(r'<%s(/?>|(\s+[^>]*>))' % tags_re)
endtag_re = re.compile('</%s>' % tags_re)
value = starttag_re.sub('', value)
value = endtag_re.sub('', value)

View File

@ -142,6 +142,7 @@ class IfEqualNode(Node):
def render(self, context):
val1 = resolve_variable(self.var1, context)
val2 = resolve_variable(self.var2, context)
if (self.negate and val1 != val2) or (not self.negate and val1 == val2):
return self.nodelist_true.render(context)
return self.nodelist_false.render(context)

View File

@ -14,7 +14,6 @@ class TemplateDebugMiddleware(object):
top = max(0, line - context_lines)
bottom = min(total, line + 1 + context_lines)
return render_to_response('template_debug', {
'message' : exception.args[0],
'source_lines' : source_lines[top:bottom],

View File

@ -25,7 +25,7 @@ for mod in modules:
# label prepended, and the add_BLAH() method will not be
# generated.
rel_mod = related.opts.get_model_module()
rel_obj_name = klass._meta.get_rel_object_method_name(related.opts, related.field)
rel_obj_name = related.get_method_name_part()
if isinstance(related.field.rel, meta.OneToOne):
# Add "get_thingie" methods for one-to-one related objects.
# EXAMPLE: Place.get_restaurants_restaurant()
@ -62,18 +62,18 @@ for mod in modules:
del rel_obj_name, rel_mod, related # clean up
# 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():
rel_mod = rel_opts.get_model_module()
rel_obj_name = klass._meta.get_rel_object_method_name(rel_opts, rel_field)
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_count' % rel_obj_name, curry(meta.method_get_related_many_to_many, 'get_count', 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, rel_field))
if rel_opts.app_label == klass._meta.app_label:
func = curry(meta.method_set_related_many_to_many, rel_opts, rel_field)
for related in klass._meta.get_all_related_many_to_many_objects():
rel_mod = related.opts.get_model_module()
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, related.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, related.field))
if related.opts.app_label == klass._meta.app_label:
func = curry(meta.method_set_related_many_to_many, related.opts, related.field)
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 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
# that are ordered with respect to this.

View File

@ -37,6 +37,8 @@ def reloader_thread():
mtimes = {}
while RUN_RELOADER:
for filename in filter(lambda v: v, map(lambda m: getattr(m, "__file__", None), sys.modules.values())) + reloadFiles:
if not os.path.exists(filename):
continue # File might be in an egg, so it can't be reloaded.
if filename.endswith(".pyc"):
filename = filename[:-1]
mtime = os.stat(filename).st_mtime

View File

@ -21,6 +21,45 @@ import datetime, md5, re
from django.conf import settings
from django.core.cache import cache
cc_delim_re = re.compile(r'\s*,\s*')
def patch_cache_control(response, **kwargs):
"""
This function patches the Cache-Control header by adding all
keyword arguments to it. The transformation is as follows:
- all keyword parameter names are turned to lowercase and
all _ will be translated to -
- if the value of a parameter is True (exatly True, not just a
true value), only the parameter name is added to the header
- all other parameters are added with their value, after applying
str to it.
"""
def dictitem(s):
t = s.split('=',1)
if len(t) > 1:
return (t[0].lower().replace('-', '_'), t[1])
else:
return (t[0].lower().replace('-', '_'), True)
def dictvalue(t):
if t[1] == True:
return t[0]
else:
return t[0] + '=' + str(t[1])
if response.has_header('Cache-Control'):
print response['Cache-Control']
cc = cc_delim_re.split(response['Cache-Control'])
print cc
cc = dict([dictitem(el) for el in cc])
else:
cc = {}
for (k,v) in kwargs.items():
cc[k.replace('_', '-')] = v
cc = ', '.join([dictvalue(el) for el in cc.items()])
response['Cache-Control'] = cc
vary_delim_re = re.compile(r',\s*')
def patch_response_headers(response, cache_timeout=None):
@ -43,8 +82,7 @@ def patch_response_headers(response, cache_timeout=None):
response['Last-Modified'] = now.strftime('%a, %d %b %Y %H:%M:%S GMT')
if not response.has_header('Expires'):
response['Expires'] = expires.strftime('%a, %d %b %Y %H:%M:%S GMT')
if not response.has_header('Cache-Control'):
response['Cache-Control'] = 'max-age=%d' % cache_timeout
patch_cache_control(response, max_age=cache_timeout)
def patch_vary_headers(response, newheaders):
"""

View File

@ -4,7 +4,6 @@ def user_passes_test(test_func):
redirecting to the log-in page if necessary. The test should be a callable
that takes the user object and returns True if the user passes.
"""
def _dec(view_func):
def _checklogin(request, *args, **kwargs):
from django.views.auth.login import redirect_to_login
@ -14,12 +13,10 @@ def user_passes_test(test_func):
return _checklogin
return _dec
login_required = user_passes_test(lambda u: not u.is_anonymous())
login_required.__doc__ = (
login_required.__doc__ = (
"""
Decorator for views that checks that the user is logged in, redirecting
to the log-in page if necessary.
"""
)

View File

@ -10,8 +10,24 @@ example, as that is unique across a Django project.
Additionally, all headers from the response's Vary header will be taken into
account on caching -- just like the middleware does.
"""
import re
from django.utils.decorators import decorator_from_middleware
from django.utils.cache import patch_cache_control
from django.middleware.cache import CacheMiddleware
cache_page = decorator_from_middleware(CacheMiddleware)
def cache_control(**kwargs):
def _cache_controller(viewfunc):
def _cache_controlled(request, *args, **kw):
response = viewfunc(request, *args, **kw)
patch_cache_control(response, **kwargs)
return response
return _cache_controlled
return _cache_controller

View File

@ -1,9 +1,35 @@
"""
Decorator for views that supports conditional get on ETag and Last-Modified
headers.
Decorators for views based on HTTP headers.
"""
from django.utils.decorators import decorator_from_middleware
from django.middleware.http import ConditionalGetMiddleware
from django.utils.httpwrappers import HttpResponseForbidden
conditional_page = decorator_from_middleware(ConditionalGetMiddleware)
def require_http_methods(request_method_list):
"""
Decorator to make a view only accept particular request methods. Usage::
@require_http_methods(["GET", "POST"])
def my_view(request):
# I can assume now that only GET or POST requests make it this far
# ...
Note that request methods ARE case sensitive.
"""
def decorator(func):
def inner(request, *args, **kwargs):
method = request.META.get("REQUEST_METHOD", None)
if method not in request_method_list:
raise HttpResponseForbidden("REQUEST_METHOD '%s' not allowed" % method)
return func(request, *args, **kwargs)
return inner
return decorator
require_GET = require_http_methods(["GET"])
require_GET.__doc__ = "Decorator to require that a view only accept the GET method."
require_POST = require_http_methods(["POST"])
require_POST.__doc__ = "Decorator to require that a view only accept the POST method."

View File

@ -1,10 +1,11 @@
from django.core.exceptions import Http404, ObjectDoesNotExist
from django.core.template import Context, loader
from django.models.core import sites
from django.models.core import sites, contenttypes
from django.utils import httpwrappers
def shortcut(request, content_type_id, object_id):
from django.models.core import contenttypes
"""Redirect to an object's page based on a content-type ID and an object ID"""
# Look up the object, making sure it's got a get_absolute_url() function.
try:
content_type = contenttypes.get_object(pk=content_type_id)
obj = content_type.get_object_for_this_type(pk=object_id)
@ -14,25 +15,37 @@ def shortcut(request, content_type_id, object_id):
absurl = obj.get_absolute_url()
except AttributeError:
raise Http404, "%s objects don't have get_absolute_url() methods" % content_type.name
# Try to figure out the object's domain so we can do a cross-site redirect
# if necessary
# If the object actually defines a domain, we're done.
if absurl.startswith('http://'):
return httpwrappers.HttpResponseRedirect(absurl)
object_domain = None
# Next, look for an many-to-many relationship to sites
if hasattr(obj, 'get_site_list'):
site_list = obj.get_site_list()
if site_list:
object_domain = site_list[0].domain
# Next, look for a many-to-one relationship to sites
elif hasattr(obj, 'get_site'):
try:
object_domain = obj.get_site().domain
except sites.SiteDoesNotExist:
pass
try:
object_domain = sites.get_current().domain
except sites.SiteDoesNotExist:
pass
if not object_domain:
return httpwrappers.HttpResponseRedirect(absurl)
return httpwrappers.HttpResponseRedirect('http://%s%s' % (object_domain, absurl))
# Then, fall back to the current site (if possible)
else:
try:
object_domain = sites.get_current().domain
except sites.SiteDoesNotExist:
# Finally, give up and use a URL without the domain name
return httpwrappers.HttpResponseRedirect(obj.get_absolute_url())
return httpwrappers.HttpResponseRedirect('http://%s%s' % (object_domain, obj.get_absolute_url()))
def page_not_found(request):
"""

View File

@ -29,7 +29,9 @@ Examples:
CACHE_BACKEND Explanation
============================== ===========================================
memcached://127.0.0.1:11211/ A memcached backend; the server is running
on localhost port 11211.
on localhost port 11211. You can use
multiple memcached servers by separating
them with semicolons.
db://tablename/ A database backend in a table named
"tablename". This table should be created
@ -270,6 +272,40 @@ and a list/tuple of header names as its second argument.
.. _`HTTP Vary headers`: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.44
Controlling cache: Using Vary headers
=====================================
Another problem with caching is the privacy of data, and the question where data can
be stored in a cascade of caches. A user usually faces two kinds of caches: his own
browser cache (a private cache) and his providers cache (a public cache). A public cache
is used by multiple users and controlled by someone else. This poses problems with private
(in the sense of sensitive) data - you don't want your social security number or your
banking account numbers stored in some public cache. So web applications need a way
to tell the caches what data is private and what is public.
Other aspects are the definition how long a page should be cached at max, or wether the
cache should allways check for newer versions and only deliver the cache content when
there were no changes (some caches might deliver cached content even if the server page
changed - just because the cache copy isn't yet expired).
So there are a multitude of options you can control for your pages. This is where the
Cache-Control header (more infos in `HTTP Cache-Control headers`_) comes in. The usage
is quite simple::
@cache_control(private=True, must_revalidate=True, max_age=3600)
def my_view(request):
...
This would define the view as private, to be revalidated on every access and cache
copies will only be stored for 3600 seconds at max.
The caching middleware already set's this header up with a max-age of the CACHE_MIDDLEWARE_SETTINGS
setting. And the cache_page decorator does the same. The cache_control decorator correctly merges
different values into one big header, though. But you should take into account that middlewares
might overwrite some of your headers or set their own defaults if you don't give that header yourself.
.. _`HTTP Cache-Control headers`: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9
Other optimizations
===================

View File

@ -750,11 +750,12 @@ Here's a list of all possible ``META`` options. No options are required. Adding
``permissions``
Extra permissions to enter into the permissions table when creating this
object. Add, delete and change permissions are automatically created for
each object. This option specifies extra permissions::
each object that has ``admin`` set. This example specifies an extra
permission, ``can_deliver_pizzas``::
permissions = (("can_deliver_pizzas", "Can deliver pizzas"),)
This is a list of 2-tuples of
This is a list or tuple of 2-tuples in the format
``(permission_code, human_readable_permission_name)``.
``unique_together``

View File

@ -0,0 +1,20 @@
"""
>>> floatformat(7.7, None)
'7.7'
>>> floatformat(7.0, None)
'7'
>>> floatformat(0.7, None)
'0.7'
>>> floatformat(0.07, None)
'0.1'
>>> floatformat(0.007, None)
'0.0'
>>> floatformat(0.0, None)
'0'
"""
from django.core.template.defaultfilters import *
if __name__ == '__main__':
import doctest
doctest.testmod()