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

View File

@ -65,8 +65,8 @@ TEMPLATE_FILE_EXTENSION = '.html'
# See the comments in django/core/template/loader.py for interface # See the comments in django/core/template/loader.py for interface
# documentation. # documentation.
TEMPLATE_LOADERS = ( TEMPLATE_LOADERS = (
# 'django.core.template.loaders.app_directories.load_template_source',
'django.core.template.loaders.filesystem.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', # '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(r, '');
s = s.replace(/[^\w\s-]/g, ''); // remove unneeded chars s = s.replace(/[^\w\s-]/g, ''); // remove unneeded chars
s = s.replace(/^\s+|\s+$/g, ''); // trim leading/trailing spaces s = s.replace(/^\s+|\s+$/g, ''); // trim leading/trailing spaces
s = s.replace(/\s+/g, '_'); // convert spaces to underscores s = s.replace(/\s+/g, '-'); // convert spaces to hyphens
s = s.toLowerCase(); // convert to lowercase s = s.toLowerCase(); // convert to lowercase
return s.substring(0, num_chars);// trim to first num_chars chars return s.substring(0, num_chars);// trim to first num_chars chars
} }

View File

@ -2,28 +2,28 @@
{% load admin_modify %} {% load admin_modify %}
{% load adminmedia %} {% load adminmedia %}
{% block extrahead %} {% 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 %} {% endblock %}
{% block coltype %}{{ coltype }}{% endblock %} {% block coltype %}{{ bound_manipulator.coltype }}{% endblock %}
{% block bodyclass %}{{app_label}}-{{object_name.lower}} change-form{% endblock %} {% block bodyclass %}{{app_label}}-{{bound_manipulator.object_name.lower}} change-form{% endblock %}
{% block breadcrumbs %}{% if not is_popup %} {% block breadcrumbs %}{% if not is_popup %}
<div class="breadcrumbs"> <div class="breadcrumbs">
<a href="../../../">Home</a> &rsaquo; <a href="../../../">Home</a> &rsaquo;
<a href="../">{{verbose_name_plural|capfirst}}</a> &rsaquo; <a href="../">{{bound_manipulator.verbose_name_plural|capfirst}}</a> &rsaquo;
{% if add %}Add {{verbose_name}}{% else %}{{original|striptags|truncatewords:"18"}}{% endif %} {% if add %}Add {{bound_manipulator.verbose_name}}{% else %}{{bound_manipulator.original|striptags|truncatewords:"18"}}{% endif %}
</div> </div>
{% endif %}{% endblock %} {% endif %}{% endblock %}
{% block content %}<div id="content-main"> {% block content %}<div id="content-main">
{% if change %}{% if not is_popup %} {% if change %}{% if not is_popup %}
<ul class="object-tools"><li><a href="history/" class="historylink">History</a></li> <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> </ul>
{% endif %}{% endif %} {% 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 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 %} {% 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 }}"> <fieldset class="module aligned {{ bound_field_set.classes }}">
{% if bound_field_set.name %}<h2>{{bound_field_set.name }}</h2>{% endif %} {% if bound_field_set.name %}<h2>{{bound_field_set.name }}</h2>{% endif %}
{% for bound_field_line in bound_field_set %} {% for bound_field_line in bound_field_set %}
@ -36,7 +36,7 @@
{% endfor %} {% endfor %}
{% block after_field_sets %}{% endblock %} {% block after_field_sets %}{% endblock %}
{% if change %} {% if change %}
{% if ordered_objects %} {% if bound_manipulator.ordered_objects %}
<fieldset class="module"><h2>Ordering</h2> <fieldset class="module"><h2>Ordering</h2>
<div class="form-row{% if form.order_.errors %} error{% endif %} "> <div class="form-row{% if form.order_.errors %} error{% endif %} ">
{% if form.order_.errors %}{{ form.order_.html_error_list }}{% endif %} {% if form.order_.errors %}{{ form.order_.html_error_list }}{% endif %}
@ -45,23 +45,23 @@
{% endif %} {% endif %}
{% 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%} {% block after_related_objects%}{%endblock%}
{% submit_row %} {% submit_row %}
{% if add %} {% 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 %} {% endif %}
{% if auto_populated_fields %} {% if bound_manipulator.auto_populated_fields %}
<script type="text/javascript"> <script type="text/javascript">
{% auto_populated_field_script auto_populated_fields change %} {% auto_populated_field_script bound_manipulator.auto_populated_fields change %}
</script> </script>
{% endif %} {% endif %}
{% if change %} {% if change %}
{% if ordered_objects %} {% if bound_manipulator.ordered_objects %}
{% if form.order_objects %}<ul id="orderthese"> {% if form.order_objects %}<ul id="orderthese">
{% for object in form.order_objects %} {% for object in form.order_objects %}
<li id="p{% firstof ordered_object_names %}"> <li id="p{% firstof bound_manipulator.ordered_object_names %}">
<span id="handlep{% firstof ordered_object_names %}">{{ object|truncatewords:"5" }}</span> <span id="handlep{% firstof bound_manipulator.ordered_object_names %}">{{ object|truncatewords:"5" }}</span>
</li> </li>
{% endfor%} {% endfor%}
{% endif %} {% 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}} {{message}}
</pre> </pre>
{%ifnotequal bottom "0"%} {%if top%}
.<br/>.<br/>.<br/> ...
{%endifnotequal%} {%endif%}
<pre class="source"> <pre class="source">
@ -17,8 +17,8 @@
{% else %}{{source_line.0|rjust:"5"}}:{{ source_line.1 }} {% else %}{{source_line.0|rjust:"5"}}:{{ source_line.1 }}
{% endifequal %}{% endfor %} {% endifequal %}{% endfor %}
</pre> </pre>
{%ifnotequal top total%} {%ifnotequal bottom total%}
.<br/>.<br/>.<br/> ...
{%endifnotequal%} {%endifnotequal%}
</div> </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): def get_readable_field_data_type(field):
# ForeignKey is a special case. Use the field type of the relation. # 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() 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=''): 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: The CACHE_BACKEND setting is a quasi-URI; examples are:
memcached://127.0.0.1:11211/ A memcached backend; the server is running 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 db://tablename/ A database backend in a table named
"tablename". This table should be created "tablename". This table should be created
@ -134,7 +136,7 @@ else:
"Memcached cache backend." "Memcached cache backend."
def __init__(self, server, params): def __init__(self, server, params):
_Cache.__init__(self, params) _Cache.__init__(self, params)
self._cache = memcache.Client([server]) self._cache = memcache.Client(server.split(';'))
def get(self, key, default=None): def get(self, key, default=None):
val = self._cache.get(key) val = self._cache.get(key)

View File

@ -315,6 +315,7 @@ class FormField:
#################### ####################
class TextField(FormField): class TextField(FormField):
input_type = "text"
def __init__(self, field_name, length=30, maxlength=None, is_required=False, validator_list=[]): def __init__(self, field_name, length=30, maxlength=None, is_required=False, validator_list=[]):
self.field_name = field_name self.field_name = field_name
self.length, self.maxlength = length, maxlength self.length, self.maxlength = length, maxlength
@ -878,8 +879,10 @@ 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): def html2python(data):
return data.split(','); return data.split(',');
html2python = classmethod(html2python)
class XMLLargeTextField(LargeTextField): class XMLLargeTextField(LargeTextField):
""" """

View File

@ -48,6 +48,9 @@ class BaseHandler:
from django.core.mail import mail_admins from django.core.mail import mail_admins
from django.conf.settings import DEBUG, INTERNAL_IPS, ROOT_URLCONF from django.conf.settings import DEBUG, INTERNAL_IPS, ROOT_URLCONF
# Reset query list per request.
db.db.queries = []
# Apply request middleware # Apply request middleware
for middleware_method in self._request_middleware: for middleware_method in self._request_middleware:
response = middleware_method(request) 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.help_doc = "Executes ``sqlall`` for the given model module name(s) in the current database."
install.args = APP_ARGS 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=''): def _start_helper(app_or_project, name, directory, other_name=''):
other = {'project': 'app', 'app': 'project'}[app_or_project] other = {'project': 'app', 'app': 'project'}[app_or_project]
if not _is_valid_dir_name(name): if not _is_valid_dir_name(name):

View File

@ -190,7 +190,7 @@ class RelatedObject(object):
def get_list(self, parent_instance = None): def get_list(self, parent_instance = None):
"Get the list of this type of object from an instance of the parent class" "Get the list of this type of object from an instance of the parent class"
if parent_instance != None: 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) func = getattr(parent_instance, func_name)
list = func() list = func()
@ -239,8 +239,9 @@ class RelatedObject(object):
return "<RelatedObject: %s related to %s>" % ( self.name, self.field.name) return "<RelatedObject: %s related to %s>" % ( self.name, self.field.name)
def get_manipulator_fields(self, opts, manipulator, change, follow): def get_manipulator_fields(self, opts, manipulator, change, follow):
#TODO: remove core fields stuff
if change: 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 = getattr(manipulator.original_object, meth_name)()
count += self.field.rel.num_extra_on_change count += self.field.rel.num_extra_on_change
if self.field.rel.min_num_in_admin: 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): def bind(self, field_mapping, original, bound_related_object_class=BoundRelatedObject):
return bound_related_object_class(self, field_mapping, original) 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: 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,
@ -461,7 +465,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
@ -856,10 +860,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 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 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
@ -958,7 +961,7 @@ def method_delete(opts, self):
self._pre_delete() self._pre_delete()
cursor = db.db.cursor() cursor = db.db.cursor()
for related in opts.get_all_related_objects(): 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): 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)()
@ -969,8 +972,8 @@ 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_id=%%s" % (rel_field.get_m2m_db_table(rel_opts), 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)]) self._meta.object_name.lower()), [getattr(self, opts.pk.column)])
for f in opts.many_to_many: 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()), 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 change:
if rel_new_data[related.opts.pk.name][0]: 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(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: except ObjectDoesNotExist:
pass pass

View File

@ -706,7 +706,7 @@ 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_default() 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)]
@ -741,7 +741,7 @@ class ManyToManyField(Field):
instance_ids = [getattr(instance, self.rel.to.pk.column) for instance in get_list_func()] instance_ids = [getattr(instance, self.rel.to.pk.column) for instance in get_list_func()]
if self.rel.raw_id_admin: if self.rel.raw_id_admin:
new_data[self.name] = ",".join([str(id) for id in instance_ids]) 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 new_data[self.name] = instance_ids
else: else:
# In required many-to-many fields with only one available choice, # 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() (command, (origin,line)) = self.command_stack.pop()
msg = "Unclosed tag '%s' starting at %s, line %d. Looking for one of: %s " % \ msg = "Unclosed tag '%s' starting at %s, line %d. Looking for one of: %s " % \
(command, origin, line, ', '.join(parse_until) ) (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): def compile_function_error(self, token, e):
if not hasattr(e, 'source'): if not hasattr(e, 'source'):

View File

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

View File

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

View File

