Compare commits
226 Commits
main
...
stable/1.3
Author | SHA1 | Date |
---|---|---|
Tim Graham | 6726d75097 | |
Luke Plant | e982cbd4a1 | |
Tim Graham | 8892b0c2c3 | |
Luke Plant | 8dc1b2e03f | |
Tim Graham | adea5a3880 | |
John Heasly | 1989df6014 | |
Tim Graham | 7dd37edf62 | |
Carl Meyer | 956b755d7e | |
James Bennett | 304a5e0628 | |
Carl Meyer | a57743c9ff | |
Carl Meyer | a6927d8219 | |
Carl Meyer | 2378c31430 | |
James Bennett | 747d3f0d03 | |
Carl Meyer | f6f6f87a98 | |
Aymeric Augustin | d7094bbce8 | |
Carl Meyer | d3a45e10c8 | |
Carl Meyer | d19a27066b | |
Carl Meyer | 27cd872e6e | |
Florian Apolloner | 6e70f67470 | |
James Bennett | 59a3e26425 | |
Florian Apolloner | 2da4ace0bc | |
Florian Apolloner | 1515eb46da | |
Preston Holmes | 6383d2358c | |
James Bennett | 25d23d9846 | |
Preston Holmes | b45c377f8f | |
James Bennett | c718b4a036 | |
Florian Apolloner | d0d5dc6cd7 | |
James Bennett | e2ac91735f | |
James Bennett | 0b0c51a095 | |
Florian Apolloner | 4dea4883e6 | |
Florian Apolloner | b2eb4787a0 | |
Florian Apolloner | 9ca0ff6268 | |
Anssi Kääriäinen | 7ca10b1dac | |
Michael Newman | a15d3b58d8 | |
Julien Phalip | e293d82c36 | |
Aymeric Augustin | 0bbe7379ee | |
Aymeric Augustin | 15fb61c62c | |
Aymeric Augustin | 8e73302070 | |
Aymeric Augustin | fd2efb35fb | |
Aymeric Augustin | 651c0414a8 | |
Ramiro Morales | 92929d5ef4 | |
Claude Paroz | 1dd8848beb | |
Julien Phalip | 2f6b8482f6 | |
Julien Phalip | 838adb2312 | |
Claude Paroz | 2acf028b4b | |
Paul McMillan | 1f924cf72d | |
Claude Paroz | d498033818 | |
Claude Paroz | ddfa89b959 | |
Jannis Leidel | 6951879023 | |
Jannis Leidel | 523d6167d6 | |
Carl Meyer | dad3e55234 | |
Timo Graham | 41cd3b2ab1 | |
Timo Graham | c0258f1da7 | |
Timo Graham | 38715d8af8 | |
Chris Beaven | b45fbc6667 | |
Timo Graham | 0af93e108e | |
Timo Graham | 4f6c36435c | |
Aymeric Augustin | 4d959686e6 | |
Aymeric Augustin | 25b5da2abd | |
Aymeric Augustin | 7baee7a03b | |
Aymeric Augustin | 2a5a0b8097 | |
Aymeric Augustin | 1addaafa0a | |
Timo Graham | 9729ad7466 | |
Timo Graham | 5144f72be2 | |
Aymeric Augustin | 813dc01cd8 | |
Timo Graham | c63a454bb6 | |
Timo Graham | ce9916a2c8 | |
Timo Graham | f202387e6c | |
Ramiro Morales | 2646df4537 | |
Timo Graham | a7a703dbdc | |
Timo Graham | 46c08c8f95 | |
Ramiro Morales | 33f9ba7ba0 | |
Ramiro Morales | 723f995658 | |
Aymeric Augustin | 0d7431774f | |
Aymeric Augustin | 405a0ba9de | |
Jacob Kaplan-Moss | 2b793b1a35 | |
Carl Meyer | 9bc6119daf | |
Aymeric Augustin | 9e12492616 | |
Aymeric Augustin | a9789c0d44 | |
Carl Meyer | ca14105128 | |
Timo Graham | 79248a7e0a | |
Timo Graham | 7bee6b451a | |
Timo Graham | 580389c588 | |
Timo Graham | 7b9016e5a2 | |
Timo Graham | c9ab2bfb39 | |
Aymeric Augustin | b5853cf043 | |
Aymeric Augustin | 68f37a9081 | |
Timo Graham | bf5fdf1397 | |
Carl Meyer | eabbb361d2 | |
Carl Meyer | 6372368b56 | |
Carl Meyer | 07c404be88 | |
Paul McMillan | a084a07ebe | |
Paul McMillan | e3bc259081 | |
Paul McMillan | ed156a44ca | |
Paul McMillan | 4443c6fd72 | |
Paul McMillan | 35e807c4a5 | |
Paul McMillan | 2a4aa8bcf7 | |
Paul McMillan | 5978d7a341 | |
Carl Meyer | 65942eb31f | |
Simon Meers | 3606f1f7b2 | |
Simon Meers | add0628528 | |
Justin Bronn | ae51b46d19 | |
Carl Meyer | c9676d035f | |
Julien Phalip | 27c8d61280 | |
Carl Meyer | ee23919a8a | |
Carl Meyer | 70a6901775 | |
James Bennett | 2c8e45e1f4 | |
James Bennett | 460150975c | |
James Bennett | 16787c6903 | |
James Bennett | f242c02fb9 | |
James Bennett | 0326e2e611 | |
James Bennett | 2954c36ff7 | |
James Bennett | 5e451b9e6f | |
James Bennett | 722d4068a7 | |
Russell Keith-Magee | 1a76dbefdf | |
Russell Keith-Magee | fbe2eead2f | |
Russell Keith-Magee | 2f7fadc38e | |
Gabriel Hurley | afe47636f7 | |
Justin Bronn | 52279a4113 | |
Gabriel Hurley | 1f7c6c011a | |
Julien Phalip | 71836f4c76 | |
Russell Keith-Magee | 8b42dfa47e | |
Russell Keith-Magee | e2d7a784c8 | |
Russell Keith-Magee | f317bd20d7 | |
Russell Keith-Magee | 38530700bf | |
Russell Keith-Magee | 3e7d79b6ac | |
Russell Keith-Magee | e9a1c03dba | |
Russell Keith-Magee | 671483f37b | |
Julien Phalip | a7ec5c433c | |
Julien Phalip | e71d0133bd | |
Timo Graham | 6f9d250698 | |
Timo Graham | 329d80faab | |
Timo Graham | 3e5fc7ebb1 | |
Timo Graham | 199f10f9c0 | |
Timo Graham | 4217f358c0 | |
Timo Graham | fe96e20a3e | |
Timo Graham | 1959aa939d | |
Jannis Leidel | c0fa1965e2 | |
Chris Beaven | 41e086cfb5 | |
Brian Rosner | 2a1874521e | |
Ramiro Morales | a925b3780e | |
Simon Meers | 220ce42333 | |
Simon Meers | c828cc1ba6 | |
Simon Meers | 00886dfd2b | |
Jannis Leidel | 5a0787f904 | |
Justin Bronn | a441032e0e | |
Simon Meers | d8ef686e24 | |
Timo Graham | a0285bb612 | |
Jannis Leidel | d90bd88d73 | |
Jannis Leidel | 7880d99900 | |
Jannis Leidel | 25ee9b4913 | |
Jannis Leidel | eb96665b7a | |
Timo Graham | 4f215cfcd7 | |
Timo Graham | 5d71bec5e4 | |
Timo Graham | 1b51aa74b8 | |
Carl Meyer | a5b44ed873 | |
Karen Tracey | c1baaa8c87 | |
Timo Graham | f578563291 | |
Luke Plant | 0e90de0a15 | |
Timo Graham | 9f71bef7e9 | |
Timo Graham | d2abec535e | |
Luke Plant | 6e87dacf62 | |
Timo Graham | 4124ef339c | |
Jannis Leidel | 879267f254 | |
Luke Plant | 7f3eda2f76 | |
Luke Plant | afa092853f | |
Chris Beaven | 18ecfad767 | |
Timo Graham | b9bdc96f9e | |
Timo Graham | 82b9fed1c7 | |
Timo Graham | 2c3d3400ef | |
Timo Graham | 08f5ac3d51 | |
Chris Beaven | b5c6b4f1d4 | |
Chris Beaven | be776521db | |
Ramiro Morales | 770b91ca7b | |
Gabriel Hurley | d12ac6d048 | |
Gabriel Hurley | 49f4a28cce | |
Jannis Leidel | e1dfa95cd1 | |
Timo Graham | 5be8fdb03e | |
Simon Meers | 8385b31c89 | |
Simon Meers | fc39163177 | |
Chris Beaven | 9a4e5112b2 | |
Luke Plant | 5c08cda611 | |
Chris Beaven | 7fd113e618 | |
Chris Beaven | 4cb2b53c22 | |
Luke Plant | fda65ffea5 | |
Simon Meers | af1943f139 | |
Luke Plant | fb052a15ed | |
Luke Plant | b3a4613595 | |
Chris Beaven | d06531d3f0 | |
Timo Graham | 64e625b6a1 | |
Timo Graham | 53354f80f6 | |
Timo Graham | fa261ea432 | |
Jannis Leidel | b44757ce51 | |
Timo Graham | c05dd20fa5 | |
Ramiro Morales | 0e9e0f0b21 | |
Simon Meers | 5aeeafba26 | |
Simon Meers | 637cf5de3a | |
Timo Graham | 44dbac6482 | |
Timo Graham | f53fe1e79e | |
Carl Meyer | 6a3d91828f | |
Chris Beaven | fe2713dd5e | |
Chris Beaven | 9269b606ba | |
Jannis Leidel | 5977193c01 | |
Jannis Leidel | e87c9da437 | |
Jannis Leidel | 4d62386cad | |
Ian Kelly | 53678ef508 | |
Gabriel Hurley | 64995cdd63 | |
Gabriel Hurley | 24adaf76f1 | |
Gabriel Hurley | b061cb97be | |
Ramiro Morales | 1d499d50d0 | |
Jacob Kaplan-Moss | 9b21a0c921 | |
Karen Tracey | cdd75e078a | |
Ian Kelly | 79bb9c1456 | |
Simon Meers | 05054aba76 | |
Russell Keith-Magee | e5aa2bdcec | |
Russell Keith-Magee | 686ef6c759 | |
Jannis Leidel | e6fe336f10 | |
Jannis Leidel | 6bf0fe6c4e | |
Justin Bronn | e37fae7fb9 | |
Justin Bronn | 032beb11da | |
Timo Graham | d935b232a2 | |
Luke Plant | ce9b216882 | |
Timo Graham | 9b0f1de566 | |
Ramiro Morales | 258957f4b9 | |
Simon Meers | d0052fcc5c | |
James Bennett | 81f7a6587f |
1
AUTHORS
1
AUTHORS
|
@ -92,6 +92,7 @@ answer newbie questions, and generally made Django that much better:
|
|||
Sean Brant
|
||||
Andrew Brehaut <http://brehaut.net/blog>
|
||||
David Brenneman <http://davidbrenneman.com>
|
||||
Anthony Briggs <anthony.briggs@gmail.com>
|
||||
brut.alll@gmail.com
|
||||
bthomas
|
||||
btoll@bestweb.net
|
||||
|
|
4
README
4
README
|
@ -28,7 +28,7 @@ http://code.djangoproject.com/newticket
|
|||
To get more help:
|
||||
|
||||
* Join the #django channel on irc.freenode.net. Lots of helpful people
|
||||
hang out there. Read the archives at http://botland.oebfare.com/logger/django/.
|
||||
hang out there. Read the archives at http://django-irc-logs.com/.
|
||||
|
||||
* Join the django-users mailing list, or read the archives, at
|
||||
http://groups.google.com/group/django-users.
|
||||
|
@ -42,5 +42,5 @@ To run Django's test suite:
|
|||
|
||||
* Follow the instructions in the "Unit tests" section of
|
||||
docs/internals/contributing.txt, published online at
|
||||
http://docs.djangoproject.com/en/dev/internals/contributing/#running-the-unit-tests
|
||||
https://docs.djangoproject.com/en/dev/internals/contributing/writing-code/unit-tests/#running-the-unit-tests
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
VERSION = (1, 3, 0, 'final', 0)
|
||||
VERSION = (1, 3, 8, 'alpha', 0)
|
||||
|
||||
def get_version():
|
||||
version = '%s.%s' % (VERSION[0], VERSION[1])
|
||||
|
|
|
@ -70,6 +70,9 @@ class BaseSettings(object):
|
|||
if name in ("MEDIA_URL", "STATIC_URL") and value and not value.endswith('/'):
|
||||
warnings.warn('If set, %s must end with a slash' % name,
|
||||
PendingDeprecationWarning)
|
||||
elif name == "ALLOWED_INCLUDE_ROOTS" and isinstance(value, basestring):
|
||||
raise ValueError("The ALLOWED_INCLUDE_ROOTS setting must be set "
|
||||
"to a tuple, not a string.")
|
||||
object.__setattr__(self, name, value)
|
||||
|
||||
|
||||
|
|
|
@ -29,6 +29,10 @@ ADMINS = ()
|
|||
# * Receive x-headers
|
||||
INTERNAL_IPS = ()
|
||||
|
||||
# Hosts/domain names that are valid for this site.
|
||||
# "*" matches anything, ".example.com" matches example.com and all subdomains
|
||||
ALLOWED_HOSTS = ['*']
|
||||
|
||||
# Local time zone for this installation. All choices can be found here:
|
||||
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name (although not all
|
||||
# systems may support all possibilities).
|
||||
|
@ -399,6 +403,8 @@ URL_VALIDATOR_USER_AGENT = "Django/%s (http://www.djangoproject.com)" % get_vers
|
|||
DEFAULT_TABLESPACE = ''
|
||||
DEFAULT_INDEX_TABLESPACE = ''
|
||||
|
||||
USE_X_FORWARDED_HOST = False
|
||||
|
||||
##############
|
||||
# MIDDLEWARE #
|
||||
##############
|
||||
|
|
|
@ -20,6 +20,10 @@ DATABASES = {
|
|||
}
|
||||
}
|
||||
|
||||
# Hosts/domain names that are valid for this site; required if DEBUG is False
|
||||
# See https://docs.djangoproject.com/en/1.3/ref/settings/#allowed-hosts
|
||||
ALLOWED_HOSTS = []
|
||||
|
||||
# Local time zone for this installation. Choices can be found here:
|
||||
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
|
||||
# although not all choices may be available on all operating systems.
|
||||
|
|
|
@ -74,9 +74,12 @@ class RelatedFilterSpec(FilterSpec):
|
|||
self.lookup_title = other_model._meta.verbose_name
|
||||
else:
|
||||
self.lookup_title = f.verbose_name # use field name
|
||||
rel_name = other_model._meta.pk.name
|
||||
if hasattr(f, 'rel'):
|
||||
rel_name = f.rel.get_related_field().name
|
||||
else:
|
||||
rel_name = other_model._meta.pk.name
|
||||
self.lookup_kwarg = '%s__%s__exact' % (self.field_path, rel_name)
|
||||
self.lookup_kwarg_isnull = '%s__isnull' % (self.field_path)
|
||||
self.lookup_kwarg_isnull = '%s__isnull' % self.field_path
|
||||
self.lookup_val = request.GET.get(self.lookup_kwarg, None)
|
||||
self.lookup_val_isnull = request.GET.get(
|
||||
self.lookup_kwarg_isnull, None)
|
||||
|
@ -176,7 +179,7 @@ FilterSpec.register(lambda f: bool(f.choices), ChoicesFilterSpec)
|
|||
|
||||
class DateFieldFilterSpec(FilterSpec):
|
||||
def __init__(self, f, request, params, model, model_admin,
|
||||
field_path=None):
|
||||
field_path=None):
|
||||
super(DateFieldFilterSpec, self).__init__(f, request, params, model,
|
||||
model_admin,
|
||||
field_path=field_path)
|
||||
|
|
|
@ -46,6 +46,11 @@ th {
|
|||
float: left;
|
||||
}
|
||||
|
||||
thead th:first-child,
|
||||
tfoot td:first-child {
|
||||
border-left: 1px solid #ddd !important;
|
||||
}
|
||||
|
||||
/* LAYOUT */
|
||||
|
||||
#user-tools {
|
||||
|
@ -73,6 +78,19 @@ div.breadcrumbs {
|
|||
margin-right: 10px !important;
|
||||
}
|
||||
|
||||
/* SORTABLE TABLES */
|
||||
|
||||
|
||||
table thead th.sorted a {
|
||||
padding-left: 13px;
|
||||
padding-right: 0px;
|
||||
}
|
||||
|
||||
table thead th.ascending a,
|
||||
table thead th.descending a {
|
||||
background-position: left;
|
||||
}
|
||||
|
||||
/* dashboard styles */
|
||||
|
||||
.dashboard .module table td a {
|
||||
|
@ -102,7 +120,7 @@ div.breadcrumbs {
|
|||
border-right: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.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: 0px !important;
|
||||
margin-left: 160px !important;
|
||||
}
|
||||
|
@ -123,6 +141,11 @@ div.breadcrumbs {
|
|||
margin-right:0 !important;
|
||||
}
|
||||
|
||||
#changelist table tbody td:first-child, #changelist table tbody th:first-child {
|
||||
border-right: 0;
|
||||
border-left: 1px solid #ddd;
|
||||
}
|
||||
|
||||
/* FORMS */
|
||||
|
||||
.aligned label {
|
||||
|
|
|
@ -225,18 +225,18 @@ class BaseModelAdmin(object):
|
|||
# if foo has been specificially included in the lookup list; so
|
||||
# drop __id if it is the last part. However, first we need to find
|
||||
# the pk attribute name.
|
||||
pk_attr_name = None
|
||||
rel_name = None
|
||||
for part in parts[:-1]:
|
||||
field, _, _, _ = model._meta.get_field_by_name(part)
|
||||
if hasattr(field, 'rel'):
|
||||
model = field.rel.to
|
||||
pk_attr_name = model._meta.pk.name
|
||||
rel_name = field.rel.get_related_field().name
|
||||
elif isinstance(field, RelatedObject):
|
||||
model = field.model
|
||||
pk_attr_name = model._meta.pk.name
|
||||
rel_name = model._meta.pk.name
|
||||
else:
|
||||
pk_attr_name = None
|
||||
if pk_attr_name and len(parts) > 1 and parts[-1] == pk_attr_name:
|
||||
rel_name = None
|
||||
if rel_name and len(parts) > 1 and parts[-1] == rel_name:
|
||||
parts.pop()
|
||||
|
||||
try:
|
||||
|
@ -1242,15 +1242,21 @@ class ModelAdmin(BaseModelAdmin):
|
|||
def history_view(self, request, object_id, extra_context=None):
|
||||
"The 'history' admin view for this model."
|
||||
from django.contrib.admin.models import LogEntry
|
||||
# First check if the user can see this history.
|
||||
model = self.model
|
||||
obj = get_object_or_404(model, pk=unquote(object_id))
|
||||
|
||||
if not self.has_change_permission(request, obj):
|
||||
raise PermissionDenied
|
||||
|
||||
# Then get the history for this object.
|
||||
opts = model._meta
|
||||
app_label = opts.app_label
|
||||
action_list = LogEntry.objects.filter(
|
||||
object_id = object_id,
|
||||
content_type__id__exact = ContentType.objects.get_for_model(model).id
|
||||
).select_related().order_by('action_time')
|
||||
# If no history was found, see whether this object even exists.
|
||||
obj = get_object_or_404(model, pk=unquote(object_id))
|
||||
|
||||
context = {
|
||||
'title': _('Change history: %s') % force_unicode(obj),
|
||||
'action_list': action_list,
|
||||
|
|
|
@ -26,6 +26,15 @@ ERROR_FLAG = 'e'
|
|||
# Text to display within change-list table cells if the value is blank.
|
||||
EMPTY_CHANGELIST_VALUE = ugettext_lazy('(None)')
|
||||
|
||||
def field_needs_distinct(field):
|
||||
if ((hasattr(field, 'rel') and
|
||||
isinstance(field.rel, models.ManyToManyRel)) or
|
||||
(isinstance(field, models.related.RelatedObject) and
|
||||
not field.field.unique)):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class ChangeList(object):
|
||||
def __init__(self, request, model, list_display, list_display_links, list_filter, date_hierarchy, search_fields, list_select_related, list_per_page, list_editable, model_admin):
|
||||
self.model = model
|
||||
|
@ -189,8 +198,7 @@ class ChangeList(object):
|
|||
f = self.lookup_opts.get_field_by_name(field_name)[0]
|
||||
except models.FieldDoesNotExist:
|
||||
raise IncorrectLookupParameters
|
||||
if hasattr(f, 'rel') and isinstance(f.rel, models.ManyToManyRel):
|
||||
use_distinct = True
|
||||
use_distinct = field_needs_distinct(f)
|
||||
|
||||
# if key ends with __in, split parameter into separate values
|
||||
if key.endswith('__in'):
|
||||
|
@ -264,7 +272,7 @@ class ChangeList(object):
|
|||
for search_spec in orm_lookups:
|
||||
field_name = search_spec.split('__', 1)[0]
|
||||
f = self.lookup_opts.get_field_by_name(field_name)[0]
|
||||
if hasattr(f, 'rel') and isinstance(f.rel, models.ManyToManyRel):
|
||||
if field_needs_distinct(f):
|
||||
use_distinct = True
|
||||
break
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ urlpatterns = urlpatterns + patterns('',
|
|||
(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'^admin_password_reset/$', 'django.contrib.auth.views.password_reset', dict(is_admin_site=True)),
|
||||
(r'^login_required/$', login_required(password_reset)),
|
||||
(r'^login_required_login_url/$', login_required(password_reset, login_url='/somewhere/')),
|
||||
)
|
||||
|
|
|
@ -9,6 +9,7 @@ from django.contrib.sites.models import Site, RequestSite
|
|||
from django.contrib.auth.models import User
|
||||
from django.test import TestCase
|
||||
from django.core import mail
|
||||
from django.core.exceptions import SuspiciousOperation
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.http import QueryDict
|
||||
|
||||
|
@ -69,6 +70,44 @@ class PasswordResetTest(AuthViewsTestCase):
|
|||
self.assertEqual(len(mail.outbox), 1)
|
||||
self.assertEqual("staffmember@example.com", mail.outbox[0].from_email)
|
||||
|
||||
def test_admin_reset(self):
|
||||
"If the reset view is marked as being for admin, the HTTP_HOST header is used for a domain override."
|
||||
response = self.client.post('/admin_password_reset/',
|
||||
{'email': 'staffmember@example.com'},
|
||||
HTTP_HOST='adminsite.com'
|
||||
)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertEqual(len(mail.outbox), 1)
|
||||
self.assertTrue("http://adminsite.com" in mail.outbox[0].body)
|
||||
self.assertEqual(settings.DEFAULT_FROM_EMAIL, mail.outbox[0].from_email)
|
||||
|
||||
def test_poisoned_http_host(self):
|
||||
"Poisoned HTTP_HOST headers can't be used for reset emails"
|
||||
# This attack is based on the way browsers handle URLs. The colon
|
||||
# should be used to separate the port, but if the URL contains an @,
|
||||
# the colon is interpreted as part of a username for login purposes,
|
||||
# making 'evil.com' the request domain. Since HTTP_HOST is used to
|
||||
# produce a meaningful reset URL, we need to be certain that the
|
||||
# HTTP_HOST header isn't poisoned. This is done as a check when get_host()
|
||||
# is invoked, but we check here as a practical consequence.
|
||||
def test_host_poisoning():
|
||||
self.client.post('/password_reset/',
|
||||
{'email': 'staffmember@example.com'},
|
||||
HTTP_HOST='www.example:dr.frankenstein@evil.tld'
|
||||
)
|
||||
self.assertRaises(SuspiciousOperation, test_host_poisoning)
|
||||
self.assertEqual(len(mail.outbox), 0)
|
||||
|
||||
def test_poisoned_http_host_admin_site(self):
|
||||
"Poisoned HTTP_HOST headers can't be used for reset emails on admin views"
|
||||
def test_host_poisoning():
|
||||
self.client.post('/admin_password_reset/',
|
||||
{'email': 'staffmember@example.com'},
|
||||
HTTP_HOST='www.example:dr.frankenstein@evil.tld'
|
||||
)
|
||||
self.assertRaises(SuspiciousOperation, test_host_poisoning)
|
||||
self.assertEqual(len(mail.outbox), 0)
|
||||
|
||||
def _test_confirm_start(self):
|
||||
# Start by creating the email
|
||||
response = self.client.post('/password_reset/', {'email': 'staffmember@example.com'})
|
||||
|
|
|
@ -5,7 +5,7 @@ from django.core.urlresolvers import reverse
|
|||
from django.http import HttpResponseRedirect, QueryDict
|
||||
from django.shortcuts import render_to_response
|
||||
from django.template import RequestContext
|
||||
from django.utils.http import base36_to_int
|
||||
from django.utils.http import base36_to_int, is_safe_url
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.views.decorators.cache import never_cache
|
||||
from django.views.decorators.csrf import csrf_protect
|
||||
|
@ -33,18 +33,11 @@ def login(request, template_name='registration/login.html',
|
|||
if request.method == "POST":
|
||||
form = authentication_form(data=request.POST)
|
||||
if form.is_valid():
|
||||
netloc = urlparse.urlparse(redirect_to)[1]
|
||||
|
||||
# Use default setting if redirect_to is empty
|
||||
if not redirect_to:
|
||||
# Ensure the user-originating redirection url is safe.
|
||||
if not is_safe_url(url=redirect_to, host=request.get_host()):
|
||||
redirect_to = settings.LOGIN_REDIRECT_URL
|
||||
|
||||
# Security check -- don't allow redirection to a different
|
||||
# host.
|
||||
elif netloc and netloc != request.get_host():
|
||||
redirect_to = settings.LOGIN_REDIRECT_URL
|
||||
|
||||
# Okay, security checks complete. Log the user in.
|
||||
# Okay, security check complete. Log the user in.
|
||||
auth_login(request, form.get_user())
|
||||
|
||||
if request.session.test_cookie_worked():
|
||||
|
@ -76,26 +69,27 @@ def logout(request, next_page=None,
|
|||
Logs out the user and displays 'You are logged out' message.
|
||||
"""
|
||||
auth_logout(request)
|
||||
redirect_to = request.REQUEST.get(redirect_field_name, '')
|
||||
if redirect_to:
|
||||
netloc = urlparse.urlparse(redirect_to)[1]
|
||||
# Security check -- don't allow redirection to a different host.
|
||||
if not (netloc and netloc != request.get_host()):
|
||||
return HttpResponseRedirect(redirect_to)
|
||||
|
||||
if next_page is None:
|
||||
current_site = get_current_site(request)
|
||||
context = {
|
||||
'site': current_site,
|
||||
'site_name': current_site.name,
|
||||
'title': _('Logged out')
|
||||
}
|
||||
context.update(extra_context or {})
|
||||
return render_to_response(template_name, context,
|
||||
context_instance=RequestContext(request, current_app=current_app))
|
||||
else:
|
||||
if redirect_field_name in request.REQUEST:
|
||||
next_page = request.REQUEST[redirect_field_name]
|
||||
# Security check -- don't allow redirection to a different host.
|
||||
if not is_safe_url(url=next_page, host=request.get_host()):
|
||||
next_page = request.path
|
||||
|
||||
if next_page:
|
||||
# Redirect to this page until the session has been cleared.
|
||||
return HttpResponseRedirect(next_page or request.path)
|
||||
return HttpResponseRedirect(next_page)
|
||||
|
||||
current_site = get_current_site(request)
|
||||
context = {
|
||||
'site': current_site,
|
||||
'site_name': current_site.name,
|
||||
'title': _('Logged out')
|
||||
}
|
||||
if extra_context is not None:
|
||||
context.update(extra_context)
|
||||
return render_to_response(template_name, context,
|
||||
context_instance=RequestContext(request, current_app=current_app))
|
||||
|
||||
def logout_then_login(request, login_url=None, current_app=None, extra_context=None):
|
||||
"""
|
||||
|
@ -151,7 +145,7 @@ def password_reset(request, is_admin_site=False,
|
|||
'request': request,
|
||||
}
|
||||
if is_admin_site:
|
||||
opts = dict(opts, domain_override=request.META['HTTP_HOST'])
|
||||
opts = dict(opts, domain_override=request.get_host())
|
||||
form.save(**opts)
|
||||
return HttpResponseRedirect(post_reset_redirect)
|
||||
else:
|
||||
|
|
|
@ -40,9 +40,6 @@ def post_comment(request, next=None, using=None):
|
|||
if not data.get('email', ''):
|
||||
data["email"] = request.user.email
|
||||
|
||||
# Check to see if the POST data overrides the view's next argument.
|
||||
next = data.get("next", next)
|
||||
|
||||
# Look up the object we're trying to comment about
|
||||
ctype = data.get("content_type")
|
||||
object_pk = data.get("object_pk")
|
||||
|
@ -94,9 +91,9 @@ def post_comment(request, next=None, using=None):
|
|||
]
|
||||
return render_to_response(
|
||||
template_list, {
|
||||
"comment" : form.data.get("comment", ""),
|
||||
"form" : form,
|
||||
"next": next,
|
||||
"comment": form.data.get("comment", ""),
|
||||
"form": form,
|
||||
"next": data.get("next", next),
|
||||
},
|
||||
RequestContext(request, {})
|
||||
)
|
||||
|
@ -127,7 +124,7 @@ def post_comment(request, next=None, using=None):
|
|||
request = request
|
||||
)
|
||||
|
||||
return next_redirect(data, next, comment_done, c=comment._get_pk_val())
|
||||
return next_redirect(request, next, comment_done, c=comment._get_pk_val())
|
||||
|
||||
comment_done = confirmation_view(
|
||||
template = "comments/posted.html",
|
||||
|
|
|
@ -7,6 +7,7 @@ from django.contrib import comments
|
|||
from django.contrib.comments import signals
|
||||
from django.views.decorators.csrf import csrf_protect
|
||||
|
||||
|
||||
@csrf_protect
|
||||
@login_required
|
||||
def flag(request, comment_id, next=None):
|
||||
|
@ -23,7 +24,7 @@ def flag(request, comment_id, next=None):
|
|||
# Flag on POST
|
||||
if request.method == 'POST':
|
||||
perform_flag(request, comment)
|
||||
return next_redirect(request.POST.copy(), next, flag_done, c=comment.pk)
|
||||
return next_redirect(request, next, flag_done, c=comment.pk)
|
||||
|
||||
# Render a form on GET
|
||||
else:
|
||||
|
@ -50,7 +51,7 @@ def delete(request, comment_id, next=None):
|
|||
if request.method == 'POST':
|
||||
# Flag the comment as deleted instead of actually deleting it.
|
||||
perform_delete(request, comment)
|
||||
return next_redirect(request.POST.copy(), next, delete_done, c=comment.pk)
|
||||
return next_redirect(request, next, delete_done, c=comment.pk)
|
||||
|
||||
# Render a form on GET
|
||||
else:
|
||||
|
@ -77,7 +78,7 @@ def approve(request, comment_id, next=None):
|
|||
if request.method == 'POST':
|
||||
# Flag the comment as approved.
|
||||
perform_approve(request, comment)
|
||||
return next_redirect(request.POST.copy(), next, approve_done, c=comment.pk)
|
||||
return next_redirect(request, next, approve_done, c=comment.pk)
|
||||
|
||||
# Render a form on GET
|
||||
else:
|
||||
|
|
|
@ -4,14 +4,15 @@ A few bits of helper functions for comment views.
|
|||
|
||||
import urllib
|
||||
import textwrap
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.core import urlresolvers
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.shortcuts import render_to_response
|
||||
from django.template import RequestContext
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.contrib import comments
|
||||
from django.utils.http import is_safe_url
|
||||
|
||||
def next_redirect(data, default, default_view, **get_kwargs):
|
||||
def next_redirect(request, default, default_view, **get_kwargs):
|
||||
"""
|
||||
Handle the "where should I go next?" part of comment views.
|
||||
|
||||
|
@ -21,9 +22,10 @@ def next_redirect(data, default, default_view, **get_kwargs):
|
|||
|
||||
Returns an ``HttpResponseRedirect``.
|
||||
"""
|
||||
next = data.get("next", default)
|
||||
if next is None:
|
||||
next = request.POST.get('next', default)
|
||||
if not is_safe_url(url=next, host=request.get_host()):
|
||||
next = urlresolvers.reverse(default_view)
|
||||
|
||||
if get_kwargs:
|
||||
if '#' in next:
|
||||
tmp = next.rsplit('#', 1)
|
||||
|
|
|
@ -122,7 +122,7 @@ class BaseSpatialOperations(object):
|
|||
raise NotImplementedError('Aggregate support not implemented for this spatial backend.')
|
||||
|
||||
def spatial_lookup_sql(self, lvalue, lookup_type, value, field):
|
||||
raise NotImplmentedError
|
||||
raise NotImplementedError
|
||||
|
||||
# Routines for getting the OGC-compliant models.
|
||||
def geometry_columns(self):
|
||||
|
|
|
@ -2,15 +2,16 @@ from ctypes.util import find_library
|
|||
from django.conf import settings
|
||||
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.db.backends.sqlite3.base import *
|
||||
from django.db.backends.sqlite3.base import DatabaseWrapper as SqliteDatabaseWrapper, \
|
||||
_sqlite_extract, _sqlite_date_trunc, _sqlite_regexp
|
||||
from django.db.backends.sqlite3.base import (
|
||||
_sqlite_extract, _sqlite_date_trunc, _sqlite_regexp, _sqlite_format_dtdelta,
|
||||
connection_created, Database, DatabaseWrapper as SQLiteDatabaseWrapper,
|
||||
SQLiteCursorWrapper)
|
||||
from django.contrib.gis.db.backends.spatialite.client import SpatiaLiteClient
|
||||
from django.contrib.gis.db.backends.spatialite.creation import SpatiaLiteCreation
|
||||
from django.contrib.gis.db.backends.spatialite.introspection import SpatiaLiteIntrospection
|
||||
from django.contrib.gis.db.backends.spatialite.operations import SpatiaLiteOperations
|
||||
|
||||
class DatabaseWrapper(SqliteDatabaseWrapper):
|
||||
class DatabaseWrapper(SQLiteDatabaseWrapper):
|
||||
def __init__(self, *args, **kwargs):
|
||||
# Before we get too far, make sure pysqlite 2.5+ is installed.
|
||||
if Database.version_info < (2, 5, 0):
|
||||
|
@ -51,6 +52,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)
|
||||
self.connection.create_function("django_format_dtdelta", 5, _sqlite_format_dtdelta)
|
||||
connection_created.send(sender=self.__class__, connection=self)
|
||||
|
||||
## From here on, customized for GeoDjango ##
|
||||
|
|
|
@ -3,7 +3,6 @@ from django.conf import settings
|
|||
from django.core.cache import get_cache
|
||||
from django.core.cache.backends.db import BaseDatabaseCache
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.core.management import call_command
|
||||
from django.db.backends.sqlite3.creation import DatabaseCreation
|
||||
|
||||
class SpatiaLiteCreation(DatabaseCreation):
|
||||
|
@ -16,26 +15,56 @@ class SpatiaLiteCreation(DatabaseCreation):
|
|||
This method is overloaded to load up the SpatiaLite initialization
|
||||
SQL prior to calling the `syncdb` command.
|
||||
"""
|
||||
if verbosity >= 1:
|
||||
print "Creating test database '%s'..." % self.connection.alias
|
||||
# Don't import django.core.management if it isn't needed.
|
||||
from django.core.management import call_command
|
||||
|
||||
test_database_name = self._create_test_db(verbosity, autoclobber)
|
||||
test_database_name = self._get_test_db_name()
|
||||
|
||||
if verbosity >= 1:
|
||||
test_db_repr = ''
|
||||
if verbosity >= 2:
|
||||
test_db_repr = " ('%s')" % test_database_name
|
||||
print "Creating test database for alias '%s'%s..." % (self.connection.alias, test_db_repr)
|
||||
|
||||
self._create_test_db(verbosity, autoclobber)
|
||||
|
||||
self.connection.close()
|
||||
|
||||
self.connection.settings_dict["NAME"] = test_database_name
|
||||
|
||||
# Confirm the feature set of the test database
|
||||
self.connection.features.confirm()
|
||||
|
||||
# Need to load the SpatiaLite initialization SQL before running `syncdb`.
|
||||
self.load_spatialite_sql()
|
||||
call_command('syncdb', verbosity=verbosity, interactive=False, database=self.connection.alias)
|
||||
|
||||
# Report syncdb messages at one level lower than that requested.
|
||||
# This ensures we don't get flooded with messages during testing
|
||||
# (unless you really ask to be flooded)
|
||||
call_command('syncdb',
|
||||
verbosity=max(verbosity - 1, 0),
|
||||
interactive=False,
|
||||
database=self.connection.alias,
|
||||
load_initial_data=False)
|
||||
|
||||
# We need to then do a flush to ensure that any data installed by
|
||||
# custom SQL has been removed. The only test data should come from
|
||||
# test fixtures, or autogenerated from post_syncdb triggers.
|
||||
# This has the side effect of loading initial data (which was
|
||||
# intentionally skipped in the syncdb).
|
||||
call_command('flush',
|
||||
verbosity=max(verbosity - 1, 0),
|
||||
interactive=False,
|
||||
database=self.connection.alias)
|
||||
|
||||
from django.core.cache import get_cache
|
||||
from django.core.cache.backends.db import BaseDatabaseCache
|
||||
for cache_alias in settings.CACHES:
|
||||
cache = get_cache(cache_alias)
|
||||
if isinstance(cache, BaseDatabaseCache):
|
||||
from django.db import router
|
||||
if router.allow_syncdb(self.connection.alias, cache.cache_model_class):
|
||||
call_command('createcachetable', cache._table, database=self.connection.alias)
|
||||
|
||||
# Get a cursor (even though we don't need one yet). This has
|
||||
# the side effect of initializing the test database.
|
||||
cursor = self.connection.cursor()
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from itertools import izip
|
||||
from django.db.backends.util import truncate_name
|
||||
from django.db.backends.util import truncate_name, typecast_timestamp
|
||||
from django.db.models.sql import compiler
|
||||
from django.db.models.sql.constants import TABLE_NAME
|
||||
from django.db.models.sql.constants import TABLE_NAME, MULTI
|
||||
from django.db.models.sql.query import get_proxied_model
|
||||
|
||||
SQLCompiler = compiler.SQLCompiler
|
||||
|
@ -194,7 +194,7 @@ class GeoSQLCompiler(compiler.SQLCompiler):
|
|||
# We resolve the rest of the columns if we're on Oracle or if
|
||||
# the `geo_values` attribute is defined.
|
||||
for value, field in map(None, row[index_start:], fields):
|
||||
values.append(self.query.convert_values(value, field, connection=self.connection))
|
||||
values.append(self.query.convert_values(value, field, self.connection))
|
||||
else:
|
||||
values.extend(row[index_start:])
|
||||
return tuple(values)
|
||||
|
@ -202,7 +202,7 @@ class GeoSQLCompiler(compiler.SQLCompiler):
|
|||
#### Routines unique to GeoQuery ####
|
||||
def get_extra_select_format(self, alias):
|
||||
sel_fmt = '%s'
|
||||
if alias in self.query.custom_select:
|
||||
if hasattr(self.query, 'custom_select') and alias in self.query.custom_select:
|
||||
sel_fmt = sel_fmt % self.query.custom_select[alias]
|
||||
return sel_fmt
|
||||
|
||||
|
@ -275,4 +275,24 @@ class SQLAggregateCompiler(compiler.SQLAggregateCompiler, GeoSQLCompiler):
|
|||
pass
|
||||
|
||||
class SQLDateCompiler(compiler.SQLDateCompiler, GeoSQLCompiler):
|
||||
pass
|
||||
"""
|
||||
This is overridden for GeoDjango to properly cast date columns, since
|
||||
`GeoQuery.resolve_columns` is used for spatial values.
|
||||
See #14648, #16757.
|
||||
"""
|
||||
def results_iter(self):
|
||||
if self.connection.ops.oracle:
|
||||
from django.db.models.fields import DateTimeField
|
||||
fields = [DateTimeField()]
|
||||
else:
|
||||
needs_string_cast = self.connection.features.needs_datetime_string_cast
|
||||
|
||||
offset = len(self.query.extra_select)
|
||||
for rows in self.execute_sql(MULTI):
|
||||
for row in rows:
|
||||
date = row[offset]
|
||||
if self.connection.ops.oracle:
|
||||
date = self.resolve_columns(row, fields)[offset]
|
||||
elif needs_string_cast:
|
||||
date = typecast_timestamp(str(date))
|
||||
yield date
|
||||
|
|
|
@ -20,7 +20,7 @@ class Field(GDALBase):
|
|||
# Setting the feature pointer and index.
|
||||
self._feat = feat
|
||||
self._index = index
|
||||
|
||||
|
||||
# Getting the pointer for this field.
|
||||
fld_ptr = capi.get_feat_field_defn(feat, index)
|
||||
if not fld_ptr:
|
||||
|
@ -33,6 +33,7 @@ class Field(GDALBase):
|
|||
# OFTReal with no precision should be an OFTInteger.
|
||||
if isinstance(self, OFTReal) and self.precision == 0:
|
||||
self.__class__ = OFTInteger
|
||||
self._double = True
|
||||
|
||||
def __str__(self):
|
||||
"Returns the string representation of the Field."
|
||||
|
@ -95,10 +96,17 @@ class Field(GDALBase):
|
|||
|
||||
### The Field sub-classes for each OGR Field type. ###
|
||||
class OFTInteger(Field):
|
||||
_double = False
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
"Returns an integer contained in this field."
|
||||
return self.as_int()
|
||||
if self._double:
|
||||
# If this is really from an OFTReal field with no precision,
|
||||
# read as a double and cast as Python int (to prevent overflow).
|
||||
return int(self.as_double())
|
||||
else:
|
||||
return self.as_int()
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
|
@ -137,7 +145,7 @@ class OFTDateTime(Field):
|
|||
"Returns a Python `datetime` object for this OFTDateTime field."
|
||||
# TODO: Adapt timezone information.
|
||||
# See http://lists.maptools.org/pipermail/gdal-dev/2006-February/007990.html
|
||||
# The `tz` variable has values of: 0=unknown, 1=localtime (ambiguous),
|
||||
# The `tz` variable has values of: 0=unknown, 1=localtime (ambiguous),
|
||||
# 100=GMT, 104=GMT+1, 80=GMT-5, etc.
|
||||
try:
|
||||
yy, mm, dd, hh, mn, ss, tz = self.as_datetime()
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import os, os.path, unittest
|
||||
import os
|
||||
import unittest
|
||||
from django.contrib.gis.gdal import DataSource, Envelope, OGRGeometry, OGRException, OGRIndexError, GDAL_VERSION
|
||||
from django.contrib.gis.gdal.field import OFTReal, OFTInteger, OFTString
|
||||
from django.contrib.gis.geometry.test_data import get_ds_file, TestDS
|
||||
from django.contrib.gis.geometry.test_data import get_ds_file, TestDS, TEST_DATA
|
||||
|
||||
# List of acceptable data sources.
|
||||
ds_list = (TestDS('test_point', nfeat=5, nfld=3, geom='POINT', gtype=1, driver='ESRI Shapefile',
|
||||
|
@ -72,7 +73,7 @@ class DataSourceTest(unittest.TestCase):
|
|||
self.assertEqual(source.nfld, len(layer.fields))
|
||||
|
||||
# Testing the layer's extent (an Envelope), and it's properties
|
||||
if source.driver == 'VRT' and (GDAL_VERSION > (1, 7, 0) and GDAL_VERSION < (1, 7, 3)):
|
||||
if source.driver == 'VRT' and (GDAL_VERSION >= (1, 7, 0) and GDAL_VERSION < (1, 7, 3)):
|
||||
# There's a known GDAL regression with retrieving the extent
|
||||
# of a VRT layer in versions 1.7.0-1.7.2:
|
||||
# http://trac.osgeo.org/gdal/ticket/3783
|
||||
|
@ -217,6 +218,16 @@ class DataSourceTest(unittest.TestCase):
|
|||
lyr.spatial_filter = None
|
||||
self.assertEqual(3, len(lyr))
|
||||
|
||||
def test07_integer_overflow(self):
|
||||
"Testing that OFTReal fields, treated as OFTInteger, do not overflow."
|
||||
# Using *.dbf from Census 2010 TIGER Shapefile for Texas,
|
||||
# which has land area ('ALAND10') stored in a Real field
|
||||
# with no precision.
|
||||
ds = DataSource(os.path.join(TEST_DATA, 'texas.dbf'))
|
||||
feat = ds[0][0]
|
||||
# Reference value obtained using `ogrinfo`.
|
||||
self.assertEqual(676586997978, feat.get('ALAND10'))
|
||||
|
||||
def suite():
|
||||
s = unittest.TestSuite()
|
||||
s.addTest(unittest.makeSuite(DataSourceTest))
|
||||
|
|
Binary file not shown.
|
@ -19,6 +19,7 @@ class City(models.Model):
|
|||
# This is an inherited model from City
|
||||
class PennsylvaniaCity(City):
|
||||
county = models.CharField(max_length=30)
|
||||
founded = models.DateTimeField(null=True)
|
||||
objects = models.GeoManager() # TODO: This should be implicitly inherited.
|
||||
|
||||
class State(models.Model):
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import os, unittest
|
||||
from datetime import datetime
|
||||
from django.contrib.gis.tests.utils import no_mysql, no_oracle, no_postgis, no_spatialite
|
||||
from django.contrib.gis.shortcuts import render_to_kmz
|
||||
from models import City
|
||||
from django.test import TestCase
|
||||
from models import City, PennsylvaniaCity
|
||||
|
||||
class GeoRegressionTests(unittest.TestCase):
|
||||
class GeoRegressionTests(TestCase):
|
||||
|
||||
def test01_update(self):
|
||||
"Testing GeoQuerySet.update(), see #10411."
|
||||
|
@ -35,3 +36,10 @@ class GeoRegressionTests(unittest.TestCase):
|
|||
extent = City.objects.filter(name='Pueblo').extent()
|
||||
for ref_val, val in zip(ref_ext, extent):
|
||||
self.assertAlmostEqual(ref_val, val, 4)
|
||||
|
||||
def test04_unicode_date(self):
|
||||
"Testing dates are converted properly, even on SpatiaLite, see #16408."
|
||||
founded = datetime(1857, 5, 23)
|
||||
mansfield = PennsylvaniaCity.objects.create(name='Mansfield', county='Tioga', point='POINT(-77.071445 41.823881)',
|
||||
founded=founded)
|
||||
self.assertEqual(founded, PennsylvaniaCity.objects.dates('founded', 'day')[0])
|
||||
|
|
Binary file not shown.
|
@ -36,6 +36,7 @@ class Parcel(models.Model):
|
|||
# These use the GeoManager but do not have any geographic fields.
|
||||
class Author(models.Model):
|
||||
name = models.CharField(max_length=100)
|
||||
dob = models.DateField()
|
||||
objects = models.GeoManager()
|
||||
|
||||
class Article(models.Model):
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
from datetime import date
|
||||
from django.test import TestCase
|
||||
|
||||
from django.contrib.gis.geos import GEOSGeometry, Point, MultiPoint
|
||||
|
@ -281,4 +282,11 @@ class RelatedGeoModelTest(TestCase):
|
|||
# evaluated as list generation swallows TypeError in CPython.
|
||||
sql = str(qs.query)
|
||||
|
||||
def test16_annotated_date_queryset(self):
|
||||
"Ensure annotated date querysets work if spatial backend is used. See #14648."
|
||||
birth_years = [dt.year for dt in
|
||||
list(Author.objects.annotate(num_books=Count('books')).dates('dob', 'year'))]
|
||||
birth_years.sort()
|
||||
self.assertEqual([1950, 1974], birth_years)
|
||||
|
||||
# TODO: Related tests for KML, GML, and distance lookups.
|
||||
|
|
|
@ -57,6 +57,7 @@ class LayerMapping(object):
|
|||
# This is a reminder that XMLField is deprecated
|
||||
# and this needs to be removed in 1.4
|
||||
models.XMLField : OFTString,
|
||||
models.BigIntegerField : (OFTInteger, OFTReal, OFTString),
|
||||
models.SmallIntegerField : (OFTInteger, OFTReal, OFTString),
|
||||
models.PositiveSmallIntegerField : (OFTInteger, OFTReal, OFTString),
|
||||
}
|
||||
|
|
|
@ -59,7 +59,6 @@ class CHIdentityCardNumberField(Field):
|
|||
* Conforms to the X1234567<0 or 1234567890 format.
|
||||
* Included checksums match calculated checksums
|
||||
|
||||
Algorithm is documented at http://adi.kousz.ch/artikel/IDCHE.htm
|
||||
"""
|
||||
default_error_messages = {
|
||||
'invalid': _('Enter a valid Swiss identity or passport card number in X1234567<0 or 1234567890 format.'),
|
||||
|
|
|
@ -11,6 +11,8 @@ markup syntaxes to HTML; currently there is support for:
|
|||
* reStructuredText, which requires docutils from http://docutils.sf.net/
|
||||
"""
|
||||
|
||||
import warnings
|
||||
|
||||
from django import template
|
||||
from django.conf import settings
|
||||
from django.utils.encoding import smart_str, force_unicode
|
||||
|
@ -65,10 +67,21 @@ def markdown(value, arg=''):
|
|||
|
||||
# Unicode support only in markdown v1.7 or above. Version_info
|
||||
# exist only in markdown v1.6.2rc-2 or above.
|
||||
if getattr(markdown, "version_info", None) < (1,7):
|
||||
markdown_vers = getattr(markdown, "version_info", None)
|
||||
if markdown_vers < (1,7):
|
||||
return mark_safe(force_unicode(markdown.markdown(smart_str(value), extensions, safe_mode=safe_mode)))
|
||||
else:
|
||||
return mark_safe(markdown.markdown(force_unicode(value), extensions, safe_mode=safe_mode))
|
||||
if markdown_vers >= (2,1):
|
||||
if safe_mode:
|
||||
return mark_safe(markdown.markdown(force_unicode(value), extensions, safe_mode=safe_mode, enable_attributes=False))
|
||||
else:
|
||||
return mark_safe(markdown.markdown(force_unicode(value), extensions, safe_mode=safe_mode))
|
||||
else:
|
||||
warnings.warn("Versions of markdown prior to 2.1 do not "
|
||||
"support disabling of attributes, no "
|
||||
"attributes have been removed and the result "
|
||||
"is insecure.")
|
||||
return mark_safe(markdown.markdown(force_unicode(value), extensions, safe_mode=safe_mode))
|
||||
else:
|
||||
return mark_safe(force_unicode(markdown.markdown(smart_str(value))))
|
||||
markdown.is_safe = True
|
||||
|
|
|
@ -14,6 +14,7 @@ except ImportError:
|
|||
|
||||
try:
|
||||
import markdown
|
||||
markdown_version = getattr(markdown, "version_info", 0)
|
||||
except ImportError:
|
||||
markdown = None
|
||||
|
||||
|
@ -38,7 +39,6 @@ Paragraph 2 with a link_
|
|||
|
||||
.. _link: http://www.example.com/"""
|
||||
|
||||
|
||||
@unittest.skipUnless(textile, 'texttile not installed')
|
||||
def test_textile(self):
|
||||
t = Template("{{ textile_content|textile }}")
|
||||
|
@ -60,6 +60,20 @@ Paragraph 2 with a link_
|
|||
pattern = re.compile("""<p>Paragraph 1\s*</p>\s*<h2>\s*An h2</h2>""")
|
||||
self.assertTrue(pattern.match(rendered))
|
||||
|
||||
@unittest.skipUnless(markdown and markdown_version >= (2,1), 'markdown >= 2.1 not installed')
|
||||
def test_markdown_attribute_disable(self):
|
||||
t = Template("{% load markup %}{{ markdown_content|markdown:'safe' }}")
|
||||
markdown_content = "{@onclick=alert('hi')}some paragraph"
|
||||
rendered = t.render(Context({'markdown_content':markdown_content})).strip()
|
||||
self.assertTrue('@' in rendered)
|
||||
|
||||
@unittest.skipUnless(markdown and markdown_version >= (2,1), 'markdown >= 2.1 not installed')
|
||||
def test_markdown_attribute_enable(self):
|
||||
t = Template("{% load markup %}{{ markdown_content|markdown }}")
|
||||
markdown_content = "{@onclick=alert('hi')}some paragraph"
|
||||
rendered = t.render(Context({'markdown_content':markdown_content})).strip()
|
||||
self.assertFalse('@' in rendered)
|
||||
|
||||
@unittest.skipIf(markdown, 'markdown is installed')
|
||||
def test_no_markdown(self):
|
||||
t = Template("{{ markdown_content|markdown }}")
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
from django.contrib.sessions.backends.base import SessionBase, CreateError
|
||||
from django.core.cache import cache
|
||||
|
||||
KEY_PREFIX = "django.contrib.sessions.cache"
|
||||
|
||||
class SessionStore(SessionBase):
|
||||
"""
|
||||
A cache-based session store.
|
||||
|
@ -10,7 +12,7 @@ class SessionStore(SessionBase):
|
|||
super(SessionStore, self).__init__(session_key)
|
||||
|
||||
def load(self):
|
||||
session_data = self._cache.get(self.session_key)
|
||||
session_data = self._cache.get(KEY_PREFIX + self.session_key)
|
||||
if session_data is not None:
|
||||
return session_data
|
||||
self.create()
|
||||
|
@ -37,13 +39,13 @@ class SessionStore(SessionBase):
|
|||
func = self._cache.add
|
||||
else:
|
||||
func = self._cache.set
|
||||
result = func(self.session_key, self._get_session(no_load=must_create),
|
||||
result = func(KEY_PREFIX + self.session_key, self._get_session(no_load=must_create),
|
||||
self.get_expiry_age())
|
||||
if must_create and not result:
|
||||
raise CreateError
|
||||
|
||||
def exists(self, session_key):
|
||||
if self._cache.has_key(session_key):
|
||||
if self._cache.has_key(KEY_PREFIX + session_key):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
@ -52,5 +54,5 @@ class SessionStore(SessionBase):
|
|||
if self._session_key is None:
|
||||
return
|
||||
session_key = self._session_key
|
||||
self._cache.delete(session_key)
|
||||
self._cache.delete(KEY_PREFIX + session_key)
|
||||
|
||||
|
|
|
@ -6,6 +6,8 @@ from django.conf import settings
|
|||
from django.contrib.sessions.backends.db import SessionStore as DBStore
|
||||
from django.core.cache import cache
|
||||
|
||||
KEY_PREFIX = "django.contrib.sessions.cached_db"
|
||||
|
||||
class SessionStore(DBStore):
|
||||
"""
|
||||
Implements cached, database backed sessions.
|
||||
|
@ -15,10 +17,11 @@ class SessionStore(DBStore):
|
|||
super(SessionStore, self).__init__(session_key)
|
||||
|
||||
def load(self):
|
||||
data = cache.get(self.session_key, None)
|
||||
data = cache.get(KEY_PREFIX + self.session_key, None)
|
||||
if data is None:
|
||||
data = super(SessionStore, self).load()
|
||||
cache.set(self.session_key, data, settings.SESSION_COOKIE_AGE)
|
||||
cache.set(KEY_PREFIX + self.session_key, data,
|
||||
settings.SESSION_COOKIE_AGE)
|
||||
return data
|
||||
|
||||
def exists(self, session_key):
|
||||
|
@ -26,11 +29,12 @@ class SessionStore(DBStore):
|
|||
|
||||
def save(self, must_create=False):
|
||||
super(SessionStore, self).save(must_create)
|
||||
cache.set(self.session_key, self._session, settings.SESSION_COOKIE_AGE)
|
||||
cache.set(KEY_PREFIX + self.session_key, self._session,
|
||||
settings.SESSION_COOKIE_AGE)
|
||||
|
||||
def delete(self, session_key=None):
|
||||
super(SessionStore, self).delete(session_key)
|
||||
cache.delete(session_key or self.session_key)
|
||||
cache.delete(KEY_PREFIX + (session_key or self.session_key))
|
||||
|
||||
def flush(self):
|
||||
"""
|
||||
|
@ -39,4 +43,4 @@ class SessionStore(DBStore):
|
|||
"""
|
||||
self.clear()
|
||||
self.delete(self.session_key)
|
||||
self.create()
|
||||
self.create()
|
||||
|
|
|
@ -3,15 +3,34 @@ Creates the default Site object.
|
|||
"""
|
||||
|
||||
from django.db.models import signals
|
||||
from django.db import connections
|
||||
from django.db import router
|
||||
from django.contrib.sites.models import Site
|
||||
from django.contrib.sites import models as site_app
|
||||
from django.core.management.color import no_style
|
||||
|
||||
def create_default_site(app, created_models, verbosity, db, **kwargs):
|
||||
if Site in created_models:
|
||||
# Only create the default sites in databases where Django created the table
|
||||
if Site in created_models and router.allow_syncdb(db, Site) :
|
||||
# The default settings set SITE_ID = 1, and some tests in Django's test
|
||||
# suite rely on this value. However, if database sequences are reused
|
||||
# (e.g. in the test suite after flush/syncdb), it isn't guaranteed that
|
||||
# the next id will be 1, so we coerce it. See #15573 and #16353. This
|
||||
# can also crop up outside of tests - see #15346.
|
||||
if verbosity >= 2:
|
||||
print "Creating example.com Site object"
|
||||
s = Site(domain="example.com", name="example.com")
|
||||
s.save(using=db)
|
||||
Site(pk=1, domain="example.com", name="example.com").save(using=db)
|
||||
|
||||
# We set an explicit pk instead of relying on auto-incrementation,
|
||||
# so we need to reset the database sequence.
|
||||
sequence_sql = connections[db].ops.sequence_reset_sql(no_style(), [Site])
|
||||
if sequence_sql:
|
||||
if verbosity >= 2:
|
||||
print "Resetting sequence"
|
||||
cursor = connections[db].cursor()
|
||||
for command in sequence_sql:
|
||||
cursor.execute(command)
|
||||
|
||||
Site.objects.clear_cache()
|
||||
|
||||
signals.post_syncdb.connect(create_default_site, sender=site_app)
|
||||
|
|
|
@ -15,6 +15,12 @@ class SitesFrameworkTests(TestCase):
|
|||
def tearDown(self):
|
||||
Site._meta.installed = self.old_Site_meta_installed
|
||||
|
||||
def test_save_another(self):
|
||||
# Regression for #17415
|
||||
# On some backends the sequence needs reset after save with explicit ID.
|
||||
# Test that there is no sequence collisions by saving another site.
|
||||
Site(domain="example2.com", name="example2.com").save()
|
||||
|
||||
def test_site_manager(self):
|
||||
# Make sure that get_current() does not return a deleted Site object.
|
||||
s = Site.objects.get_current()
|
||||
|
|
|
@ -76,6 +76,7 @@ Type 'yes' to continue, or 'no' to cancel: """)
|
|||
if confirm != 'yes':
|
||||
raise CommandError("Collecting static files cancelled.")
|
||||
|
||||
processed_files = []
|
||||
for finder in finders.get_finders():
|
||||
for path, storage in finder.list(ignore_patterns):
|
||||
# Prefix the relative path if the source storage contains it
|
||||
|
@ -83,10 +84,13 @@ Type 'yes' to continue, or 'no' to cancel: """)
|
|||
prefixed_path = os.path.join(storage.prefix, path)
|
||||
else:
|
||||
prefixed_path = path
|
||||
if prefixed_path in processed_files:
|
||||
continue
|
||||
if symlink:
|
||||
self.link_file(path, prefixed_path, storage, **options)
|
||||
else:
|
||||
self.copy_file(path, prefixed_path, storage, **options)
|
||||
processed_files.append(prefixed_path)
|
||||
|
||||
actual_count = len(self.copied_files) + len(self.symlinked_files)
|
||||
unmodified_count = len(self.unmodified_files)
|
||||
|
@ -196,9 +200,7 @@ Type 'yes' to continue, or 'no' to cancel: """)
|
|||
os.makedirs(os.path.dirname(full_path))
|
||||
except OSError:
|
||||
pass
|
||||
shutil.copy2(source_path, full_path)
|
||||
else:
|
||||
source_file = source_storage.open(path)
|
||||
self.storage.save(prefixed_path, source_file)
|
||||
source_file = source_storage.open(path)
|
||||
self.storage.save(prefixed_path, source_file)
|
||||
if not prefixed_path in self.copied_files:
|
||||
self.copied_files.append(prefixed_path)
|
||||
|
|
|
@ -132,7 +132,13 @@ class DatabaseCache(BaseDatabaseCache):
|
|||
cursor.execute("SELECT COUNT(*) FROM %s" % table)
|
||||
num = cursor.fetchone()[0]
|
||||
if num > self._max_entries:
|
||||
cursor.execute("SELECT cache_key FROM %s ORDER BY cache_key LIMIT 1 OFFSET %%s" % table, [num / self._cull_frequency])
|
||||
cull_num = num / self._cull_frequency
|
||||
if connections[db].vendor == 'oracle':
|
||||
# Special case for Oracle because it doesn't support LIMIT + OFFSET
|
||||
cursor.execute("SELECT cache_key FROM (SELECT ROW_NUMBER() OVER (ORDER BY cache_key) AS counter, cache_key FROM %s) WHERE counter > %%s AND COUNTER <= %%s" % table, [cull_num, cull_num + 1])
|
||||
else:
|
||||
# This isn't standard SQL, it's likely to break with some non officially supported databases
|
||||
cursor.execute("SELECT cache_key FROM %s ORDER BY cache_key LIMIT 1 OFFSET %%s" % table, [cull_num])
|
||||
cursor.execute("DELETE FROM %s WHERE cache_key < %%s" % table, [cursor.fetchone()[0]])
|
||||
|
||||
def clear(self):
|
||||
|
|
|
@ -47,13 +47,18 @@ def get_image_dimensions(file_or_path, close=False):
|
|||
file = open(file_or_path, 'rb')
|
||||
close = True
|
||||
try:
|
||||
# Most of the time PIL only needs a small chunk to parse the image and
|
||||
# get the dimensions, but with some TIFF files PIL needs to parse the
|
||||
# whole file.
|
||||
chunk_size = 1024
|
||||
while 1:
|
||||
data = file.read(1024)
|
||||
data = file.read(chunk_size)
|
||||
if not data:
|
||||
break
|
||||
p.feed(data)
|
||||
if p.image:
|
||||
return p.image.size
|
||||
chunk_size = chunk_size*2
|
||||
return None
|
||||
finally:
|
||||
if close:
|
||||
|
|
|
@ -133,7 +133,7 @@ class BaseHandler(object):
|
|||
if hasattr(response, 'render') and callable(response.render):
|
||||
for middleware_method in self._template_response_middleware:
|
||||
response = middleware_method(request, response)
|
||||
response.render()
|
||||
response = response.render()
|
||||
|
||||
except http.Http404, e:
|
||||
logger.warning('Not Found: %s' % request.path,
|
||||
|
|
|
@ -179,11 +179,10 @@ class ModPythonHandler(BaseHandler):
|
|||
try:
|
||||
request = self.request_class(req)
|
||||
except UnicodeDecodeError:
|
||||
logger.warning('Bad Request (UnicodeDecodeError): %s' % request.path,
|
||||
logger.warning('Bad Request (UnicodeDecodeError)',
|
||||
exc_info=sys.exc_info(),
|
||||
extra={
|
||||
'status_code': 400,
|
||||
'request': request
|
||||
}
|
||||
)
|
||||
response = http.HttpResponseBadRequest()
|
||||
|
|
|
@ -261,11 +261,10 @@ class WSGIHandler(base.BaseHandler):
|
|||
try:
|
||||
request = self.request_class(environ)
|
||||
except UnicodeDecodeError:
|
||||
logger.warning('Bad Request (UnicodeDecodeError): %s' % request.path,
|
||||
logger.warning('Bad Request (UnicodeDecodeError)',
|
||||
exc_info=sys.exc_info(),
|
||||
extra={
|
||||
'status_code': 400,
|
||||
'request': request
|
||||
}
|
||||
)
|
||||
response = http.HttpResponseBadRequest()
|
||||
|
|
|
@ -13,9 +13,8 @@ class Command(NoArgsCommand):
|
|||
|
||||
def ipython(self):
|
||||
try:
|
||||
from IPython.frontend.terminal.embed import TerminalInteractiveShell
|
||||
shell = TerminalInteractiveShell()
|
||||
shell.mainloop()
|
||||
from IPython import embed
|
||||
embed()
|
||||
except ImportError:
|
||||
# IPython < 0.11
|
||||
# Explicitly pass an empty list as arguments, because otherwise
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import sys
|
||||
|
||||
from django.contrib.contenttypes.generic import GenericForeignKey, GenericRelation
|
||||
from django.core.management.color import color_style
|
||||
from django.utils.itercompat import is_iterable
|
||||
|
||||
|
@ -240,12 +239,6 @@ def get_validation_errors(outfile, app=None):
|
|||
e.add(opts, "'%s' specifies an m2m relation through model %s, "
|
||||
"which has not been installed" % (f.name, f.rel.through)
|
||||
)
|
||||
elif isinstance(f, GenericRelation):
|
||||
if not any([isinstance(vfield, GenericForeignKey) for vfield in f.rel.to._meta.virtual_fields]):
|
||||
e.add(opts, "Model '%s' must have a GenericForeignKey in "
|
||||
"order to create a GenericRelation that points to it."
|
||||
% f.rel.to.__name__
|
||||
)
|
||||
|
||||
rel_opts = f.rel.to._meta
|
||||
rel_name = RelatedObject(f.rel.to, cls, f).get_accessor_name()
|
||||
|
|
|
@ -8,6 +8,8 @@ from django.db import models, DEFAULT_DB_ALIAS
|
|||
from django.utils.xmlutils import SimplerXMLGenerator
|
||||
from django.utils.encoding import smart_unicode
|
||||
from xml.dom import pulldom
|
||||
from xml.sax import handler
|
||||
from xml.sax.expatreader import ExpatParser as _ExpatParser
|
||||
|
||||
class Serializer(base.Serializer):
|
||||
"""
|
||||
|
@ -154,9 +156,13 @@ class Deserializer(base.Deserializer):
|
|||
|
||||
def __init__(self, stream_or_string, **options):
|
||||
super(Deserializer, self).__init__(stream_or_string, **options)
|
||||
self.event_stream = pulldom.parse(self.stream)
|
||||
self.event_stream = pulldom.parse(self.stream, self._make_parser())
|
||||
self.db = options.pop('using', DEFAULT_DB_ALIAS)
|
||||
|
||||
def _make_parser(self):
|
||||
"""Create a hardened XML parser (no custom/external entities)."""
|
||||
return DefusedExpatParser()
|
||||
|
||||
def next(self):
|
||||
for event, node in self.event_stream:
|
||||
if event == "START_ELEMENT" and node.nodeName == "object":
|
||||
|
@ -295,3 +301,89 @@ def getInnerText(node):
|
|||
else:
|
||||
pass
|
||||
return u"".join(inner_text)
|
||||
|
||||
|
||||
# Below code based on Christian Heimes' defusedxml
|
||||
|
||||
|
||||
class DefusedExpatParser(_ExpatParser):
|
||||
"""
|
||||
An expat parser hardened against XML bomb attacks.
|
||||
|
||||
Forbids DTDs, external entity references
|
||||
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
_ExpatParser.__init__(self, *args, **kwargs)
|
||||
self.setFeature(handler.feature_external_ges, False)
|
||||
self.setFeature(handler.feature_external_pes, False)
|
||||
|
||||
def start_doctype_decl(self, name, sysid, pubid, has_internal_subset):
|
||||
raise DTDForbidden(name, sysid, pubid)
|
||||
|
||||
def entity_decl(self, name, is_parameter_entity, value, base,
|
||||
sysid, pubid, notation_name):
|
||||
raise EntitiesForbidden(name, value, base, sysid, pubid, notation_name)
|
||||
|
||||
def unparsed_entity_decl(self, name, base, sysid, pubid, notation_name):
|
||||
# expat 1.2
|
||||
raise EntitiesForbidden(name, None, base, sysid, pubid, notation_name)
|
||||
|
||||
def external_entity_ref_handler(self, context, base, sysid, pubid):
|
||||
raise ExternalReferenceForbidden(context, base, sysid, pubid)
|
||||
|
||||
def reset(self):
|
||||
_ExpatParser.reset(self)
|
||||
parser = self._parser
|
||||
parser.StartDoctypeDeclHandler = self.start_doctype_decl
|
||||
parser.EntityDeclHandler = self.entity_decl
|
||||
parser.UnparsedEntityDeclHandler = self.unparsed_entity_decl
|
||||
parser.ExternalEntityRefHandler = self.external_entity_ref_handler
|
||||
|
||||
|
||||
class DefusedXmlException(ValueError):
|
||||
"""Base exception."""
|
||||
def __repr__(self):
|
||||
return str(self)
|
||||
|
||||
|
||||
class DTDForbidden(DefusedXmlException):
|
||||
"""Document type definition is forbidden."""
|
||||
def __init__(self, name, sysid, pubid):
|
||||
self.name = name
|
||||
self.sysid = sysid
|
||||
self.pubid = pubid
|
||||
|
||||
def __str__(self):
|
||||
tpl = "DTDForbidden(name='{}', system_id={!r}, public_id={!r})"
|
||||
return tpl.format(self.name, self.sysid, self.pubid)
|
||||
|
||||
|
||||
class EntitiesForbidden(DefusedXmlException):
|
||||
"""Entity definition is forbidden."""
|
||||
def __init__(self, name, value, base, sysid, pubid, notation_name):
|
||||
super(EntitiesForbidden, self).__init__()
|
||||
self.name = name
|
||||
self.value = value
|
||||
self.base = base
|
||||
self.sysid = sysid
|
||||
self.pubid = pubid
|
||||
self.notation_name = notation_name
|
||||
|
||||
def __str__(self):
|
||||
tpl = "EntitiesForbidden(name='{}', system_id={!r}, public_id={!r})"
|
||||
return tpl.format(self.name, self.sysid, self.pubid)
|
||||
|
||||
|
||||
class ExternalReferenceForbidden(DefusedXmlException):
|
||||
"""Resolving an external reference is forbidden."""
|
||||
def __init__(self, context, base, sysid, pubid):
|
||||
super(ExternalReferenceForbidden, self).__init__()
|
||||
self.context = context
|
||||
self.base = base
|
||||
self.sysid = sysid
|
||||
self.pubid = pubid
|
||||
|
||||
def __str__(self):
|
||||
tpl = "ExternalReferenceForbidden(system_id='{}', public_id={})"
|
||||
return tpl.format(self.sysid, self.pubid)
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import platform
|
||||
import re
|
||||
import urllib
|
||||
import urllib2
|
||||
import urlparse
|
||||
|
||||
|
@ -39,10 +41,6 @@ class RegexValidator(object):
|
|||
if not self.regex.search(smart_unicode(value)):
|
||||
raise ValidationError(self.message, code=self.code)
|
||||
|
||||
class HeadRequest(urllib2.Request):
|
||||
def get_method(self):
|
||||
return "HEAD"
|
||||
|
||||
class URLValidator(RegexValidator):
|
||||
regex = re.compile(
|
||||
r'^(?:http|ftp)s?://' # http:// or https://
|
||||
|
@ -52,7 +50,8 @@ class URLValidator(RegexValidator):
|
|||
r'(?::\d+)?' # optional port
|
||||
r'(?:/?|[/?]\S+)$', re.IGNORECASE)
|
||||
|
||||
def __init__(self, verify_exists=False, validator_user_agent=URL_VALIDATOR_USER_AGENT):
|
||||
def __init__(self, verify_exists=False,
|
||||
validator_user_agent=URL_VALIDATOR_USER_AGENT):
|
||||
super(URLValidator, self).__init__()
|
||||
self.verify_exists = verify_exists
|
||||
self.user_agent = validator_user_agent
|
||||
|
@ -76,6 +75,7 @@ class URLValidator(RegexValidator):
|
|||
else:
|
||||
url = value
|
||||
|
||||
#This is deprecated and will be removed in a future release.
|
||||
if self.verify_exists:
|
||||
headers = {
|
||||
"Accept": "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5",
|
||||
|
@ -85,24 +85,41 @@ class URLValidator(RegexValidator):
|
|||
"User-Agent": self.user_agent,
|
||||
}
|
||||
url = url.encode('utf-8')
|
||||
# Quote characters from the unreserved set, refs #16812
|
||||
url = urllib.quote(url, "!*'();:@&=+$,/?#[]")
|
||||
broken_error = ValidationError(
|
||||
_(u'This URL appears to be a broken link.'), code='invalid_link')
|
||||
try:
|
||||
req = HeadRequest(url, None, headers)
|
||||
u = urllib2.urlopen(req)
|
||||
req = urllib2.Request(url, None, headers)
|
||||
req.get_method = lambda: 'HEAD'
|
||||
#Create an opener that does not support local file access
|
||||
opener = urllib2.OpenerDirector()
|
||||
|
||||
#Don't follow redirects, but don't treat them as errors either
|
||||
error_nop = lambda *args, **kwargs: True
|
||||
http_error_processor = urllib2.HTTPErrorProcessor()
|
||||
http_error_processor.http_error_301 = error_nop
|
||||
http_error_processor.http_error_302 = error_nop
|
||||
http_error_processor.http_error_307 = error_nop
|
||||
|
||||
handlers = [urllib2.UnknownHandler(),
|
||||
urllib2.HTTPHandler(),
|
||||
urllib2.HTTPDefaultErrorHandler(),
|
||||
urllib2.FTPHandler(),
|
||||
http_error_processor]
|
||||
try:
|
||||
import ssl
|
||||
handlers.append(urllib2.HTTPSHandler())
|
||||
except:
|
||||
#Python isn't compiled with SSL support
|
||||
pass
|
||||
map(opener.add_handler, handlers)
|
||||
if platform.python_version_tuple() >= (2, 6):
|
||||
opener.open(req, timeout=10)
|
||||
else:
|
||||
opener.open(req)
|
||||
except ValueError:
|
||||
raise ValidationError(_(u'Enter a valid URL.'), code='invalid')
|
||||
except urllib2.HTTPError, e:
|
||||
if e.code in (405, 501):
|
||||
# Try a GET request (HEAD refused)
|
||||
# See also: http://www.w3.org/Protocols/rfc2616/rfc2616.html
|
||||
try:
|
||||
req = urllib2.Request(url, None, headers)
|
||||
u = urllib2.urlopen(req)
|
||||
except:
|
||||
raise broken_error
|
||||
else:
|
||||
raise broken_error
|
||||
except: # urllib2.URLError, httplib.InvalidURL, etc.
|
||||
raise broken_error
|
||||
|
||||
|
@ -133,7 +150,8 @@ class EmailValidator(RegexValidator):
|
|||
|
||||
email_re = re.compile(
|
||||
r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*" # dot-atom
|
||||
r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-011\013\014\016-\177])*"' # quoted-string
|
||||
# quoted-string, see also http://tools.ietf.org/html/rfc2822#section-3.2.5
|
||||
r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-\011\013\014\016-\177])*"'
|
||||
r')@(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?$', re.IGNORECASE) # domain
|
||||
validate_email = EmailValidator(email_re, _(u'Enter a valid e-mail address.'), 'invalid')
|
||||
|
||||
|
|
|
@ -220,7 +220,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
|
|||
if self.settings_dict['NAME'] != ":memory:":
|
||||
BaseDatabaseWrapper.close(self)
|
||||
|
||||
FORMAT_QMARK_REGEX = re.compile(r'(?![^%])%s')
|
||||
FORMAT_QMARK_REGEX = re.compile(r'(?<!%)%s')
|
||||
|
||||
class SQLiteCursorWrapper(Database.Cursor):
|
||||
"""
|
||||
|
|
|
@ -83,8 +83,8 @@ class Collector(object):
|
|||
def add(self, objs, source=None, nullable=False, reverse_dependency=False):
|
||||
"""
|
||||
Adds 'objs' to the collection of objects to be deleted. If the call is
|
||||
the result of a cascade, 'source' should be the model that caused it
|
||||
and 'nullable' should be set to True, if the relation can be null.
|
||||
the result of a cascade, 'source' should be the model that caused it,
|
||||
and 'nullable' should be set to True if the relation can be null.
|
||||
|
||||
Returns a list of all objects that were not already collected.
|
||||
"""
|
||||
|
@ -100,7 +100,7 @@ class Collector(object):
|
|||
# Nullable relationships can be ignored -- they are nulled out before
|
||||
# deleting, and therefore do not affect the order in which objects have
|
||||
# to be deleted.
|
||||
if new_objs and source is not None and not nullable:
|
||||
if source is not None and not nullable:
|
||||
if reverse_dependency:
|
||||
source, model = model, source
|
||||
self.dependencies.setdefault(source, set()).add(model)
|
||||
|
|
|
@ -1119,7 +1119,7 @@ class TimeField(Field):
|
|||
class URLField(CharField):
|
||||
description = _("URL")
|
||||
|
||||
def __init__(self, verbose_name=None, name=None, verify_exists=True, **kwargs):
|
||||
def __init__(self, verbose_name=None, name=None, verify_exists=False, **kwargs):
|
||||
kwargs['max_length'] = kwargs.get('max_length', 200)
|
||||
CharField.__init__(self, verbose_name, name, **kwargs)
|
||||
self.validators.append(validators.URLValidator(verify_exists=verify_exists))
|
||||
|
|
|
@ -390,7 +390,7 @@ class Options(object):
|
|||
cache[obj] = model
|
||||
for klass in get_models(include_auto_created=True):
|
||||
for f in klass._meta.local_fields:
|
||||
if f.rel and not isinstance(f.rel.to, str) and self == f.rel.to._meta:
|
||||
if f.rel and not isinstance(f.rel.to, basestring) and self == f.rel.to._meta:
|
||||
cache[RelatedObject(f.rel.to, klass, f)] = None
|
||||
self._related_objects_cache = cache
|
||||
|
||||
|
@ -427,7 +427,7 @@ class Options(object):
|
|||
cache[obj] = model
|
||||
for klass in get_models():
|
||||
for f in klass._meta.local_many_to_many:
|
||||
if f.rel and not isinstance(f.rel.to, str) and self == f.rel.to._meta:
|
||||
if f.rel and not isinstance(f.rel.to, basestring) and self == f.rel.to._meta:
|
||||
cache[RelatedObject(f.rel.to, klass, f)] = None
|
||||
if app_cache_ready():
|
||||
self._related_many_to_many_cache = cache
|
||||
|
|
|
@ -440,8 +440,6 @@ class Query(object):
|
|||
"Cannot combine a unique query with a non-unique query."
|
||||
|
||||
self.remove_inherited_models()
|
||||
l_tables = set([a for a in self.tables if self.alias_refcount[a]])
|
||||
r_tables = set([a for a in rhs.tables if rhs.alias_refcount[a]])
|
||||
# Work out how to relabel the rhs aliases, if necessary.
|
||||
change_map = {}
|
||||
used = set()
|
||||
|
@ -462,16 +460,27 @@ class Query(object):
|
|||
# all joins exclusive to either the lhs or the rhs must be converted
|
||||
# to an outer join.
|
||||
if not conjunction:
|
||||
l_tables = set(self.tables)
|
||||
r_tables = set(rhs.tables)
|
||||
# Update r_tables aliases.
|
||||
for alias in change_map:
|
||||
if alias in r_tables:
|
||||
r_tables.remove(alias)
|
||||
r_tables.add(change_map[alias])
|
||||
# r_tables may contain entries that have a refcount of 0
|
||||
# if the query has references to a table that can be
|
||||
# trimmed because only the foreign key is used.
|
||||
# We only need to fix the aliases for the tables that
|
||||
# actually have aliases.
|
||||
if rhs.alias_refcount[alias]:
|
||||
r_tables.remove(alias)
|
||||
r_tables.add(change_map[alias])
|
||||
# Find aliases that are exclusive to rhs or lhs.
|
||||
# These are promoted to outer joins.
|
||||
outer_aliases = (l_tables | r_tables) - (l_tables & r_tables)
|
||||
for alias in outer_aliases:
|
||||
self.promote_alias(alias, True)
|
||||
outer_tables = (l_tables | r_tables) - (l_tables & r_tables)
|
||||
for alias in outer_tables:
|
||||
# Again, some of the tables won't have aliases due to
|
||||
# the trimming of unnecessary tables.
|
||||
if self.alias_refcount.get(alias) or rhs.alias_refcount.get(alias):
|
||||
self.promote_alias(alias, True)
|
||||
|
||||
# Now relabel a copy of the rhs where-clause and add it to the current
|
||||
# one.
|
||||
|
@ -656,7 +665,7 @@ class Query(object):
|
|||
False, the join is only promoted if it is nullable, otherwise it is
|
||||
always promoted.
|
||||
|
||||
Returns True if the join was promoted.
|
||||
Returns True if the join was promoted by this call.
|
||||
"""
|
||||
if ((unconditional or self.alias_map[alias][NULLABLE]) and
|
||||
self.alias_map[alias][JOIN_TYPE] != self.LOUTER):
|
||||
|
@ -1063,17 +1072,20 @@ class Query(object):
|
|||
can_reuse)
|
||||
return
|
||||
|
||||
table_promote = False
|
||||
join_promote = False
|
||||
|
||||
if (lookup_type == 'isnull' and value is True and not negate and
|
||||
len(join_list) > 1):
|
||||
# If the comparison is against NULL, we may need to use some left
|
||||
# outer joins when creating the join chain. This is only done when
|
||||
# needed, as it's less efficient at the database level.
|
||||
self.promote_alias_chain(join_list)
|
||||
join_promote = True
|
||||
|
||||
# Process the join list to see if we can remove any inner joins from
|
||||
# the far end (fewer tables in a query is better).
|
||||
col, alias, join_list = self.trim_joins(target, join_list, last, trim)
|
||||
|
||||
if connector == OR:
|
||||
# Some joins may need to be promoted when adding a new filter to a
|
||||
# disjunction. We walk the list of new joins and where it diverges
|
||||
|
@ -1083,19 +1095,29 @@ class Query(object):
|
|||
join_it = iter(join_list)
|
||||
table_it = iter(self.tables)
|
||||
join_it.next(), table_it.next()
|
||||
table_promote = False
|
||||
join_promote = False
|
||||
unconditional = False
|
||||
for join in join_it:
|
||||
table = table_it.next()
|
||||
# Once we hit an outer join, all subsequent joins must
|
||||
# also be promoted, regardless of whether they have been
|
||||
# promoted as a result of this pass through the tables.
|
||||
unconditional = (unconditional or
|
||||
self.alias_map[join][JOIN_TYPE] == self.LOUTER)
|
||||
if join == table and self.alias_refcount[join] > 1:
|
||||
# We have more than one reference to this join table.
|
||||
# This means that we are dealing with two different query
|
||||
# subtrees, so we don't need to do any join promotion.
|
||||
continue
|
||||
join_promote = self.promote_alias(join)
|
||||
join_promote = join_promote or self.promote_alias(join, unconditional)
|
||||
if table != join:
|
||||
table_promote = self.promote_alias(table)
|
||||
# We only get here if we have found a table that exists
|
||||
# in the join list, but isn't on the original tables list.
|
||||
# This means we've reached the point where we only have
|
||||
# new tables, so we can break out of this promotion loop.
|
||||
break
|
||||
self.promote_alias_chain(join_it, join_promote)
|
||||
self.promote_alias_chain(table_it, table_promote)
|
||||
|
||||
self.promote_alias_chain(table_it, table_promote or join_promote)
|
||||
|
||||
if having_clause or force_having:
|
||||
if (alias, col) not in self.group_by:
|
||||
|
|
|
@ -26,16 +26,14 @@ from django.utils.functional import lazy
|
|||
from django.core.validators import EMPTY_VALUES
|
||||
|
||||
from util import ErrorList
|
||||
from widgets import TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, \
|
||||
ClearableFileInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple, \
|
||||
DateInput, DateTimeInput, TimeInput, SplitDateTimeWidget, SplitHiddenDateTimeWidget, \
|
||||
FILE_INPUT_CONTRADICTION
|
||||
from widgets import (TextInput, PasswordInput, HiddenInput,
|
||||
MultipleHiddenInput, ClearableFileInput, CheckboxInput, Select,
|
||||
NullBooleanSelect, SelectMultiple, DateInput, DateTimeInput, TimeInput,
|
||||
SplitDateTimeWidget, SplitHiddenDateTimeWidget, FILE_INPUT_CONTRADICTION)
|
||||
|
||||
__all__ = (
|
||||
'Field', 'CharField', 'IntegerField',
|
||||
'DEFAULT_DATE_INPUT_FORMATS', 'DateField',
|
||||
'DEFAULT_TIME_INPUT_FORMATS', 'TimeField',
|
||||
'DEFAULT_DATETIME_INPUT_FORMATS', 'DateTimeField', 'TimeField',
|
||||
'DateField', 'TimeField', 'DateTimeField', 'TimeField',
|
||||
'RegexField', 'EmailField', 'FileField', 'ImageField', 'URLField',
|
||||
'BooleanField', 'NullBooleanField', 'ChoiceField', 'MultipleChoiceField',
|
||||
'ComboField', 'MultiValueField', 'FloatField', 'DecimalField',
|
||||
|
@ -54,10 +52,6 @@ def en_format(name):
|
|||
)
|
||||
return getattr(formats, name)
|
||||
|
||||
DEFAULT_DATE_INPUT_FORMATS = lazy(lambda: en_format('DATE_INPUT_FORMATS'), tuple, list)()
|
||||
DEFAULT_TIME_INPUT_FORMATS = lazy(lambda: en_format('TIME_INPUT_FORMATS'), tuple, list)()
|
||||
DEFAULT_DATETIME_INPUT_FORMATS = lazy(lambda: en_format('DATETIME_INPUT_FORMATS'), tuple, list)()
|
||||
|
||||
class Field(object):
|
||||
widget = TextInput # Default widget to use when rendering this type of Field.
|
||||
hidden_widget = HiddenInput # Default widget to use when rendering this as "hidden".
|
||||
|
@ -544,20 +538,10 @@ class ImageField(FileField):
|
|||
file = StringIO(data['content'])
|
||||
|
||||
try:
|
||||
# load() is the only method that can spot a truncated JPEG,
|
||||
# but it cannot be called sanely after verify()
|
||||
trial_image = Image.open(file)
|
||||
trial_image.load()
|
||||
|
||||
# Since we're about to use the file again we have to reset the
|
||||
# file object if possible.
|
||||
if hasattr(file, 'reset'):
|
||||
file.reset()
|
||||
|
||||
# verify() is the only method that can spot a corrupt PNG,
|
||||
# but it must be called immediately after the constructor
|
||||
trial_image = Image.open(file)
|
||||
trial_image.verify()
|
||||
# load() could spot a truncated JPEG, but it loads the entire
|
||||
# image in memory, which is a DoS vector. See #3848 and #18520.
|
||||
# verify() must be called immediately after the constructor.
|
||||
Image.open(file).verify()
|
||||
except ImportError:
|
||||
# Under PyPy, it is possible to import PIL. However, the underlying
|
||||
# _imaging C module isn't available, so an ImportError will be
|
||||
|
|
|
@ -16,6 +16,9 @@ MAX_NUM_FORM_COUNT = 'MAX_NUM_FORMS'
|
|||
ORDERING_FIELD_NAME = 'ORDER'
|
||||
DELETION_FIELD_NAME = 'DELETE'
|
||||
|
||||
# default maximum number of forms in a formset, to prevent memory exhaustion
|
||||
DEFAULT_MAX_NUM = 1000
|
||||
|
||||
class ManagementForm(Form):
|
||||
"""
|
||||
``ManagementForm`` is used to keep track of how many form instances
|
||||
|
@ -104,7 +107,7 @@ class BaseFormSet(StrAndUnicode):
|
|||
def _construct_forms(self):
|
||||
# instantiate all the forms and put them in self.forms
|
||||
self.forms = []
|
||||
for i in xrange(self.total_form_count()):
|
||||
for i in xrange(min(self.total_form_count(), self.absolute_max)):
|
||||
self.forms.append(self._construct_form(i))
|
||||
|
||||
def _construct_form(self, i, **kwargs):
|
||||
|
@ -348,9 +351,14 @@ class BaseFormSet(StrAndUnicode):
|
|||
def formset_factory(form, formset=BaseFormSet, extra=1, can_order=False,
|
||||
can_delete=False, max_num=None):
|
||||
"""Return a FormSet for the given form class."""
|
||||
if max_num is None:
|
||||
max_num = DEFAULT_MAX_NUM
|
||||
# hard limit on forms instantiated, to prevent memory-exhaustion attacks
|
||||
# limit defaults to DEFAULT_MAX_NUM, but developer can increase it via max_num
|
||||
absolute_max = max(DEFAULT_MAX_NUM, max_num)
|
||||
attrs = {'form': form, 'extra': extra,
|
||||
'can_order': can_order, 'can_delete': can_delete,
|
||||
'max_num': max_num}
|
||||
'max_num': max_num, 'absolute_max': absolute_max}
|
||||
return type(form.__name__ + 'FormSet', (formset,), attrs)
|
||||
|
||||
def all_valid(formsets):
|
||||
|
|
|
@ -4,7 +4,7 @@ import re
|
|||
import time
|
||||
from pprint import pformat
|
||||
from urllib import urlencode, quote
|
||||
from urlparse import urljoin
|
||||
from urlparse import urljoin, urlparse
|
||||
try:
|
||||
from cStringIO import StringIO
|
||||
except ImportError:
|
||||
|
@ -92,7 +92,7 @@ else:
|
|||
if not _cookie_allows_colon_in_names:
|
||||
def load(self, rawdata, ignore_parse_errors=False):
|
||||
if ignore_parse_errors:
|
||||
self.bad_cookies = []
|
||||
self.bad_cookies = set()
|
||||
self._BaseCookie__set = self._loose_set
|
||||
super(SimpleCookie, self).load(rawdata)
|
||||
if ignore_parse_errors:
|
||||
|
@ -106,8 +106,8 @@ else:
|
|||
try:
|
||||
self._strict_set(key, real_value, coded_value)
|
||||
except Cookie.CookieError:
|
||||
self.bad_cookies.append(key)
|
||||
dict.__setitem__(self, key, None)
|
||||
self.bad_cookies.add(key)
|
||||
dict.__setitem__(self, key, Cookie.Morsel())
|
||||
|
||||
|
||||
class CompatCookie(SimpleCookie):
|
||||
|
@ -117,6 +117,7 @@ class CompatCookie(SimpleCookie):
|
|||
warnings.warn("CompatCookie is deprecated, use django.http.SimpleCookie instead.",
|
||||
PendingDeprecationWarning)
|
||||
|
||||
from django.core.exceptions import SuspiciousOperation
|
||||
from django.utils.datastructures import MultiValueDict, ImmutableList
|
||||
from django.utils.encoding import smart_str, iri_to_uri, force_unicode
|
||||
from django.utils.http import cookie_date
|
||||
|
@ -128,6 +129,8 @@ from utils import *
|
|||
RESERVED_CHARS="!*'();:@&=+$,/?%#[]"
|
||||
|
||||
absolute_http_url_re = re.compile(r"^https?://", re.I)
|
||||
host_validation_re = re.compile(r"^([a-z0-9.-]+|\[[a-f0-9]*:[a-f0-9:]+\])(:\d+)?$")
|
||||
|
||||
|
||||
class Http404(Exception):
|
||||
pass
|
||||
|
@ -153,7 +156,8 @@ class HttpRequest(object):
|
|||
def get_host(self):
|
||||
"""Returns the HTTP host using the environment or request headers."""
|
||||
# We try three options, in order of decreasing preference.
|
||||
if 'HTTP_X_FORWARDED_HOST' in self.META:
|
||||
if settings.USE_X_FORWARDED_HOST and (
|
||||
'HTTP_X_FORWARDED_HOST' in self.META):
|
||||
host = self.META['HTTP_X_FORWARDED_HOST']
|
||||
elif 'HTTP_HOST' in self.META:
|
||||
host = self.META['HTTP_HOST']
|
||||
|
@ -163,7 +167,16 @@ class HttpRequest(object):
|
|||
server_port = str(self.META['SERVER_PORT'])
|
||||
if server_port != (self.is_secure() and '443' or '80'):
|
||||
host = '%s:%s' % (host, server_port)
|
||||
return host
|
||||
|
||||
if settings.DEBUG:
|
||||
allowed_hosts = ['*']
|
||||
else:
|
||||
allowed_hosts = settings.ALLOWED_HOSTS
|
||||
if validate_host(host, allowed_hosts):
|
||||
return host
|
||||
else:
|
||||
raise SuspiciousOperation(
|
||||
"Invalid HTTP_HOST header (you may need to set ALLOWED_HOSTS): %s" % host)
|
||||
|
||||
def get_full_path(self):
|
||||
# RFC 3986 requires query string arguments to be in the ASCII range.
|
||||
|
@ -262,14 +275,18 @@ class HttpRequest(object):
|
|||
if self.method != 'POST':
|
||||
self._post, self._files = QueryDict('', encoding=self._encoding), MultiValueDict()
|
||||
return
|
||||
if self._read_started:
|
||||
if self._read_started and not hasattr(self, '_raw_post_data'):
|
||||
self._mark_post_parse_error()
|
||||
return
|
||||
|
||||
if self.META.get('CONTENT_TYPE', '').startswith('multipart'):
|
||||
self._raw_post_data = ''
|
||||
if hasattr(self, '_raw_post_data'):
|
||||
# Use already read data
|
||||
data = StringIO(self._raw_post_data)
|
||||
else:
|
||||
data = self
|
||||
try:
|
||||
self._post, self._files = self.parse_file_upload(self.META, self)
|
||||
self._post, self._files = self.parse_file_upload(self.META, data)
|
||||
except:
|
||||
# An error occured while parsing POST data. Since when
|
||||
# formatting the error the request handler might access
|
||||
|
@ -630,20 +647,22 @@ class HttpResponse(object):
|
|||
raise Exception("This %s instance cannot tell its position" % self.__class__)
|
||||
return sum([len(chunk) for chunk in self._container])
|
||||
|
||||
class HttpResponseRedirect(HttpResponse):
|
||||
class HttpResponseRedirectBase(HttpResponse):
|
||||
allowed_schemes = ['http', 'https', 'ftp']
|
||||
|
||||
def __init__(self, redirect_to):
|
||||
super(HttpResponseRedirectBase, self).__init__()
|
||||
parsed = urlparse(redirect_to)
|
||||
if parsed[0] and parsed[0] not in self.allowed_schemes:
|
||||
raise SuspiciousOperation("Unsafe redirect to URL with scheme '%s'" % parsed[0])
|
||||
self['Location'] = iri_to_uri(redirect_to)
|
||||
|
||||
class HttpResponseRedirect(HttpResponseRedirectBase):
|
||||
status_code = 302
|
||||
|
||||
def __init__(self, redirect_to):
|
||||
super(HttpResponseRedirect, self).__init__()
|
||||
self['Location'] = iri_to_uri(redirect_to)
|
||||
|
||||
class HttpResponsePermanentRedirect(HttpResponse):
|
||||
class HttpResponsePermanentRedirect(HttpResponseRedirectBase):
|
||||
status_code = 301
|
||||
|
||||
def __init__(self, redirect_to):
|
||||
super(HttpResponsePermanentRedirect, self).__init__()
|
||||
self['Location'] = iri_to_uri(redirect_to)
|
||||
|
||||
class HttpResponseNotModified(HttpResponse):
|
||||
status_code = 304
|
||||
|
||||
|
@ -689,3 +708,43 @@ def str_to_unicode(s, encoding):
|
|||
else:
|
||||
return s
|
||||
|
||||
def validate_host(host, allowed_hosts):
|
||||
"""
|
||||
Validate the given host header value for this site.
|
||||
|
||||
Check that the host looks valid and matches a host or host pattern in the
|
||||
given list of ``allowed_hosts``. Any pattern beginning with a period
|
||||
matches a domain and all its subdomains (e.g. ``.example.com`` matches
|
||||
``example.com`` and any subdomain), ``*`` matches anything, and anything
|
||||
else must match exactly.
|
||||
|
||||
Return ``True`` for a valid host, ``False`` otherwise.
|
||||
|
||||
"""
|
||||
# All validation is case-insensitive
|
||||
host = host.lower()
|
||||
|
||||
# Basic sanity check
|
||||
if not host_validation_re.match(host):
|
||||
return False
|
||||
|
||||
# Validate only the domain part.
|
||||
if host[-1] == ']':
|
||||
# It's an IPv6 address without a port.
|
||||
domain = host
|
||||
else:
|
||||
domain = host.rsplit(':', 1)[0]
|
||||
|
||||
for pattern in allowed_hosts:
|
||||
pattern = pattern.lower()
|
||||
match = (
|
||||
pattern == '*' or
|
||||
pattern.startswith('.') and (
|
||||
domain.endswith(pattern) or domain == pattern[1:]
|
||||
) or
|
||||
pattern == domain
|
||||
)
|
||||
if match:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
|
|
@ -75,7 +75,7 @@ class MultiPartParser(object):
|
|||
# For now set it to 0; we'll try again later on down.
|
||||
content_length = 0
|
||||
|
||||
if content_length <= 0:
|
||||
if content_length < 0:
|
||||
# This means we shouldn't continue...raise an error.
|
||||
raise MultiPartParserError("Invalid content length: %r" % content_length)
|
||||
|
||||
|
@ -105,6 +105,11 @@ class MultiPartParser(object):
|
|||
encoding = self._encoding
|
||||
handlers = self._upload_handlers
|
||||
|
||||
# HTTP spec says that Content-Length >= 0 is valid
|
||||
# handling content-length == 0 before continuing
|
||||
if self._content_length == 0:
|
||||
return QueryDict(MultiValueDict(), encoding=self._encoding), MultiValueDict()
|
||||
|
||||
limited_input_data = LimitBytes(self._input_data, self._content_length)
|
||||
|
||||
# See if the handler will want to take care of the parsing.
|
||||
|
@ -145,6 +150,8 @@ class MultiPartParser(object):
|
|||
continue
|
||||
|
||||
transfer_encoding = meta_data.get('content-transfer-encoding')
|
||||
if transfer_encoding is not None:
|
||||
transfer_encoding = transfer_encoding[0].strip()
|
||||
field_name = force_unicode(field_name, encoding, errors='replace')
|
||||
|
||||
if item_type == FIELD:
|
||||
|
|
|
@ -76,7 +76,8 @@ def fix_IE_for_vary(request, response):
|
|||
|
||||
# The first part of the Content-Type field will be the MIME type,
|
||||
# everything after ';', such as character-set, can be ignored.
|
||||
if response['Content-Type'].split(';')[0] not in safe_mime_types:
|
||||
mime_type = response.get('Content-Type', '').split(';', 1)[0]
|
||||
if mime_type not in safe_mime_types:
|
||||
try:
|
||||
del response['Vary']
|
||||
except KeyError:
|
||||
|
|
|
@ -14,21 +14,16 @@ class ContextPopException(Exception):
|
|||
"pop() has been called more times than push()"
|
||||
pass
|
||||
|
||||
class EmptyClass(object):
|
||||
# No-op class which takes no args to its __init__ method, to help implement
|
||||
# __copy__
|
||||
pass
|
||||
|
||||
class BaseContext(object):
|
||||
def __init__(self, dict_=None):
|
||||
dict_ = dict_ or {}
|
||||
self.dicts = [dict_]
|
||||
self._reset_dicts(dict_)
|
||||
|
||||
def _reset_dicts(self, value=None):
|
||||
self.dicts = [value or {}]
|
||||
|
||||
def __copy__(self):
|
||||
duplicate = EmptyClass()
|
||||
duplicate.__class__ = self.__class__
|
||||
duplicate.__dict__ = self.__dict__.copy()
|
||||
duplicate.dicts = duplicate.dicts[:]
|
||||
duplicate = copy(super(BaseContext, self))
|
||||
duplicate.dicts = self.dicts[:]
|
||||
return duplicate
|
||||
|
||||
def __repr__(self):
|
||||
|
@ -78,6 +73,15 @@ class BaseContext(object):
|
|||
return d[key]
|
||||
return otherwise
|
||||
|
||||
def new(self, values=None):
|
||||
"""
|
||||
Returns a new context with the same properties, but with only the
|
||||
values given in 'values' stored.
|
||||
"""
|
||||
new_context = copy(self)
|
||||
new_context._reset_dicts(values)
|
||||
return new_context
|
||||
|
||||
class Context(BaseContext):
|
||||
"A stack container for variable context"
|
||||
def __init__(self, dict_=None, autoescape=True, current_app=None, use_l10n=None):
|
||||
|
@ -99,14 +103,6 @@ class Context(BaseContext):
|
|||
self.dicts.append(other_dict)
|
||||
return other_dict
|
||||
|
||||
def new(self, values=None):
|
||||
"""
|
||||
Returns a new Context with the same 'autoescape' value etc, but with
|
||||
only the values given in 'values' stored.
|
||||
"""
|
||||
return self.__class__(dict_=values, autoescape=self.autoescape,
|
||||
current_app=self.current_app, use_l10n=self.use_l10n)
|
||||
|
||||
class RenderContext(BaseContext):
|
||||
"""
|
||||
A stack container for storing Template state.
|
||||
|
|
|
@ -92,11 +92,14 @@ class SimpleTemplateResponse(HttpResponse):
|
|||
|
||||
Returns the baked response instance.
|
||||
"""
|
||||
retval = self
|
||||
if not self._is_rendered:
|
||||
self._set_content(self.rendered_content)
|
||||
for post_callback in self._post_render_callbacks:
|
||||
post_callback(self)
|
||||
return self
|
||||
newretval = post_callback(retval)
|
||||
if newretval is not None:
|
||||
retval = newretval
|
||||
return retval
|
||||
|
||||
is_rendered = property(lambda self: self._is_rendered)
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ def ssi(parser, token):
|
|||
|
||||
{% ssi "/home/html/ljworld.com/includes/right_generic.html" parsed %}
|
||||
"""
|
||||
bits = token.contents.split()
|
||||
bits = token.split_contents()
|
||||
parsed = False
|
||||
if len(bits) not in (2, 3):
|
||||
raise TemplateSyntaxError("'ssi' tag takes one argument: the path to"
|
||||
|
|
|
@ -207,6 +207,18 @@ class RequestFactory(object):
|
|||
"Construct a generic request object."
|
||||
return WSGIRequest(self._base_environ(**request))
|
||||
|
||||
def _encode_data(self, data, content_type, ):
|
||||
if content_type is MULTIPART_CONTENT:
|
||||
return encode_multipart(BOUNDARY, data)
|
||||
else:
|
||||
# Encode the content so that the byte representation is correct.
|
||||
match = CONTENT_TYPE_RE.match(content_type)
|
||||
if match:
|
||||
charset = match.group(1)
|
||||
else:
|
||||
charset = settings.DEFAULT_CHARSET
|
||||
return smart_str(data, encoding=charset)
|
||||
|
||||
def _get_path(self, parsed):
|
||||
# If there are parameters, add them
|
||||
if parsed[3]:
|
||||
|
@ -232,16 +244,7 @@ class RequestFactory(object):
|
|||
**extra):
|
||||
"Construct a POST request."
|
||||
|
||||
if content_type is MULTIPART_CONTENT:
|
||||
post_data = encode_multipart(BOUNDARY, data)
|
||||
else:
|
||||
# Encode the content so that the byte representation is correct.
|
||||
match = CONTENT_TYPE_RE.match(content_type)
|
||||
if match:
|
||||
charset = match.group(1)
|
||||
else:
|
||||
charset = settings.DEFAULT_CHARSET
|
||||
post_data = smart_str(data, encoding=charset)
|
||||
post_data = self._encode_data(data, content_type)
|
||||
|
||||
parsed = urlparse(path)
|
||||
r = {
|
||||
|
@ -286,25 +289,16 @@ class RequestFactory(object):
|
|||
**extra):
|
||||
"Construct a PUT request."
|
||||
|
||||
if content_type is MULTIPART_CONTENT:
|
||||
post_data = encode_multipart(BOUNDARY, data)
|
||||
else:
|
||||
post_data = data
|
||||
|
||||
# Make `data` into a querystring only if it's not already a string. If
|
||||
# it is a string, we'll assume that the caller has already encoded it.
|
||||
query_string = None
|
||||
if not isinstance(data, basestring):
|
||||
query_string = urlencode(data, doseq=True)
|
||||
put_data = self._encode_data(data, content_type)
|
||||
|
||||
parsed = urlparse(path)
|
||||
r = {
|
||||
'CONTENT_LENGTH': len(post_data),
|
||||
'CONTENT_LENGTH': len(put_data),
|
||||
'CONTENT_TYPE': content_type,
|
||||
'PATH_INFO': self._get_path(parsed),
|
||||
'QUERY_STRING': query_string or parsed[4],
|
||||
'QUERY_STRING': parsed[4],
|
||||
'REQUEST_METHOD': 'PUT',
|
||||
'wsgi.input': FakePayload(post_data),
|
||||
'wsgi.input': FakePayload(put_data),
|
||||
}
|
||||
r.update(extra)
|
||||
return self.request(**r)
|
||||
|
|
|
@ -6,12 +6,15 @@ from django.conf import settings
|
|||
from django.core import mail
|
||||
from django.core.mail.backends import locmem
|
||||
from django.test import signals
|
||||
from django.template import Template
|
||||
from django.template import Template, loader, TemplateDoesNotExist
|
||||
from django.template.loaders import cached
|
||||
from django.utils.translation import deactivate
|
||||
|
||||
__all__ = ('Approximate', 'ContextList', 'setup_test_environment',
|
||||
'teardown_test_environment', 'get_runner')
|
||||
|
||||
RESTORE_LOADERS_ATTR = '_original_template_source_loaders'
|
||||
|
||||
|
||||
class Approximate(object):
|
||||
def __init__(self, val, places=7):
|
||||
|
@ -73,6 +76,9 @@ def setup_test_environment():
|
|||
mail.original_email_backend = settings.EMAIL_BACKEND
|
||||
settings.EMAIL_BACKEND = 'django.core.mail.backends.locmem.EmailBackend'
|
||||
|
||||
settings._original_allowed_hosts = settings.ALLOWED_HOSTS
|
||||
settings.ALLOWED_HOSTS = ['*']
|
||||
|
||||
mail.outbox = []
|
||||
|
||||
deactivate()
|
||||
|
@ -94,6 +100,9 @@ def teardown_test_environment():
|
|||
settings.EMAIL_BACKEND = mail.original_email_backend
|
||||
del mail.original_email_backend
|
||||
|
||||
settings.ALLOWED_HOSTS = settings._original_allowed_hosts
|
||||
del settings._original_allowed_hosts
|
||||
|
||||
del mail.outbox
|
||||
|
||||
|
||||
|
@ -125,3 +134,41 @@ def get_runner(settings):
|
|||
test_module = __import__(test_module_name, {}, {}, test_path[-1])
|
||||
test_runner = getattr(test_module, test_path[-1])
|
||||
return test_runner
|
||||
|
||||
|
||||
def setup_test_template_loader(templates_dict, use_cached_loader=False):
|
||||
"""
|
||||
Changes Django to only find templates from within a dictionary (where each
|
||||
key is the template name and each value is the corresponding template
|
||||
content to return).
|
||||
|
||||
Use meth:`restore_template_loaders` to restore the original loaders.
|
||||
"""
|
||||
if hasattr(loader, RESTORE_LOADERS_ATTR):
|
||||
raise Exception("loader.%s already exists" % RESTORE_LOADERS_ATTR)
|
||||
|
||||
def test_template_loader(template_name, template_dirs=None):
|
||||
"A custom template loader that loads templates from a dictionary."
|
||||
try:
|
||||
return (templates_dict[template_name], "test:%s" % template_name)
|
||||
except KeyError:
|
||||
raise TemplateDoesNotExist(template_name)
|
||||
|
||||
if use_cached_loader:
|
||||
template_loader = cached.Loader(('test_template_loader',))
|
||||
template_loader._cached_loaders = (test_template_loader,)
|
||||
else:
|
||||
template_loader = test_template_loader
|
||||
|
||||
setattr(loader, RESTORE_LOADERS_ATTR, loader.template_source_loaders)
|
||||
loader.template_source_loaders = (template_loader,)
|
||||
return template_loader
|
||||
|
||||
|
||||
def restore_template_loaders():
|
||||
"""
|
||||
Restores the original template loaders after
|
||||
:meth:`setup_test_template_loader` has been run.
|
||||
"""
|
||||
loader.template_source_loaders = getattr(loader, RESTORE_LOADERS_ATTR)
|
||||
delattr(loader, RESTORE_LOADERS_ATTR)
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
import os, sys, time
|
||||
import os, sys, time, signal
|
||||
|
||||
try:
|
||||
import thread
|
||||
|
@ -73,11 +73,18 @@ def code_changed():
|
|||
|
||||
def ensure_echo_on():
|
||||
if termios:
|
||||
fd = sys.stdin.fileno()
|
||||
attr_list = termios.tcgetattr(fd)
|
||||
if not attr_list[3] & termios.ECHO:
|
||||
attr_list[3] |= termios.ECHO
|
||||
termios.tcsetattr(fd, termios.TCSANOW, attr_list)
|
||||
fd = sys.stdin
|
||||
if fd.isatty():
|
||||
attr_list = termios.tcgetattr(fd)
|
||||
if not attr_list[3] & termios.ECHO:
|
||||
attr_list[3] |= termios.ECHO
|
||||
if hasattr(signal, 'SIGTTOU'):
|
||||
old_handler = signal.signal(signal.SIGTTOU, signal.SIG_IGN)
|
||||
else:
|
||||
old_handler = None
|
||||
termios.tcsetattr(fd, termios.TCSANOW, attr_list)
|
||||
if old_handler is not None:
|
||||
signal.signal(signal.SIGTTOU, old_handler)
|
||||
|
||||
def reloader_thread():
|
||||
ensure_echo_on()
|
||||
|
|
|
@ -67,6 +67,12 @@ def patch_cache_control(response, **kwargs):
|
|||
if 'max-age' in cc and 'max_age' in kwargs:
|
||||
kwargs['max_age'] = min(cc['max-age'], kwargs['max_age'])
|
||||
|
||||
# Allow overriding private caching and vice versa
|
||||
if 'private' in cc and 'public' in kwargs:
|
||||
del cc['private']
|
||||
elif 'public' in cc and 'private' in kwargs:
|
||||
del cc['public']
|
||||
|
||||
for (k, v) in kwargs.items():
|
||||
cc[k.replace('_', '-')] = v
|
||||
cc = ', '.join([dictvalue(el) for el in cc.items()])
|
||||
|
|
|
@ -319,17 +319,20 @@ class MultiValueDict(dict):
|
|||
def setdefault(self, key, default=None):
|
||||
if key not in self:
|
||||
self[key] = default
|
||||
return default
|
||||
return self[key]
|
||||
|
||||
def setlistdefault(self, key, default_list=()):
|
||||
def setlistdefault(self, key, default_list=None):
|
||||
if key not in self:
|
||||
if default_list is None:
|
||||
default_list = []
|
||||
self.setlist(key, default_list)
|
||||
return default_list
|
||||
return self.getlist(key)
|
||||
|
||||
def appendlist(self, key, value):
|
||||
"""Appends an item to the internal list associated with key."""
|
||||
self.setlistdefault(key, [])
|
||||
super(MultiValueDict, self).__setitem__(key, self.getlist(key) + [value])
|
||||
self.setlistdefault(key).append(value)
|
||||
|
||||
def items(self):
|
||||
"""
|
||||
|
@ -378,15 +381,15 @@ class MultiValueDict(dict):
|
|||
other_dict = args[0]
|
||||
if isinstance(other_dict, MultiValueDict):
|
||||
for key, value_list in other_dict.lists():
|
||||
self.setlistdefault(key, []).extend(value_list)
|
||||
self.setlistdefault(key).extend(value_list)
|
||||
else:
|
||||
try:
|
||||
for key, value in other_dict.items():
|
||||
self.setlistdefault(key, []).append(value)
|
||||
self.setlistdefault(key).append(value)
|
||||
except TypeError:
|
||||
raise ValueError("MultiValueDict.update() takes either a MultiValueDict or dictionary")
|
||||
for key, value in kwargs.iteritems():
|
||||
self.setlistdefault(key, []).append(value)
|
||||
self.setlistdefault(key).append(value)
|
||||
|
||||
class DotExpandedDict(dict):
|
||||
"""
|
||||
|
|
|
@ -97,10 +97,17 @@ def make_middleware_decorator(middleware_class):
|
|||
if result is not None:
|
||||
return result
|
||||
raise
|
||||
if hasattr(middleware, 'process_response'):
|
||||
result = middleware.process_response(request, response)
|
||||
if result is not None:
|
||||
return result
|
||||
if hasattr(response, 'render') and callable(response.render):
|
||||
if hasattr(middleware, 'process_template_response'):
|
||||
response = middleware.process_template_response(request, response)
|
||||
# Defer running of process_response until after the template
|
||||
# has been rendered:
|
||||
if hasattr(middleware, 'process_response'):
|
||||
callback = lambda response: middleware.process_response(request, response)
|
||||
response.add_post_render_callback(callback)
|
||||
else:
|
||||
if hasattr(middleware, 'process_response'):
|
||||
return middleware.process_response(request, response)
|
||||
return response
|
||||
return wraps(view_func, assigned=available_attrs(view_func))(_wrapped_view)
|
||||
return _decorator
|
||||
|
|
|
@ -204,3 +204,15 @@ else:
|
|||
"""
|
||||
p1, p2 = urlparse.urlparse(url1), urlparse.urlparse(url2)
|
||||
return p1[0:2] == p2[0:2]
|
||||
|
||||
def is_safe_url(url, host=None):
|
||||
"""
|
||||
Return ``True`` if the url is a safe redirection (i.e. it doesn't point to
|
||||
a different host).
|
||||
|
||||
Always returns ``False`` on an empty url.
|
||||
"""
|
||||
if not url:
|
||||
return False
|
||||
netloc = urlparse.urlparse(url)[1]
|
||||
return not netloc or netloc == host
|
||||
|
|
|
@ -447,16 +447,16 @@ def templatize(src, origin=None):
|
|||
for t in Lexer(src, origin).tokenize():
|
||||
if incomment:
|
||||
if t.token_type == TOKEN_BLOCK and t.contents == 'endcomment':
|
||||
content = u''.join(comment)
|
||||
content = ''.join(comment)
|
||||
translators_comment_start = None
|
||||
for lineno, line in enumerate(content.splitlines(True)):
|
||||
if line.lstrip().startswith(TRANSLATOR_COMMENT_MARK):
|
||||
translators_comment_start = lineno
|
||||
for lineno, line in enumerate(content.splitlines(True)):
|
||||
if translators_comment_start is not None and lineno >= translators_comment_start:
|
||||
out.write(u' # %s' % line)
|
||||
out.write(' # %s' % line)
|
||||
else:
|
||||
out.write(u' #\n')
|
||||
out.write(' #\n')
|
||||
incomment = False
|
||||
comment = []
|
||||
else:
|
||||
|
|
|
@ -161,3 +161,18 @@ class RedirectView(View):
|
|||
'request': self.request
|
||||
})
|
||||
return http.HttpResponseGone()
|
||||
|
||||
def head(self, request, *args, **kwargs):
|
||||
return self.get(request, *args, **kwargs)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
return self.get(request, *args, **kwargs)
|
||||
|
||||
def options(self, request, *args, **kwargs):
|
||||
return self.get(request, *args, **kwargs)
|
||||
|
||||
def delete(self, request, *args, **kwargs):
|
||||
return self.get(request, *args, **kwargs)
|
||||
|
||||
def put(self, request, *args, **kwargs):
|
||||
return self.get(request, *args, **kwargs)
|
||||
|
|
|
@ -211,9 +211,9 @@ class BaseDateListView(MultipleObjectMixin, DateMixin, View):
|
|||
|
||||
date_list = queryset.dates(date_field, date_type)[::-1]
|
||||
if date_list is not None and not date_list and not allow_empty:
|
||||
raise Http404(_(u"No %(verbose_name_plural)s available") % {
|
||||
'verbose_name_plural': force_unicode(qs.model._meta.verbose_name_plural)
|
||||
})
|
||||
name = force_unicode(queryset.model._meta.verbose_name_plural)
|
||||
raise Http404(_(u"No %(verbose_name_plural)s available") %
|
||||
{'verbose_name_plural': name})
|
||||
|
||||
return date_list
|
||||
|
||||
|
|
|
@ -89,6 +89,7 @@ class MultipleObjectMixin(object):
|
|||
"""
|
||||
queryset = kwargs.pop('object_list')
|
||||
page_size = self.get_paginate_by(queryset)
|
||||
context_object_name = self.get_context_object_name(queryset)
|
||||
if page_size:
|
||||
paginator, page, queryset, is_paginated = self.paginate_queryset(queryset, page_size)
|
||||
context = {
|
||||
|
@ -105,7 +106,6 @@ class MultipleObjectMixin(object):
|
|||
'object_list': queryset
|
||||
}
|
||||
context.update(kwargs)
|
||||
context_object_name = self.get_context_object_name(queryset)
|
||||
if context_object_name is not None:
|
||||
context[context_object_name] = queryset
|
||||
return context
|
||||
|
|
|
@ -8,6 +8,8 @@ from django.utils.translation import check_for_language, activate, to_locale, ge
|
|||
from django.utils.text import javascript_quote
|
||||
from django.utils.encoding import smart_unicode
|
||||
from django.utils.formats import get_format_modules, get_format
|
||||
from django.utils.http import is_safe_url
|
||||
|
||||
|
||||
def set_language(request):
|
||||
"""
|
||||
|
@ -20,11 +22,11 @@ def set_language(request):
|
|||
redirect to the page in the request (the 'next' parameter) without changing
|
||||
any state.
|
||||
"""
|
||||
next = request.REQUEST.get('next', None)
|
||||
if not next:
|
||||
next = request.META.get('HTTP_REFERER', None)
|
||||
if not next:
|
||||
next = '/'
|
||||
next = request.REQUEST.get('next')
|
||||
if not is_safe_url(url=next, host=request.get_host()):
|
||||
next = request.META.get('HTTP_REFERER')
|
||||
if not is_safe_url(url=next, host=request.get_host()):
|
||||
next = '/'
|
||||
response = http.HttpResponseRedirect(next)
|
||||
if request.method == 'POST':
|
||||
lang_code = request.POST.get('language', None)
|
||||
|
|
|
@ -127,12 +127,22 @@ class DjangoHTMLTranslator(SmartyPantsHTMLTranslator):
|
|||
|
||||
# Don't use border=1, which docutils does by default.
|
||||
def visit_table(self, node):
|
||||
self.context.append(self.compact_p)
|
||||
self.compact_p = True
|
||||
self._table_row_index = 0 # Needed by Sphinx
|
||||
self.body.append(self.starttag(node, 'table', CLASS='docutils'))
|
||||
|
||||
# <big>? Really?
|
||||
def depart_table(self, node):
|
||||
self.compact_p = self.context.pop()
|
||||
self.body.append('</table>\n')
|
||||
|
||||
def visit_desc_parameterlist(self, node):
|
||||
self.body.append('(')
|
||||
self.body.append('(') # by default sphinx puts <big> around the "("
|
||||
self.first_param = 1
|
||||
self.optional_param_level = 0
|
||||
self.param_separator = node.child_text_separator
|
||||
self.required_params_left = sum([isinstance(c, addnodes.desc_parameter)
|
||||
for c in node.children])
|
||||
|
||||
def depart_desc_parameterlist(self, node):
|
||||
self.body.append(')')
|
||||
|
|
|
@ -50,11 +50,11 @@ copyright = 'Django Software Foundation and contributors'
|
|||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '1.3'
|
||||
version = '1.3.7'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '1.3'
|
||||
release = '1.3.7'
|
||||
# The next version to be released
|
||||
django_next_version = '1.3'
|
||||
django_next_version = '1.4'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
|
|
|
@ -8,8 +8,8 @@ The login cookie isn't being set correctly, because the domain of the cookie
|
|||
sent out by Django doesn't match the domain in your browser. Try these two
|
||||
things:
|
||||
|
||||
* Set the ``SESSION_COOKIE_DOMAIN`` setting in your admin config file
|
||||
to match your domain. For example, if you're going to
|
||||
* Set the :setting:`SESSION_COOKIE_DOMAIN` setting in your admin config
|
||||
file to match your domain. For example, if you're going to
|
||||
"http://www.example.com/admin/" in your browser, in
|
||||
"myproject.settings" you should set ``SESSION_COOKIE_DOMAIN = 'www.example.com'``.
|
||||
|
||||
|
@ -17,7 +17,7 @@ things:
|
|||
don't have dots in them. If you're running the admin site on "localhost"
|
||||
or another domain that doesn't have a dot in it, try going to
|
||||
"localhost.localdomain" or "127.0.0.1". And set
|
||||
``SESSION_COOKIE_DOMAIN`` accordingly.
|
||||
:setting:`SESSION_COOKIE_DOMAIN` accordingly.
|
||||
|
||||
I can't log in. When I enter a valid username and password, it brings up the login page again, with a "Please enter a correct username and password" error.
|
||||
-----------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
|
|
@ -6,16 +6,17 @@ FAQ: Databases and models
|
|||
How can I see the raw SQL queries Django is running?
|
||||
----------------------------------------------------
|
||||
|
||||
Make sure your Django ``DEBUG`` setting is set to ``True``. Then, just do
|
||||
this::
|
||||
Make sure your Django :setting:`DEBUG` setting is set to ``True``.
|
||||
Then, just do this::
|
||||
|
||||
>>> from django.db import connection
|
||||
>>> connection.queries
|
||||
[{'sql': 'SELECT polls_polls.id,polls_polls.question,polls_polls.pub_date FROM polls_polls',
|
||||
'time': '0.002'}]
|
||||
|
||||
``connection.queries`` is only available if ``DEBUG`` is ``True``. It's a list
|
||||
of dictionaries in order of query execution. Each dictionary has the following::
|
||||
``connection.queries`` is only available if :setting:`DEBUG` is ``True``.
|
||||
It's a list of dictionaries in order of query execution. Each dictionary has
|
||||
the following::
|
||||
|
||||
``sql`` -- The raw SQL statement
|
||||
``time`` -- How long the statement took to execute, in seconds.
|
||||
|
@ -90,13 +91,13 @@ Why is Django leaking memory?
|
|||
|
||||
Django isn't known to leak memory. If you find your Django processes are
|
||||
allocating more and more memory, with no sign of releasing it, check to make
|
||||
sure your ``DEBUG`` setting is set to ``False``. If ``DEBUG`` is ``True``, then
|
||||
Django saves a copy of every SQL statement it has executed.
|
||||
sure your :setting:`DEBUG` setting is set to ``False``. If :setting:`DEBUG`
|
||||
is ``True``, then Django saves a copy of every SQL statement it has executed.
|
||||
|
||||
(The queries are saved in ``django.db.connection.queries``. See
|
||||
`How can I see the raw SQL queries Django is running?`_.)
|
||||
|
||||
To fix the problem, set ``DEBUG`` to ``False``.
|
||||
To fix the problem, set :setting:`DEBUG` to ``False``.
|
||||
|
||||
If you need to clear the query list manually at any point in your functions,
|
||||
just call ``reset_queries()``, like this::
|
||||
|
|
|
@ -71,7 +71,7 @@ The new custom command can be called using ``python manage.py closepoll
|
|||
<poll_id>``.
|
||||
|
||||
The ``handle()`` method takes zero or more ``poll_ids`` and sets ``poll.opened``
|
||||
to ``False`` for each one. If the user referenced any nonexistant polls, a
|
||||
to ``False`` for each one. If the user referenced any nonexistent polls, a
|
||||
:class:`CommandError` is raised. The ``poll.opened`` attribute does not exist
|
||||
in the :doc:`tutorial</intro/tutorial01>` and was added to
|
||||
``polls.models.Poll`` for this example.
|
||||
|
|
|
@ -627,13 +627,13 @@ resolve the string passed to it in the current context of the page.
|
|||
Shortcut for simple tags
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Many template tags take a number of arguments -- strings or a template variables
|
||||
Many template tags take a number of arguments -- strings or template variables
|
||||
-- and return a string after doing some processing based solely on
|
||||
the input argument and some external information. For example, the
|
||||
the input arguments and some external information. For example, the
|
||||
``current_time`` tag we wrote above is of this variety: we give it a format
|
||||
string, it returns the time as a string.
|
||||
|
||||
To ease the creation of the types of tags, Django provides a helper function,
|
||||
To ease the creation of these types of tags, Django provides a helper function,
|
||||
``simple_tag``. This function, which is a method of
|
||||
``django.template.Library``, takes a function that accepts any number of
|
||||
arguments, wraps it in a ``render`` function and the other necessary bits
|
||||
|
@ -829,7 +829,7 @@ Here's how you'd use this new version of the tag:
|
|||
.. admonition:: Variable scope in context
|
||||
|
||||
Any variable set in the context will only be available in the same ``block``
|
||||
of the template in which it was assigned. This behaviour is intentional;
|
||||
of the template in which it was assigned. This behavior is intentional;
|
||||
it provides a scope for variables so that they don't conflict with
|
||||
context in other blocks.
|
||||
|
||||
|
|
|
@ -246,9 +246,9 @@ Django -- for serving media. Here are some good choices:
|
|||
* A stripped-down version of Apache_
|
||||
* Cherokee_
|
||||
|
||||
If, however, you have no option but to serve media files on the same Apache
|
||||
``VirtualHost`` as Django, here's how you can turn off mod_python for a
|
||||
particular part of the site::
|
||||
If, however, you have no option but to serve media or static files on the
|
||||
same Apache ``VirtualHost`` as Django, here's how you can turn off mod_python
|
||||
for a particular part of the site::
|
||||
|
||||
<Location "/media">
|
||||
SetHandler None
|
||||
|
@ -257,9 +257,9 @@ particular part of the site::
|
|||
Just change ``Location`` to the root URL of your media files. You can also use
|
||||
``<LocationMatch>`` to match a regular expression.
|
||||
|
||||
This example sets up Django at the site root but explicitly disables Django for
|
||||
the ``media`` subdirectory and any URL that ends with ``.jpg``, ``.gif`` or
|
||||
``.png``::
|
||||
This example sets up Django at the site root but explicitly disables Django
|
||||
for the ``media`` and ``static`` subdirectories and any URL that ends with
|
||||
``.jpg``, ``.gif`` or ``.png``::
|
||||
|
||||
<Location "/">
|
||||
SetHandler python-program
|
||||
|
@ -271,11 +271,14 @@ the ``media`` subdirectory and any URL that ends with ``.jpg``, ``.gif`` or
|
|||
SetHandler None
|
||||
</Location>
|
||||
|
||||
<Location "/static">
|
||||
SetHandler None
|
||||
</Location>
|
||||
|
||||
<LocationMatch "\.(jpg|gif|png)$">
|
||||
SetHandler None
|
||||
</LocationMatch>
|
||||
|
||||
|
||||
.. _lighttpd: http://www.lighttpd.net/
|
||||
.. _Nginx: http://wiki.nginx.org/Main
|
||||
.. _TUX: http://en.wikipedia.org/wiki/TUX_web_server
|
||||
|
@ -285,22 +288,24 @@ the ``media`` subdirectory and any URL that ends with ``.jpg``, ``.gif`` or
|
|||
Serving the admin files
|
||||
=======================
|
||||
|
||||
Note that the Django development server automagically serves admin media files,
|
||||
but this is not the case when you use any other server arrangement. You're
|
||||
responsible for setting up Apache, or whichever media server you're using, to
|
||||
serve the admin files.
|
||||
Note that the Django development server automagically serves the static files
|
||||
of the admin app, but this is not the case when you use any other server
|
||||
arrangement. You're responsible for setting up Apache, or whichever media
|
||||
server you're using, to serve the admin files.
|
||||
|
||||
The admin files live in (:file:`django/contrib/admin/media`) of the Django
|
||||
distribution.
|
||||
The admin files live in (:file:`django/contrib/admin/media/admin`) of the
|
||||
Django distribution.
|
||||
|
||||
Here are two recommended approaches:
|
||||
We **strongly** recommend using :mod:`django.contrib.staticfiles` to handle the
|
||||
admin files (this means using the :djadmin:`collectstatic` management command
|
||||
to collect the static files in :setting:`STATIC_ROOT`, and then configuring
|
||||
your webserver to serve :setting:`STATIC_ROOT` at :setting:`STATIC_URL`), but
|
||||
here are two other approaches:
|
||||
|
||||
1. Create a symbolic link to the admin media files from within your
|
||||
document root. This way, all of your Django-related files -- code **and**
|
||||
templates -- stay in one place, and you'll still be able to ``svn
|
||||
update`` your code to get the latest admin templates, if they change.
|
||||
1. Create a symbolic link to the admin static files from within your
|
||||
document root.
|
||||
|
||||
2. Or, copy the admin media files so that they live within your Apache
|
||||
2. Or, copy the admin static files so that they live within your Apache
|
||||
document root.
|
||||
|
||||
Using "eggs" with mod_python
|
||||
|
|
|
@ -55,12 +55,12 @@ just below the ``import sys`` line to place your project on the path. Remember t
|
|||
replace 'mysite.settings' with your correct settings file, and '/path/to/mysite'
|
||||
with your own project's location.
|
||||
|
||||
.. _serving-media-files:
|
||||
.. _serving-files:
|
||||
|
||||
Serving media files
|
||||
===================
|
||||
Serving files
|
||||
=============
|
||||
|
||||
Django doesn't serve media files itself; it leaves that job to whichever Web
|
||||
Django doesn't serve files itself; it leaves that job to whichever Web
|
||||
server you choose.
|
||||
|
||||
We recommend using a separate Web server -- i.e., one that's not also running
|
||||
|
@ -76,22 +76,29 @@ If, however, you have no option but to serve media files on the same Apache
|
|||
``VirtualHost`` as Django, you can set up Apache to serve some URLs as
|
||||
static media, and others using the mod_wsgi interface to Django.
|
||||
|
||||
This example sets up Django at the site root, but explicitly serves ``robots.txt``,
|
||||
``favicon.ico``, any CSS file, and anything in the ``/media/`` URL space as a static
|
||||
file. All other URLs will be served using mod_wsgi::
|
||||
This example sets up Django at the site root, but explicitly serves
|
||||
``robots.txt``, ``favicon.ico``, any CSS file, and anything in the
|
||||
``/static/`` and ``/media/`` URL space as a static file. All other URLs
|
||||
will be served using mod_wsgi::
|
||||
|
||||
Alias /robots.txt /usr/local/wsgi/static/robots.txt
|
||||
Alias /favicon.ico /usr/local/wsgi/static/favicon.ico
|
||||
|
||||
AliasMatch ^/([^/]*\.css) /usr/local/wsgi/static/styles/$1
|
||||
|
||||
Alias /media/ /usr/local/wsgi/static/media/
|
||||
Alias /media/ /usr/local/wsgi/media/
|
||||
Alias /static/ /usr/local/wsgi/static/
|
||||
|
||||
<Directory /usr/local/wsgi/static>
|
||||
Order deny,allow
|
||||
Allow from all
|
||||
</Directory>
|
||||
|
||||
<Directory /usr/local/wsgi/media>
|
||||
Order deny,allow
|
||||
Allow from all
|
||||
</Directory>
|
||||
|
||||
WSGIScriptAlias / /usr/local/wsgi/scripts/django.wsgi
|
||||
|
||||
<Directory /usr/local/wsgi/scripts>
|
||||
|
@ -105,8 +112,8 @@ file. All other URLs will be served using mod_wsgi::
|
|||
.. _Apache: http://httpd.apache.org/
|
||||
.. _Cherokee: http://www.cherokee-project.com/
|
||||
|
||||
More details on configuring a mod_wsgi site to serve static files can be found
|
||||
in the mod_wsgi documentation on `hosting static files`_.
|
||||
.. More details on configuring a mod_wsgi site to serve static files can be found
|
||||
.. in the mod_wsgi documentation on `hosting static files`_.
|
||||
|
||||
.. _hosting static files: http://code.google.com/p/modwsgi/wiki/ConfigurationGuidelines#Hosting_Of_Static_Files
|
||||
|
||||
|
@ -115,22 +122,24 @@ in the mod_wsgi documentation on `hosting static files`_.
|
|||
Serving the admin files
|
||||
=======================
|
||||
|
||||
Note that the Django development server automagically serves admin media files,
|
||||
but this is not the case when you use any other server arrangement. You're
|
||||
responsible for setting up Apache, or whichever media server you're using, to
|
||||
serve the admin files.
|
||||
Note that the Django development server automagically serves the static files
|
||||
of the admin app, but this is not the case when you use any other server
|
||||
arrangement. You're responsible for setting up Apache, or whichever media
|
||||
server you're using, to serve the admin files.
|
||||
|
||||
The admin files live in (:file:`django/contrib/admin/media`) of the Django
|
||||
distribution.
|
||||
The admin files live in (:file:`django/contrib/admin/media/admin`) of the
|
||||
Django distribution.
|
||||
|
||||
Here are two recommended approaches:
|
||||
We **strongly** recommend using :mod:`django.contrib.staticfiles` to handle the
|
||||
admin files (this means using the :djadmin:`collectstatic` management command
|
||||
to collect the static files in :setting:`STATIC_ROOT`, and then configuring
|
||||
your webserver to serve :setting:`STATIC_ROOT` at :setting:`STATIC_URL`), but
|
||||
here are two other approaches:
|
||||
|
||||
1. Create a symbolic link to the admin media files from within your
|
||||
document root. This way, all of your Django-related files -- code **and**
|
||||
templates -- stay in one place, and you'll still be able to ``svn
|
||||
update`` your code to get the latest admin templates, if they change.
|
||||
1. Create a symbolic link to the admin static files from within your
|
||||
document root.
|
||||
|
||||
2. Or, copy the admin media files so that they live within your Apache
|
||||
2. Or, copy the admin static files so that they live within your Apache
|
||||
document root.
|
||||
|
||||
Details
|
||||
|
|
|
@ -55,7 +55,7 @@ message file specific to the project you are composing. The choice is yours.
|
|||
|
||||
All message file repositories are structured the same way. They are:
|
||||
|
||||
* All paths listed in ``LOCALE_PATHS`` in your settings file are
|
||||
* All paths listed in :setting:`LOCALE_PATHS` in your settings file are
|
||||
searched for ``<language>/LC_MESSAGES/django.(po|mo)``
|
||||
* ``$PROJECTPATH/locale/<language>/LC_MESSAGES/django.(po|mo)`` --
|
||||
deprecated, see above.
|
||||
|
|
|
@ -13,6 +13,8 @@ but initial SQL is also quite a bit more flexible.
|
|||
.. _initial data as sql: `providing initial sql data`_
|
||||
.. _initial data via fixtures: `providing initial data with fixtures`_
|
||||
|
||||
.. _initial-data-via-fixtures:
|
||||
|
||||
Providing initial data with fixtures
|
||||
====================================
|
||||
|
||||
|
@ -65,12 +67,12 @@ And here's that same fixture as YAML:
|
|||
|
||||
You'll store this data in a ``fixtures`` directory inside your app.
|
||||
|
||||
Loading data is easy: just call :djadmin:`manage.py loaddata fixturename
|
||||
<loaddata>`, where *fixturename* is the name of the fixture file you've created.
|
||||
Every time you run :djadmin:`loaddata` the data will be read from the fixture
|
||||
and re-loaded into the database. Note that this means that if you change one of
|
||||
the rows created by a fixture and then run :djadmin:`loaddata` again you'll
|
||||
wipe out any changes you've made.
|
||||
Loading data is easy: just call :djadmin:`manage.py loaddata <fixturename>
|
||||
<loaddata>`, where ``<fixturename>`` is the name of the fixture file you've
|
||||
created. Every time you run :djadmin:`loaddata` the data will be read from the
|
||||
fixture and re-loaded into the database. Note that this means that if you
|
||||
change one of the rows created by a fixture and then run :djadmin:`loaddata`
|
||||
again you'll wipe out any changes you've made.
|
||||
|
||||
Automatically loading initial data fixtures
|
||||
-------------------------------------------
|
||||
|
@ -80,6 +82,17 @@ be loaded every time you run :djadmin:`syncdb`. This is extremely convenient,
|
|||
but be careful: remember that the data will be refreshed *every time* you run
|
||||
:djadmin:`syncdb`. So don't use ``initial_data`` for data you'll want to edit.
|
||||
|
||||
Where Django finds fixture files
|
||||
--------------------------------
|
||||
|
||||
By default, Django looks in the ``fixtures`` directory inside each app for
|
||||
fixtures. You can set the :setting:`FIXTURE_DIRS` setting to a list of
|
||||
additional directories where Django should look.
|
||||
|
||||
When running :djadmin:`manage.py loaddata <loaddata>`, you can also
|
||||
specify an absolute path to a fixture file, which overrides searching
|
||||
the usual directories.
|
||||
|
||||
.. seealso::
|
||||
|
||||
Fixtures are also used by the :ref:`testing framework
|
||||
|
|
|
@ -65,7 +65,7 @@ At this point, Django on Jython should behave nearly identically to Django
|
|||
running on standard Python. However, are a few differences to keep in mind:
|
||||
|
||||
* Remember to use the ``jython`` command instead of ``python``. The
|
||||
documentation uses ``python`` for consistancy, but if you're using Jython
|
||||
documentation uses ``python`` for consistency, but if you're using Jython
|
||||
you'll want to mentally replace ``python`` with ``jython`` every time it
|
||||
occurs.
|
||||
|
||||
|
|
|
@ -34,81 +34,91 @@ single location that can easily be served in production.
|
|||
Using ``django.contrib.staticfiles``
|
||||
====================================
|
||||
|
||||
Here's the basic usage in a nutshell:
|
||||
Basic usage
|
||||
-----------
|
||||
|
||||
1. Put your static files somewhere that ``staticfiles`` will find them.
|
||||
1. Put your static files somewhere that ``staticfiles`` will find them.
|
||||
|
||||
By default, this means within ``static/`` subdirectories of apps in your
|
||||
:setting:`INSTALLED_APPS`.
|
||||
By default, this means within ``static/`` subdirectories of apps in your
|
||||
:setting:`INSTALLED_APPS`.
|
||||
|
||||
Many projects will also have static assets that aren't tied to a
|
||||
particular app; you can give ``staticfiles`` additional directories to
|
||||
search via the :setting:`STATICFILES_DIRS` setting .
|
||||
Your project will probably also have static assets that aren't tied to a
|
||||
particular app. The :setting:`STATICFILES_DIRS` setting is a tuple of
|
||||
filesystem directories to check when loading static files. It's a search
|
||||
path that is by default empty. See the :setting:`STATICFILES_DIRS` docs
|
||||
how to extend this list of additional paths.
|
||||
|
||||
See the documentation for the :setting:`STATICFILES_FINDERS` setting for
|
||||
details on how ``staticfiles`` finds your files.
|
||||
Additionally, see the documentation for the :setting:`STATICFILES_FINDERS`
|
||||
setting for details on how ``staticfiles`` finds your files.
|
||||
|
||||
2. Make sure that ``django.contrib.staticfiles`` is in your
|
||||
:setting:`INSTALLED_APPS`.
|
||||
2. Make sure that ``django.contrib.staticfiles`` is included in your
|
||||
:setting:`INSTALLED_APPS`.
|
||||
|
||||
For :ref:`local development<staticfiles-development>`, if you are using
|
||||
:ref:`runserver<staticfiles-runserver>` or adding
|
||||
:ref:`staticfiles_urlpatterns<staticfiles-development>` to your URLconf,
|
||||
you're done! Your static files will automatically be served at the
|
||||
default :setting:`STATIC_URL` of ``/static/``.
|
||||
For :ref:`local development<staticfiles-development>`, if you are using
|
||||
:ref:`runserver<staticfiles-runserver>` or adding
|
||||
:ref:`staticfiles_urlpatterns<staticfiles-development>` to your
|
||||
URLconf, you're done with the setup -- your static files will
|
||||
automatically be served at the default (for
|
||||
:djadmin:`newly created<startproject>` projects) :setting:`STATIC_URL`
|
||||
of ``/static/``.
|
||||
|
||||
3. You'll probably need to refer to these files in your templates. The
|
||||
easiest method is to use the included context processor which will allow
|
||||
template code like:
|
||||
3. You'll probably need to refer to these files in your templates. The
|
||||
easiest method is to use the included context processor which allows
|
||||
template code like:
|
||||
|
||||
.. code-block:: html+django
|
||||
.. code-block:: html+django
|
||||
|
||||
<img src="{{ STATIC_URL }}images/hi.jpg />
|
||||
<img src="{{ STATIC_URL }}images/hi.jpg" />
|
||||
|
||||
See :ref:`staticfiles-in-templates` for more details, including an
|
||||
alternate method (using a template tag).
|
||||
See :ref:`staticfiles-in-templates` for more details, including an
|
||||
alternate method using a template tag.
|
||||
|
||||
Deploying static files in a nutshell
|
||||
------------------------------------
|
||||
|
||||
When you're ready to move out of local development and deploy your project:
|
||||
|
||||
1. Set the :setting:`STATIC_URL` setting to the public URL for your static
|
||||
files (in some cases, the default value of ``/static/`` may still be
|
||||
fine).
|
||||
1. Set the :setting:`STATIC_URL` setting to the public URL for your static
|
||||
files (in most cases, the default value of ``/static/`` is just fine).
|
||||
|
||||
2. Set the :setting:`STATIC_ROOT` setting to point to where you'd like your
|
||||
static files collected to when you use the :djadmin:`collectstatic`
|
||||
management command. For example::
|
||||
2. Set the :setting:`STATIC_ROOT` setting to point to the filesystem path
|
||||
you'd like your static files collected to when you use the
|
||||
:djadmin:`collectstatic` management command. For example::
|
||||
|
||||
STATIC_ROOT = "/home/jacob/projects/mysite.com/sitestatic"
|
||||
STATIC_ROOT = "/home/jacob/projects/mysite.com/sitestatic"
|
||||
|
||||
3. Run the :djadmin:`collectstatic` management command::
|
||||
3. Run the :djadmin:`collectstatic` management command::
|
||||
|
||||
./manage.py collectstatic
|
||||
./manage.py collectstatic
|
||||
|
||||
This'll churn through your static file storage and copy them into the
|
||||
directory given by :setting:`STATIC_ROOT`.
|
||||
This'll churn through your static file storage and copy them into the
|
||||
directory given by :setting:`STATIC_ROOT`.
|
||||
|
||||
4. Deploy those files by configuring your webserver of choice to serve the
|
||||
files in :setting:`STATIC_ROOT` at :setting:`STATIC_URL`.
|
||||
4. Deploy those files by configuring your webserver of choice to serve the
|
||||
files in :setting:`STATIC_ROOT` at :setting:`STATIC_URL`.
|
||||
|
||||
:ref:`staticfiles-production` covers some common deployment strategies
|
||||
for static files.
|
||||
:ref:`staticfiles-production` covers some common deployment strategies
|
||||
for static files.
|
||||
|
||||
Those are the basics. For more details on common configuration options, read on;
|
||||
for a detailed reference of the settings, commands, and other bits included with
|
||||
the framework see :doc:`the staticfiles reference </ref/contrib/staticfiles>`.
|
||||
Those are the **basics**. For more details on common configuration options,
|
||||
read on; for a detailed reference of the settings, commands, and other bits
|
||||
included with the framework see
|
||||
:doc:`the staticfiles reference </ref/contrib/staticfiles>`.
|
||||
|
||||
.. note::
|
||||
|
||||
In previous versions of Django, it was common to place static assets in
|
||||
:setting:`MEDIA_ROOT` along with user-uploaded files, and serve them both at
|
||||
:setting:`MEDIA_URL`. Part of the purpose of introducing the ``staticfiles``
|
||||
app is to make it easier to keep static files separate from user-uploaded
|
||||
files. For this reason, you need to make your :setting:`MEDIA_ROOT` and
|
||||
:setting:`MEDIA_ROOT` along with user-uploaded files, and serve them both
|
||||
at :setting:`MEDIA_URL`. Part of the purpose of introducing the
|
||||
``staticfiles`` app is to make it easier to keep static files separate
|
||||
from user-uploaded files.
|
||||
|
||||
For this reason, you need to make your :setting:`MEDIA_ROOT` and
|
||||
:setting:`MEDIA_URL` different from your :setting:`STATIC_ROOT` and
|
||||
:setting:`STATIC_URL`. You will need to arrange for serving of files in
|
||||
:setting:`MEDIA_ROOT` yourself; ``staticfiles`` does not deal with
|
||||
user-uploaded files at all. You can, however, use
|
||||
:func:`~django.views.static.serve` view for serving :setting:`MEDIA_ROOT`
|
||||
:func:`django.views.static.serve` view for serving :setting:`MEDIA_ROOT`
|
||||
in development; see :ref:`staticfiles-other-directories`.
|
||||
|
||||
.. _staticfiles-in-templates:
|
||||
|
@ -133,7 +143,7 @@ A far better way is to use the value of the :setting:`STATIC_URL` setting
|
|||
directly in your templates. This means that a switch of static files servers
|
||||
only requires changing that single value. Much better!
|
||||
|
||||
``staticfiles`` inludes two built-in ways of getting at this setting in your
|
||||
``staticfiles`` includes two built-in ways of getting at this setting in your
|
||||
templates: a context processor and a template tag.
|
||||
|
||||
With a context processor
|
||||
|
@ -157,7 +167,7 @@ Once that's done, you can refer to :setting:`STATIC_URL` in your templates:
|
|||
|
||||
.. code-block:: html+django
|
||||
|
||||
<img src="{{ STATIC_URL }}images/hi.jpg />
|
||||
<img src="{{ STATIC_URL }}images/hi.jpg" />
|
||||
|
||||
If ``{{ STATIC_URL }}`` isn't working in your template, you're probably not
|
||||
using :class:`~django.template.RequestContext` when rendering the template.
|
||||
|
@ -166,7 +176,7 @@ As a brief refresher, context processors add variables into the contexts of
|
|||
every template. However, context processors require that you use
|
||||
:class:`~django.template.RequestContext` when rendering templates. This happens
|
||||
automatically if you're using a :doc:`generic view </ref/class-based-views>`,
|
||||
but in views written by hand you'll need to explicitally use ``RequestContext``
|
||||
but in views written by hand you'll need to explicitly use ``RequestContext``
|
||||
To see how that works, and to read more details, check out
|
||||
:ref:`subclassing-context-requestcontext`.
|
||||
|
||||
|
@ -282,7 +292,7 @@ parameter.
|
|||
|
||||
Since it can become a bit cumbersome to define this URL pattern, Django
|
||||
ships with a small URL helper function
|
||||
:func:`~django.conf.urls.static.static` that taks as parameters the prefix
|
||||
:func:`~django.conf.urls.static.static` that takes as parameters the prefix
|
||||
such as :setting:`MEDIA_URL` and a dotted path to a view, such as
|
||||
``'django.views.static.serve'``. Any other function parameter will be
|
||||
transparently passed to the view.
|
||||
|
@ -299,7 +309,7 @@ development::
|
|||
|
||||
.. note::
|
||||
|
||||
The helper function will only be operational in debug mode and if
|
||||
This helper function will only be operational in debug mode and if
|
||||
the given prefix is local (e.g. ``/static/``) and not a URL (e.g.
|
||||
``http://static.example.com/``).
|
||||
|
||||
|
@ -327,7 +337,7 @@ serving your site, the basic outline gets modified to look something like:
|
|||
* On the server, run :djadmin:`collectstatic` to copy all the static files
|
||||
into :setting:`STATIC_ROOT`.
|
||||
* Point your web server at :setting:`STATIC_ROOT`. For example, here's
|
||||
:ref:`how to do this under Apache and mod_wsgi <serving-media-files>`.
|
||||
:ref:`how to do this under Apache and mod_wsgi <serving-files>`.
|
||||
|
||||
You'll probably want to automate this process, especially if you've got
|
||||
multiple web servers. There's any number of ways to do this automation, but
|
||||
|
@ -435,7 +445,7 @@ For example, if you've written an S3 storage backend in
|
|||
|
||||
Once that's done, all you have to do is run :djadmin:`collectstatic` and your
|
||||
static files would be pushed through your storage package up to S3. If you
|
||||
later needed to swich to a different storage provider, it could be as simple
|
||||
later needed to switch to a different storage provider, it could be as simple
|
||||
as changing your :setting:`STATICFILES_STORAGE` setting.
|
||||
|
||||
For details on how you'd write one of these backends,
|
||||
|
@ -447,8 +457,8 @@ For details on how you'd write one of these backends,
|
|||
storage backends for many common file storage APIs (including `S3`__).
|
||||
|
||||
__ http://s3.amazonaws.com/
|
||||
__ http://code.welldev.org/django-storages/
|
||||
__ http://code.welldev.org/django-storages/wiki/S3Storage
|
||||
__ http://code.larlet.fr/django-storages/
|
||||
__ http://django-storages.readthedocs.org/en/latest/backends/amazon-S3.html
|
||||
|
||||
Upgrading from ``django-staticfiles``
|
||||
=====================================
|
||||
|
|
|
@ -28,7 +28,7 @@ Having trouble? We'd like to help!
|
|||
.. _archives of the django-users mailing list: http://groups.google.com/group/django-users/
|
||||
.. _post a question: http://groups.google.com/group/django-users/
|
||||
.. _#django IRC channel: irc://irc.freenode.net/django
|
||||
.. _IRC logs: http://botland.oebfare.com/logger/django/
|
||||
.. _IRC logs: http://django-irc-logs.com/
|
||||
.. _ticket tracker: http://code.djangoproject.com/
|
||||
|
||||
First steps
|
||||
|
|
|
@ -104,19 +104,19 @@ following actions:
|
|||
fix is forthcoming. We'll give a rough timeline and ask the reporter
|
||||
to keep the issue confidential until we announce it.
|
||||
|
||||
* Halt all other development as long as is needed to develop a fix,
|
||||
including patches against the current and two previous releases.
|
||||
* Focus on developing a fix as quickly as possible and produce patches
|
||||
against the current and two previous releases.
|
||||
|
||||
* Determine a go-public date for announcing the vulnerability and the fix.
|
||||
To try to mitigate a possible "arms race" between those applying the
|
||||
patch and those trying to exploit the hole, we will not announce
|
||||
security problems immediately.
|
||||
|
||||
* Pre-notify everyone we know to be running the affected version(s) of
|
||||
Django. We will send these notifications through private e-mail
|
||||
which will include documentation of the vulnerability, links to the
|
||||
relevant patch(es), and a request to keep the vulnerability
|
||||
confidential until the official go-public date.
|
||||
* Pre-notify third-party distributors of Django ("vendors"). We will send
|
||||
these vendor notifications through private email which will include
|
||||
documentation of the vulnerability, links to the relevant patch(es), and a
|
||||
request to keep the vulnerability confidential until the official
|
||||
go-public date.
|
||||
|
||||
* Publicly announce the vulnerability and the fix on the pre-determined
|
||||
go-public date. This will probably mean a new release of Django, but
|
||||
|
@ -454,9 +454,8 @@ infrastructure available to Django applications described in the
|
|||
|
||||
These translations are contributed by Django users worldwide. If you find an
|
||||
incorrect translation or want to discuss specific translations, go to the
|
||||
`translation team`_ page for that language. If you would like to help out
|
||||
with translating or add a language that isn't yet translated, here's what
|
||||
to do:
|
||||
`Django project page`_ on `Transifex`_. If you would like to help out with
|
||||
translating or add a language that isn't yet translated, here's what to do:
|
||||
|
||||
* Join the `Django i18n mailing list`_ and introduce yourself.
|
||||
|
||||
|
@ -464,15 +463,15 @@ to do:
|
|||
|
||||
* Signup at `Transifex`_ and visit the `Django project page`_.
|
||||
|
||||
* On the "`Translation Teams`_" page, choose the language team you want
|
||||
to work with, **or** -- in case the language team doesn't exist yet --
|
||||
request a new team by clicking on the "Request a new team" button
|
||||
and select the appropriate language.
|
||||
* On the `Django project page`_ page, choose the language you want
|
||||
to work on, **or** -- in case the language doesn't exist yet --
|
||||
request a new language team by clicking on the "Request language"
|
||||
link and select the appropriate language.
|
||||
|
||||
* Then, click the "Join this Team" button to become a member of this team.
|
||||
* Then, click the "Join Team" button to become a member of this team.
|
||||
Every team has at least one coordinator who is responsible to review
|
||||
your membership request. You can of course also contact the team
|
||||
coordinator to clarify procedual problems and handle the actual
|
||||
coordinator to clarify procedural problems and handle the actual
|
||||
translation process.
|
||||
|
||||
* Once you are a member of a team choose the translation resource you
|
||||
|
@ -499,8 +498,6 @@ to do:
|
|||
.. _Django i18n mailing list: http://groups.google.com/group/django-i18n/
|
||||
.. _Transifex: http://www.transifex.net/
|
||||
.. _Django project page: http://www.transifex.net/projects/p/django/
|
||||
.. _translation teams: http://www.transifex.net/projects/p/django/teams/
|
||||
.. _translation team: http://www.transifex.net/projects/p/django/teams/
|
||||
.. _Transifex Help: http://help.transifex.net/
|
||||
|
||||
Submitting javascript patches
|
||||
|
@ -539,7 +536,7 @@ binary, so relinking that command may be necessary as well.
|
|||
Please don't forget to run ``compress.py`` and include the ``diff`` of the
|
||||
minified scripts when submitting patches for Django's javascript.
|
||||
|
||||
.. _Closure Compiler: http://code.google.com/closure/compiler/
|
||||
.. _Closure Compiler: https://developers.google.com/closure/compiler/
|
||||
|
||||
Django conventions
|
||||
==================
|
||||
|
@ -872,7 +869,7 @@ repository:
|
|||
|
||||
For the curious: We're using a `Trac post-commit hook`_ for this.
|
||||
|
||||
.. _Trac post-commit hook: http://trac.edgewall.org/browser/trunk/contrib/trac-post-commit-hook
|
||||
.. _Trac post-commit hook: http://trac.edgewall.org/browser/trunk/contrib/trac-svn-post-commit-hook.cmd
|
||||
|
||||
* If your commit references a ticket in the Django `ticket tracker`_ but
|
||||
does *not* close the ticket, include the phrase "Refs #abc", where "abc"
|
||||
|
|
|
@ -20,7 +20,7 @@ their deprecation, as per the :ref:`Django deprecation policy
|
|||
|
||||
* 1.4
|
||||
* ``CsrfResponseMiddleware``. This has been deprecated since the 1.2
|
||||
release, in favour of the template tag method for inserting the CSRF
|
||||
release, in favor of the template tag method for inserting the CSRF
|
||||
token. ``CsrfMiddleware``, which combines ``CsrfResponseMiddleware``
|
||||
and ``CsrfViewMiddleware``, is also deprecated.
|
||||
|
||||
|
@ -108,6 +108,12 @@ their deprecation, as per the :ref:`Django deprecation policy
|
|||
beyond that of a simple ``TextField`` since the removal of oldforms.
|
||||
All uses of ``XMLField`` can be replaced with ``TextField``.
|
||||
|
||||
* ``django.db.models.fields.URLField.verify_exists`` has been
|
||||
deprecated due to intractable security and performance
|
||||
issues. Validation behavior has been removed in 1.4, and the
|
||||
argument will be removed in 1.5.
|
||||
|
||||
|
||||
* 1.5
|
||||
* The ``mod_python`` request handler has been deprecated since the 1.3
|
||||
release. The ``mod_wsgi`` handler should be used instead.
|
||||
|
@ -126,7 +132,7 @@ their deprecation, as per the :ref:`Django deprecation policy
|
|||
|
||||
* The undocumented function
|
||||
:func:`django.contrib.formtools.utils.security_hash`
|
||||
is deprecated, in favour of :func:`django.contrib.formtools.utils.form_hmac`
|
||||
is deprecated, in favor of :func:`django.contrib.formtools.utils.form_hmac`
|
||||
|
||||
* The function-based generic views have been deprecated in
|
||||
favor of their class-based cousins. The following modules
|
||||
|
@ -158,7 +164,7 @@ their deprecation, as per the :ref:`Django deprecation policy
|
|||
a :class:`~django.contrib.gis.geos.GEOSException` when called
|
||||
on a geometry with no SRID value.
|
||||
|
||||
* :class:`~django.http.CompatCookie` will be removed in favour of
|
||||
* :class:`~django.http.CompatCookie` will be removed in favor of
|
||||
:class:`~django.http.SimpleCookie`.
|
||||
|
||||
* :class:`django.core.context_processors.PermWrapper` and
|
||||
|
@ -167,9 +173,15 @@ their deprecation, as per the :ref:`Django deprecation policy
|
|||
and :class:`django.contrib.auth.context_processors.PermLookupDict`,
|
||||
respectively.
|
||||
|
||||
* The ``MEDIA_URL`` or ``STATIC_URL`` settings are required to end
|
||||
with a trailing slash to ensure there is a consistent way to
|
||||
combine paths in templates.
|
||||
* The :setting:`MEDIA_URL` or :setting:`STATIC_URL` settings are
|
||||
required to end with a trailing slash to ensure there is a consistent
|
||||
way to combine paths in templates.
|
||||
|
||||
* Translations located under the so-called *project path* will be
|
||||
ignored during the translation building process performed at runtime.
|
||||
The :setting:`LOCALE_PATHS` setting can be used for the same task by
|
||||
including the filesystem path to a ``locale`` directory containing
|
||||
non-app-specific translations in its value.
|
||||
|
||||
* 2.0
|
||||
* ``django.views.defaults.shortcut()``. This function has been moved
|
||||
|
|
|
@ -99,6 +99,13 @@ varying levels:
|
|||
* Security fixes will be applied to the current trunk and the previous two
|
||||
minor releases.
|
||||
|
||||
* Documentation fixes will generally be more freely backported to the last
|
||||
release branch (at the discretion of the committer), and don't need to meet
|
||||
the "critical fixes only" bar as it's highly advantageous to have the docs
|
||||
for the last release be up-to-date and correct, and the downside of
|
||||
backporting (risk of introducing regressions) is much less of a concern
|
||||
with doc fixes.
|
||||
|
||||
As a concrete example, consider a moment in time halfway between the release of
|
||||
Django 1.3 and 1.4. At this point in time:
|
||||
|
||||
|
@ -111,6 +118,9 @@ Django 1.3 and 1.4. At this point in time:
|
|||
``1.2.X`` branch. Security fixes will trigger the release of ``1.3.1``,
|
||||
``1.2.1``, etc.
|
||||
|
||||
* Documentation fixes will be applied to trunk, and if easily backported, to
|
||||
the ``1.3.X`` branch.
|
||||
|
||||
.. _release-process:
|
||||
|
||||
Release process
|
||||
|
|
|
@ -199,7 +199,7 @@ branch ``django/branches/releases/1.0.X`` was created to receive bug
|
|||
fixes, and shortly after the release of Django 1.1 the branch
|
||||
``django/branches/releases/1.1.X`` was created.
|
||||
|
||||
Prior to the Django 1.0 release, these branches were maintaind within
|
||||
Prior to the Django 1.0 release, these branches were maintained within
|
||||
the top-level ``django/branches`` directory, and so the following
|
||||
branches exist there and provided support for older Django releases:
|
||||
|
||||
|
|
|
@ -31,6 +31,6 @@ place: read this material to quickly get up and running.
|
|||
|
||||
.. _python: http://python.org/
|
||||
.. _list of Python resources for non-programmers: http://wiki.python.org/moin/BeginnersGuide/NonProgrammers
|
||||
.. _dive into python: http://diveintopython.org/
|
||||
.. _dive into python: http://diveintopython.net/
|
||||
.. _dead-tree version: http://www.amazon.com/exec/obidos/ASIN/1590593561/ref=nosim/jacobian20
|
||||
.. _books about Python: http://wiki.python.org/moin/PythonBooks
|
|
@ -230,7 +230,7 @@ templates. In your Django settings, you specify a list of directories to check
|
|||
for templates. If a template doesn't exist in the first directory, it checks the
|
||||
second, and so on.
|
||||
|
||||
Let's say the ``news/article_detail.html`` template was found. Here's what that
|
||||
Let's say the ``news/year_archive.html`` template was found. Here's what that
|
||||
might look like:
|
||||
|
||||
.. code-block:: html+django
|
||||
|
|
|
@ -36,8 +36,13 @@ including database configuration, Django-specific options and
|
|||
application-specific settings.
|
||||
|
||||
From the command line, ``cd`` into a directory where you'd like to store your
|
||||
code, then run the command ``django-admin.py startproject mysite``. This will
|
||||
create a ``mysite`` directory in your current directory.
|
||||
code, then run the following command:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
django-admin.py startproject mysite
|
||||
|
||||
This will create a ``mysite`` directory in your current directory.
|
||||
|
||||
.. admonition:: Script name may differ in distribution packages
|
||||
|
||||
|
@ -54,7 +59,7 @@ create a ``mysite`` directory in your current directory.
|
|||
can be run as a program. To do this, open Terminal.app and navigate (using
|
||||
the ``cd`` command) to the directory where :doc:`django-admin.py
|
||||
</ref/django-admin>` is installed, then run the command
|
||||
``chmod +x django-admin.py``.
|
||||
``sudo chmod +x django-admin.py``.
|
||||
|
||||
.. note::
|
||||
|
||||
|
@ -168,11 +173,11 @@ module-level variables representing Django settings. Change the
|
|||
following keys in the :setting:`DATABASES` ``'default'`` item to match
|
||||
your databases connection settings.
|
||||
|
||||
* :setting:`ENGINE` -- Either
|
||||
* :setting:`ENGINE <DATABASE-ENGINE>` -- Either
|
||||
``'django.db.backends.postgresql_psycopg2'``,
|
||||
``'django.db.backends.mysql'`` or
|
||||
``'django.db.backends.sqlite3'``. Other backends are
|
||||
:setting:`also available <ENGINE>`.
|
||||
:setting:`also available <DATABASE-ENGINE>`.
|
||||
|
||||
* :setting:`NAME` -- The name of your database. If you're using
|
||||
SQLite, the database will be a file on your computer; in that
|
||||
|
@ -593,7 +598,7 @@ automatically-generated admin.
|
|||
Unicode string, and ``str(p)`` will return a normal string, with characters
|
||||
encoded as UTF-8.
|
||||
|
||||
If all of this is jibberish to you, just remember to add
|
||||
If all of this is gibberish to you, just remember to add
|
||||
:meth:`~django.db.models.Model.__unicode__` methods to your models. With any
|
||||
luck, things should Just Work for you.
|
||||
|
||||
|
@ -687,10 +692,9 @@ Save these changes and start a new Python interactive shell by running
|
|||
|
||||
For more information on model relations, see :doc:`Accessing related objects
|
||||
</ref/models/relations>`. For more on how to use double underscores to perform
|
||||
field lookups via the API, see `Field lookups`__. For full details on the
|
||||
database API, see our :doc:`Database API reference </topics/db/queries>`.
|
||||
|
||||
__ http://docs.djangoproject.com/en/1.2/topics/db/queries/#field-lookups
|
||||
field lookups via the API, see :ref:`Field lookups <field-lookups-intro>`. For
|
||||
full details on the database API, see our :doc:`Database API reference
|
||||
</topics/db/queries>`.
|
||||
|
||||
When you're comfortable with the API, read :doc:`part 2 of this tutorial
|
||||
</intro/tutorial02>` to get Django's automatic admin working.
|
||||
|
|
|
@ -40,22 +40,22 @@ activate the admin site for your installation, do these three things:
|
|||
|
||||
.. parsed-literal::
|
||||
|
||||
from django.conf.urls.defaults import *
|
||||
from django.conf.urls.defaults import patterns, include, url
|
||||
|
||||
# Uncomment the next two lines to enable the admin:
|
||||
**from django.contrib import admin**
|
||||
**admin.autodiscover()**
|
||||
|
||||
urlpatterns = patterns('',
|
||||
# Example:
|
||||
# (r'^mysite/', include('mysite.foo.urls')),
|
||||
# Examples:
|
||||
# url(r'^$', 'mysite.views.home', name='home'),
|
||||
# url(r'^mysite/', include('mysite.foo.urls')),
|
||||
|
||||
# Uncomment the admin/doc line below and add 'django.contrib.admindocs'
|
||||
# to INSTALLED_APPS to enable admin documentation:
|
||||
# (r'^admin/doc/', include('django.contrib.admindocs.urls')),
|
||||
# Uncomment the admin/doc line below to enable admin documentation:
|
||||
# url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
|
||||
|
||||
# Uncomment the next line to enable the admin:
|
||||
**(r'^admin/', include(admin.site.urls)),**
|
||||
**url(r'^admin/', include(admin.site.urls)),**
|
||||
)
|
||||
|
||||
(The bold lines are the ones that needed to be uncommented.)
|
||||
|
@ -343,9 +343,11 @@ an arbitrary method is not supported. Also note that the column header for
|
|||
underscores replaced with spaces). But you can change that by giving that
|
||||
method (in ``models.py``) a ``short_description`` attribute::
|
||||
|
||||
def was_published_today(self):
|
||||
return self.pub_date.date() == datetime.date.today()
|
||||
was_published_today.short_description = 'Published today?'
|
||||
class Poll(models.Model):
|
||||
# ...
|
||||
def was_published_today(self):
|
||||
return self.pub_date.date() == datetime.date.today()
|
||||
was_published_today.short_description = 'Published today?'
|
||||
|
||||
Edit your admin.py file again and add an improvement to the Poll change list page: Filters. Add the
|
||||
following line to ``PollAdmin``::
|
||||
|
@ -405,14 +407,14 @@ By default, :setting:`TEMPLATE_DIRS` is empty. So, let's add a line to it, to
|
|||
tell Django where our templates live::
|
||||
|
||||
TEMPLATE_DIRS = (
|
||||
"/home/my_username/mytemplates", # Change this to your own directory.
|
||||
'/home/my_username/mytemplates', # Change this to your own directory.
|
||||
)
|
||||
|
||||
Now copy the template ``admin/base_site.html`` from within the default Django
|
||||
admin template directory in the source code of Django itself
|
||||
(``django/contrib/admin/templates``) into an ``admin`` subdirectory of
|
||||
whichever directory you're using in :setting:`TEMPLATE_DIRS`. For example, if
|
||||
your :setting:`TEMPLATE_DIRS` includes ``"/home/my_username/mytemplates"``, as
|
||||
your :setting:`TEMPLATE_DIRS` includes ``'/home/my_username/mytemplates'``, as
|
||||
above, then copy ``django/contrib/admin/templates/admin/base_site.html`` to
|
||||
``/home/my_username/mytemplates/admin/base_site.html``. Don't forget that
|
||||
``admin`` subdirectory.
|
||||
|
|
|
@ -78,17 +78,17 @@ point at that file::
|
|||
|
||||
Time for an example. Edit ``mysite/urls.py`` so it looks like this::
|
||||
|
||||
from django.conf.urls.defaults import *
|
||||
from django.conf.urls.defaults import patterns, include, url
|
||||
|
||||
from django.contrib import admin
|
||||
admin.autodiscover()
|
||||
|
||||
urlpatterns = patterns('',
|
||||
(r'^polls/$', 'polls.views.index'),
|
||||
(r'^polls/(?P<poll_id>\d+)/$', 'polls.views.detail'),
|
||||
(r'^polls/(?P<poll_id>\d+)/results/$', 'polls.views.results'),
|
||||
(r'^polls/(?P<poll_id>\d+)/vote/$', 'polls.views.vote'),
|
||||
(r'^admin/', include(admin.site.urls)),
|
||||
url(r'^polls/$', 'polls.views.index'),
|
||||
url(r'^polls/(?P<poll_id>\d+)/$', 'polls.views.detail'),
|
||||
url(r'^polls/(?P<poll_id>\d+)/results/$', 'polls.views.results'),
|
||||
url(r'^polls/(?P<poll_id>\d+)/vote/$', 'polls.views.vote'),
|
||||
url(r'^admin/', include(admin.site.urls)),
|
||||
)
|
||||
|
||||
This is worth a review. When somebody requests a page from your Web site -- say,
|
||||
|
@ -112,7 +112,7 @@ what you can do with them. And there's no need to add URL cruft such as ``.php``
|
|||
-- unless you have a sick sense of humor, in which case you can do something
|
||||
like this::
|
||||
|
||||
(r'^polls/latest\.php$', 'polls.views.index'),
|
||||
url(r'^polls/latest\.php$', 'polls.views.index'),
|
||||
|
||||
But, don't do that. It's silly.
|
||||
|
||||
|
@ -357,22 +357,23 @@ the list is empty.
|
|||
Write a 404 (page not found) view
|
||||
=================================
|
||||
|
||||
When you raise :exc:`~django.http.Http404` from within a view, Django will load
|
||||
a special view devoted to handling 404 errors. It finds it by looking for the
|
||||
variable ``handler404``, which is a string in Python dotted syntax -- the same
|
||||
format the normal URLconf callbacks use. A 404 view itself has nothing special:
|
||||
It's just a normal view.
|
||||
When you raise :exc:`~django.http.Http404` from within a view, Django
|
||||
will load a special view devoted to handling 404 errors. It finds it
|
||||
by looking for the variable ``handler404`` in your root URLconf (and
|
||||
only in your root URLconf; setting ``handler404`` anywhere else will
|
||||
have no effect), which is a string in Python dotted syntax -- the same
|
||||
format the normal URLconf callbacks use. A 404 view itself has nothing
|
||||
special: It's just a normal view.
|
||||
|
||||
You normally won't have to bother with writing 404 views. By default, URLconfs
|
||||
have the following line up top::
|
||||
You normally won't have to bother with writing 404 views. If you don't set
|
||||
``handler404``, the built-in view :func:`django.views.defaults.page_not_found`
|
||||
is used by default. In this case, you still have one obligation: To create a
|
||||
``404.html`` template in the root of your template directory. The default 404
|
||||
view will use that template for all 404 errors. If :setting:`DEBUG` is set to
|
||||
``False`` (in your settings module) and if you didn't create a ``404.html``
|
||||
file, an ``Http500`` is raised instead. So remember to create a ``404.html``.
|
||||
|
||||
from django.conf.urls.defaults import *
|
||||
|
||||
That takes care of setting ``handler404`` in the current module. As you can see
|
||||
in ``django/conf/urls/defaults.py``, ``handler404`` is set to
|
||||
:func:`django.views.defaults.page_not_found` by default.
|
||||
|
||||
Four more things to note about 404 views:
|
||||
A couple more things to note about 404 views:
|
||||
|
||||
* If :setting:`DEBUG` is set to ``True`` (in your settings module) then your
|
||||
404 view will never be used (and thus the ``404.html`` template will never
|
||||
|
@ -381,21 +382,12 @@ Four more things to note about 404 views:
|
|||
* The 404 view is also called if Django doesn't find a match after checking
|
||||
every regular expression in the URLconf.
|
||||
|
||||
* If you don't define your own 404 view -- and simply use the default, which
|
||||
is recommended -- you still have one obligation: To create a ``404.html``
|
||||
template in the root of your template directory. The default 404 view will
|
||||
use that template for all 404 errors.
|
||||
|
||||
* If :setting:`DEBUG` is set to ``False`` (in your settings module) and if
|
||||
you didn't create a ``404.html`` file, an ``Http500`` is raised instead.
|
||||
So remember to create a ``404.html``.
|
||||
|
||||
Write a 500 (server error) view
|
||||
===============================
|
||||
|
||||
Similarly, URLconfs may define a ``handler500``, which points to a view to call
|
||||
in case of server errors. Server errors happen when you have runtime errors in
|
||||
view code.
|
||||
Similarly, your root URLconf may define a ``handler500``, which points
|
||||
to a view to call in case of server errors. Server errors happen when
|
||||
you have runtime errors in view code.
|
||||
|
||||
Use the template system
|
||||
=======================
|
||||
|
@ -432,10 +424,10 @@ Take some time to play around with the views and template system. As you edit
|
|||
the URLconf, you may notice there's a fair bit of redundancy in it::
|
||||
|
||||
urlpatterns = patterns('',
|
||||
(r'^polls/$', 'polls.views.index'),
|
||||
(r'^polls/(?P<poll_id>\d+)/$', 'polls.views.detail'),
|
||||
(r'^polls/(?P<poll_id>\d+)/results/$', 'polls.views.results'),
|
||||
(r'^polls/(?P<poll_id>\d+)/vote/$', 'polls.views.vote'),
|
||||
url(r'^polls/$', 'polls.views.index'),
|
||||
url(r'^polls/(?P<poll_id>\d+)/$', 'polls.views.detail'),
|
||||
url(r'^polls/(?P<poll_id>\d+)/results/$', 'polls.views.results'),
|
||||
url(r'^polls/(?P<poll_id>\d+)/vote/$', 'polls.views.vote'),
|
||||
)
|
||||
|
||||
Namely, ``polls.views`` is in every callback.
|
||||
|
@ -445,10 +437,10 @@ common prefixes. You can factor out the common prefixes and add them as the
|
|||
first argument to :func:`~django.conf.urls.defaults.patterns`, like so::
|
||||
|
||||
urlpatterns = patterns('polls.views',
|
||||
(r'^polls/$', 'index'),
|
||||
(r'^polls/(?P<poll_id>\d+)/$', 'detail'),
|
||||
(r'^polls/(?P<poll_id>\d+)/results/$', 'results'),
|
||||
(r'^polls/(?P<poll_id>\d+)/vote/$', 'vote'),
|
||||
url(r'^polls/$', 'index'),
|
||||
url(r'^polls/(?P<poll_id>\d+)/$', 'detail'),
|
||||
url(r'^polls/(?P<poll_id>\d+)/results/$', 'results'),
|
||||
url(r'^polls/(?P<poll_id>\d+)/vote/$', 'vote'),
|
||||
)
|
||||
|
||||
This is functionally identical to the previous formatting. It's just a bit
|
||||
|
@ -459,20 +451,20 @@ callback in your URLconf, you can concatenate multiple
|
|||
:func:`~django.conf.urls.defaults.patterns`. Your full ``mysite/urls.py`` might
|
||||
now look like this::
|
||||
|
||||
from django.conf.urls.defaults import *
|
||||
from django.conf.urls.defaults import patterns, include, url
|
||||
|
||||
from django.contrib import admin
|
||||
admin.autodiscover()
|
||||
|
||||
urlpatterns = patterns('polls.views',
|
||||
(r'^polls/$', 'index'),
|
||||
(r'^polls/(?P<poll_id>\d+)/$', 'detail'),
|
||||
(r'^polls/(?P<poll_id>\d+)/results/$', 'results'),
|
||||
(r'^polls/(?P<poll_id>\d+)/vote/$', 'vote'),
|
||||
url(r'^polls/$', 'index'),
|
||||
url(r'^polls/(?P<poll_id>\d+)/$', 'detail'),
|
||||
url(r'^polls/(?P<poll_id>\d+)/results/$', 'results'),
|
||||
url(r'^polls/(?P<poll_id>\d+)/vote/$', 'vote'),
|
||||
)
|
||||
|
||||
urlpatterns += patterns('',
|
||||
(r'^admin/', include(admin.site.urls)),
|
||||
url(r'^admin/', include(admin.site.urls)),
|
||||
)
|
||||
|
||||
Decoupling the URLconfs
|
||||
|
@ -502,8 +494,8 @@ Copy the file ``mysite/urls.py`` to ``polls/urls.py``. Then, change
|
|||
admin.autodiscover()
|
||||
|
||||
urlpatterns = patterns('',
|
||||
(r'^polls/', include('polls.urls')),
|
||||
(r'^admin/', include(admin.site.urls)),
|
||||
url(r'^polls/', include('polls.urls')),
|
||||
url(r'^admin/', include(admin.site.urls)),
|
||||
)
|
||||
|
||||
:func:`~django.conf.urls.defaults.include` simply references another URLconf.
|
||||
|
@ -523,16 +515,16 @@ Here's what happens if a user goes to "/polls/34/" in this system:
|
|||
|
||||
Now that we've decoupled that, we need to decouple the ``polls.urls``
|
||||
URLconf by removing the leading "polls/" from each line, and removing the
|
||||
lines registering the admin site. Your ``polls.urls`` file should now look like
|
||||
lines registering the admin site. Your ``polls/urls.py`` file should now look like
|
||||
this::
|
||||
|
||||
from django.conf.urls.defaults import *
|
||||
from django.conf.urls.defaults import patterns, include, url
|
||||
|
||||
urlpatterns = patterns('polls.views',
|
||||
(r'^$', 'index'),
|
||||
(r'^(?P<poll_id>\d+)/$', 'detail'),
|
||||
(r'^(?P<poll_id>\d+)/results/$', 'results'),
|
||||
(r'^(?P<poll_id>\d+)/vote/$', 'vote'),
|
||||
url(r'^$', 'index'),
|
||||
url(r'^(?P<poll_id>\d+)/$', 'detail'),
|
||||
url(r'^(?P<poll_id>\d+)/results/$', 'results'),
|
||||
url(r'^(?P<poll_id>\d+)/vote/$', 'vote'),
|
||||
)
|
||||
|
||||
The idea behind :func:`~django.conf.urls.defaults.include` and URLconf
|
||||
|
|
|
@ -218,13 +218,13 @@ Read on for details.
|
|||
First, open the ``polls/urls.py`` URLconf. It looks like this, according to the
|
||||
tutorial so far::
|
||||
|
||||
from django.conf.urls.defaults import *
|
||||
from django.conf.urls.defaults import patterns, include, url
|
||||
|
||||
urlpatterns = patterns('polls.views',
|
||||
(r'^$', 'index'),
|
||||
(r'^(?P<poll_id>\d+)/$', 'detail'),
|
||||
(r'^(?P<poll_id>\d+)/results/$', 'results'),
|
||||
(r'^(?P<poll_id>\d+)/vote/$', 'vote'),
|
||||
url(r'^$', 'index'),
|
||||
url(r'^(?P<poll_id>\d+)/$', 'detail'),
|
||||
url(r'^(?P<poll_id>\d+)/results/$', 'results'),
|
||||
url(r'^(?P<poll_id>\d+)/vote/$', 'vote'),
|
||||
)
|
||||
|
||||
Change it like so::
|
||||
|
@ -234,12 +234,12 @@ Change it like so::
|
|||
from polls.models import Poll
|
||||
|
||||
urlpatterns = patterns('',
|
||||
(r'^$',
|
||||
url(r'^$',
|
||||
ListView.as_view(
|
||||
queryset=Poll.objects.order_by('-pub_date')[:5],
|
||||
context_object_name='latest_poll_list',
|
||||
template_name='polls/index.html')),
|
||||
(r'^(?P<pk>\d+)/$',
|
||||
url(r'^(?P<pk>\d+)/$',
|
||||
DetailView.as_view(
|
||||
model=Poll,
|
||||
template_name='polls/detail.html')),
|
||||
|
@ -248,7 +248,7 @@ Change it like so::
|
|||
model=Poll,
|
||||
template_name='polls/results.html'),
|
||||
name='poll_results'),
|
||||
(r'^(?P<poll_id>\d+)/vote/$', 'polls.views.vote'),
|
||||
url(r'^(?P<poll_id>\d+)/vote/$', 'polls.views.vote'),
|
||||
)
|
||||
|
||||
We're using two generic views here:
|
||||
|
|
|
@ -81,20 +81,20 @@ TemplateResponseMixin
|
|||
The response class to be returned by ``render_to_response`` method.
|
||||
Default is
|
||||
:class:`TemplateResponse <django.template.response.TemplateResponse>`.
|
||||
The template and context of TemplateResponse instances can be
|
||||
The template and context of ``TemplateResponse`` instances can be
|
||||
altered later (e.g. in
|
||||
:ref:`template response middleware <template-response-middleware>`).
|
||||
|
||||
Create TemplateResponse subclass and pass set it to
|
||||
``template_response_class`` if you need custom template loading or
|
||||
custom context object instantiation.
|
||||
If you need custom template loading or custom context object
|
||||
instantiation, create a ``TemplateResponse`` subclass and assign it to
|
||||
``response_class``.
|
||||
|
||||
.. method:: render_to_response(context, **response_kwargs)
|
||||
|
||||
Returns a ``self.template_response_class`` instance.
|
||||
Returns a ``self.response_class`` instance.
|
||||
|
||||
If any keyword arguments are provided, they will be
|
||||
passed to the constructor of the response instance.
|
||||
passed to the constructor of the response class.
|
||||
|
||||
Calls :meth:`~TemplateResponseMixin.get_template_names()` to obtain the
|
||||
list of template names that will be searched looking for an existent
|
||||
|
@ -431,7 +431,7 @@ FormMixin
|
|||
|
||||
.. method:: get_form_kwargs()
|
||||
|
||||
Build the keyword arguments requried to instanciate an the form.
|
||||
Build the keyword arguments required to instantiate the form.
|
||||
|
||||
The ``initial`` argument is set to :meth:`.get_initial`. If the
|
||||
request is a ``POST`` or ``PUT``, the request data (``request.POST``
|
||||
|
@ -781,11 +781,11 @@ BaseDateListView
|
|||
|
||||
.. method:: get_dated_items():
|
||||
|
||||
Returns a 3-tuple containing (``date_list``, ``latest``,
|
||||
Returns a 3-tuple containing (``date_list``, ``object_list``,
|
||||
``extra_context``).
|
||||
|
||||
``date_list`` is the list of dates for which data is available.
|
||||
``object_list`` is the list of objects ``extra_context`` is a
|
||||
``object_list`` is the list of objects. ``extra_context`` is a
|
||||
dictionary of context data that will be added to any context data
|
||||
provided by the
|
||||
:class:`~django.views.generic.list.MultipleObjectMixin`.
|
||||
|
@ -1062,8 +1062,8 @@ DeleteView
|
|||
|
||||
**Mixins**
|
||||
|
||||
* :class:`django.views.generic.edit.ModelFormMixin`
|
||||
* :class:`django.views.generic.edit.ProcessFormView`
|
||||
* :class:`django.views.generic.edit.DeletionMixin`
|
||||
* :class:`django.views.generic.detail.BaseDetailView`
|
||||
|
||||
**Notes**
|
||||
|
||||
|
|
|
@ -19,8 +19,10 @@ There are six steps in activating the Django admin site:
|
|||
1. Add ``'django.contrib.admin'`` to your :setting:`INSTALLED_APPS`
|
||||
setting.
|
||||
|
||||
2. Admin has two dependencies - :mod:`django.contrib.auth` and
|
||||
:mod:`django.contrib.contenttypes`. If these applications are not
|
||||
2. The admin has four dependencies - :mod:`django.contrib.auth`,
|
||||
:mod:`django.contrib.contenttypes`,
|
||||
:mod:`django.contrib.messages` and
|
||||
:mod:`django.contrib.sessions`. If these applications are not
|
||||
in your :setting:`INSTALLED_APPS` list, add them.
|
||||
|
||||
3. Determine which of your application's models should be editable in the
|
||||
|
@ -46,8 +48,8 @@ Other topics
|
|||
|
||||
.. seealso::
|
||||
|
||||
For information about serving the media files (images, JavaScript, and CSS)
|
||||
associated with the admin in production, see :ref:`serving-media-files`.
|
||||
For information about serving the static files (images, JavaScript, and
|
||||
CSS) associated with the admin in production, see :ref:`serving-files`.
|
||||
|
||||
``ModelAdmin`` objects
|
||||
======================
|
||||
|
@ -531,17 +533,19 @@ subclass::
|
|||
list_display = ('username', 'email', 'first_name', 'last_name', 'is_staff')
|
||||
list_filter = ('is_staff', 'is_superuser')
|
||||
|
||||
Fields in ``list_filter`` can also span relations using the ``__`` lookup::
|
||||
|
||||
class UserAdminWithLookup(UserAdmin):
|
||||
list_filter = ('groups__name')
|
||||
|
||||
The above code results in an admin change list page that looks like this:
|
||||
|
||||
.. image:: _images/users_changelist.png
|
||||
|
||||
(This example also has ``search_fields`` defined. See below.)
|
||||
|
||||
.. versionadded:: 1.3
|
||||
|
||||
Fields in ``list_filter`` can also span relations using the ``__`` lookup::
|
||||
|
||||
class UserAdminWithLookup(UserAdmin):
|
||||
list_filter = ('groups__name',)
|
||||
|
||||
.. attribute:: ModelAdmin.list_per_page
|
||||
|
||||
Set ``list_per_page`` to control how many items appear on each paginated
|
||||
|
@ -1041,11 +1045,10 @@ provided some extra mapping data that would not otherwise be available::
|
|||
pass
|
||||
|
||||
def change_view(self, request, object_id, extra_context=None):
|
||||
my_context = {
|
||||
'osm_data': self.get_osm_info(),
|
||||
}
|
||||
extra_context = extra_context or {}
|
||||
extra_context['osm_data'] = self.get_osm_info()
|
||||
return super(MyModelAdmin, self).change_view(request, object_id,
|
||||
extra_context=my_context)
|
||||
extra_context=extra_context)
|
||||
|
||||
``ModelAdmin`` media definitions
|
||||
--------------------------------
|
||||
|
@ -1622,7 +1625,7 @@ In this example, we register the default ``AdminSite`` instance
|
|||
)
|
||||
|
||||
Above we used ``admin.autodiscover()`` to automatically load the
|
||||
``INSTALLED_APPS`` admin.py modules.
|
||||
:setting:`INSTALLED_APPS` admin.py modules.
|
||||
|
||||
In this example, we register the ``AdminSite`` instance
|
||||
``myproject.admin.admin_site`` at the URL ``/myadmin/`` ::
|
||||
|
|
|
@ -103,13 +103,16 @@ But let's look at a simple example::
|
|||
<!-- A context variable called form is created with the necessary hidden
|
||||
fields, timestamps and security hashes -->
|
||||
<table>
|
||||
<form action="{% comment_form_target %}" method="post">
|
||||
{{ form }}
|
||||
<tr>
|
||||
<td></td>
|
||||
<td><input type="submit" name="preview" class="submit-post" value="Preview"></td>
|
||||
</tr>
|
||||
</form>
|
||||
<form action="{% comment_form_target %}" method="post">
|
||||
{% csrf_token %}
|
||||
{{ form }}
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<input type="submit" name="submit" value="Post">
|
||||
<input type="submit" name="preview" value="Preview">
|
||||
</td>
|
||||
</tr>
|
||||
</form>
|
||||
</table>
|
||||
|
||||
Flagging
|
||||
|
|
|
@ -218,13 +218,18 @@ you can use in the template::
|
|||
A complete form might look like::
|
||||
|
||||
{% get_comment_form for event as form %}
|
||||
<form action="{% comment_form_target %}" method="post">
|
||||
{{ form }}
|
||||
<tr>
|
||||
<td></td>
|
||||
<td><input type="submit" name="preview" class="submit-post" value="Preview"></td>
|
||||
</tr>
|
||||
</form>
|
||||
<table>
|
||||
<form action="{% comment_form_target %}" method="post">
|
||||
{% csrf_token %}
|
||||
{{ form }}
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<input type="submit" name="submit" value="Post">
|
||||
<input type="submit" name="preview" value="Preview">
|
||||
</td>
|
||||
</tr>
|
||||
</form>
|
||||
</table>
|
||||
|
||||
Be sure to read the `notes on the comment form`_, below, for some special
|
||||
considerations you'll need to make if you're using this approach.
|
||||
|
|
|
@ -192,6 +192,15 @@ The ``ContentTypeManager``
|
|||
:class:`~django.contrib.contenttypes.models.ContentType` instance
|
||||
representing that model.
|
||||
|
||||
.. method:: get_by_natural_key(app_label, model)
|
||||
|
||||
Returns the :class:`~django.contrib.contenttypes.models.ContentType`
|
||||
instance uniquely identified by the given application label and model
|
||||
name. The primary purpose of this method is to allow
|
||||
:class:`~django.contrib.contenttypes.models.ContentType` objects to be
|
||||
referenced via a :ref:`natural key<topics-serialization-natural-keys>`
|
||||
during deserialization.
|
||||
|
||||
The :meth:`~ContentTypeManager.get_for_model()` method is especially
|
||||
useful when you know you need to work with a
|
||||
:class:`ContentType <django.contrib.contenttypes.models.ContentType>` but don't
|
||||
|
@ -285,6 +294,15 @@ model:
|
|||
should evaluate the models you expect to be pointing to and determine
|
||||
which solution will be most effective for your use case.
|
||||
|
||||
.. admonition:: Serializing references to ``ContentType`` objects
|
||||
|
||||
If you're serializing data (for example, when generating
|
||||
:class:`~django.test.TestCase.fixtures`) from a model that implements
|
||||
generic relations, you should probably be using a natural key to uniquely
|
||||
identify related :class:`~django.contrib.contenttypes.models.ContentType`
|
||||
objects. See :ref:`natural keys<topics-serialization-natural-keys>` and
|
||||
:djadminopt:`dumpdata --natural <--natural>` for more information.
|
||||
|
||||
This will enable an API similar to the one used for a normal
|
||||
:class:`~django.db.models.ForeignKey`;
|
||||
each ``TaggedItem`` will have a ``content_object`` field that returns the
|
||||
|
@ -406,7 +424,7 @@ Generic relations in forms and admin
|
|||
------------------------------------
|
||||
|
||||
The :mod:`django.contrib.contenttypes.generic` module provides
|
||||
:class:`~django.contrib.contenttypes.generic.GenericInlineFormSet`,
|
||||
:class:`~django.contrib.contenttypes.generic.BaseGenericInlineFormSet`,
|
||||
:class:`~django.contrib.contenttypes.generic.GenericTabularInline`
|
||||
and :class:`~django.contrib.contenttypes.generic.GenericStackedInline`
|
||||
(the last two are subclasses of
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue