summaryrefslogtreecommitdiff
path: root/django/contrib
diff options
context:
space:
mode:
Diffstat (limited to 'django/contrib')
-rw-r--r--django/contrib/admin/media/css/base.css13
-rw-r--r--django/contrib/admin/media/css/changelists.css4
-rw-r--r--django/contrib/admin/media/js/dateparse.js20
-rw-r--r--django/contrib/admin/options.py12
-rw-r--r--django/contrib/admin/sites.py6
-rw-r--r--django/contrib/admin/templates/admin/auth/user/add_form.html5
-rw-r--r--django/contrib/admin/templates/admin/base.html4
-rw-r--r--django/contrib/admin/templates/admin/change_list_results.html7
-rw-r--r--django/contrib/admin/templatetags/admin_list.py9
-rw-r--r--django/contrib/admin/widgets.py15
-rw-r--r--django/contrib/admindocs/templates/admin_doc/template_filter_index.html4
-rw-r--r--django/contrib/admindocs/templates/admin_doc/template_tag_index.html4
-rw-r--r--django/contrib/admindocs/views.py8
-rw-r--r--django/contrib/auth/decorators.py3
-rw-r--r--django/contrib/auth/forms.py10
-rw-r--r--django/contrib/auth/tests/__init__.py3
-rw-r--r--django/contrib/auth/tests/decorators.py30
-rw-r--r--django/contrib/auth/tests/forms.py483
-rw-r--r--django/contrib/auth/tests/urls.py5
-rw-r--r--django/contrib/auth/tests/views.py37
-rw-r--r--django/contrib/auth/views.py3
-rw-r--r--django/contrib/databrowse/plugins/calendars.py2
-rw-r--r--django/contrib/databrowse/plugins/fieldchoices.py2
-rw-r--r--django/contrib/flatpages/admin.py4
-rw-r--r--django/contrib/flatpages/fixtures/sample_flatpages.json63
-rw-r--r--django/contrib/flatpages/templatetags/__init__.py0
-rw-r--r--django/contrib/flatpages/templatetags/flatpages.py99
-rw-r--r--django/contrib/flatpages/tests/__init__.py5
-rw-r--r--django/contrib/flatpages/tests/csrf.py76
-rw-r--r--django/contrib/flatpages/tests/forms.py22
-rw-r--r--django/contrib/flatpages/tests/middleware.py67
-rw-r--r--django/contrib/flatpages/tests/templates/404.html1
-rw-r--r--django/contrib/flatpages/tests/templates/flatpages/default.html10
-rw-r--r--django/contrib/flatpages/tests/templates/registration/login.html0
-rw-r--r--django/contrib/flatpages/tests/templatetags.py134
-rw-r--r--django/contrib/flatpages/tests/urls.py8
-rw-r--r--django/contrib/flatpages/tests/views.py72
-rw-r--r--django/contrib/flatpages/views.py14
-rw-r--r--django/contrib/formtools/wizard.py6
-rw-r--r--django/contrib/gis/db/backends/mysql/creation.py2
-rw-r--r--django/contrib/gis/db/backends/postgis/operations.py2
-rw-r--r--django/contrib/gis/db/backends/spatialite/base.py2
-rw-r--r--django/contrib/gis/db/models/sql/compiler.py4
-rw-r--r--django/contrib/gis/gdal/libgdal.py4
-rw-r--r--django/contrib/gis/tests/__init__.py209
-rw-r--r--django/contrib/gis/tests/geogapp/tests.py4
-rw-r--r--django/contrib/gis/tests/relatedapp/models.py5
-rw-r--r--django/contrib/gis/tests/relatedapp/tests.py10
-rw-r--r--django/contrib/localflavor/in_/in_states.py70
-rw-r--r--django/contrib/markup/tests.py2
-rw-r--r--django/contrib/sessions/backends/file.py2
-rw-r--r--django/contrib/sessions/tests.py661
-rw-r--r--django/contrib/sitemaps/__init__.py5
-rw-r--r--django/contrib/sitemaps/models.py1
-rw-r--r--django/contrib/sitemaps/tests/__init__.py1
-rw-r--r--django/contrib/sitemaps/tests/basic.py77
-rw-r--r--django/contrib/sitemaps/tests/urls.py33
57 files changed, 1523 insertions, 841 deletions
diff --git a/django/contrib/admin/media/css/base.css b/django/contrib/admin/media/css/base.css
index da502f357a..9c1f71f810 100644
--- a/django/contrib/admin/media/css/base.css
+++ b/django/contrib/admin/media/css/base.css
@@ -445,6 +445,14 @@ ul.messagelist li {
background: #ffc url(../img/admin/icon_success.gif) 5px .3em no-repeat;
}
+ul.messagelist li.warning{
+ background-image: url(../img/admin/icon_alert.gif);
+}
+
+ul.messagelist li.error{
+ background-image: url(../img/admin/icon_error.gif);
+}
+
.errornote {
font-size: 12px !important;
display: block;
@@ -470,6 +478,11 @@ ul.errorlist {
background: red url(../img/admin/icon_alert.gif) 5px .3em no-repeat;
}
+.errorlist li a {
+ color: white;
+ text-decoration: underline;
+}
+
td ul.errorlist {
margin: 0 !important;
padding: 0 !important;
diff --git a/django/contrib/admin/media/css/changelists.css b/django/contrib/admin/media/css/changelists.css
index 99ff8bc0cf..282833c440 100644
--- a/django/contrib/admin/media/css/changelists.css
+++ b/django/contrib/admin/media/css/changelists.css
@@ -9,6 +9,8 @@
width: 100%;
}
+.change-list .hiddenfields { display:none; }
+
.change-list .filtered table {
border-right: 1px solid #ddd;
}
@@ -21,7 +23,7 @@
background: white url(../img/admin/changelist-bg.gif) top right repeat-y !important;
}
-.change-list .filtered table, .change-list .filtered .paginator, .filtered #toolbar, .filtered div.xfull {
+.change-list .filtered .results, .change-list .filtered .paginator, .filtered #toolbar, .filtered div.xfull {
margin-right: 160px !important;
width: auto !important;
}
diff --git a/django/contrib/admin/media/js/dateparse.js b/django/contrib/admin/media/js/dateparse.js
index e1c870e146..3cb82dea13 100644
--- a/django/contrib/admin/media/js/dateparse.js
+++ b/django/contrib/admin/media/js/dateparse.js
@@ -100,8 +100,9 @@ var dateParsePatterns = [
{ re: /^(\d{1,2})(?:st|nd|rd|th)? (\w+)$/i,
handler: function(bits) {
var d = new Date();
- d.setDate(parseInt(bits[1], 10));
+ d.setDate(1);
d.setMonth(parseMonth(bits[2]));
+ d.setDate(parseInt(bits[1], 10));
return d;
}
},
@@ -109,9 +110,10 @@ var dateParsePatterns = [
{ re: /^(\d{1,2})(?:st|nd|rd|th)? (\w+),? (\d{4})$/i,
handler: function(bits) {
var d = new Date();
- d.setDate(parseInt(bits[1], 10));
- d.setMonth(parseMonth(bits[2]));
+ d.setDate(1);
d.setYear(bits[3]);
+ d.setMonth(parseMonth(bits[2]));
+ d.setDate(parseInt(bits[1], 10));
return d;
}
},
@@ -119,8 +121,9 @@ var dateParsePatterns = [
{ re: /^(\w+) (\d{1,2})(?:st|nd|rd|th)?$/i,
handler: function(bits) {
var d = new Date();
- d.setDate(parseInt(bits[2], 10));
+ d.setDate(1);
d.setMonth(parseMonth(bits[1]));
+ d.setDate(parseInt(bits[2], 10));
return d;
}
},
@@ -128,9 +131,10 @@ var dateParsePatterns = [
{ re: /^(\w+) (\d{1,2})(?:st|nd|rd|th)?,? (\d{4})$/i,
handler: function(bits) {
var d = new Date();
- d.setDate(parseInt(bits[2], 10));
- d.setMonth(parseMonth(bits[1]));
+ d.setDate(1);
d.setYear(bits[3]);
+ d.setMonth(parseMonth(bits[1]));
+ d.setDate(parseInt(bits[2], 10));
return d;
}
},
@@ -158,9 +162,10 @@ var dateParsePatterns = [
{ re: /(\d{1,2})\/(\d{1,2})\/(\d{4})/,
handler: function(bits) {
var d = new Date();
+ d.setDate(1);
d.setYear(bits[3]);
- d.setDate(parseInt(bits[2], 10));
d.setMonth(parseInt(bits[1], 10) - 1); // Because months indexed from 0
+ d.setDate(parseInt(bits[2], 10));
return d;
}
},
@@ -168,6 +173,7 @@ var dateParsePatterns = [
{ re: /(\d{4})-(\d{1,2})-(\d{1,2})/,
handler: function(bits) {
var d = new Date();
+ d.setDate(1);
d.setYear(parseInt(bits[1]));
d.setMonth(parseInt(bits[2], 10) - 1);
d.setDate(parseInt(bits[3], 10));
diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
index abbe14c948..ffa3a533e9 100644
--- a/django/contrib/admin/options.py
+++ b/django/contrib/admin/options.py
@@ -108,7 +108,13 @@ class BaseModelAdmin(object):
# rendered output. formfield can be None if it came from a
# OneToOneField with parent_link=True or a M2M intermediary.
if formfield and db_field.name not in self.raw_id_fields:
- formfield.widget = widgets.RelatedFieldWidgetWrapper(formfield.widget, db_field.rel, self.admin_site)
+ related_modeladmin = self.admin_site._registry.get(
+ db_field.rel.to)
+ can_add_related = bool(related_modeladmin and
+ related_modeladmin.has_add_permission(request))
+ formfield.widget = widgets.RelatedFieldWidgetWrapper(
+ formfield.widget, db_field.rel, self.admin_site,
+ can_add_related=can_add_related)
return formfield
@@ -502,7 +508,7 @@ class ModelAdmin(BaseModelAdmin):
# Convert the actions into a SortedDict keyed by name
# and sorted by description.
- actions.sort(lambda a,b: cmp(a[2].lower(), b[2].lower()))
+ actions.sort(key=lambda k: k[2].lower())
actions = SortedDict([
(name, (func, name, desc))
for func, name, desc in actions
@@ -755,7 +761,7 @@ class ModelAdmin(BaseModelAdmin):
if isinstance(response, HttpResponse):
return response
else:
- return HttpResponseRedirect(".")
+ return HttpResponseRedirect(request.get_full_path())
else:
msg = _("No action selected.")
self.message_user(request, msg)
diff --git a/django/contrib/admin/sites.py b/django/contrib/admin/sites.py
index f4b6953347..ddc4f8c84b 100644
--- a/django/contrib/admin/sites.py
+++ b/django/contrib/admin/sites.py
@@ -380,11 +380,11 @@ class AdminSite(object):
# Sort the apps alphabetically.
app_list = app_dict.values()
- app_list.sort(lambda x, y: cmp(x['name'], y['name']))
+ app_list.sort(key=lambda x: x['name'])
# Sort the models alphabetically within each app.
for app in app_list:
- app['models'].sort(lambda x, y: cmp(x['name'], y['name']))
+ app['models'].sort(key=lambda x: x['name'])
context = {
'title': _('Site administration'),
@@ -445,7 +445,7 @@ class AdminSite(object):
if not app_dict:
raise http.Http404('The requested admin page does not exist.')
# Sort the models alphabetically within each app.
- app_dict['models'].sort(lambda x, y: cmp(x['name'], y['name']))
+ app_dict['models'].sort(key=lambda x: x['name'])
context = {
'title': _('%s administration') % app_instance.verbose_name,
'app_list': [app_dict],
diff --git a/django/contrib/admin/templates/admin/auth/user/add_form.html b/django/contrib/admin/templates/admin/auth/user/add_form.html
index b57f59b82e..c8889eb069 100644
--- a/django/contrib/admin/templates/admin/auth/user/add_form.html
+++ b/django/contrib/admin/templates/admin/auth/user/add_form.html
@@ -2,8 +2,11 @@
{% load i18n %}
{% block form_top %}
+ {% if not is_popup %}
<p>{% trans "First, enter a username and password. Then, you'll be able to edit more user options." %}</p>
- <input type="hidden" name="_continue" value="1" />
+ {% else %}
+ <p>{% trans "Enter a username and password." %}</p>
+ {% endif %}
{% endblock %}
{% block after_field_sets %}
diff --git a/django/contrib/admin/templates/admin/base.html b/django/contrib/admin/templates/admin/base.html
index 3fd9eb3770..4221e2d1a7 100644
--- a/django/contrib/admin/templates/admin/base.html
+++ b/django/contrib/admin/templates/admin/base.html
@@ -56,7 +56,9 @@
{% endif %}
{% if messages %}
- <ul class="messagelist">{% for message in messages %}<li>{{ message }}</li>{% endfor %}</ul>
+ <ul class="messagelist">{% for message in messages %}
+ <li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</li>
+ {% endfor %}</ul>
{% endif %}
<!-- Content -->
diff --git a/django/contrib/admin/templates/admin/change_list_results.html b/django/contrib/admin/templates/admin/change_list_results.html
index 0efcc9b24a..6d4de2df0e 100644
--- a/django/contrib/admin/templates/admin/change_list_results.html
+++ b/django/contrib/admin/templates/admin/change_list_results.html
@@ -1,4 +1,10 @@
+{% if result_hidden_fields %}
+<div class="hiddenfields"> {# DIV for HTML validation #}
+{% for item in result_hidden_fields %}{{ item }}{% endfor %}
+</div>
+{% endif %}
{% if results %}
+<div class="results">
<table cellspacing="0" id="result_list">
<thead>
<tr>
@@ -14,4 +20,5 @@
{% endfor %}
</tbody>
</table>
+</div>
{% endif %}
diff --git a/django/contrib/admin/templatetags/admin_list.py b/django/contrib/admin/templatetags/admin_list.py
index 565db32251..c05af0b7fe 100644
--- a/django/contrib/admin/templatetags/admin_list.py
+++ b/django/contrib/admin/templatetags/admin_list.py
@@ -189,7 +189,7 @@ def items_for_result(cl, result, form):
else:
result_repr = conditional_escape(result_repr)
yield mark_safe(u'<td%s>%s</td>' % (row_class, result_repr))
- if form:
+ if form and not form[cl.model._meta.pk.name].is_hidden:
yield mark_safe(u'<td>%s</td>' % force_unicode(form[cl.model._meta.pk.name]))
def results(cl):
@@ -200,11 +200,18 @@ def results(cl):
for res in cl.result_list:
yield list(items_for_result(cl, res, None))
+def result_hidden_fields(cl):
+ if cl.formset:
+ for res, form in zip(cl.result_list, cl.formset.forms):
+ if form[cl.model._meta.pk.name].is_hidden:
+ yield mark_safe(force_unicode(form[cl.model._meta.pk.name]))
+
def result_list(cl):
"""
Displays the headers and data list together
"""
return {'cl': cl,
+ 'result_hidden_fields': list(result_hidden_fields(cl)),
'result_headers': list(result_headers(cl)),
'results': list(results(cl))}
result_list = register.inclusion_tag("admin/change_list_results.html")(result_list)
diff --git a/django/contrib/admin/widgets.py b/django/contrib/admin/widgets.py
index 1d321d0620..2c7ac5c794 100644
--- a/django/contrib/admin/widgets.py
+++ b/django/contrib/admin/widgets.py
@@ -154,9 +154,9 @@ class ForeignKeyRawIdWidget(forms.TextInput):
key = self.rel.get_related_field().name
try:
obj = self.rel.to._default_manager.using(self.db).get(**{key: value})
- except self.rel.to.DoesNotExist:
+ return '&nbsp;<strong>%s</strong>' % escape(truncate_words(obj, 14))
+ except (ValueError, self.rel.to.DoesNotExist):
return ''
- return '&nbsp;<strong>%s</strong>' % escape(truncate_words(obj, 14))
class ManyToManyRawIdWidget(ForeignKeyRawIdWidget):
"""
@@ -169,7 +169,7 @@ class ManyToManyRawIdWidget(ForeignKeyRawIdWidget):
def render(self, name, value, attrs=None):
attrs['class'] = 'vManyToManyRawIdAdminField'
if value:
- value = ','.join([str(v) for v in value])
+ value = ','.join([force_unicode(v) for v in value])
else:
value = ''
return super(ManyToManyRawIdWidget, self).render(name, value, attrs)
@@ -205,13 +205,18 @@ class RelatedFieldWidgetWrapper(forms.Widget):
This class is a wrapper to a given widget to add the add icon for the
admin interface.
"""
- def __init__(self, widget, rel, admin_site):
+ def __init__(self, widget, rel, admin_site, can_add_related=None):
self.is_hidden = widget.is_hidden
self.needs_multipart_form = widget.needs_multipart_form
self.attrs = widget.attrs
self.choices = widget.choices
self.widget = widget
self.rel = rel
+ # Backwards compatible check for whether a user can add related
+ # objects.
+ if can_add_related is None:
+ can_add_related = rel_to in self.admin_site._registry
+ self.can_add_related = can_add_related
# so we can check if the related object is registered with this AdminSite
self.admin_site = admin_site
@@ -236,7 +241,7 @@ class RelatedFieldWidgetWrapper(forms.Widget):
related_url = '%s%s/%s/add/' % info
self.widget.choices = self.choices
output = [self.widget.render(name, value, *args, **kwargs)]
- if rel_to in self.admin_site._registry: # If the related object has an admin interface:
+ if self.can_add_related:
# TODO: "id_" is hard-coded here. This should instead use the correct
# API to determine the ID dynamically.
output.append(u'<a href="%s" class="add-another" id="add_id_%s" onclick="return showAddAnotherPopup(this);"> ' % \
diff --git a/django/contrib/admindocs/templates/admin_doc/template_filter_index.html b/django/contrib/admindocs/templates/admin_doc/template_filter_index.html
index 53383edc23..7470762775 100644
--- a/django/contrib/admindocs/templates/admin_doc/template_filter_index.html
+++ b/django/contrib/admindocs/templates/admin_doc/template_filter_index.html
@@ -15,7 +15,7 @@
<h2>{% firstof library.grouper "Built-in filters" %}</h2>
{% if library.grouper %}<p class="small quiet">To use these filters, put <code>{% templatetag openblock %} load {{ library.grouper }} {% templatetag closeblock %}</code> in your template before using the filter.</p><hr />{% endif %}
{% for filter in library.list|dictsort:"name" %}
- <h3 id="{{ filter.name }}">{{ filter.name }}</h3>
+ <h3 id="{{ library.grouper|default_if_none:"built_in" }}-{{ filter.name }}">{{ filter.name }}</h3>
<p>{{ filter.title }}</p>
<p>{{ filter.body }}</p>
{% if not forloop.last %}<hr />{% endif %}
@@ -36,7 +36,7 @@
<h2>{% firstof library.grouper "Built-in filters" %}</h2>
<ul>
{% for filter in library.list|dictsort:"name" %}
- <li><a href="#{{ filter.name }}">{{ filter.name }}</a></li>
+ <li><a href="#{{ library.grouper|default_if_none:"built_in" }}-{{ filter.name }}">{{ filter.name }}</a></li>
{% endfor %}
</ul>
</div>
diff --git a/django/contrib/admindocs/templates/admin_doc/template_tag_index.html b/django/contrib/admindocs/templates/admin_doc/template_tag_index.html
index a4ad66fd96..774130bd70 100644
--- a/django/contrib/admindocs/templates/admin_doc/template_tag_index.html
+++ b/django/contrib/admindocs/templates/admin_doc/template_tag_index.html
@@ -15,7 +15,7 @@
<h2>{% firstof library.grouper "Built-in tags" %}</h2>
{% if library.grouper %}<p class="small quiet">To use these tags, put <code>{% templatetag openblock %} load {{ library.grouper }} {% templatetag closeblock %}</code> in your template before using the tag.</p><hr />{% endif %}
{% for tag in library.list|dictsort:"name" %}
- <h3 id="{{ tag.name }}">{{ tag.name }}</h3>
+ <h3 id="{{ library.grouper|default_if_none:"built_in" }}-{{ tag.name }}">{{ tag.name }}</h3>
<h4>{{ tag.title }}</h4>
<p>{{ tag.body }}</p>
{% if not forloop.last %}<hr />{% endif %}
@@ -36,7 +36,7 @@
<h2>{% firstof library.grouper "Built-in tags" %}</h2>
<ul>
{% for tag in library.list|dictsort:"name" %}
- <li><a href="#{{ tag.name }}">{{ tag.name }}</a></li>
+ <li><a href="#{{ library.grouper|default_if_none:"built_in" }}-{{ tag.name }}">{{ tag.name }}</a></li>
{% endfor %}
</ul>
</div>
diff --git a/django/contrib/admindocs/views.py b/django/contrib/admindocs/views.py
index e154c9299a..5bfa0f7184 100644
--- a/django/contrib/admindocs/views.py
+++ b/django/contrib/admindocs/views.py
@@ -54,7 +54,9 @@ def template_tag_index(request):
load_all_installed_template_libraries()
tags = []
- for module_name, library in template.libraries.items():
+ app_libs = template.libraries.items()
+ builtin_libs = [(None, lib) for lib in template.builtins]
+ for module_name, library in builtin_libs + app_libs:
for tag_name, tag_func in library.tags.items():
title, body, metadata = utils.parse_docstring(tag_func.__doc__)
if title:
@@ -87,7 +89,9 @@ def template_filter_index(request):
load_all_installed_template_libraries()
filters = []
- for module_name, library in template.libraries.items():
+ app_libs = template.libraries.items()
+ builtin_libs = [(None, lib) for lib in template.builtins]
+ for module_name, library in builtin_libs + app_libs:
for filter_name, filter_func in library.filters.items():
title, body, metadata = utils.parse_docstring(filter_func.__doc__)
if title:
diff --git a/django/contrib/auth/decorators.py b/django/contrib/auth/decorators.py
index 09dcf42e42..7d7a0cddb7 100644
--- a/django/contrib/auth/decorators.py
+++ b/django/contrib/auth/decorators.py
@@ -30,13 +30,14 @@ def user_passes_test(test_func, login_url=None, redirect_field_name=REDIRECT_FIE
return decorator
-def login_required(function=None, redirect_field_name=REDIRECT_FIELD_NAME):
+def login_required(function=None, redirect_field_name=REDIRECT_FIELD_NAME, login_url=None):
"""
Decorator for views that checks that the user is logged in, redirecting
to the log-in page if necessary.
"""
actual_decorator = user_passes_test(
lambda u: u.is_authenticated(),
+ login_url=login_url,
redirect_field_name=redirect_field_name
)
if function:
diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py
index 086acf3349..cc0f7071c3 100644
--- a/django/contrib/auth/forms.py
+++ b/django/contrib/auth/forms.py
@@ -52,6 +52,12 @@ class UserChangeForm(forms.ModelForm):
class Meta:
model = User
+ def __init__(self, *args, **kwargs):
+ super(UserChangeForm, self).__init__(*args, **kwargs)
+ f = self.fields.get('user_permissions', None)
+ if f is not None:
+ f.queryset = f.queryset.select_related('content_type')
+
class AuthenticationForm(forms.Form):
"""
Base class for authenticating users. Extend this to get a form that accepts
@@ -111,7 +117,7 @@ class PasswordResetForm(forms.Form):
return email
def save(self, domain_override=None, email_template_name='registration/password_reset_email.html',
- use_https=False, token_generator=default_token_generator):
+ use_https=False, token_generator=default_token_generator, from_email=None):
"""
Generates a one-use only link for resetting password and sends to the user
"""
@@ -134,7 +140,7 @@ class PasswordResetForm(forms.Form):
'protocol': use_https and 'https' or 'http',
}
send_mail(_("Password reset on %s") % site_name,
- t.render(Context(c)), None, [user.email])
+ t.render(Context(c)), from_email, [user.email])
class SetPasswordForm(forms.Form):
"""
diff --git a/django/contrib/auth/tests/__init__.py b/django/contrib/auth/tests/__init__.py
index 965ea2d6be..a1d02b6014 100644
--- a/django/contrib/auth/tests/__init__.py
+++ b/django/contrib/auth/tests/__init__.py
@@ -1,7 +1,7 @@
from django.contrib.auth.tests.auth_backends import BackendTest, RowlevelBackendTest, AnonymousUserBackendTest, NoAnonymousUserBackendTest
from django.contrib.auth.tests.basic import BASIC_TESTS
from django.contrib.auth.tests.decorators import LoginRequiredTestCase
-from django.contrib.auth.tests.forms import FORM_TESTS
+from django.contrib.auth.tests.forms import UserCreationFormTest, AuthenticationFormTest, SetPasswordFormTest, PasswordChangeFormTest, UserChangeFormTest, PasswordResetFormTest
from django.contrib.auth.tests.remote_user \
import RemoteUserTest, RemoteUserNoCreateTest, RemoteUserCustomTest
from django.contrib.auth.tests.models import ProfileTestCase
@@ -13,6 +13,5 @@ from django.contrib.auth.tests.views \
__test__ = {
'BASIC_TESTS': BASIC_TESTS,
- 'FORM_TESTS': FORM_TESTS,
'TOKEN_GENERATOR_TESTS': TOKEN_GENERATOR_TESTS,
}
diff --git a/django/contrib/auth/tests/decorators.py b/django/contrib/auth/tests/decorators.py
index 7efd9d8ccf..0240a76eb7 100644
--- a/django/contrib/auth/tests/decorators.py
+++ b/django/contrib/auth/tests/decorators.py
@@ -1,12 +1,12 @@
-from unittest import TestCase
-
from django.contrib.auth.decorators import login_required
+from django.contrib.auth.tests.views import AuthViewsTestCase
-
-class LoginRequiredTestCase(TestCase):
+class LoginRequiredTestCase(AuthViewsTestCase):
"""
Tests the login_required decorators
"""
+ urls = 'django.contrib.auth.tests.urls'
+
def testCallable(self):
"""
Check that login_required is assignable to callable objects.
@@ -22,4 +22,24 @@ class LoginRequiredTestCase(TestCase):
"""
def normal_view(request):
pass
- login_required(normal_view) \ No newline at end of file
+ login_required(normal_view)
+
+ def testLoginRequired(self, view_url='/login_required/', login_url='/login/'):
+ """
+ Check that login_required works on a simple view wrapped in a
+ login_required decorator.
+ """
+ response = self.client.get(view_url)
+ self.assertEqual(response.status_code, 302)
+ self.assert_(login_url in response['Location'])
+ self.login()
+ response = self.client.get(view_url)
+ self.assertEqual(response.status_code, 200)
+
+ def testLoginRequiredNextUrl(self):
+ """
+ Check that login_required works on a simple view wrapped in a
+ login_required decorator with a login_url set.
+ """
+ self.testLoginRequired(view_url='/login_required_login_url/',
+ login_url='/somewhere/') \ No newline at end of file
diff --git a/django/contrib/auth/tests/forms.py b/django/contrib/auth/tests/forms.py
index b691c560be..5aa49e09c3 100644
--- a/django/contrib/auth/tests/forms.py
+++ b/django/contrib/auth/tests/forms.py
@@ -1,231 +1,252 @@
-
-FORM_TESTS = """
->>> from django.contrib.auth.models import User
->>> from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
->>> from django.contrib.auth.forms import PasswordChangeForm, SetPasswordForm
-
-# The user already exists.
-
->>> user = User.objects.create_user("jsmith", "jsmith@example.com", "test123")
->>> data = {
-... 'username': 'jsmith',
-... 'password1': 'test123',
-... 'password2': 'test123',
-... }
->>> form = UserCreationForm(data)
->>> form.is_valid()
-False
->>> form["username"].errors
-[u'A user with that username already exists.']
-
-# The username contains invalid data.
-
->>> data = {
-... 'username': 'jsmith!',
-... 'password1': 'test123',
-... 'password2': 'test123',
-... }
->>> form = UserCreationForm(data)
->>> form.is_valid()
-False
->>> form["username"].errors
-[u'This value may contain only letters, numbers and @/./+/-/_ characters.']
-
-# The verification password is incorrect.
-
->>> data = {
-... 'username': 'jsmith2',
-... 'password1': 'test123',
-... 'password2': 'test',
-... }
->>> form = UserCreationForm(data)
->>> form.is_valid()
-False
->>> form["password2"].errors
-[u"The two password fields didn't match."]
-
-# One (or both) passwords weren't given
-
->>> data = {'username': 'jsmith2'}
->>> form = UserCreationForm(data)
->>> form.is_valid()
-False
->>> form['password1'].errors
-[u'This field is required.']
->>> form['password2'].errors
-[u'This field is required.']
-
->>> data['password2'] = 'test123'
->>> form = UserCreationForm(data)
->>> form.is_valid()
-False
->>> form['password1'].errors
-[u'This field is required.']
-
-# The success case.
-
->>> data = {
-... 'username': 'jsmith2@example.com',
-... 'password1': 'test123',
-... 'password2': 'test123',
-... }
->>> form = UserCreationForm(data)
->>> form.is_valid()
-True
->>> form.save()
-<User: jsmith2@example.com>
-
-# The user submits an invalid username.
-
->>> data = {
-... 'username': 'jsmith_does_not_exist',
-... 'password': 'test123',
-... }
-
->>> form = AuthenticationForm(None, data)
->>> form.is_valid()
-False
->>> form.non_field_errors()
-[u'Please enter a correct username and password. Note that both fields are case-sensitive.']
-
-# The user is inactive.
-
->>> data = {
-... 'username': 'jsmith',
-... 'password': 'test123',
-... }
->>> user.is_active = False
->>> user.save()
->>> form = AuthenticationForm(None, data)
->>> form.is_valid()
-False
->>> form.non_field_errors()
-[u'This account is inactive.']
-
->>> user.is_active = True
->>> user.save()
-
-# The success case
-
->>> form = AuthenticationForm(None, data)
->>> form.is_valid()
-True
->>> form.non_field_errors()
-[]
-
-### SetPasswordForm:
-
-# The two new passwords do not match.
-
->>> data = {
-... 'new_password1': 'abc123',
-... 'new_password2': 'abc',
-... }
->>> form = SetPasswordForm(user, data)
->>> form.is_valid()
-False
->>> form["new_password2"].errors
-[u"The two password fields didn't match."]
-
-# The success case.
-
->>> data = {
-... 'new_password1': 'abc123',
-... 'new_password2': 'abc123',
-... }
->>> form = SetPasswordForm(user, data)
->>> form.is_valid()
-True
-
-### PasswordChangeForm:
-
-The old password is incorrect.
-
->>> data = {
-... 'old_password': 'test',
-... 'new_password1': 'abc123',
-... 'new_password2': 'abc123',
-... }
->>> form = PasswordChangeForm(user, data)
->>> form.is_valid()
-False
->>> form["old_password"].errors
-[u'Your old password was entered incorrectly. Please enter it again.']
-
-# The two new passwords do not match.
-
->>> data = {
-... 'old_password': 'test123',
-... 'new_password1': 'abc123',
-... 'new_password2': 'abc',
-... }
->>> form = PasswordChangeForm(user, data)
->>> form.is_valid()
-False
->>> form["new_password2"].errors
-[u"The two password fields didn't match."]
-
-# The success case.
-
->>> data = {
-... 'old_password': 'test123',
-... 'new_password1': 'abc123',
-... 'new_password2': 'abc123',
-... }
->>> form = PasswordChangeForm(user, data)
->>> form.is_valid()
-True
-
-# Regression test - check the order of fields:
-
->>> PasswordChangeForm(user, {}).fields.keys()
-['old_password', 'new_password1', 'new_password2']
-
-### UserChangeForm
-
->>> from django.contrib.auth.forms import UserChangeForm
->>> data = {'username': 'not valid'}
->>> form = UserChangeForm(data, instance=user)
->>> form.is_valid()
-False
->>> form['username'].errors
-[u'This value may contain only letters, numbers and @/./+/-/_ characters.']
-
-
-### PasswordResetForm
-
->>> from django.contrib.auth.forms import PasswordResetForm
->>> data = {'email':'not valid'}
->>> form = PasswordResetForm(data)
->>> form.is_valid()
-False
->>> form['email'].errors
-[u'Enter a valid e-mail address.']
-
-# Test nonexistant email address
->>> data = {'email':'foo@bar.com'}
->>> form = PasswordResetForm(data)
->>> form.is_valid()
-False
->>> form.errors
-{'email': [u"That e-mail address doesn't have an associated user account. Are you sure you've registered?"]}
-
-# Test cleaned_data bug fix
->>> user = User.objects.create_user("jsmith3", "jsmith3@example.com", "test123")
->>> data = {'email':'jsmith3@example.com'}
->>> form = PasswordResetForm(data)
->>> form.is_valid()
-True
->>> form.cleaned_data['email']
-u'jsmith3@example.com'
-
-# bug #5605, preserve the case of the user name (before the @ in the email address)
-# when creating a user.
->>> user = User.objects.create_user('forms_test2', 'tesT@EXAMple.com', 'test')
->>> user.email
-'tesT@example.com'
->>> user = User.objects.create_user('forms_test3', 'tesT', 'test')
->>> user.email
-'tesT'
-
-"""
+from django.contrib.auth.models import User
+from django.contrib.auth.forms import UserCreationForm, AuthenticationForm, PasswordChangeForm, SetPasswordForm, UserChangeForm, PasswordResetForm
+from django.test import TestCase
+
+
+class UserCreationFormTest(TestCase):
+
+ fixtures = ['authtestdata.json']
+
+ def test_user_already_exists(self):
+ data = {
+ 'username': 'testclient',
+ 'password1': 'test123',
+ 'password2': 'test123',
+ }
+ form = UserCreationForm(data)
+ self.assertFalse(form.is_valid())
+ self.assertEqual(form["username"].errors,
+ [u'A user with that username already exists.'])
+
+ def test_invalid_data(self):
+ data = {
+ 'username': 'jsmith!',
+ 'password1': 'test123',
+ 'password2': 'test123',
+ }
+ form = UserCreationForm(data)
+ self.assertFalse(form.is_valid())
+ self.assertEqual(form["username"].errors,
+ [u'This value may contain only letters, numbers and @/./+/-/_ characters.'])
+
+
+ def test_password_verification(self):
+ # The verification password is incorrect.
+ data = {
+ 'username': 'jsmith',
+ 'password1': 'test123',
+ 'password2': 'test',
+ }
+ form = UserCreationForm(data)
+ self.assertFalse(form.is_valid())
+ self.assertEqual(form["password2"].errors,
+ [u"The two password fields didn't match."])
+
+
+ def test_both_passwords(self):
+ # One (or both) passwords weren't given
+ data = {'username': 'jsmith'}
+ form = UserCreationForm(data)
+ self.assertFalse(form.is_valid())
+ self.assertEqual(form['password1'].errors,
+ [u'This field is required.'])
+ self.assertEqual(form['password2'].errors,
+ [u'This field is required.'])
+
+
+ data['password2'] = 'test123'
+ form = UserCreationForm(data)
+ self.assertFalse(form.is_valid())
+ self.assertEqual(form['password1'].errors,
+ [u'This field is required.'])
+
+ def test_success(self):
+ # The success case.
+
+ data = {
+ 'username': 'jsmith@example.com',
+ 'password1': 'test123',
+ 'password2': 'test123',
+ }
+ form = UserCreationForm(data)
+ self.assertTrue(form.is_valid())
+ u = form.save()
+ self.assertEqual(repr(u), '<User: jsmith@example.com>')
+
+
+class AuthenticationFormTest(TestCase):
+
+ fixtures = ['authtestdata.json']
+
+ def test_invalid_username(self):
+ # The user submits an invalid username.
+
+ data = {
+ 'username': 'jsmith_does_not_exist',
+ 'password': 'test123',
+ }
+ form = AuthenticationForm(None, data)
+ self.assertFalse(form.is_valid())
+ self.assertEqual(form.non_field_errors(),
+ [u'Please enter a correct username and password. Note that both fields are case-sensitive.'])
+
+ def test_inactive_user(self):
+ # The user is inactive.
+ data = {
+ 'username': 'inactive',
+ 'password': 'password',
+ }
+ form = AuthenticationForm(None, data)
+ self.assertFalse(form.is_valid())
+ self.assertEqual(form.non_field_errors(),
+ [u'This account is inactive.'])
+
+
+ def test_success(self):
+ # The success case
+ data = {
+ 'username': 'testclient',
+ 'password': 'password',
+ }
+ form = AuthenticationForm(None, data)
+ self.assertTrue(form.is_valid())
+ self.assertEqual(form.non_field_errors(), [])
+
+
+class SetPasswordFormTest(TestCase):
+
+ fixtures = ['authtestdata.json']
+
+ def test_password_verification(self):
+ # The two new passwords do not match.
+ user = User.objects.get(username='testclient')
+ data = {
+ 'new_password1': 'abc123',
+ 'new_password2': 'abc',
+ }
+ form = SetPasswordForm(user, data)
+ self.assertFalse(form.is_valid())
+ self.assertEqual(form["new_password2"].errors,
+ [u"The two password fields didn't match."])
+
+ def test_success(self):
+ user = User.objects.get(username='testclient')
+ data = {
+ 'new_password1': 'abc123',
+ 'new_password2': 'abc123',
+ }
+ form = SetPasswordForm(user, data)
+ self.assertTrue(form.is_valid())
+
+
+class PasswordChangeFormTest(TestCase):
+
+ fixtures = ['authtestdata.json']
+
+ def test_incorrect_password(self):
+ user = User.objects.get(username='testclient')
+ data = {
+ 'old_password': 'test',
+ 'new_password1': 'abc123',
+ 'new_password2': 'abc123',
+ }
+ form = PasswordChangeForm(user, data)
+ self.assertFalse(form.is_valid())
+ self.assertEqual(form["old_password"].errors,
+ [u'Your old password was entered incorrectly. Please enter it again.'])
+
+
+ def test_password_verification(self):
+ # The two new passwords do not match.
+ user = User.objects.get(username='testclient')
+ data = {
+ 'old_password': 'password',
+ 'new_password1': 'abc123',
+ 'new_password2': 'abc',
+ }
+ form = PasswordChangeForm(user, data)
+ self.assertFalse(form.is_valid())
+ self.assertEqual(form["new_password2"].errors,
+ [u"The two password fields didn't match."])
+
+
+ def test_success(self):
+ # The success case.
+ user = User.objects.get(username='testclient')
+ data = {
+ 'old_password': 'password',
+ 'new_password1': 'abc123',
+ 'new_password2': 'abc123',
+ }
+ form = PasswordChangeForm(user, data)
+ self.assertTrue(form.is_valid())
+
+ def test_field_order(self):
+ # Regression test - check the order of fields:
+ user = User.objects.get(username='testclient')
+ self.assertEqual(PasswordChangeForm(user, {}).fields.keys(),
+ ['old_password', 'new_password1', 'new_password2'])
+
+class UserChangeFormTest(TestCase):
+
+ fixtures = ['authtestdata.json']
+
+ def test_username_validity(self):
+ user = User.objects.get(username='testclient')
+ data = {'username': 'not valid'}
+ form = UserChangeForm(data, instance=user)
+ self.assertFalse(form.is_valid())
+ self.assertEqual(form['username'].errors,
+ [u'This value may contain only letters, numbers and @/./+/-/_ characters.'])
+
+ def test_bug_14242(self):
+ # A regression test, introduce by adding an optimization for the
+ # UserChangeForm.
+
+ class MyUserForm(UserChangeForm):
+ def __init__(self, *args, **kwargs):
+ super(MyUserForm, self).__init__(*args, **kwargs)
+ self.fields['groups'].help_text = 'These groups give users different permissions'
+
+ class Meta(UserChangeForm.Meta):
+ fields = ('groups',)
+
+ # Just check we can create it
+ form = MyUserForm({})
+
+
+class PasswordResetFormTest(TestCase):
+
+ fixtures = ['authtestdata.json']
+
+ def test_invalid_email(self):
+ data = {'email':'not valid'}
+ form = PasswordResetForm(data)
+ self.assertFalse(form.is_valid())
+ self.assertEqual(form['email'].errors,
+ [u'Enter a valid e-mail address.'])
+
+ def test_nonexistant_email(self):
+ # Test nonexistant email address
+ data = {'email':'foo@bar.com'}
+ form = PasswordResetForm(data)
+ self.assertFalse(form.is_valid())
+ self.assertEqual(form.errors,
+ {'email': [u"That e-mail address doesn't have an associated user account. Are you sure you've registered?"]})
+
+ def test_cleaned_data(self):
+ # Regression test
+ user = User.objects.create_user("jsmith3", "jsmith3@example.com", "test123")
+ data = {'email':'jsmith3@example.com'}
+ form = PasswordResetForm(data)
+ self.assertTrue(form.is_valid())
+ self.assertEqual(form.cleaned_data['email'], u'jsmith3@example.com')
+
+
+ def test_bug_5605(self):
+ # bug #5605, preserve the case of the user name (before the @ in the
+ # email address) when creating a user.
+ user = User.objects.create_user('forms_test2', 'tesT@EXAMple.com', 'test')
+ self.assertEqual(user.email, 'tesT@example.com')
+ user = User.objects.create_user('forms_test3', 'tesT', 'test')
+ self.assertEqual(user.email, 'tesT')
diff --git a/django/contrib/auth/tests/urls.py b/django/contrib/auth/tests/urls.py
index f94b8daa7f..bc2f7e705f 100644
--- a/django/contrib/auth/tests/urls.py
+++ b/django/contrib/auth/tests/urls.py
@@ -1,5 +1,7 @@
from django.conf.urls.defaults import patterns
from django.contrib.auth.urls import urlpatterns
+from django.contrib.auth.views import password_reset
+from django.contrib.auth.decorators import login_required
from django.http import HttpResponse
from django.template import Template, RequestContext
@@ -14,5 +16,8 @@ urlpatterns += patterns('',
(r'^logout/custom_query/$', 'django.contrib.auth.views.logout', dict(redirect_field_name='follow')),
(r'^logout/next_page/$', 'django.contrib.auth.views.logout', dict(next_page='/somewhere/')),
(r'^remote_user/$', remote_user_auth_view),
+ (r'^password_reset_from_email/$', 'django.contrib.auth.views.password_reset', dict(from_email='staffmember@example.com')),
+ (r'^login_required/$', login_required(password_reset)),
+ (r'^login_required_login_url/$', login_required(password_reset, login_url='/somewhere/')),
)
diff --git a/django/contrib/auth/tests/views.py b/django/contrib/auth/tests/views.py
index d894e6dafd..e20afbc5b0 100644
--- a/django/contrib/auth/tests/views.py
+++ b/django/contrib/auth/tests/views.py
@@ -36,6 +36,16 @@ class AuthViewsTestCase(TestCase):
settings.LANGUAGE_CODE = self.old_LANGUAGE_CODE
settings.TEMPLATE_DIRS = self.old_TEMPLATE_DIRS
+ def login(self, password='password'):
+ response = self.client.post('/login/', {
+ 'username': 'testclient',
+ 'password': password
+ }
+ )
+ self.assertEquals(response.status_code, 302)
+ self.assert_(response['Location'].endswith(settings.LOGIN_REDIRECT_URL))
+ self.assert_(SESSION_KEY in self.client.session)
+
class PasswordResetTest(AuthViewsTestCase):
def test_email_not_found(self):
@@ -52,6 +62,14 @@ class PasswordResetTest(AuthViewsTestCase):
self.assertEquals(response.status_code, 302)
self.assertEquals(len(mail.outbox), 1)
self.assert_("http://" in mail.outbox[0].body)
+ self.assertEquals(settings.DEFAULT_FROM_EMAIL, mail.outbox[0].from_email)
+
+ def test_email_found_custom_from(self):
+ "Email is sent if a valid email address is provided for password reset when a custom from_email is provided."
+ response = self.client.post('/password_reset_from_email/', {'email': 'staffmember@example.com'})
+ self.assertEquals(response.status_code, 302)
+ self.assertEquals(len(mail.outbox), 1)
+ self.assertEquals("staffmember@example.com", mail.outbox[0].from_email)
def _test_confirm_start(self):
# Start by creating the email
@@ -118,15 +136,6 @@ class PasswordResetTest(AuthViewsTestCase):
class ChangePasswordTest(AuthViewsTestCase):
- def login(self, password='password'):
- response = self.client.post('/login/', {
- 'username': 'testclient',
- 'password': password
- }
- )
- self.assertEquals(response.status_code, 302)
- self.assert_(response['Location'].endswith(settings.LOGIN_REDIRECT_URL))
-
def fail_login(self, password='password'):
response = self.client.post('/login/', {
'username': 'testclient',
@@ -228,16 +237,6 @@ class LoginTest(AuthViewsTestCase):
class LogoutTest(AuthViewsTestCase):
urls = 'django.contrib.auth.tests.urls'
- def login(self, password='password'):
- response = self.client.post('/login/', {
- 'username': 'testclient',
- 'password': password
- }
- )
- self.assertEquals(response.status_code, 302)
- self.assert_(response['Location'].endswith(settings.LOGIN_REDIRECT_URL))
- self.assert_(SESSION_KEY in self.client.session)
-
def confirm_logged_out(self):
self.assert_(SESSION_KEY not in self.client.session)
diff --git a/django/contrib/auth/views.py b/django/contrib/auth/views.py
index b2e875a869..eaa0dbeba2 100644
--- a/django/contrib/auth/views.py
+++ b/django/contrib/auth/views.py
@@ -105,7 +105,7 @@ def redirect_to_login(next, login_url=None, redirect_field_name=REDIRECT_FIELD_N
def password_reset(request, is_admin_site=False, template_name='registration/password_reset_form.html',
email_template_name='registration/password_reset_email.html',
password_reset_form=PasswordResetForm, token_generator=default_token_generator,
- post_reset_redirect=None):
+ post_reset_redirect=None, from_email=None):
if post_reset_redirect is None:
post_reset_redirect = reverse('django.contrib.auth.views.password_reset_done')
if request.method == "POST":
@@ -114,6 +114,7 @@ def password_reset(request, is_admin_site=False, template_name='registration/pas
opts = {}
opts['use_https'] = request.is_secure()
opts['token_generator'] = token_generator
+ opts['from_email'] = from_email
if is_admin_site:
opts['domain_override'] = request.META['HTTP_HOST']
else:
diff --git a/django/contrib/databrowse/plugins/calendars.py b/django/contrib/databrowse/plugins/calendars.py
index 9bbd02da26..8a08cfd8eb 100644
--- a/django/contrib/databrowse/plugins/calendars.py
+++ b/django/contrib/databrowse/plugins/calendars.py
@@ -60,7 +60,7 @@ class CalendarPlugin(DatabrowsePlugin):
def homepage_view(self, request):
easy_model = EasyModel(self.site, self.model)
field_list = self.fields.values()
- field_list.sort(lambda x, y: cmp(x.verbose_name, y.verbose_name))
+ field_list.sort(key=lambda k:k.verbose_name)
return render_to_response('databrowse/calendar_homepage.html', {'root_url': self.site.root_url, 'model': easy_model, 'field_list': field_list})
def calendar_view(self, request, field, year=None, month=None, day=None):
diff --git a/django/contrib/databrowse/plugins/fieldchoices.py b/django/contrib/databrowse/plugins/fieldchoices.py
index 8f77792579..e0c01e95e6 100644
--- a/django/contrib/databrowse/plugins/fieldchoices.py
+++ b/django/contrib/databrowse/plugins/fieldchoices.py
@@ -61,7 +61,7 @@ class FieldChoicePlugin(DatabrowsePlugin):
def homepage_view(self, request):
easy_model = EasyModel(self.site, self.model)
field_list = self.fields.values()
- field_list.sort(lambda x, y: cmp(x.verbose_name, y.verbose_name))
+ field_list.sort(key=lambda k: k.verbose_name)
return render_to_response('databrowse/fieldchoice_homepage.html', {'root_url': self.site.root_url, 'model': easy_model, 'field_list': field_list})
def field_view(self, request, field, value=None):
diff --git a/django/contrib/flatpages/admin.py b/django/contrib/flatpages/admin.py
index b6fdba3d53..1b377e967b 100644
--- a/django/contrib/flatpages/admin.py
+++ b/django/contrib/flatpages/admin.py
@@ -5,11 +5,11 @@ from django.utils.translation import ugettext_lazy as _
class FlatpageForm(forms.ModelForm):
- url = forms.RegexField(label=_("URL"), max_length=100, regex=r'^[-\w/]+$',
+ url = forms.RegexField(label=_("URL"), max_length=100, regex=r'^[-\w/\.~]+$',
help_text = _("Example: '/about/contact/'. Make sure to have leading"
" and trailing slashes."),
error_message = _("This value must contain only letters, numbers,"
- " underscores, dashes or slashes."))
+ " dots, underscores, dashes, slashes or tildes."))
class Meta:
model = FlatPage
diff --git a/django/contrib/flatpages/fixtures/sample_flatpages.json b/django/contrib/flatpages/fixtures/sample_flatpages.json
new file mode 100644
index 0000000000..885af1eb60
--- /dev/null
+++ b/django/contrib/flatpages/fixtures/sample_flatpages.json
@@ -0,0 +1,63 @@
+[
+ {
+ "pk": 1,
+ "model": "flatpages.flatpage",
+ "fields": {
+ "registration_required": false,
+ "title": "A Flatpage",
+ "url": "/flatpage/",
+ "template_name": "",
+ "sites": [
+ 1
+ ],
+ "content": "Isn't it flat!",
+ "enable_comments": false
+ }
+ },
+ {
+ "pk": 2,
+ "model": "flatpages.flatpage",
+ "fields": {
+ "registration_required": false,
+ "title": "A Nested Flatpage",
+ "url": "/location/flatpage/",
+ "template_name": "",
+ "sites": [
+ 1
+ ],
+ "content": "Isn't it flat and deep!",
+ "enable_comments": false
+ }
+ },
+
+ {
+ "pk": 101,
+ "model": "flatpages.flatpage",
+ "fields": {
+ "registration_required": true,
+ "title": "Sekrit Flatpage",
+ "url": "/sekrit/",
+ "template_name": "",
+ "sites": [
+ 1
+ ],
+ "content": "Isn't it sekrit!",
+ "enable_comments": false
+ }
+ },
+ {
+ "pk": 102,
+ "model": "flatpages.flatpage",
+ "fields": {
+ "registration_required": true,
+ "title": "Sekrit Nested Flatpage",
+ "url": "/location/sekrit/",
+ "template_name": "",
+ "sites": [
+ 1
+ ],
+ "content": "Isn't it sekrit and deep!",
+ "enable_comments": false
+ }
+ }
+] \ No newline at end of file
diff --git a/django/contrib/flatpages/templatetags/__init__.py b/django/contrib/flatpages/templatetags/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/django/contrib/flatpages/templatetags/__init__.py
diff --git a/django/contrib/flatpages/templatetags/flatpages.py b/django/contrib/flatpages/templatetags/flatpages.py
new file mode 100644
index 0000000000..5c76699b79
--- /dev/null
+++ b/django/contrib/flatpages/templatetags/flatpages.py
@@ -0,0 +1,99 @@
+from django import template
+from django.contrib.flatpages.models import FlatPage
+from django.utils.translation import ugettext as _
+from django.conf import settings
+
+
+register = template.Library()
+
+
+class FlatpageNode(template.Node):
+ def __init__(self, context_name, starts_with=None, user=None):
+ self.context_name = context_name
+ if starts_with:
+ self.starts_with = template.Variable(starts_with)
+ else:
+ self.starts_with = None
+ if user:
+ self.user = template.Variable(user)
+ else:
+ self.user = None
+
+ def render(self, context):
+ flatpages = FlatPage.objects.filter(sites__id=settings.SITE_ID)
+ # If a prefix was specified, add a filter
+ if self.starts_with:
+ flatpages = flatpages.filter(
+ url__startswith=self.starts_with.resolve(context))
+
+ # If the provided user is not authenticated, or no user
+ # was provided, filter the list to only public flatpages.
+ if self.user:
+ user = self.user.resolve(context)
+ if not user.is_authenticated():
+ flatpages = flatpages.filter(registration_required=False)
+ else:
+ flatpages = flatpages.filter(registration_required=False)
+
+ context[self.context_name] = flatpages
+ return ''
+
+
+def get_flatpages(parser, token):
+ """
+ Retrieves all flatpage objects available for the current site and
+ visible to the specific user (or visible to all users if no user is
+ specified). Populates the template context with them in a variable
+ whose name is defined by the ``as`` clause.
+
+ An optional ``for`` clause can be used to control the user whose
+ permissions are to be used in determining which flatpages are visible.
+
+ An optional argument, ``starts_with``, can be applied to limit the
+ returned flatpages to those beginning with a particular base URL.
+ This argument can be passed as a variable or a string, as it resolves
+ from the template context.
+
+ Syntax::
+
+ {% get_flatpages ['url_starts_with'] [for user] as context_name %}
+
+ Example usage::
+
+ {% get_flatpages as flatpages %}
+ {% get_flatpages for someuser as flatpages %}
+ {% get_flatpages '/about/' as about_pages %}
+ {% get_flatpages prefix as about_pages %}
+ {% get_flatpages '/about/' for someuser as about_pages %}
+ """
+ bits = token.split_contents()
+ syntax_message = _("%(tag_name)s expects a syntax of %(tag_name)s "
+ "['url_starts_with'] [for user] as context_name" %
+ dict(tag_name=bits[0]))
+ # Must have at 3-6 bits in the tag
+ if len(bits) >= 3 and len(bits) <= 6:
+
+ # If there's an even number of bits, there's no prefix
+ if len(bits) % 2 == 0:
+ prefix = bits[1]
+ else:
+ prefix = None
+
+ # The very last bit must be the context name
+ if bits[-2] != 'as':
+ raise template.TemplateSyntaxError(syntax_message)
+ context_name = bits[-1]
+
+ # If there are 5 or 6 bits, there is a user defined
+ if len(bits) >= 5:
+ if bits[-4] != 'for':
+ raise template.TemplateSyntaxError(syntax_message)
+ user = bits[-3]
+ else:
+ user = None
+
+ return FlatpageNode(context_name, starts_with=prefix, user=user)
+ else:
+ raise template.TemplateSyntaxError(syntax_message)
+
+register.tag('get_flatpages', get_flatpages)
diff --git a/django/contrib/flatpages/tests/__init__.py b/django/contrib/flatpages/tests/__init__.py
new file mode 100644
index 0000000000..5dd5e89dca
--- /dev/null
+++ b/django/contrib/flatpages/tests/__init__.py
@@ -0,0 +1,5 @@
+from django.contrib.flatpages.tests.csrf import *
+from django.contrib.flatpages.tests.forms import *
+from django.contrib.flatpages.tests.middleware import *
+from django.contrib.flatpages.tests.templatetags import *
+from django.contrib.flatpages.tests.views import *
diff --git a/django/contrib/flatpages/tests/csrf.py b/django/contrib/flatpages/tests/csrf.py
new file mode 100644
index 0000000000..b65ee382a6
--- /dev/null
+++ b/django/contrib/flatpages/tests/csrf.py
@@ -0,0 +1,76 @@
+import os
+from django.conf import settings
+from django.contrib.auth.models import User
+from django.test import TestCase, Client
+
+class FlatpageCSRFTests(TestCase):
+ fixtures = ['sample_flatpages']
+ urls = 'django.contrib.flatpages.tests.urls'
+
+ def setUp(self):
+ self.client = Client(enforce_csrf_checks=True)
+ self.old_MIDDLEWARE_CLASSES = settings.MIDDLEWARE_CLASSES
+ flatpage_middleware_class = 'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware'
+ csrf_middleware_class = 'django.middleware.csrf.CsrfViewMiddleware'
+ if csrf_middleware_class not in settings.MIDDLEWARE_CLASSES:
+ settings.MIDDLEWARE_CLASSES += (csrf_middleware_class,)
+ if flatpage_middleware_class not in settings.MIDDLEWARE_CLASSES:
+ settings.MIDDLEWARE_CLASSES += (flatpage_middleware_class,)
+ self.old_TEMPLATE_DIRS = settings.TEMPLATE_DIRS
+ settings.TEMPLATE_DIRS = (
+ os.path.join(
+ os.path.dirname(__file__),
+ 'templates'
+ ),
+ )
+
+ def tearDown(self):
+ settings.MIDDLEWARE_CLASSES = self.old_MIDDLEWARE_CLASSES
+ settings.TEMPLATE_DIRS = self.old_TEMPLATE_DIRS
+
+ def test_view_flatpage(self):
+ "A flatpage can be served through a view, even when the middleware is in use"
+ response = self.client.get('/flatpage_root/flatpage/')
+ self.assertEquals(response.status_code, 200)
+ self.assertContains(response, "<p>Isn't it flat!</p>")
+
+ def test_view_non_existent_flatpage(self):
+ "A non-existent flatpage raises 404 when served through a view, even when the middleware is in use"
+ response = self.client.get('/flatpage_root/no_such_flatpage/')
+ self.assertEquals(response.status_code, 404)
+
+ def test_view_authenticated_flatpage(self):
+ "A flatpage served through a view can require authentication"
+ response = self.client.get('/flatpage_root/sekrit/')
+ self.assertRedirects(response, '/accounts/login/?next=/flatpage_root/sekrit/')
+ User.objects.create_user('testuser', 'test@example.com', 's3krit')
+ self.client.login(username='testuser',password='s3krit')
+ response = self.client.get('/flatpage_root/sekrit/')
+ self.assertEquals(response.status_code, 200)
+ self.assertContains(response, "<p>Isn't it sekrit!</p>")
+
+ def test_fallback_flatpage(self):
+ "A flatpage can be served by the fallback middlware"
+ response = self.client.get('/flatpage/')
+ self.assertEquals(response.status_code, 200)
+ self.assertContains(response, "<p>Isn't it flat!</p>")
+
+ def test_fallback_non_existent_flatpage(self):
+ "A non-existent flatpage raises a 404 when served by the fallback middlware"
+ response = self.client.get('/no_such_flatpage/')
+ self.assertEquals(response.status_code, 404)
+
+ def test_post_view_flatpage(self):
+ "POSTing to a flatpage served through a view will raise a CSRF error if no token is provided (Refs #14156)"
+ response = self.client.post('/flatpage_root/flatpage/')
+ self.assertEquals(response.status_code, 403)
+
+ def test_post_fallback_flatpage(self):
+ "POSTing to a flatpage served by the middleware will raise a CSRF error if no token is provided (Refs #14156)"
+ response = self.client.post('/flatpage/')
+ self.assertEquals(response.status_code, 403)
+
+ def test_post_unknown_page(self):
+ "POSTing to an unknown page isn't caught as a 403 CSRF error"
+ response = self.client.post('/no_such_page/')
+ self.assertEquals(response.status_code, 404)
diff --git a/django/contrib/flatpages/tests/forms.py b/django/contrib/flatpages/tests/forms.py
new file mode 100644
index 0000000000..969d347b39
--- /dev/null
+++ b/django/contrib/flatpages/tests/forms.py
@@ -0,0 +1,22 @@
+from django.contrib.flatpages.admin import FlatpageForm
+from django.test import TestCase
+
+class FlatpageAdminFormTests(TestCase):
+ def setUp(self):
+ self.form_data = {
+ 'title': "A test page",
+ 'content': "This is a test",
+ 'sites': [1],
+ }
+
+ def test_flatpage_admin_form_url_validation(self):
+ "The flatpage admin form validates correctly validates urls"
+ self.assertTrue(FlatpageForm(data=dict(url='/new_flatpage/', **self.form_data)).is_valid())
+ self.assertTrue(FlatpageForm(data=dict(url='/some.special~chars/', **self.form_data)).is_valid())
+ self.assertTrue(FlatpageForm(data=dict(url='/some.very_special~chars-here/', **self.form_data)).is_valid())
+
+ self.assertFalse(FlatpageForm(data=dict(url='/a space/', **self.form_data)).is_valid())
+ self.assertFalse(FlatpageForm(data=dict(url='/a % char/', **self.form_data)).is_valid())
+ self.assertFalse(FlatpageForm(data=dict(url='/a ! char/', **self.form_data)).is_valid())
+ self.assertFalse(FlatpageForm(data=dict(url='/a & char/', **self.form_data)).is_valid())
+ self.assertFalse(FlatpageForm(data=dict(url='/a ? char/', **self.form_data)).is_valid())
diff --git a/django/contrib/flatpages/tests/middleware.py b/django/contrib/flatpages/tests/middleware.py
new file mode 100644
index 0000000000..bedaffc8ac
--- /dev/null
+++ b/django/contrib/flatpages/tests/middleware.py
@@ -0,0 +1,67 @@
+import os
+from django.conf import settings
+from django.contrib.auth.models import User
+from django.test import TestCase
+
+class FlatpageMiddlewareTests(TestCase):
+ fixtures = ['sample_flatpages']
+ urls = 'django.contrib.flatpages.tests.urls'
+
+ def setUp(self):
+ self.old_MIDDLEWARE_CLASSES = settings.MIDDLEWARE_CLASSES
+ flatpage_middleware_class = 'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware'
+ if flatpage_middleware_class not in settings.MIDDLEWARE_CLASSES:
+ settings.MIDDLEWARE_CLASSES += (flatpage_middleware_class,)
+ self.old_TEMPLATE_DIRS = settings.TEMPLATE_DIRS
+ settings.TEMPLATE_DIRS = (
+ os.path.join(
+ os.path.dirname(__file__),
+ 'templates'
+ ),
+ )
+
+ def tearDown(self):
+ settings.MIDDLEWARE_CLASSES = self.old_MIDDLEWARE_CLASSES
+ settings.TEMPLATE_DIRS = self.old_TEMPLATE_DIRS
+
+ def test_view_flatpage(self):
+ "A flatpage can be served through a view, even when the middleware is in use"
+ response = self.client.get('/flatpage_root/flatpage/')
+ self.assertEquals(response.status_code, 200)
+ self.assertContains(response, "<p>Isn't it flat!</p>")
+
+ def test_view_non_existent_flatpage(self):
+ "A non-existent flatpage raises 404 when served through a view, even when the middleware is in use"
+ response = self.client.get('/flatpage_root/no_such_flatpage/')
+ self.assertEquals(response.status_code, 404)
+
+ def test_view_authenticated_flatpage(self):
+ "A flatpage served through a view can require authentication"
+ response = self.client.get('/flatpage_root/sekrit/')
+ self.assertRedirects(response, '/accounts/login/?next=/flatpage_root/sekrit/')
+ User.objects.create_user('testuser', 'test@example.com', 's3krit')
+ self.client.login(username='testuser',password='s3krit')
+ response = self.client.get('/flatpage_root/sekrit/')
+ self.assertEquals(response.status_code, 200)
+ self.assertContains(response, "<p>Isn't it sekrit!</p>")
+
+ def test_fallback_flatpage(self):
+ "A flatpage can be served by the fallback middlware"
+ response = self.client.get('/flatpage/')
+ self.assertEquals(response.status_code, 200)
+ self.assertContains(response, "<p>Isn't it flat!</p>")
+
+ def test_fallback_non_existent_flatpage(self):
+ "A non-existent flatpage raises a 404 when served by the fallback middlware"
+ response = self.client.get('/no_such_flatpage/')
+ self.assertEquals(response.status_code, 404)
+
+ def test_fallback_authenticated_flatpage(self):
+ "A flatpage served by the middleware can require authentication"
+ response = self.client.get('/sekrit/')
+ self.assertRedirects(response, '/accounts/login/?next=/sekrit/')
+ User.objects.create_user('testuser', 'test@example.com', 's3krit')
+ self.client.login(username='testuser',password='s3krit')
+ response = self.client.get('/sekrit/')
+ self.assertEquals(response.status_code, 200)
+ self.assertContains(response, "<p>Isn't it sekrit!</p>")
diff --git a/django/contrib/flatpages/tests/templates/404.html b/django/contrib/flatpages/tests/templates/404.html
new file mode 100644
index 0000000000..5fd5f3cf3b
--- /dev/null
+++ b/django/contrib/flatpages/tests/templates/404.html
@@ -0,0 +1 @@
+<h1>Oh Noes!</h1> \ No newline at end of file
diff --git a/django/contrib/flatpages/tests/templates/flatpages/default.html b/django/contrib/flatpages/tests/templates/flatpages/default.html
new file mode 100644
index 0000000000..1410e17adf
--- /dev/null
+++ b/django/contrib/flatpages/tests/templates/flatpages/default.html
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"
+ "http://www.w3.org/TR/REC-html40/loose.dtd">
+<html>
+<head>
+<title>{{ flatpage.title }}</title>
+</head>
+<body>
+<p>{{ flatpage.content }}</p>
+</body>
+</html>
diff --git a/django/contrib/flatpages/tests/templates/registration/login.html b/django/contrib/flatpages/tests/templates/registration/login.html
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/django/contrib/flatpages/tests/templates/registration/login.html
diff --git a/django/contrib/flatpages/tests/templatetags.py b/django/contrib/flatpages/tests/templatetags.py
new file mode 100644
index 0000000000..9f42381c29
--- /dev/null
+++ b/django/contrib/flatpages/tests/templatetags.py
@@ -0,0 +1,134 @@
+import os
+from django.conf import settings
+from django.contrib.auth.models import AnonymousUser, User
+from django.template import Template, Context, TemplateSyntaxError
+from django.test import TestCase
+
+class FlatpageTemplateTagTests(TestCase):
+ fixtures = ['sample_flatpages']
+ urls = 'django.contrib.flatpages.tests.urls'
+
+ def setUp(self):
+ self.old_MIDDLEWARE_CLASSES = settings.MIDDLEWARE_CLASSES
+ flatpage_middleware_class = 'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware'
+ if flatpage_middleware_class not in settings.MIDDLEWARE_CLASSES:
+ settings.MIDDLEWARE_CLASSES += (flatpage_middleware_class,)
+ self.old_TEMPLATE_DIRS = settings.TEMPLATE_DIRS
+ settings.TEMPLATE_DIRS = (
+ os.path.join(
+ os.path.dirname(__file__),
+ 'templates'
+ ),
+ )
+ self.me = User.objects.create_user('testuser', 'test@example.com', 's3krit')
+
+ def tearDown(self):
+ settings.MIDDLEWARE_CLASSES = self.old_MIDDLEWARE_CLASSES
+ settings.TEMPLATE_DIRS = self.old_TEMPLATE_DIRS
+
+ def test_get_flatpages_tag(self):
+ "The flatpage template tag retrives unregistered prefixed flatpages by default"
+ out = Template(
+ "{% load flatpages %}"
+ "{% get_flatpages as flatpages %}"
+ "{% for page in flatpages %}"
+ "{{ page.title }},"
+ "{% endfor %}"
+ ).render(Context())
+ self.assertEquals(out, "A Flatpage,A Nested Flatpage,")
+
+ def test_get_flatpages_tag_for_anon_user(self):
+ "The flatpage template tag retrives unregistered flatpages for an anonymous user"
+ out = Template(
+ "{% load flatpages %}"
+ "{% get_flatpages for anonuser as flatpages %}"
+ "{% for page in flatpages %}"
+ "{{ page.title }},"
+ "{% endfor %}"
+ ).render(Context({
+ 'anonuser': AnonymousUser()
+ }))
+ self.assertEquals(out, "A Flatpage,A Nested Flatpage,")
+
+ def test_get_flatpages_tag_for_user(self):
+ "The flatpage template tag retrives all flatpages for an authenticated user"
+ out = Template(
+ "{% load flatpages %}"
+ "{% get_flatpages for me as flatpages %}"
+ "{% for page in flatpages %}"
+ "{{ page.title }},"
+ "{% endfor %}"
+ ).render(Context({
+ 'me': self.me
+ }))
+ self.assertEquals(out, "A Flatpage,A Nested Flatpage,Sekrit Nested Flatpage,Sekrit Flatpage,")
+
+ def test_get_flatpages_with_prefix(self):
+ "The flatpage template tag retrives unregistered prefixed flatpages by default"
+ out = Template(
+ "{% load flatpages %}"
+ "{% get_flatpages '/location/' as location_flatpages %}"
+ "{% for page in location_flatpages %}"
+ "{{ page.title }},"
+ "{% endfor %}"
+ ).render(Context())
+ self.assertEquals(out, "A Nested Flatpage,")
+
+ def test_get_flatpages_with_prefix_for_anon_user(self):
+ "The flatpage template tag retrives unregistered prefixed flatpages for an anonymous user"
+ out = Template(
+ "{% load flatpages %}"
+ "{% get_flatpages '/location/' for anonuser as location_flatpages %}"
+ "{% for page in location_flatpages %}"
+ "{{ page.title }},"
+ "{% endfor %}"
+ ).render(Context({
+ 'anonuser': AnonymousUser()
+ }))
+ self.assertEquals(out, "A Nested Flatpage,")
+
+ def test_get_flatpages_with_prefix_for_user(self):
+ "The flatpage template tag retrive prefixed flatpages for an authenticated user"
+ out = Template(
+ "{% load flatpages %}"
+ "{% get_flatpages '/location/' for me as location_flatpages %}"
+ "{% for page in location_flatpages %}"
+ "{{ page.title }},"
+ "{% endfor %}"
+ ).render(Context({
+ 'me': self.me
+ }))
+ self.assertEquals(out, "A Nested Flatpage,Sekrit Nested Flatpage,")
+
+ def test_get_flatpages_with_variable_prefix(self):
+ "The prefix for the flatpage template tag can be a template variable"
+ out = Template(
+ "{% load flatpages %}"
+ "{% get_flatpages location_prefix as location_flatpages %}"
+ "{% for page in location_flatpages %}"
+ "{{ page.title }},"
+ "{% endfor %}"
+ ).render(Context({
+ 'location_prefix': '/location/'
+ }))
+ self.assertEquals(out, "A Nested Flatpage,")
+
+ def test_parsing_errors(self):
+ "There are various ways that the flatpages template tag won't parse"
+ render = lambda t: Template(t).render(Context())
+
+ self.assertRaises(TemplateSyntaxError, render,
+ "{% load flatpages %}{% get_flatpages %}")
+ self.assertRaises(TemplateSyntaxError, render,
+ "{% load flatpages %}{% get_flatpages as %}")
+ self.assertRaises(TemplateSyntaxError, render,
+ "{% load flatpages %}{% get_flatpages cheesecake flatpages %}")
+ self.assertRaises(TemplateSyntaxError, render,
+ "{% load flatpages %}{% get_flatpages as flatpages asdf%}")
+ self.assertRaises(TemplateSyntaxError, render,
+ "{% load flatpages %}{% get_flatpages cheesecake user as flatpages %}")
+ self.assertRaises(TemplateSyntaxError, render,
+ "{% load flatpages %}{% get_flatpages for user as flatpages asdf%}")
+ self.assertRaises(TemplateSyntaxError, render,
+ "{% load flatpages %}{% get_flatpages prefix for user as flatpages asdf%}")
+
diff --git a/django/contrib/flatpages/tests/urls.py b/django/contrib/flatpages/tests/urls.py
new file mode 100644
index 0000000000..3cffd09d0f
--- /dev/null
+++ b/django/contrib/flatpages/tests/urls.py
@@ -0,0 +1,8 @@
+from django.conf.urls.defaults import *
+
+# special urls for flatpage test cases
+urlpatterns = patterns('',
+ (r'^flatpage_root', include('django.contrib.flatpages.urls')),
+ (r'^accounts/', include('django.contrib.auth.urls')),
+)
+
diff --git a/django/contrib/flatpages/tests/views.py b/django/contrib/flatpages/tests/views.py
new file mode 100644
index 0000000000..89bdde2d92
--- /dev/null
+++ b/django/contrib/flatpages/tests/views.py
@@ -0,0 +1,72 @@
+import os
+from django.conf import settings
+from django.contrib.auth.models import User
+from django.contrib.flatpages.models import FlatPage
+from django.test import TestCase
+
+class FlatpageViewTests(TestCase):
+ fixtures = ['sample_flatpages']
+ urls = 'django.contrib.flatpages.tests.urls'
+
+ def setUp(self):
+ self.old_MIDDLEWARE_CLASSES = settings.MIDDLEWARE_CLASSES
+ flatpage_middleware_class = 'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware'
+ if flatpage_middleware_class in settings.MIDDLEWARE_CLASSES:
+ settings.MIDDLEWARE_CLASSES = tuple(m for m in settings.MIDDLEWARE_CLASSES if m != flatpage_middleware_class)
+ self.old_TEMPLATE_DIRS = settings.TEMPLATE_DIRS
+ settings.TEMPLATE_DIRS = (
+ os.path.join(
+ os.path.dirname(__file__),
+ 'templates'
+ ),
+ )
+
+ def tearDown(self):
+ settings.MIDDLEWARE_CLASSES = self.old_MIDDLEWARE_CLASSES
+ settings.TEMPLATE_DIRS = self.old_TEMPLATE_DIRS
+
+ def test_view_flatpage(self):
+ "A flatpage can be served through a view"
+ response = self.client.get('/flatpage_root/flatpage/')
+ self.assertEquals(response.status_code, 200)
+ self.assertContains(response, "<p>Isn't it flat!</p>")
+
+ def test_view_non_existent_flatpage(self):
+ "A non-existent flatpage raises 404 when served through a view"
+ response = self.client.get('/flatpage_root/no_such_flatpage/')
+ self.assertEquals(response.status_code, 404)
+
+ def test_view_authenticated_flatpage(self):
+ "A flatpage served through a view can require authentication"
+ response = self.client.get('/flatpage_root/sekrit/')
+ self.assertRedirects(response, '/accounts/login/?next=/flatpage_root/sekrit/')
+ User.objects.create_user('testuser', 'test@example.com', 's3krit')
+ self.client.login(username='testuser',password='s3krit')
+ response = self.client.get('/flatpage_root/sekrit/')
+ self.assertEquals(response.status_code, 200)
+ self.assertContains(response, "<p>Isn't it sekrit!</p>")
+
+ def test_fallback_flatpage(self):
+ "A fallback flatpage won't be served if the middleware is disabled"
+ response = self.client.get('/flatpage/')
+ self.assertEquals(response.status_code, 404)
+
+ def test_fallback_non_existent_flatpage(self):
+ "A non-existent flatpage won't be served if the fallback middlware is disabled"
+ response = self.client.get('/no_such_flatpage/')
+ self.assertEquals(response.status_code, 404)
+
+ def test_view_flatpage_special_chars(self):
+ "A flatpage with special chars in the URL can be served through a view"
+ fp = FlatPage.objects.create(
+ url="/some.very_special~chars-here/",
+ title="A very special page",
+ content="Isn't it special!",
+ enable_comments=False,
+ registration_required=False,
+ )
+ fp.sites.add(1)
+
+ response = self.client.get('/flatpage_root/some.very_special~chars-here/')
+ self.assertEquals(response.status_code, 200)
+ self.assertContains(response, "<p>Isn't it special!</p>")
diff --git a/django/contrib/flatpages/views.py b/django/contrib/flatpages/views.py
index 336600328d..88ef4da65e 100644
--- a/django/contrib/flatpages/views.py
+++ b/django/contrib/flatpages/views.py
@@ -13,10 +13,13 @@ DEFAULT_TEMPLATE = 'flatpages/default.html'
# when a 404 is raised, which often means CsrfViewMiddleware.process_view
# has not been called even if CsrfViewMiddleware is installed. So we need
# to use @csrf_protect, in case the template needs {% csrf_token %}.
-@csrf_protect
+# However, we can't just wrap this view; if no matching flatpage exists,
+# or a redirect is required for authentication, the 404 needs to be returned
+# without any CSRF checks. Therefore, we only
+# CSRF protect the internal implementation.
def flatpage(request, url):
"""
- Flat page view.
+ Public interface to the flat page view.
Models: `flatpages.flatpages`
Templates: Uses the template defined by the ``template_name`` field,
@@ -30,6 +33,13 @@ def flatpage(request, url):
if not url.startswith('/'):
url = "/" + url
f = get_object_or_404(FlatPage, url__exact=url, sites__id__exact=settings.SITE_ID)
+ return render_flatpage(request, f)
+
+@csrf_protect
+def render_flatpage(request, f):
+ """
+ Internal interface to the flat page view.
+ """
# If registration is required for accessing this page, and the user isn't
# logged in, redirect to the login page.
if f.registration_required and not request.user.is_authenticated():
diff --git a/django/contrib/formtools/wizard.py b/django/contrib/formtools/wizard.py
index 02d8fd71d4..32e27df574 100644
--- a/django/contrib/formtools/wizard.py
+++ b/django/contrib/formtools/wizard.py
@@ -27,7 +27,7 @@ class FormWizard(object):
def __init__(self, form_list, initial=None):
"""
Start a new wizard with a list of forms.
-
+
form_list should be a list of Form classes (not instances).
"""
self.form_list = form_list[:]
@@ -37,7 +37,7 @@ class FormWizard(object):
self.extra_context = {}
# A zero-based counter keeping track of which step we're in.
- self.step = 0
+ self.step = 0
def __repr__(self):
return "step: %d\nform_list: %s\ninitial_data: %s" % (self.step, self.form_list, self.initial)
@@ -48,7 +48,7 @@ class FormWizard(object):
def num_steps(self):
"Helper method that returns the number of steps."
- # You might think we should just set "self.form_list = len(form_list)"
+ # You might think we should just set "self.num_steps = len(form_list)"
# in __init__(), but this calculation needs to be dynamic, because some
# hook methods might alter self.form_list.
return len(self.form_list)
diff --git a/django/contrib/gis/db/backends/mysql/creation.py b/django/contrib/gis/db/backends/mysql/creation.py
index 93fd2e6166..dda77ea6ab 100644
--- a/django/contrib/gis/db/backends/mysql/creation.py
+++ b/django/contrib/gis/db/backends/mysql/creation.py
@@ -6,7 +6,7 @@ class MySQLCreation(DatabaseCreation):
from django.contrib.gis.db.models.fields import GeometryField
output = super(MySQLCreation, self).sql_indexes_for_field(model, f, style)
- if isinstance(f, GeometryField):
+ if isinstance(f, GeometryField) and f.spatial_index:
qn = self.connection.ops.quote_name
db_table = model._meta.db_table
idx_name = '%s_%s_id' % (db_table, f.column)
diff --git a/django/contrib/gis/db/backends/postgis/operations.py b/django/contrib/gis/db/backends/postgis/operations.py
index b1a9e31529..5affcf9a18 100644
--- a/django/contrib/gis/db/backends/postgis/operations.py
+++ b/django/contrib/gis/db/backends/postgis/operations.py
@@ -233,8 +233,6 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations):
})
self.geography_operators = {
'bboverlaps' : PostGISOperator('&&'),
- 'exact' : PostGISOperator('~='),
- 'same_as' : PostGISOperator('~='),
}
# Creating a dictionary lookup of all GIS terms for PostGIS.
diff --git a/django/contrib/gis/db/backends/spatialite/base.py b/django/contrib/gis/db/backends/spatialite/base.py
index d419dab5e1..729fc152e7 100644
--- a/django/contrib/gis/db/backends/spatialite/base.py
+++ b/django/contrib/gis/db/backends/spatialite/base.py
@@ -51,7 +51,7 @@ class DatabaseWrapper(SqliteDatabaseWrapper):
self.connection.create_function("django_extract", 2, _sqlite_extract)
self.connection.create_function("django_date_trunc", 2, _sqlite_date_trunc)
self.connection.create_function("regexp", 2, _sqlite_regexp)
- connection_created.send(sender=self.__class__)
+ connection_created.send(sender=self.__class__, connection=self)
## From here on, customized for GeoDjango ##
diff --git a/django/contrib/gis/db/models/sql/compiler.py b/django/contrib/gis/db/models/sql/compiler.py
index 78eeeafe19..55dc4a66ec 100644
--- a/django/contrib/gis/db/models/sql/compiler.py
+++ b/django/contrib/gis/db/models/sql/compiler.py
@@ -95,7 +95,7 @@ class GeoSQLCompiler(compiler.SQLCompiler):
return result
def get_default_columns(self, with_aliases=False, col_aliases=None,
- start_alias=None, opts=None, as_pairs=False):
+ start_alias=None, opts=None, as_pairs=False, local_only=False):
"""
Computes the default columns for selecting every field in the base
model. Will sometimes be called to pull in related models (e.g. via
@@ -121,6 +121,8 @@ class GeoSQLCompiler(compiler.SQLCompiler):
if start_alias:
seen = {None: start_alias}
for field, model in opts.get_fields_with_model():
+ if local_only and model is not None:
+ continue
if start_alias:
try:
alias = seen[model]
diff --git a/django/contrib/gis/gdal/libgdal.py b/django/contrib/gis/gdal/libgdal.py
index 6589c5647c..a7a5658c49 100644
--- a/django/contrib/gis/gdal/libgdal.py
+++ b/django/contrib/gis/gdal/libgdal.py
@@ -14,10 +14,10 @@ if lib_path:
lib_names = None
elif os.name == 'nt':
# Windows NT shared library
- lib_names = ['gdal16', 'gdal15']
+ lib_names = ['gdal17', 'gdal16', 'gdal15']
elif os.name == 'posix':
# *NIX library names.
- lib_names = ['gdal', 'GDAL', 'gdal1.6.0', 'gdal1.5.0', 'gdal1.4.0']
+ lib_names = ['gdal', 'GDAL', 'gdal1.7.0', 'gdal1.6.0', 'gdal1.5.0', 'gdal1.4.0']
else:
raise OGRException('Unsupported OS "%s"' % os.name)
diff --git a/django/contrib/gis/tests/__init__.py b/django/contrib/gis/tests/__init__.py
index 0d862190c0..44d62c2a98 100644
--- a/django/contrib/gis/tests/__init__.py
+++ b/django/contrib/gis/tests/__init__.py
@@ -1,115 +1,108 @@
import sys
+import unittest
+
+from django.conf import settings
+from django.db.models import get_app
+from django.test.simple import build_suite, DjangoTestSuiteRunner
def run_tests(*args, **kwargs):
from django.test.simple import run_tests as base_run_tests
return base_run_tests(*args, **kwargs)
-def geo_suite():
- """
- Builds a test suite for the GIS package. This is not named
- `suite` so it will not interfere with the Django test suite (since
- spatial database tables are required to execute these tests on
- some backends).
- """
- from django.conf import settings
- from django.contrib.gis.geos import GEOS_PREPARE
- from django.contrib.gis.gdal import HAS_GDAL
- from django.contrib.gis.utils import HAS_GEOIP
- from django.contrib.gis.tests.utils import postgis, mysql
- from django.db import connection
- from django.utils.importlib import import_module
-
- gis_tests = []
-
- # Adding the GEOS tests.
- from django.contrib.gis.geos import tests as geos_tests
- gis_tests.append(geos_tests.suite())
-
- # Tests that require use of a spatial database (e.g., creation of models)
- test_apps = ['geoapp', 'relatedapp']
- if postgis and connection.ops.geography:
- # Test geography support with PostGIS 1.5+.
- test_apps.append('geogapp')
-
- # Tests that do not require setting up and tearing down a spatial database.
- test_suite_names = [
- 'test_measure',
- ]
-
- if HAS_GDAL:
- # These tests require GDAL.
- if not mysql:
- test_apps.append('distapp')
-
- # Only PostGIS using GEOS 3.1+ can support 3D so far.
- if postgis and GEOS_PREPARE:
- test_apps.append('geo3d')
-
- test_suite_names.extend(['test_spatialrefsys', 'test_geoforms'])
- test_apps.append('layermap')
+def run_gis_tests(test_labels, verbosity=1, interactive=True, failfast=False, extra_tests=None):
+ import warnings
+ warnings.warn(
+ 'The run_gis_tests() test runner has been deprecated in favor of GeoDjangoTestSuiteRunner.',
+ PendingDeprecationWarning
+ )
+ test_runner = GeoDjangoTestSuiteRunner(verbosity=verbosity, interactive=interactive, failfast=failfast)
+ return test_runner.run_tests(test_labels, extra_tests=extra_tests)
+
+class GeoDjangoTestSuiteRunner(DjangoTestSuiteRunner):
+
+ def setup_test_environment(self, **kwargs):
+ super(GeoDjangoTestSuiteRunner, self).setup_test_environment(**kwargs)
- # Adding the GDAL tests.
- from django.contrib.gis.gdal import tests as gdal_tests
- gis_tests.append(gdal_tests.suite())
- else:
- print >>sys.stderr, "GDAL not available - no tests requiring GDAL will be run."
-
- if HAS_GEOIP and hasattr(settings, 'GEOIP_PATH'):
- test_suite_names.append('test_geoip')
-
- # Adding the rest of the suites from the modules specified
- # in the `test_suite_names`.
- for suite_name in test_suite_names:
- tsuite = import_module('django.contrib.gis.tests.' + suite_name)
- gis_tests.append(tsuite.suite())
-
- return gis_tests, test_apps
-
-def run_gis_tests(test_labels, **kwargs):
- """
- Use this routine as the TEST_RUNNER in your settings in order to run the
- GeoDjango test suite. This must be done as a database superuser for
- PostGIS, so read the docstring in `run_test()` below for more details.
- """
- from django.conf import settings
- from django.db.models import loading
- from django.contrib.gis.tests.utils import mysql
-
- # Getting initial values.
- old_installed = settings.INSTALLED_APPS
- old_root_urlconf = settings.ROOT_URLCONF
-
- # Overridding the INSTALLED_APPS with only what we need,
- # to prevent unnecessary database table creation.
- new_installed = ['django.contrib.sites',
- 'django.contrib.sitemaps',
- 'django.contrib.gis',
- ]
-
- # Setting the URLs.
- settings.ROOT_URLCONF = 'django.contrib.gis.tests.urls'
-
- # Creating the test suite, adding the test models to INSTALLED_APPS
- # so they will be tested.
- gis_tests, test_apps = geo_suite()
- for test_model in test_apps:
- module_name = 'django.contrib.gis.tests.%s' % test_model
- new_installed.append(module_name)
-
- # Resetting the loaded flag to take into account what we appended to
- # the INSTALLED_APPS (since this routine is invoked through
- # django/core/management, it caches the apps; this ensures that syncdb
- # will see our appended models)
- settings.INSTALLED_APPS = new_installed
- loading.cache.loaded = False
-
- kwargs['extra_tests'] = gis_tests
-
- # Running the tests using the GIS test runner.
- result = run_tests(test_labels, **kwargs)
-
- # Restoring modified settings.
- settings.INSTALLED_APPS = old_installed
- settings.ROOT_URLCONF = old_root_urlconf
-
- return result
+ from django.db import connection
+ from django.contrib.gis.geos import GEOS_PREPARE
+ from django.contrib.gis.gdal import HAS_GDAL
+
+ # Getting and storing the original values of INSTALLED_APPS and
+ # the ROOT_URLCONF.
+ self.old_installed = settings.INSTALLED_APPS
+ self.old_root_urlconf = settings.ROOT_URLCONF
+
+ # Tests that require use of a spatial database (e.g., creation of models)
+ self.geo_apps = ['geoapp', 'relatedapp']
+ if connection.ops.postgis and connection.ops.geography:
+ # Test geography support with PostGIS 1.5+.
+ self.geo_apps.append('geogapp')
+
+ if HAS_GDAL:
+ # The following GeoDjango test apps depend on GDAL support.
+ if not connection.ops.mysql:
+ self.geo_apps.append('distapp')
+
+ # 3D apps use LayerMapping, which uses GDAL.
+ if connection.ops.postgis and GEOS_PREPARE:
+ self.geo_apps.append('geo3d')
+
+ self.geo_apps.append('layermap')
+
+ # Constructing the new INSTALLED_APPS, and including applications
+ # within the GeoDjango test namespace (`self.geo_apps`).
+ new_installed = ['django.contrib.sites',
+ 'django.contrib.sitemaps',
+ 'django.contrib.gis',
+ ]
+ new_installed.extend(['django.contrib.gis.tests.%s' % app
+ for app in self.geo_apps])
+ settings.INSTALLED_APPS = new_installed
+
+ # Setting the URLs.
+ settings.ROOT_URLCONF = 'django.contrib.gis.tests.urls'
+
+ def teardown_test_environment(self, **kwargs):
+ super(GeoDjangoTestSuiteRunner, self).teardown_test_environment(**kwargs)
+ settings.INSTALLED_APPS = self.old_installed
+ settings.ROOT_URLCONF = self.old_root_urlconf
+
+ def build_suite(self, test_labels, extra_tests=None, **kwargs):
+ """
+ This method is overridden to construct a suite consisting only of tests
+ for GeoDjango.
+ """
+ suite = unittest.TestSuite()
+
+ # Adding the GEOS tests.
+ from django.contrib.gis.geos import tests as geos_tests
+ suite.addTest(geos_tests.suite())
+
+ # Adding the measurment tests.
+ from django.contrib.gis.tests import test_measure
+ suite.addTest(test_measure.suite())
+
+ # Adding GDAL tests, and any test suite that depends on GDAL, to the
+ # suite if GDAL is available.
+ from django.contrib.gis.gdal import HAS_GDAL
+ if HAS_GDAL:
+ from django.contrib.gis.gdal import tests as gdal_tests
+ suite.addTest(gdal_tests.suite())
+
+ from django.contrib.gis.tests import test_spatialrefsys, test_geoforms
+ suite.addTest(test_spatialrefsys.suite())
+ suite.addTest(test_geoforms.suite())
+ else:
+ sys.stderr.write('GDAL not available - no tests requiring GDAL will be run.\n')
+
+ # Add GeoIP tests to the suite, if the library and data is available.
+ from django.contrib.gis.utils import HAS_GEOIP
+ if HAS_GEOIP and hasattr(settings, 'GEOIP_PATH'):
+ from django.contrib.gis.tests import test_geoip
+ suite.addTest(test_geoip.suite())
+
+ # Finally, adding the suites for each of the GeoDjango test apps.
+ for app_name in self.geo_apps:
+ suite.addTest(build_suite(get_app(app_name)))
+
+ return suite
diff --git a/django/contrib/gis/tests/geogapp/tests.py b/django/contrib/gis/tests/geogapp/tests.py
index 7be5193103..3dea930afd 100644
--- a/django/contrib/gis/tests/geogapp/tests.py
+++ b/django/contrib/gis/tests/geogapp/tests.py
@@ -44,6 +44,10 @@ class GeographyTest(TestCase):
# `@` operator not available.
self.assertRaises(ValueError, City.objects.filter(point__contained=z.poly).count)
+ # Regression test for #14060, `~=` was never really implemented for PostGIS.
+ htown = City.objects.get(name='Houston')
+ self.assertRaises(ValueError, City.objects.get, point__exact=htown.point)
+
def test05_geography_layermapping(self):
"Testing LayerMapping support on models with geography fields."
# There is a similar test in `layermap` that uses the same data set,
diff --git a/django/contrib/gis/tests/relatedapp/models.py b/django/contrib/gis/tests/relatedapp/models.py
index 726f9826c0..2e9a62b61f 100644
--- a/django/contrib/gis/tests/relatedapp/models.py
+++ b/django/contrib/gis/tests/relatedapp/models.py
@@ -38,6 +38,11 @@ class Author(models.Model):
name = models.CharField(max_length=100)
objects = models.GeoManager()
+class Article(models.Model):
+ title = models.CharField(max_length=100)
+ author = models.ForeignKey(Author, unique=True)
+ objects = models.GeoManager()
+
class Book(models.Model):
title = models.CharField(max_length=100)
author = models.ForeignKey(Author, related_name='books', null=True)
diff --git a/django/contrib/gis/tests/relatedapp/tests.py b/django/contrib/gis/tests/relatedapp/tests.py
index 184b65b9c7..5d3d00f08d 100644
--- a/django/contrib/gis/tests/relatedapp/tests.py
+++ b/django/contrib/gis/tests/relatedapp/tests.py
@@ -4,7 +4,7 @@ from django.contrib.gis.db.models import Collect, Count, Extent, F, Union
from django.contrib.gis.geometry.backend import Geometry
from django.contrib.gis.tests.utils import mysql, oracle, postgis, spatialite, no_mysql, no_oracle, no_spatialite
from django.conf import settings
-from models import City, Location, DirectoryEntry, Parcel, Book, Author
+from models import City, Location, DirectoryEntry, Parcel, Book, Author, Article
cities = (('Aurora', 'TX', -97.516111, 33.058333),
('Roswell', 'NM', -104.528056, 33.387222),
@@ -291,6 +291,14 @@ class RelatedGeoModelTest(unittest.TestCase):
self.assertEqual(4, len(coll))
self.assertEqual(ref_geom, coll)
+ def test15_invalid_select_related(self):
+ "Testing doing select_related on the related name manager of a unique FK. See #13934."
+ qs = Article.objects.select_related('author__article')
+ # This triggers TypeError when `get_default_columns` has no `local_only`
+ # keyword. The TypeError is swallowed if QuerySet is actually
+ # evaluated as list generation swallows TypeError in CPython.
+ sql = str(qs.query)
+
# TODO: Related tests for KML, GML, and distance lookups.
def suite():
diff --git a/django/contrib/localflavor/in_/in_states.py b/django/contrib/localflavor/in_/in_states.py
index 498efe7069..bb4a7482ca 100644
--- a/django/contrib/localflavor/in_/in_states.py
+++ b/django/contrib/localflavor/in_/in_states.py
@@ -7,43 +7,43 @@ when explicitly needed.
"""
STATE_CHOICES = (
- 'KA', 'Karnataka',
- 'AP', 'Andhra Pradesh',
- 'KL', 'Kerala',
- 'TN', 'Tamil Nadu',
- 'MH', 'Maharashtra',
- 'UP', 'Uttar Pradesh',
- 'GA', 'Goa',
- 'GJ', 'Gujarat',
- 'RJ', 'Rajasthan',
- 'HP', 'Himachal Pradesh',
- 'JK', 'Jammu and Kashmir',
- 'AR', 'Arunachal Pradesh',
- 'AS', 'Assam',
- 'BR', 'Bihar',
- 'CG', 'Chattisgarh',
- 'HR', 'Haryana',
- 'JH', 'Jharkhand',
- 'MP', 'Madhya Pradesh',
- 'MN', 'Manipur',
- 'ML', 'Meghalaya',
- 'MZ', 'Mizoram',
- 'NL', 'Nagaland',
- 'OR', 'Orissa',
- 'PB', 'Punjab',
- 'SK', 'Sikkim',
- 'TR', 'Tripura',
- 'UA', 'Uttarakhand',
- 'WB', 'West Bengal',
+ ('KA', 'Karnataka'),
+ ('AP', 'Andhra Pradesh'),
+ ('KL', 'Kerala'),
+ ('TN', 'Tamil Nadu'),
+ ('MH', 'Maharashtra'),
+ ('UP', 'Uttar Pradesh'),
+ ('GA', 'Goa'),
+ ('GJ', 'Gujarat'),
+ ('RJ', 'Rajasthan'),
+ ('HP', 'Himachal Pradesh'),
+ ('JK', 'Jammu and Kashmir'),
+ ('AR', 'Arunachal Pradesh'),
+ ('AS', 'Assam'),
+ ('BR', 'Bihar'),
+ ('CG', 'Chattisgarh'),
+ ('HR', 'Haryana'),
+ ('JH', 'Jharkhand'),
+ ('MP', 'Madhya Pradesh'),
+ ('MN', 'Manipur'),
+ ('ML', 'Meghalaya'),
+ ('MZ', 'Mizoram'),
+ ('NL', 'Nagaland'),
+ ('OR', 'Orissa'),
+ ('PB', 'Punjab'),
+ ('SK', 'Sikkim'),
+ ('TR', 'Tripura'),
+ ('UA', 'Uttarakhand'),
+ ('WB', 'West Bengal'),
# Union Territories
- 'AN', 'Andaman and Nicobar',
- 'CH', 'Chandigarh',
- 'DN', 'Dadra and Nagar Haveli',
- 'DD', 'Daman and Diu',
- 'DL', 'Delhi',
- 'LD', 'Lakshadweep',
- 'PY', 'Pondicherry',
+ ('AN', 'Andaman and Nicobar'),
+ ('CH', 'Chandigarh'),
+ ('DN', 'Dadra and Nagar Haveli'),
+ ('DD', 'Daman and Diu'),
+ ('DL', 'Delhi'),
+ ('LD', 'Lakshadweep'),
+ ('PY', 'Pondicherry'),
)
STATES_NORMALIZED = {
diff --git a/django/contrib/markup/tests.py b/django/contrib/markup/tests.py
index 9a96f8cda4..6a22e530ba 100644
--- a/django/contrib/markup/tests.py
+++ b/django/contrib/markup/tests.py
@@ -22,7 +22,7 @@ Paragraph 2 with "quotes" and @code@"""
t = Template("{{ textile_content|textile }}")
rendered = t.render(Context(locals())).strip()
if textile:
- self.assertEqual(rendered, """<p>Paragraph 1</p>
+ self.assertEqual(rendered.replace('\t', ''), """<p>Paragraph 1</p>
<p>Paragraph 2 with &#8220;quotes&#8221; and <code>code</code></p>""")
else:
diff --git a/django/contrib/sessions/backends/file.py b/django/contrib/sessions/backends/file.py
index 3f6350345f..843edca9cf 100644
--- a/django/contrib/sessions/backends/file.py
+++ b/django/contrib/sessions/backends/file.py
@@ -58,7 +58,7 @@ class SessionStore(SessionBase):
finally:
session_file.close()
except IOError:
- pass
+ self.create()
return session_data
def create(self):
diff --git a/django/contrib/sessions/tests.py b/django/contrib/sessions/tests.py
index f0a3c4ec8c..e645b73817 100644
--- a/django/contrib/sessions/tests.py
+++ b/django/contrib/sessions/tests.py
@@ -1,388 +1,273 @@
-r"""
-
->>> from django.conf import settings
->>> from django.contrib.sessions.backends.db import SessionStore as DatabaseSession
->>> from django.contrib.sessions.backends.cache import SessionStore as CacheSession
->>> from django.contrib.sessions.backends.cached_db import SessionStore as CacheDBSession
->>> from django.contrib.sessions.backends.file import SessionStore as FileSession
->>> from django.contrib.sessions.backends.base import SessionBase
->>> from django.contrib.sessions.models import Session
-
->>> db_session = DatabaseSession()
->>> db_session.modified
-False
->>> db_session.get('cat')
->>> db_session['cat'] = "dog"
->>> db_session.modified
-True
->>> db_session.pop('cat')
-'dog'
->>> db_session.pop('some key', 'does not exist')
-'does not exist'
->>> db_session.save()
->>> db_session.exists(db_session.session_key)
-True
->>> db_session.delete(db_session.session_key)
->>> db_session.exists(db_session.session_key)
-False
-
->>> db_session['foo'] = 'bar'
->>> db_session.save()
->>> db_session.exists(db_session.session_key)
-True
->>> prev_key = db_session.session_key
->>> db_session.flush()
->>> db_session.exists(prev_key)
-False
->>> db_session.session_key == prev_key
-False
->>> db_session.modified, db_session.accessed
-(True, True)
->>> db_session['a'], db_session['b'] = 'c', 'd'
->>> db_session.save()
->>> prev_key = db_session.session_key
->>> prev_data = db_session.items()
->>> db_session.cycle_key()
->>> db_session.session_key == prev_key
-False
->>> db_session.items() == prev_data
-True
-
-# Submitting an invalid session key (either by guessing, or if the db has
-# removed the key) results in a new key being generated.
->>> Session.objects.filter(pk=db_session.session_key).delete()
->>> db_session = DatabaseSession(db_session.session_key)
->>> db_session.save()
->>> DatabaseSession('1').get('cat')
-
-#
-# Cached DB session tests
-#
-
->>> cdb_session = CacheDBSession()
->>> cdb_session.modified
-False
->>> cdb_session['cat'] = "dog"
->>> cdb_session.modified
-True
->>> cdb_session.pop('cat')
-'dog'
->>> cdb_session.pop('some key', 'does not exist')
-'does not exist'
->>> cdb_session.save()
->>> cdb_session.exists(cdb_session.session_key)
-True
->>> cdb_session.delete(cdb_session.session_key)
->>> cdb_session.exists(cdb_session.session_key)
-False
-
-#
-# File session tests.
-#
-
-# Do file session tests in an isolated directory, and kill it after we're done.
->>> original_session_file_path = settings.SESSION_FILE_PATH
->>> import tempfile
->>> temp_session_store = settings.SESSION_FILE_PATH = tempfile.mkdtemp()
-
->>> file_session = FileSession()
->>> file_session.modified
-False
->>> file_session['cat'] = "dog"
->>> file_session.modified
-True
->>> file_session.pop('cat')
-'dog'
->>> file_session.pop('some key', 'does not exist')
-'does not exist'
->>> file_session.save()
->>> file_session.exists(file_session.session_key)
-True
->>> file_session.delete(file_session.session_key)
->>> file_session.exists(file_session.session_key)
-False
->>> FileSession('1').get('cat')
-
->>> file_session['foo'] = 'bar'
->>> file_session.save()
->>> file_session.exists(file_session.session_key)
-True
->>> prev_key = file_session.session_key
->>> file_session.flush()
->>> file_session.exists(prev_key)
-False
->>> file_session.session_key == prev_key
-False
->>> file_session.modified, file_session.accessed
-(True, True)
->>> file_session['a'], file_session['b'] = 'c', 'd'
->>> file_session.save()
->>> prev_key = file_session.session_key
->>> prev_data = file_session.items()
->>> file_session.cycle_key()
->>> file_session.session_key == prev_key
-False
->>> file_session.items() == prev_data
-True
-
->>> Session.objects.filter(pk=file_session.session_key).delete()
->>> file_session = FileSession(file_session.session_key)
->>> file_session.save()
-
-# Make sure the file backend checks for a good storage dir
->>> settings.SESSION_FILE_PATH = "/if/this/directory/exists/you/have/a/weird/computer"
->>> FileSession()
-Traceback (innermost last):
- ...
-ImproperlyConfigured: The session storage path '/if/this/directory/exists/you/have/a/weird/computer' doesn't exist. Please set your SESSION_FILE_PATH setting to an existing directory in which Django can store session data.
-
-# Clean up after the file tests
->>> settings.SESSION_FILE_PATH = original_session_file_path
->>> import shutil
->>> shutil.rmtree(temp_session_store)
-
-#
-# Cache-based tests
-# NB: be careful to delete any sessions created; stale sessions fill up the
-# /tmp and eventually overwhelm it after lots of runs (think buildbots)
-#
-
->>> cache_session = CacheSession()
->>> cache_session.modified
-False
->>> cache_session['cat'] = "dog"
->>> cache_session.modified
-True
->>> cache_session.pop('cat')
-'dog'
->>> cache_session.pop('some key', 'does not exist')
-'does not exist'
->>> cache_session.save()
->>> cache_session.delete(cache_session.session_key)
->>> cache_session.exists(cache_session.session_key)
-False
->>> cache_session['foo'] = 'bar'
->>> cache_session.save()
->>> cache_session.exists(cache_session.session_key)
-True
->>> prev_key = cache_session.session_key
->>> cache_session.flush()
->>> cache_session.exists(prev_key)
-False
->>> cache_session.session_key == prev_key
-False
->>> cache_session.modified, cache_session.accessed
-(True, True)
->>> cache_session['a'], cache_session['b'] = 'c', 'd'
->>> cache_session.save()
->>> prev_key = cache_session.session_key
->>> prev_data = cache_session.items()
->>> cache_session.cycle_key()
->>> cache_session.session_key == prev_key
-False
->>> cache_session.items() == prev_data
-True
->>> cache_session = CacheSession()
->>> cache_session.save()
->>> key = cache_session.session_key
->>> cache_session.exists(key)
-True
-
->>> Session.objects.filter(pk=cache_session.session_key).delete()
->>> cache_session = CacheSession(cache_session.session_key)
->>> cache_session.save()
->>> cache_session.delete(cache_session.session_key)
-
->>> s = SessionBase()
->>> s._session['some key'] = 'exists' # Pre-populate the session with some data
->>> s.accessed = False # Reset to pretend this wasn't accessed previously
-
->>> s.accessed, s.modified
-(False, False)
-
->>> s.pop('non existant key', 'does not exist')
-'does not exist'
->>> s.accessed, s.modified
-(True, False)
-
->>> s.setdefault('foo', 'bar')
-'bar'
->>> s.setdefault('foo', 'baz')
-'bar'
-
->>> s.accessed = False # Reset the accessed flag
-
->>> s.pop('some key')
-'exists'
->>> s.accessed, s.modified
-(True, True)
-
->>> s.pop('some key', 'does not exist')
-'does not exist'
-
-
->>> s.get('update key', None)
-
-# test .update()
->>> s.modified = s.accessed = False # Reset to pretend this wasn't accessed previously
->>> s.update({'update key':1})
->>> s.accessed, s.modified
-(True, True)
->>> s.get('update key', None)
-1
-
-# test .has_key()
->>> s.modified = s.accessed = False # Reset to pretend this wasn't accessed previously
->>> s.has_key('update key')
-True
->>> s.accessed, s.modified
-(True, False)
-
-# test .values()
->>> s = SessionBase()
->>> s.values()
-[]
->>> s.accessed
-True
->>> s['x'] = 1
->>> s.values()
-[1]
-
-# test .iterkeys()
->>> s.accessed = False
->>> i = s.iterkeys()
->>> hasattr(i,'__iter__')
-True
->>> s.accessed
-True
->>> list(i)
-['x']
-
-# test .itervalues()
->>> s.accessed = False
->>> i = s.itervalues()
->>> hasattr(i,'__iter__')
-True
->>> s.accessed
-True
->>> list(i)
-[1]
-
-# test .iteritems()
->>> s.accessed = False
->>> i = s.iteritems()
->>> hasattr(i,'__iter__')
-True
->>> s.accessed
-True
->>> list(i)
-[('x', 1)]
-
-# test .clear()
->>> s.modified = s.accessed = False
->>> s.items()
-[('x', 1)]
->>> s.clear()
->>> s.items()
-[]
->>> s.accessed, s.modified
-(True, True)
-
-#########################
-# Custom session expiry #
-#########################
-
->>> from django.conf import settings
->>> from datetime import datetime, timedelta
-
->>> td10 = timedelta(seconds=10)
-
-# A normal session has a max age equal to settings
->>> s.get_expiry_age() == settings.SESSION_COOKIE_AGE
-True
-
-# So does a custom session with an idle expiration time of 0 (but it'll expire
-# at browser close)
->>> s.set_expiry(0)
->>> s.get_expiry_age() == settings.SESSION_COOKIE_AGE
-True
-
-# Custom session idle expiration time
->>> s.set_expiry(10)
->>> delta = s.get_expiry_date() - datetime.now()
->>> delta.seconds in (9, 10)
-True
->>> age = s.get_expiry_age()
->>> age in (9, 10)
-True
-
-# Custom session fixed expiry date (timedelta)
->>> s.set_expiry(td10)
->>> delta = s.get_expiry_date() - datetime.now()
->>> delta.seconds in (9, 10)
-True
->>> age = s.get_expiry_age()
->>> age in (9, 10)
-True
-
-# Custom session fixed expiry date (fixed datetime)
->>> s.set_expiry(datetime.now() + td10)
->>> delta = s.get_expiry_date() - datetime.now()
->>> delta.seconds in (9, 10)
-True
->>> age = s.get_expiry_age()
->>> age in (9, 10)
-True
-
-# Set back to default session age
->>> s.set_expiry(None)
->>> s.get_expiry_age() == settings.SESSION_COOKIE_AGE
-True
-
-# Allow to set back to default session age even if no alternate has been set
->>> s.set_expiry(None)
-
-
-# We're changing the setting then reverting back to the original setting at the
-# end of these tests.
->>> original_expire_at_browser_close = settings.SESSION_EXPIRE_AT_BROWSER_CLOSE
->>> settings.SESSION_EXPIRE_AT_BROWSER_CLOSE = False
-
-# Custom session age
->>> s.set_expiry(10)
->>> s.get_expire_at_browser_close()
-False
-
-# Custom expire-at-browser-close
->>> s.set_expiry(0)
->>> s.get_expire_at_browser_close()
-True
-
-# Default session age
->>> s.set_expiry(None)
->>> s.get_expire_at_browser_close()
-False
-
->>> settings.SESSION_EXPIRE_AT_BROWSER_CLOSE = True
-
-# Custom session age
->>> s.set_expiry(10)
->>> s.get_expire_at_browser_close()
-False
-
-# Custom expire-at-browser-close
->>> s.set_expiry(0)
->>> s.get_expire_at_browser_close()
-True
-
-# Default session age
->>> s.set_expiry(None)
->>> s.get_expire_at_browser_close()
-True
-
->>> settings.SESSION_EXPIRE_AT_BROWSER_CLOSE = original_expire_at_browser_close
-"""
-
-if __name__ == '__main__':
- import doctest
- doctest.testmod()
+from datetime import datetime, timedelta
+from django.conf import settings
+from django.contrib.sessions.backends.db import SessionStore as DatabaseSession
+from django.contrib.sessions.backends.cache import SessionStore as CacheSession
+from django.contrib.sessions.backends.cached_db import SessionStore as CacheDBSession
+from django.contrib.sessions.backends.file import SessionStore as FileSession
+from django.contrib.sessions.backends.base import SessionBase
+from django.contrib.sessions.models import Session
+from django.core.exceptions import ImproperlyConfigured
+from django.test import TestCase
+import shutil
+import tempfile
+import unittest
+
+
+class SessionTestsMixin(object):
+ # This does not inherit from TestCase to avoid any tests being run with this
+ # class, which wouldn't work, and to allow different TestCase subclasses to
+ # be used.
+
+ backend = None # subclasses must specify
+
+ def setUp(self):
+ self.session = self.backend()
+
+ def tearDown(self):
+ # NB: be careful to delete any sessions created; stale sessions fill up
+ # the /tmp (with some backends) and eventually overwhelm it after lots
+ # of runs (think buildbots)
+ self.session.delete()
+
+ def test_new_session(self):
+ self.assertFalse(self.session.modified)
+ self.assertFalse(self.session.accessed)
+
+ def test_get_empty(self):
+ self.assertEqual(self.session.get('cat'), None)
+
+ def test_store(self):
+ self.session['cat'] = "dog"
+ self.assertTrue(self.session.modified)
+ self.assertEqual(self.session.pop('cat'), 'dog')
+
+ def test_pop(self):
+ self.session['some key'] = 'exists'
+ # Need to reset these to pretend we haven't accessed it:
+ self.accessed = False
+ self.modified = False
+
+ self.assertEqual(self.session.pop('some key'), 'exists')
+ self.assertTrue(self.session.accessed)
+ self.assertTrue(self.session.modified)
+ self.assertEqual(self.session.get('some key'), None)
+
+ def test_pop_default(self):
+ self.assertEqual(self.session.pop('some key', 'does not exist'),
+ 'does not exist')
+ self.assertTrue(self.session.accessed)
+ self.assertFalse(self.session.modified)
+
+ def test_setdefault(self):
+ self.assertEqual(self.session.setdefault('foo', 'bar'), 'bar')
+ self.assertEqual(self.session.setdefault('foo', 'baz'), 'bar')
+ self.assertTrue(self.session.accessed)
+ self.assertTrue(self.session.modified)
+
+ def test_update(self):
+ self.session.update({'update key': 1})
+ self.assertTrue(self.session.accessed)
+ self.assertTrue(self.session.modified)
+ self.assertEqual(self.session.get('update key', None), 1)
+
+ def test_has_key(self):
+ self.session['some key'] = 1
+ self.session.modified = False
+ self.session.accessed = False
+ self.assertTrue(self.session.has_key('some key'))
+ self.assertTrue(self.session.accessed)
+ self.assertFalse(self.session.modified)
+
+ def test_values(self):
+ self.assertEqual(self.session.values(), [])
+ self.assertTrue(self.session.accessed)
+ self.session['some key'] = 1
+ self.assertEqual(self.session.values(), [1])
+
+ def test_iterkeys(self):
+ self.session['x'] = 1
+ self.session.modified = False
+ self.session.accessed = False
+ i = self.session.iterkeys()
+ self.assertTrue(hasattr(i, '__iter__'))
+ self.assertTrue(self.session.accessed)
+ self.assertFalse(self.session.modified)
+ self.assertEqual(list(i), ['x'])
+
+ def test_iterkeys(self):
+ self.session['x'] = 1
+ self.session.modified = False
+ self.session.accessed = False
+ i = self.session.itervalues()
+ self.assertTrue(hasattr(i, '__iter__'))
+ self.assertTrue(self.session.accessed)
+ self.assertFalse(self.session.modified)
+ self.assertEqual(list(i), [1])
+
+ def test_iteritems(self):
+ self.session['x'] = 1
+ self.session.modified = False
+ self.session.accessed = False
+ i = self.session.iteritems()
+ self.assertTrue(hasattr(i, '__iter__'))
+ self.assertTrue(self.session.accessed)
+ self.assertFalse(self.session.modified)
+ self.assertEqual(list(i), [('x',1)])
+
+ def test_clear(self):
+ self.session['x'] = 1
+ self.session.modified = False
+ self.session.accessed = False
+ self.assertEqual(self.session.items(), [('x',1)])
+ self.session.clear()
+ self.assertEqual(self.session.items(), [])
+ self.assertTrue(self.session.accessed)
+ self.assertTrue(self.session.modified)
+
+ def test_save(self):
+ self.session.save()
+ self.assertTrue(self.session.exists(self.session.session_key))
+
+ def test_delete(self):
+ self.session.delete(self.session.session_key)
+ self.assertFalse(self.session.exists(self.session.session_key))
+
+ def test_flush(self):
+ self.session['foo'] = 'bar'
+ self.session.save()
+ prev_key = self.session.session_key
+ self.session.flush()
+ self.assertFalse(self.session.exists(prev_key))
+ self.assertNotEqual(self.session.session_key, prev_key)
+ self.assertTrue(self.session.modified)
+ self.assertTrue(self.session.accessed)
+
+ def test_cycle(self):
+ self.session['a'], self.session['b'] = 'c', 'd'
+ self.session.save()
+ prev_key = self.session.session_key
+ prev_data = self.session.items()
+ self.session.cycle_key()
+ self.assertNotEqual(self.session.session_key, prev_key)
+ self.assertEqual(self.session.items(), prev_data)
+
+ def test_invalid_key(self):
+ # Submitting an invalid session key (either by guessing, or if the db has
+ # removed the key) results in a new key being generated.
+ session = self.backend('1')
+ session.save()
+ self.assertNotEqual(session.session_key, '1')
+ self.assertEqual(session.get('cat'), None)
+ session.delete()
+
+ # Custom session expiry
+ def test_default_expiry(self):
+ # A normal session has a max age equal to settings
+ self.assertEqual(self.session.get_expiry_age(), settings.SESSION_COOKIE_AGE)
+
+ # So does a custom session with an idle expiration time of 0 (but it'll
+ # expire at browser close)
+ self.session.set_expiry(0)
+ self.assertEqual(self.session.get_expiry_age(), settings.SESSION_COOKIE_AGE)
+
+ def test_custom_expiry_seconds(self):
+ # Using seconds
+ self.session.set_expiry(10)
+ delta = self.session.get_expiry_date() - datetime.now()
+ self.assertTrue(delta.seconds in (9, 10))
+
+ age = self.session.get_expiry_age()
+ self.assertTrue(age in (9, 10))
+
+ def test_custom_expiry_timedelta(self):
+ # Using timedelta
+ self.session.set_expiry(timedelta(seconds=10))
+ delta = self.session.get_expiry_date() - datetime.now()
+ self.assertTrue(delta.seconds in (9, 10))
+
+ age = self.session.get_expiry_age()
+ self.assertTrue(age in (9, 10))
+
+ def test_custom_expiry_timedelta(self):
+ # Using timedelta
+ self.session.set_expiry(datetime.now() + timedelta(seconds=10))
+ delta = self.session.get_expiry_date() - datetime.now()
+ self.assertTrue(delta.seconds in (9, 10))
+
+ age = self.session.get_expiry_age()
+ self.assertTrue(age in (9, 10))
+
+ def test_custom_expiry_reset(self):
+ self.session.set_expiry(None)
+ self.session.set_expiry(10)
+ self.session.set_expiry(None)
+ self.assertEqual(self.session.get_expiry_age(), settings.SESSION_COOKIE_AGE)
+
+ def test_get_expire_at_browser_close(self):
+ # Tests get_expire_at_browser_close with different settings and different
+ # set_expiry calls
+ try:
+ try:
+ original_expire_at_browser_close = settings.SESSION_EXPIRE_AT_BROWSER_CLOSE
+ settings.SESSION_EXPIRE_AT_BROWSER_CLOSE = False
+
+ self.session.set_expiry(10)
+ self.assertFalse(self.session.get_expire_at_browser_close())
+
+ self.session.set_expiry(0)
+ self.assertTrue(self.session.get_expire_at_browser_close())
+
+ self.session.set_expiry(None)
+ self.assertFalse(self.session.get_expire_at_browser_close())
+
+ settings.SESSION_EXPIRE_AT_BROWSER_CLOSE = True
+
+ self.session.set_expiry(10)
+ self.assertFalse(self.session.get_expire_at_browser_close())
+
+ self.session.set_expiry(0)
+ self.assertTrue(self.session.get_expire_at_browser_close())
+
+ self.session.set_expiry(None)
+ self.assertTrue(self.session.get_expire_at_browser_close())
+
+ except:
+ raise
+ finally:
+ settings.SESSION_EXPIRE_AT_BROWSER_CLOSE = original_expire_at_browser_close
+
+
+class DatabaseSessionTests(SessionTestsMixin, TestCase):
+
+ backend = DatabaseSession
+
+
+class CacheDBSessionTests(SessionTestsMixin, TestCase):
+
+ backend = CacheDBSession
+
+# Don't need DB flushing for these tests, so can use unittest.TestCase as base class
+class FileSessionTests(SessionTestsMixin, unittest.TestCase):
+
+ backend = FileSession
+
+ def setUp(self):
+ super(FileSessionTests, self).setUp()
+ # Do file session tests in an isolated directory, and kill it after we're done.
+ self.original_session_file_path = settings.SESSION_FILE_PATH
+ self.temp_session_store = settings.SESSION_FILE_PATH = tempfile.mkdtemp()
+
+ def tearDown(self):
+ settings.SESSION_FILE_PATH = self.original_session_file_path
+ shutil.rmtree(self.temp_session_store)
+ super(FileSessionTests, self).tearDown()
+
+ def test_configuration_check(self):
+ # Make sure the file backend checks for a good storage dir
+ settings.SESSION_FILE_PATH = "/if/this/directory/exists/you/have/a/weird/computer"
+ self.assertRaises(ImproperlyConfigured, self.backend)
+
+
+class CacheSessionTests(SessionTestsMixin, unittest.TestCase):
+
+ backend = CacheSession
diff --git a/django/contrib/sitemaps/__init__.py b/django/contrib/sitemaps/__init__.py
index f877317f16..eaa7f85de9 100644
--- a/django/contrib/sitemaps/__init__.py
+++ b/django/contrib/sitemaps/__init__.py
@@ -65,11 +65,12 @@ class Sitemap(object):
urls = []
for item in self.paginator.page(page).object_list:
loc = "http://%s%s" % (current_site.domain, self.__get('location', item))
+ priority = self.__get('priority', item, None)
url_info = {
'location': loc,
'lastmod': self.__get('lastmod', item, None),
'changefreq': self.__get('changefreq', item, None),
- 'priority': self.__get('priority', item, None)
+ 'priority': str(priority is not None and priority or '')
}
urls.append(url_info)
return urls
@@ -78,7 +79,7 @@ class FlatPageSitemap(Sitemap):
def items(self):
from django.contrib.sites.models import Site
current_site = Site.objects.get_current()
- return current_site.flatpage_set.all()
+ return current_site.flatpage_set.filter(registration_required=False)
class GenericSitemap(Sitemap):
priority = None
diff --git a/django/contrib/sitemaps/models.py b/django/contrib/sitemaps/models.py
new file mode 100644
index 0000000000..7ff128fa69
--- /dev/null
+++ b/django/contrib/sitemaps/models.py
@@ -0,0 +1 @@
+# This file intentionally left blank \ No newline at end of file
diff --git a/django/contrib/sitemaps/tests/__init__.py b/django/contrib/sitemaps/tests/__init__.py
new file mode 100644
index 0000000000..c5b483cde2
--- /dev/null
+++ b/django/contrib/sitemaps/tests/__init__.py
@@ -0,0 +1 @@
+from django.contrib.sitemaps.tests.basic import *
diff --git a/django/contrib/sitemaps/tests/basic.py b/django/contrib/sitemaps/tests/basic.py
new file mode 100644
index 0000000000..ad04db258f
--- /dev/null
+++ b/django/contrib/sitemaps/tests/basic.py
@@ -0,0 +1,77 @@
+from datetime import date
+from django.conf import settings
+from django.contrib.auth.models import User
+from django.contrib.flatpages.models import FlatPage
+from django.test import TestCase
+from django.utils.formats import localize
+from django.utils.translation import activate
+
+
+class SitemapTests(TestCase):
+ urls = 'django.contrib.sitemaps.tests.urls'
+
+ def setUp(self):
+ self.old_USE_L10N = settings.USE_L10N
+ # Create a user that will double as sitemap content
+ User.objects.create_user('testuser', 'test@example.com', 's3krit')
+
+ def tearDown(self):
+ settings.USE_L10N = self.old_USE_L10N
+
+ def test_simple_sitemap(self):
+ "A simple sitemap can be rendered"
+ # Retrieve the sitemap.
+ response = self.client.get('/simple/sitemap.xml')
+ # Check for all the important bits:
+ self.assertEquals(response.content, """<?xml version="1.0" encoding="UTF-8"?>
+<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
+<url><loc>http://example.com/location/</loc><lastmod>%s</lastmod><changefreq>never</changefreq><priority>0.5</priority></url>
+</urlset>
+""" % date.today().strftime('%Y-%m-%d'))
+
+ def test_localized_priority(self):
+ "The priority value should not be localized (Refs #14164)"
+ # Localization should be active
+ settings.USE_L10N = True
+ activate('fr')
+ self.assertEqual(u'0,3', localize(0.3))
+
+ # Retrieve the sitemap. Check that priorities
+ # haven't been rendered in localized format
+ response = self.client.get('/simple/sitemap.xml')
+ self.assertContains(response, '<priority>0.5</priority>')
+ self.assertContains(response, '<lastmod>%s</lastmod>' % date.today().strftime('%Y-%m-%d'))
+
+ def test_generic_sitemap(self):
+ "A minimal generic sitemap can be rendered"
+ # Retrieve the sitemap.
+ response = self.client.get('/generic/sitemap.xml')
+ # Check for all the important bits:
+ self.assertEquals(response.content, """<?xml version="1.0" encoding="UTF-8"?>
+<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
+<url><loc>http://example.com/users/testuser/</loc></url>
+</urlset>
+""")
+
+ def test_flatpage_sitemap(self):
+ "Basic FlatPage sitemap test"
+ public = FlatPage.objects.create(
+ url=u'/public/',
+ title=u'Public Page',
+ enable_comments=True,
+ registration_required=False,
+ )
+ public.sites.add(settings.SITE_ID)
+ private = FlatPage.objects.create(
+ url=u'/private/',
+ title=u'Public Page',
+ enable_comments=True,
+ registration_required=True
+ )
+ private.sites.add(settings.SITE_ID)
+ response = self.client.get('/flatpages/sitemap.xml')
+ # Public flatpage should be in the sitemap
+ self.assertContains(response, '<loc>http://example.com%s</loc>' % public.url)
+ # Private flatpage should not be in the sitemap
+ self.assertNotContains(response, '<loc>http://example.com%s</loc>' % private.url)
+
diff --git a/django/contrib/sitemaps/tests/urls.py b/django/contrib/sitemaps/tests/urls.py
new file mode 100644
index 0000000000..6cdba36b02
--- /dev/null
+++ b/django/contrib/sitemaps/tests/urls.py
@@ -0,0 +1,33 @@
+from datetime import datetime
+from django.conf.urls.defaults import *
+from django.contrib.sitemaps import Sitemap, GenericSitemap, FlatPageSitemap
+from django.contrib.auth.models import User
+
+class SimpleSitemap(Sitemap):
+ changefreq = "never"
+ priority = 0.5
+ location = '/location/'
+ lastmod = datetime.now()
+
+ def items(self):
+ return [object()]
+
+simple_sitemaps = {
+ 'simple': SimpleSitemap,
+}
+
+generic_sitemaps = {
+ 'generic': GenericSitemap({
+ 'queryset': User.objects.all()
+ }),
+}
+
+flatpage_sitemaps = {
+ 'flatpages': FlatPageSitemap,
+}
+
+urlpatterns = patterns('django.contrib.sitemaps.views',
+ (r'^simple/sitemap\.xml$', 'sitemap', {'sitemaps': simple_sitemaps}),
+ (r'^generic/sitemap\.xml$', 'sitemap', {'sitemaps': generic_sitemaps}),
+ (r'^flatpages/sitemap\.xml$', 'sitemap', {'sitemaps': flatpage_sitemaps}),
+)