@ -14,7 +14,6 @@ class TemplateDebugMiddleware(object):
top = max(0, line - context_lines) top = max(0, line - context_lines)
bottom = min(total, line + 1 + context_lines) bottom = min(total, line + 1 + context_lines)
return render_to_response('template_debug', { return render_to_response('template_debug', {
'message' : exception.args[0], 'message' : exception.args[0],
'source_lines' : source_lines[top:bottom], '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 # label prepended, and the add_BLAH() method will not be
# generated. # generated.
rel_mod = related.opts.get_model_module() 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): 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()
@ -62,18 +62,18 @@ for mod in modules:
del rel_obj_name, rel_mod, related # 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

@ -37,6 +37,8 @@ def reloader_thread():
mtimes = {} mtimes = {}
while RUN_RELOADER: while RUN_RELOADER:
for filename in filter(lambda v: v, map(lambda m: getattr(m, "__file__", None), sys.modules.values())) + reloadFiles: 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"): if filename.endswith(".pyc"):
filename = filename[:-1] filename = filename[:-1]
mtime = os.stat(filename).st_mtime mtime = os.stat(filename).st_mtime

View File

@ -21,6 +21,45 @@ import datetime, md5, re
from django.conf import settings from django.conf import settings
from django.core.cache import cache 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*') vary_delim_re = re.compile(r',\s*')
def patch_response_headers(response, cache_timeout=None): 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') response['Last-Modified'] = now.strftime('%a, %d %b %Y %H:%M:%S GMT')
if not response.has_header('Expires'): if not response.has_header('Expires'):
response['Expires'] = expires.strftime('%a, %d %b %Y %H:%M:%S GMT') response['Expires'] = expires.strftime('%a, %d %b %Y %H:%M:%S GMT')
if not response.has_header('Cache-Control'): patch_cache_control(response, max_age=cache_timeout)
response['Cache-Control'] = 'max-age=%d' % cache_timeout
def patch_vary_headers(response, newheaders): 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 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. that takes the user object and returns True if the user passes.
""" """
def _dec(view_func): def _dec(view_func):
def _checklogin(request, *args, **kwargs): def _checklogin(request, *args, **kwargs):
from django.views.auth.login import redirect_to_login from django.views.auth.login import redirect_to_login
@ -14,7 +13,6 @@ def user_passes_test(test_func):
return _checklogin return _checklogin
return _dec return _dec
login_required = user_passes_test(lambda u: not u.is_anonymous()) login_required = user_passes_test(lambda u: not u.is_anonymous())
login_required.__doc__ = ( login_required.__doc__ = (
""" """
@ -22,4 +20,3 @@ login_required.__doc__ = (
to the log-in page if necessary. 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 Additionally, all headers from the response's Vary header will be taken into
account on caching -- just like the middleware does. account on caching -- just like the middleware does.
""" """
import re
from django.utils.decorators import decorator_from_middleware from django.utils.decorators import decorator_from_middleware
from django.utils.cache import patch_cache_control
from django.middleware.cache import CacheMiddleware from django.middleware.cache import CacheMiddleware
cache_page = decorator_from_middleware(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 Decorators for views based on HTTP headers.
headers.
""" """
from django.utils.decorators import decorator_from_middleware from django.utils.decorators import decorator_from_middleware
from django.middleware.http import ConditionalGetMiddleware from django.middleware.http import ConditionalGetMiddleware
from django.utils.httpwrappers import HttpResponseForbidden
conditional_page = decorator_from_middleware(ConditionalGetMiddleware) 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.exceptions import Http404, ObjectDoesNotExist
from django.core.template import Context, loader 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 from django.utils import httpwrappers
def shortcut(request, content_type_id, object_id): 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: try:
content_type = contenttypes.get_object(pk=content_type_id) content_type = contenttypes.get_object(pk=content_type_id)
obj = content_type.get_object_for_this_type(pk=object_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() absurl = obj.get_absolute_url()
except AttributeError: except AttributeError:
raise Http404, "%s objects don't have get_absolute_url() methods" % content_type.name 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://'): if absurl.startswith('http://'):
return httpwrappers.HttpResponseRedirect(absurl) return httpwrappers.HttpResponseRedirect(absurl)
object_domain = None object_domain = None
# Next, look for an many-to-many relationship to sites
if hasattr(obj, 'get_site_list'): if hasattr(obj, 'get_site_list'):
site_list = obj.get_site_list() site_list = obj.get_site_list()
if site_list: if site_list:
object_domain = site_list[0].domain object_domain = site_list[0].domain
# Next, look for a many-to-one relationship to sites
elif hasattr(obj, 'get_site'): elif hasattr(obj, 'get_site'):
try: try:
object_domain = obj.get_site().domain object_domain = obj.get_site().domain
except sites.SiteDoesNotExist: except sites.SiteDoesNotExist:
pass pass
try:
object_domain = sites.get_current().domain # Then, fall back to the current site (if possible)
except sites.SiteDoesNotExist: else:
pass try:
if not object_domain: object_domain = sites.get_current().domain
return httpwrappers.HttpResponseRedirect(absurl) except sites.SiteDoesNotExist:
return httpwrappers.HttpResponseRedirect('http://%s%s' % (object_domain, absurl)) # 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): def page_not_found(request):
""" """

View File

@ -29,7 +29,9 @@ Examples:
CACHE_BACKEND Explanation CACHE_BACKEND Explanation
============================== =========================================== ============================== ===========================================
memcached://127.0.0.1:11211/ A memcached backend; the server is running 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 db://tablename/ A database backend in a table named
"tablename". This table should be created "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 .. _`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 Other optimizations
=================== ===================

View File

@ -750,11 +750,12 @@ Here's a list of all possible ``META`` options. No options are required. Adding
``permissions`` ``permissions``
Extra permissions to enter into the permissions table when creating this Extra permissions to enter into the permissions table when creating this
object. Add, delete and change permissions are automatically created for 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"),) 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)``. ``(permission_code, human_readable_permission_name)``.
``unique_together`` ``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()