mirror of
https://github.com/django/django.git
synced 2025-07-04 09:49:12 +00:00
[multi-db] Merge trunk to [3875]. Some tests still failing.
git-svn-id: http://code.djangoproject.com/svn/django/branches/multiple-db-support@4151 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
f8217026f9
commit
261fb45ba8
5
AUTHORS
5
AUTHORS
@ -64,6 +64,7 @@ answer newbie questions, and generally made Django that much better:
|
|||||||
Ian Clelland <clelland@gmail.com>
|
Ian Clelland <clelland@gmail.com>
|
||||||
crankycoder@gmail.com
|
crankycoder@gmail.com
|
||||||
Matt Croydon <http://www.postneo.com/>
|
Matt Croydon <http://www.postneo.com/>
|
||||||
|
dackze+django@gmail.com
|
||||||
Jonathan Daugherty (cygnus) <http://www.cprogrammer.org/>
|
Jonathan Daugherty (cygnus) <http://www.cprogrammer.org/>
|
||||||
Jason Davies (Esaj) <http://www.jasondavies.com/>
|
Jason Davies (Esaj) <http://www.jasondavies.com/>
|
||||||
Alex Dedul
|
Alex Dedul
|
||||||
@ -104,7 +105,9 @@ answer newbie questions, and generally made Django that much better:
|
|||||||
Eugene Lazutkin <http://lazutkin.com/blog/>
|
Eugene Lazutkin <http://lazutkin.com/blog/>
|
||||||
Jeong-Min Lee
|
Jeong-Min Lee
|
||||||
Christopher Lenz <http://www.cmlenz.net/>
|
Christopher Lenz <http://www.cmlenz.net/>
|
||||||
|
lerouxb@gmail.com
|
||||||
limodou
|
limodou
|
||||||
|
mattmcc
|
||||||
Martin Maney <http://www.chipy.org/Martin_Maney>
|
Martin Maney <http://www.chipy.org/Martin_Maney>
|
||||||
Manuzhai
|
Manuzhai
|
||||||
Petar Marić
|
Petar Marić
|
||||||
@ -116,6 +119,7 @@ answer newbie questions, and generally made Django that much better:
|
|||||||
Eric Moritz <http://eric.themoritzfamily.com/>
|
Eric Moritz <http://eric.themoritzfamily.com/>
|
||||||
Robin Munn <http://www.geekforgod.com/>
|
Robin Munn <http://www.geekforgod.com/>
|
||||||
Nebojša Dorđević
|
Nebojša Dorđević
|
||||||
|
Fraser Nevett <mail@nevett.org>
|
||||||
Sam Newman <http://www.magpiebrain.com/>
|
Sam Newman <http://www.magpiebrain.com/>
|
||||||
Neal Norwitz <nnorwitz@google.com>
|
Neal Norwitz <nnorwitz@google.com>
|
||||||
oggie rob <oz.robharvey@gmail.com>
|
oggie rob <oz.robharvey@gmail.com>
|
||||||
@ -143,6 +147,7 @@ answer newbie questions, and generally made Django that much better:
|
|||||||
Radek Švarz <http://www.svarz.cz/translate/>
|
Radek Švarz <http://www.svarz.cz/translate/>
|
||||||
Swaroop C H <http://www.swaroopch.info>
|
Swaroop C H <http://www.swaroopch.info>
|
||||||
Aaron Swartz <http://www.aaronsw.com/>
|
Aaron Swartz <http://www.aaronsw.com/>
|
||||||
|
Tyson Tate <tyson@fallingbullets.com>
|
||||||
Tom Tobin
|
Tom Tobin
|
||||||
Tom Insam
|
Tom Insam
|
||||||
Joe Topjian <http://joe.terrarum.net/geek/code/python/django/>
|
Joe Topjian <http://joe.terrarum.net/geek/code/python/django/>
|
||||||
|
@ -1,16 +1,17 @@
|
|||||||
"Daily cleanup file"
|
"""
|
||||||
|
Daily cleanup job.
|
||||||
|
|
||||||
|
Can be run as a cronjob to clean out old data from the database (only expired
|
||||||
|
sessions at the moment).
|
||||||
|
"""
|
||||||
|
|
||||||
from django.db import backend, connection, transaction
|
from django.db import backend, connection, transaction
|
||||||
|
|
||||||
DOCUMENTATION_DIRECTORY = '/home/html/documentation/'
|
|
||||||
|
|
||||||
def clean_up():
|
def clean_up():
|
||||||
# Clean up old database records
|
# Clean up old database records
|
||||||
cursor = connection.cursor()
|
cursor = connection.cursor()
|
||||||
cursor.execute("DELETE FROM %s WHERE %s < NOW()" % \
|
cursor.execute("DELETE FROM %s WHERE %s < NOW()" % \
|
||||||
(backend.quote_name('core_sessions'), backend.quote_name('expire_date')))
|
(backend.quote_name('django_session'), backend.quote_name('expire_date')))
|
||||||
cursor.execute("DELETE FROM %s WHERE %s < NOW() - INTERVAL '1 week'" % \
|
|
||||||
(backend.quote_name('registration_challenges'), backend.quote_name('request_date')))
|
|
||||||
transaction.commit_unless_managed()
|
transaction.commit_unless_managed()
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
<h1>{{ name }}</h1>
|
<h1>{{ name }}</h1>
|
||||||
|
|
||||||
<h2 class="subhead">{{ summary|escape }}</h2>
|
<h2 class="subhead">{{ summary }}</h2>
|
||||||
|
|
||||||
<p>{{ body }}</p>
|
<p>{{ body }}</p>
|
||||||
|
|
||||||
|
@ -46,6 +46,7 @@ def createsuperuser(username=None, email=None, password=None):
|
|||||||
if not username.isalnum():
|
if not username.isalnum():
|
||||||
sys.stderr.write("Error: That username is invalid. Use only letters, digits and underscores.\n")
|
sys.stderr.write("Error: That username is invalid. Use only letters, digits and underscores.\n")
|
||||||
username = None
|
username = None
|
||||||
|
continue
|
||||||
try:
|
try:
|
||||||
User.objects.get(username=username)
|
User.objects.get(username=username)
|
||||||
except User.DoesNotExist:
|
except User.DoesNotExist:
|
||||||
|
@ -4,7 +4,6 @@ from django.contrib.sites.models import Site
|
|||||||
from django.template import Context, loader
|
from django.template import Context, loader
|
||||||
from django.core import validators
|
from django.core import validators
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
|
|
||||||
class UserCreationForm(forms.Manipulator):
|
class UserCreationForm(forms.Manipulator):
|
||||||
"A form that creates a user, with no privileges, from the given username and password."
|
"A form that creates a user, with no privileges, from the given username and password."
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<urlset xmlns="http://www.google.com/schemas/sitemap/0.84">
|
<urlset xmlns="http://www.google.com/schemas/sitemap/0.84">
|
||||||
|
{% spaceless %}
|
||||||
{% for url in urlset %}
|
{% for url in urlset %}
|
||||||
<url>
|
<url>
|
||||||
<loc>{{ url.location|escape }}</loc>
|
<loc>{{ url.location|escape }}</loc>
|
||||||
@ -8,4 +9,5 @@
|
|||||||
{% if url.priority %}<priority>{{ url.priority }}</priority>{% endif %}
|
{% if url.priority %}<priority>{{ url.priority }}</priority>{% endif %}
|
||||||
</url>
|
</url>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
{% endspaceless %}
|
||||||
</urlset>
|
</urlset>
|
||||||
|
@ -1,8 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<sitemapindex xmlns="http://www.google.com/schemas/sitemap/0.84">
|
<sitemapindex xmlns="http://www.google.com/schemas/sitemap/0.84">
|
||||||
{% for location in sitemaps %}
|
{% for location in sitemaps %}<sitemap><loc>{{ location|escape }}</loc></sitemap>{% endfor %}
|
||||||
<sitemap>
|
|
||||||
<loc>{{ location|escape }}</loc>
|
|
||||||
</sitemap>
|
|
||||||
{% endfor %}
|
|
||||||
</sitemapindex>
|
</sitemapindex>
|
||||||
|
@ -48,7 +48,7 @@ class BaseHandler(object):
|
|||||||
if hasattr(mw_instance, 'process_exception'):
|
if hasattr(mw_instance, 'process_exception'):
|
||||||
self._exception_middleware.insert(0, mw_instance.process_exception)
|
self._exception_middleware.insert(0, mw_instance.process_exception)
|
||||||
|
|
||||||
def get_response(self, path, request):
|
def get_response(self, request):
|
||||||
"Returns an HttpResponse object for the given HttpRequest"
|
"Returns an HttpResponse object for the given HttpRequest"
|
||||||
from django.core import exceptions, urlresolvers
|
from django.core import exceptions, urlresolvers
|
||||||
from django.core.mail import mail_admins
|
from django.core.mail import mail_admins
|
||||||
@ -62,7 +62,7 @@ class BaseHandler(object):
|
|||||||
|
|
||||||
resolver = urlresolvers.RegexURLResolver(r'^/', settings.ROOT_URLCONF)
|
resolver = urlresolvers.RegexURLResolver(r'^/', settings.ROOT_URLCONF)
|
||||||
try:
|
try:
|
||||||
callback, callback_args, callback_kwargs = resolver.resolve(path)
|
callback, callback_args, callback_kwargs = resolver.resolve(request.path)
|
||||||
|
|
||||||
# Apply view middleware
|
# Apply view middleware
|
||||||
for middleware_method in self._view_middleware:
|
for middleware_method in self._view_middleware:
|
||||||
@ -105,7 +105,7 @@ class BaseHandler(object):
|
|||||||
exc_info = sys.exc_info()
|
exc_info = sys.exc_info()
|
||||||
receivers = dispatcher.send(signal=signals.got_request_exception)
|
receivers = dispatcher.send(signal=signals.got_request_exception)
|
||||||
# When DEBUG is False, send an error message to the admins.
|
# When DEBUG is False, send an error message to the admins.
|
||||||
subject = 'Error (%s IP): %s' % ((request.META.get('REMOTE_ADDR') in settings.INTERNAL_IPS and 'internal' or 'EXTERNAL'), getattr(request, 'path', ''))
|
subject = 'Error (%s IP): %s' % ((request.META.get('REMOTE_ADDR') in settings.INTERNAL_IPS and 'internal' or 'EXTERNAL'), request.path)
|
||||||
try:
|
try:
|
||||||
request_repr = repr(request)
|
request_repr = repr(request)
|
||||||
except:
|
except:
|
||||||
|
@ -102,7 +102,7 @@ class ModPythonRequest(http.HttpRequest):
|
|||||||
'REQUEST_METHOD': self._req.method,
|
'REQUEST_METHOD': self._req.method,
|
||||||
'SCRIPT_NAME': None, # Not supported
|
'SCRIPT_NAME': None, # Not supported
|
||||||
'SERVER_NAME': self._req.server.server_hostname,
|
'SERVER_NAME': self._req.server.server_hostname,
|
||||||
'SERVER_PORT': self._req.server.port,
|
'SERVER_PORT': str(self._req.connection.local_addr[1]),
|
||||||
'SERVER_PROTOCOL': self._req.protocol,
|
'SERVER_PROTOCOL': self._req.protocol,
|
||||||
'SERVER_SOFTWARE': 'mod_python'
|
'SERVER_SOFTWARE': 'mod_python'
|
||||||
}
|
}
|
||||||
@ -150,7 +150,7 @@ class ModPythonHandler(BaseHandler):
|
|||||||
dispatcher.send(signal=signals.request_started)
|
dispatcher.send(signal=signals.request_started)
|
||||||
try:
|
try:
|
||||||
request = ModPythonRequest(req)
|
request = ModPythonRequest(req)
|
||||||
response = self.get_response(req.uri, request)
|
response = self.get_response(request)
|
||||||
|
|
||||||
# Apply response middleware
|
# Apply response middleware
|
||||||
for middleware_method in self._response_middleware:
|
for middleware_method in self._response_middleware:
|
||||||
|
@ -74,7 +74,7 @@ class WSGIRequest(http.HttpRequest):
|
|||||||
def __init__(self, environ):
|
def __init__(self, environ):
|
||||||
self.environ = environ
|
self.environ = environ
|
||||||
self.path = environ['PATH_INFO']
|
self.path = environ['PATH_INFO']
|
||||||
self.META = environ
|
self.META = environ
|
||||||
self.method = environ['REQUEST_METHOD'].upper()
|
self.method = environ['REQUEST_METHOD'].upper()
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
@ -186,7 +186,7 @@ class WSGIHandler(BaseHandler):
|
|||||||
dispatcher.send(signal=signals.request_started)
|
dispatcher.send(signal=signals.request_started)
|
||||||
try:
|
try:
|
||||||
request = WSGIRequest(environ)
|
request = WSGIRequest(environ)
|
||||||
response = self.get_response(request.path, request)
|
response = self.get_response(request)
|
||||||
|
|
||||||
# Apply response middleware
|
# Apply response middleware
|
||||||
for middleware_method in self._response_middleware:
|
for middleware_method in self._response_middleware:
|
||||||
|
@ -669,7 +669,8 @@ def get_validation_errors(outfile, app=None):
|
|||||||
validates all models of all installed apps. Writes errors, if any, to outfile.
|
validates all models of all installed apps. Writes errors, if any, to outfile.
|
||||||
Returns number of errors.
|
Returns number of errors.
|
||||||
"""
|
"""
|
||||||
from django.db import models, model_connection_name
|
from django.conf import settings
|
||||||
|
from django.db import connections, models, model_connection_name
|
||||||
from django.db.models.loading import get_app_errors
|
from django.db.models.loading import get_app_errors
|
||||||
from django.db.models.fields.related import RelatedObject
|
from django.db.models.fields.related import RelatedObject
|
||||||
|
|
||||||
@ -681,6 +682,7 @@ def get_validation_errors(outfile, app=None):
|
|||||||
for cls in models.get_models(app):
|
for cls in models.get_models(app):
|
||||||
opts = cls._meta
|
opts = cls._meta
|
||||||
connection_name = model_connection_name(cls)
|
connection_name = model_connection_name(cls)
|
||||||
|
connection = connections[connection_name]
|
||||||
|
|
||||||
# Do field-specific validation.
|
# Do field-specific validation.
|
||||||
for f in opts.fields:
|
for f in opts.fields:
|
||||||
@ -712,6 +714,12 @@ def get_validation_errors(outfile, app=None):
|
|||||||
if f.db_index not in (None, True, False):
|
if f.db_index not in (None, True, False):
|
||||||
e.add(opts, '"%s": "db_index" should be either None, True or False.' % f.name)
|
e.add(opts, '"%s": "db_index" should be either None, True or False.' % f.name)
|
||||||
|
|
||||||
|
# Check that maxlength <= 255 if using older MySQL versions.
|
||||||
|
if settings.DATABASE_ENGINE == 'mysql':
|
||||||
|
db_version = connection.connection.get_server_version()
|
||||||
|
if db_version < (5, 0, 3) and isinstance(f, (models.CharField, models.CommaSeparatedIntegerField, models.SlugField)) and f.maxlength > 255:
|
||||||
|
e.add(opts, '"%s": %s cannot have a "maxlength" greater than 255 when you are using a version of MySQL prior to 5.0.3 (you are using %s).' % (f.name, f.__class__.__name__, '.'.join([str(n) for n in db_version[:3]])))
|
||||||
|
|
||||||
# Check to see if the related field will clash with any
|
# Check to see if the related field will clash with any
|
||||||
# existing fields, m2m fields, m2m related objects or related objects
|
# existing fields, m2m fields, m2m related objects or related objects
|
||||||
if f.rel:
|
if f.rel:
|
||||||
|
@ -13,6 +13,7 @@ except ImportError, e:
|
|||||||
from MySQLdb.converters import conversions
|
from MySQLdb.converters import conversions
|
||||||
from MySQLdb.constants import FIELD_TYPE
|
from MySQLdb.constants import FIELD_TYPE
|
||||||
import types
|
import types
|
||||||
|
import re
|
||||||
|
|
||||||
DatabaseError = Database.DatabaseError
|
DatabaseError = Database.DatabaseError
|
||||||
|
|
||||||
@ -24,6 +25,12 @@ django_conversions.update({
|
|||||||
FIELD_TYPE.TIME: util.typecast_time,
|
FIELD_TYPE.TIME: util.typecast_time,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
# This should match the numerical portion of the version numbers (we can treat
|
||||||
|
# versions like 5.0.24 and 5.0.24a as the same). Based on the list of version
|
||||||
|
# at http://dev.mysql.com/doc/refman/4.1/en/news.html and
|
||||||
|
# http://dev.mysql.com/doc/refman/5.0/en/news.html .
|
||||||
|
server_version_re = re.compile(r'(\d{1,2})\.(\d{1,2})\.(\d{1,2})')
|
||||||
|
|
||||||
# This is an extra debug layer over MySQL queries, to display warnings.
|
# This is an extra debug layer over MySQL queries, to display warnings.
|
||||||
# It's only used when DEBUG=True.
|
# It's only used when DEBUG=True.
|
||||||
class MysqlDebugWrapper:
|
class MysqlDebugWrapper:
|
||||||
@ -56,6 +63,7 @@ class DatabaseWrapper(object):
|
|||||||
self.settings = settings
|
self.settings = settings
|
||||||
self.connection = None
|
self.connection = None
|
||||||
self.queries = []
|
self.queries = []
|
||||||
|
self.server_version = None
|
||||||
|
|
||||||
def _valid_connection(self):
|
def _valid_connection(self):
|
||||||
if self.connection is not None:
|
if self.connection is not None:
|
||||||
@ -106,6 +114,16 @@ class DatabaseWrapper(object):
|
|||||||
self.connection.close()
|
self.connection.close()
|
||||||
self.connection = None
|
self.connection = None
|
||||||
|
|
||||||
|
def get_server_version(self):
|
||||||
|
if not self.server_version:
|
||||||
|
if not self._valid_connection():
|
||||||
|
self.cursor()
|
||||||
|
m = server_version_re.match(self.connection.get_server_info())
|
||||||
|
if not m:
|
||||||
|
raise Exception('Unable to determine MySQL version from version string %r' % self.connection.get_server_info())
|
||||||
|
self.server_version = tuple([int(x) for x in m.groups()])
|
||||||
|
return self.server_version
|
||||||
|
|
||||||
supports_constraints = True
|
supports_constraints = True
|
||||||
supports_compound_statements = True
|
supports_compound_statements = True
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ from django.core import validators
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from django.utils.functional import curry
|
from django.utils.functional import curry
|
||||||
|
from django.utils.itercompat import tee
|
||||||
from django.utils.text import capfirst
|
from django.utils.text import capfirst
|
||||||
from django.utils.translation import gettext, gettext_lazy
|
from django.utils.translation import gettext, gettext_lazy
|
||||||
import datetime, os, time
|
import datetime, os, time
|
||||||
@ -80,7 +81,7 @@ class Field(object):
|
|||||||
self.prepopulate_from = prepopulate_from
|
self.prepopulate_from = prepopulate_from
|
||||||
self.unique_for_date, self.unique_for_month = unique_for_date, unique_for_month
|
self.unique_for_date, self.unique_for_month = unique_for_date, unique_for_month
|
||||||
self.unique_for_year = unique_for_year
|
self.unique_for_year = unique_for_year
|
||||||
self.choices = choices or []
|
self._choices = choices or []
|
||||||
self.radio_admin = radio_admin
|
self.radio_admin = radio_admin
|
||||||
self.help_text = help_text
|
self.help_text = help_text
|
||||||
self.db_column = db_column
|
self.db_column = db_column
|
||||||
@ -324,6 +325,14 @@ class Field(object):
|
|||||||
def bind(self, fieldmapping, original, bound_field_class):
|
def bind(self, fieldmapping, original, bound_field_class):
|
||||||
return bound_field_class(self, fieldmapping, original)
|
return bound_field_class(self, fieldmapping, original)
|
||||||
|
|
||||||
|
def _get_choices(self):
|
||||||
|
if hasattr(self._choices, 'next'):
|
||||||
|
choices, self._choices = tee(self._choices)
|
||||||
|
return choices
|
||||||
|
else:
|
||||||
|
return self._choices
|
||||||
|
choices = property(_get_choices)
|
||||||
|
|
||||||
class AutoField(Field):
|
class AutoField(Field):
|
||||||
empty_strings_allowed = False
|
empty_strings_allowed = False
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
@ -177,7 +177,7 @@ class AutomaticManipulator(forms.Manipulator):
|
|||||||
# case, because they'll be dealt with later.
|
# case, because they'll be dealt with later.
|
||||||
|
|
||||||
if f == related.field:
|
if f == related.field:
|
||||||
param = getattr(new_object, related.field.rel.field_name)
|
param = getattr(new_object, related.field.rel.get_related_field().attname)
|
||||||
elif (not self.change) and isinstance(f, AutoField):
|
elif (not self.change) and isinstance(f, AutoField):
|
||||||
param = None
|
param = None
|
||||||
elif self.change and (isinstance(f, FileField) or not child_follow.get(f.name, None)):
|
elif self.change and (isinstance(f, FileField) or not child_follow.get(f.name, None)):
|
||||||
@ -215,7 +215,10 @@ class AutomaticManipulator(forms.Manipulator):
|
|||||||
# Save many-to-many objects.
|
# Save many-to-many objects.
|
||||||
for f in related.opts.many_to_many:
|
for f in related.opts.many_to_many:
|
||||||
if child_follow.get(f.name, None) and not f.rel.edit_inline:
|
if child_follow.get(f.name, None) and not f.rel.edit_inline:
|
||||||
setattr(new_rel_obj, f.name, f.rel.to.objects.filter(pk__in=rel_new_data[f.attname]))
|
new_value = rel_new_data[f.attname]
|
||||||
|
if f.rel.raw_id_admin:
|
||||||
|
new_value = new_value[0]
|
||||||
|
setattr(new_rel_obj, f.name, f.rel.to.objects.filter(pk__in=new_value))
|
||||||
if self.change:
|
if self.change:
|
||||||
self.fields_changed.append('%s for %s "%s"' % (f.verbose_name, related.opts.verbose_name, new_rel_obj))
|
self.fields_changed.append('%s for %s "%s"' % (f.verbose_name, related.opts.verbose_name, new_rel_obj))
|
||||||
|
|
||||||
@ -300,7 +303,7 @@ def manipulator_validator_unique_together(field_name_list, opts, self, field_dat
|
|||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
raise validators.ValidationError, _("%(object)s with this %(type)s already exists for the given %(field)s.") % \
|
raise validators.ValidationError, _("%(object)s with this %(type)s already exists for the given %(field)s.") % \
|
||||||
{'object': capfirst(opts.verbose_name), 'type': field_list[0].verbose_name, 'field': get_text_list(field_name_list[1:], 'and')}
|
{'object': capfirst(opts.verbose_name), 'type': field_list[0].verbose_name, 'field': get_text_list([f.verbose_name for f in field_list[1:]], 'and')}
|
||||||
|
|
||||||
def manipulator_validator_unique_for_date(from_field, date_field, opts, lookup_type, self, field_data, all_data):
|
def manipulator_validator_unique_for_date(from_field, date_field, opts, lookup_type, self, field_data, all_data):
|
||||||
from django.db.models.fields.related import ManyToOneRel
|
from django.db.models.fields.related import ManyToOneRel
|
||||||
|
@ -2,6 +2,7 @@ from django.conf import settings
|
|||||||
from django import http
|
from django import http
|
||||||
from django.core.mail import mail_managers
|
from django.core.mail import mail_managers
|
||||||
import md5
|
import md5
|
||||||
|
import re
|
||||||
|
|
||||||
class CommonMiddleware(object):
|
class CommonMiddleware(object):
|
||||||
"""
|
"""
|
||||||
@ -61,7 +62,7 @@ class CommonMiddleware(object):
|
|||||||
# send a note to the managers.
|
# send a note to the managers.
|
||||||
domain = http.get_host(request)
|
domain = http.get_host(request)
|
||||||
referer = request.META.get('HTTP_REFERER', None)
|
referer = request.META.get('HTTP_REFERER', None)
|
||||||
is_internal = referer and (domain in referer)
|
is_internal = _is_internal_request(domain, referer)
|
||||||
path = request.get_full_path()
|
path = request.get_full_path()
|
||||||
if referer and not _is_ignorable_404(path) and (is_internal or '?' not in referer):
|
if referer and not _is_ignorable_404(path) and (is_internal or '?' not in referer):
|
||||||
ua = request.META.get('HTTP_USER_AGENT', '<none>')
|
ua = request.META.get('HTTP_USER_AGENT', '<none>')
|
||||||
@ -88,3 +89,8 @@ def _is_ignorable_404(uri):
|
|||||||
if uri.endswith(end):
|
if uri.endswith(end):
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def _is_internal_request(domain, referer):
|
||||||
|
"Return true if the referring URL is the same domain as the current request"
|
||||||
|
# Different subdomains are treated as different domains.
|
||||||
|
return referer is not None and re.match("^https?://%s/" % re.escape(domain), referer)
|
||||||
|
@ -532,7 +532,7 @@ class FilterExpression(object):
|
|||||||
constant_arg, i18n_arg, var_arg = match.group("constant_arg", "i18n_arg", "var_arg")
|
constant_arg, i18n_arg, var_arg = match.group("constant_arg", "i18n_arg", "var_arg")
|
||||||
if i18n_arg:
|
if i18n_arg:
|
||||||
args.append((False, _(i18n_arg.replace(r'\"', '"'))))
|
args.append((False, _(i18n_arg.replace(r'\"', '"'))))
|
||||||
elif constant_arg:
|
elif constant_arg is not None:
|
||||||
args.append((False, constant_arg.replace(r'\"', '"')))
|
args.append((False, constant_arg.replace(r'\"', '"')))
|
||||||
elif var_arg:
|
elif var_arg:
|
||||||
args.append((True, var_arg))
|
args.append((True, var_arg))
|
||||||
|
@ -8,7 +8,7 @@ from django.utils.functional import curry
|
|||||||
|
|
||||||
class ClientHandler(BaseHandler):
|
class ClientHandler(BaseHandler):
|
||||||
"""
|
"""
|
||||||
A HTTP Handler that can be used for testing purposes.
|
A HTTP Handler that can be used for testing purposes.
|
||||||
Uses the WSGI interface to compose requests, but returns
|
Uses the WSGI interface to compose requests, but returns
|
||||||
the raw HttpResponse object
|
the raw HttpResponse object
|
||||||
"""
|
"""
|
||||||
@ -24,7 +24,7 @@ class ClientHandler(BaseHandler):
|
|||||||
dispatcher.send(signal=signals.request_started)
|
dispatcher.send(signal=signals.request_started)
|
||||||
try:
|
try:
|
||||||
request = WSGIRequest(environ)
|
request = WSGIRequest(environ)
|
||||||
response = self.get_response(request.path, request)
|
response = self.get_response(request)
|
||||||
|
|
||||||
# Apply response middleware
|
# Apply response middleware
|
||||||
for middleware_method in self._response_middleware:
|
for middleware_method in self._response_middleware:
|
||||||
@ -32,7 +32,7 @@ class ClientHandler(BaseHandler):
|
|||||||
|
|
||||||
finally:
|
finally:
|
||||||
dispatcher.send(signal=signals.request_finished)
|
dispatcher.send(signal=signals.request_finished)
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def store_rendered_templates(store, signal, sender, template, context):
|
def store_rendered_templates(store, signal, sender, template, context):
|
||||||
@ -44,7 +44,7 @@ def encode_multipart(boundary, data):
|
|||||||
"""
|
"""
|
||||||
A simple method for encoding multipart POST data from a dictionary of
|
A simple method for encoding multipart POST data from a dictionary of
|
||||||
form values.
|
form values.
|
||||||
|
|
||||||
The key will be used as the form data name; the value will be transmitted
|
The key will be used as the form data name; the value will be transmitted
|
||||||
as content. If the value is a file, the contents of the file will be sent
|
as content. If the value is a file, the contents of the file will be sent
|
||||||
as an application/octet-stream; otherwise, str(value) will be sent.
|
as an application/octet-stream; otherwise, str(value) will be sent.
|
||||||
@ -69,7 +69,7 @@ def encode_multipart(boundary, data):
|
|||||||
'',
|
'',
|
||||||
str(value)
|
str(value)
|
||||||
])
|
])
|
||||||
|
|
||||||
lines.extend([
|
lines.extend([
|
||||||
'--' + boundary + '--',
|
'--' + boundary + '--',
|
||||||
'',
|
'',
|
||||||
@ -78,8 +78,8 @@ def encode_multipart(boundary, data):
|
|||||||
|
|
||||||
class Client:
|
class Client:
|
||||||
"""
|
"""
|
||||||
A class that can act as a client for testing purposes.
|
A class that can act as a client for testing purposes.
|
||||||
|
|
||||||
It allows the user to compose GET and POST requests, and
|
It allows the user to compose GET and POST requests, and
|
||||||
obtain the response that the server gave to those requests.
|
obtain the response that the server gave to those requests.
|
||||||
The server Response objects are annotated with the details
|
The server Response objects are annotated with the details
|
||||||
@ -88,7 +88,7 @@ class Client:
|
|||||||
|
|
||||||
Client objects are stateful - they will retain cookie (and
|
Client objects are stateful - they will retain cookie (and
|
||||||
thus session) details for the lifetime of the Client instance.
|
thus session) details for the lifetime of the Client instance.
|
||||||
|
|
||||||
This is not intended as a replacement for Twill/Selenium or
|
This is not intended as a replacement for Twill/Selenium or
|
||||||
the like - it is here to allow testing against the
|
the like - it is here to allow testing against the
|
||||||
contexts and templates produced by a view, rather than the
|
contexts and templates produced by a view, rather than the
|
||||||
@ -98,10 +98,10 @@ class Client:
|
|||||||
self.handler = ClientHandler()
|
self.handler = ClientHandler()
|
||||||
self.defaults = defaults
|
self.defaults = defaults
|
||||||
self.cookie = SimpleCookie()
|
self.cookie = SimpleCookie()
|
||||||
|
|
||||||
def request(self, **request):
|
def request(self, **request):
|
||||||
"""
|
"""
|
||||||
The master request method. Composes the environment dictionary
|
The master request method. Composes the environment dictionary
|
||||||
and passes to the handler, returning the result of the handler.
|
and passes to the handler, returning the result of the handler.
|
||||||
Assumes defaults for the query environment, which can be overridden
|
Assumes defaults for the query environment, which can be overridden
|
||||||
using the arguments to the request.
|
using the arguments to the request.
|
||||||
@ -112,24 +112,24 @@ class Client:
|
|||||||
'PATH_INFO': '/',
|
'PATH_INFO': '/',
|
||||||
'QUERY_STRING': '',
|
'QUERY_STRING': '',
|
||||||
'REQUEST_METHOD': 'GET',
|
'REQUEST_METHOD': 'GET',
|
||||||
'SCRIPT_NAME': None,
|
'SCRIPT_NAME': None,
|
||||||
'SERVER_NAME': 'testserver',
|
'SERVER_NAME': 'testserver',
|
||||||
'SERVER_PORT': 80,
|
'SERVER_PORT': 80,
|
||||||
'SERVER_PROTOCOL': 'HTTP/1.1',
|
'SERVER_PROTOCOL': 'HTTP/1.1',
|
||||||
}
|
}
|
||||||
environ.update(self.defaults)
|
environ.update(self.defaults)
|
||||||
environ.update(request)
|
environ.update(request)
|
||||||
|
|
||||||
# Curry a data dictionary into an instance of
|
# Curry a data dictionary into an instance of
|
||||||
# the template renderer callback function
|
# the template renderer callback function
|
||||||
data = {}
|
data = {}
|
||||||
on_template_render = curry(store_rendered_templates, data)
|
on_template_render = curry(store_rendered_templates, data)
|
||||||
dispatcher.connect(on_template_render, signal=signals.template_rendered)
|
dispatcher.connect(on_template_render, signal=signals.template_rendered)
|
||||||
|
|
||||||
response = self.handler(environ)
|
response = self.handler(environ)
|
||||||
|
|
||||||
# Add any rendered template detail to the response
|
# Add any rendered template detail to the response
|
||||||
# If there was only one template rendered (the most likely case),
|
# If there was only one template rendered (the most likely case),
|
||||||
# flatten the list to a single element
|
# flatten the list to a single element
|
||||||
for detail in ('template', 'context'):
|
for detail in ('template', 'context'):
|
||||||
if data.get(detail):
|
if data.get(detail):
|
||||||
@ -139,12 +139,12 @@ class Client:
|
|||||||
setattr(response, detail, data[detail])
|
setattr(response, detail, data[detail])
|
||||||
else:
|
else:
|
||||||
setattr(response, detail, None)
|
setattr(response, detail, None)
|
||||||
|
|
||||||
if response.cookies:
|
if response.cookies:
|
||||||
self.cookie.update(response.cookies)
|
self.cookie.update(response.cookies)
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def get(self, path, data={}, **extra):
|
def get(self, path, data={}, **extra):
|
||||||
"Request a response from the server using GET."
|
"Request a response from the server using GET."
|
||||||
r = {
|
r = {
|
||||||
@ -155,12 +155,12 @@ class Client:
|
|||||||
'REQUEST_METHOD': 'GET',
|
'REQUEST_METHOD': 'GET',
|
||||||
}
|
}
|
||||||
r.update(extra)
|
r.update(extra)
|
||||||
|
|
||||||
return self.request(**r)
|
return self.request(**r)
|
||||||
|
|
||||||
def post(self, path, data={}, **extra):
|
def post(self, path, data={}, **extra):
|
||||||
"Request a response from the server using POST."
|
"Request a response from the server using POST."
|
||||||
|
|
||||||
BOUNDARY = 'BoUnDaRyStRiNg'
|
BOUNDARY = 'BoUnDaRyStRiNg'
|
||||||
|
|
||||||
encoded = encode_multipart(BOUNDARY, data)
|
encoded = encode_multipart(BOUNDARY, data)
|
||||||
@ -173,25 +173,25 @@ class Client:
|
|||||||
'wsgi.input': stream,
|
'wsgi.input': stream,
|
||||||
}
|
}
|
||||||
r.update(extra)
|
r.update(extra)
|
||||||
|
|
||||||
return self.request(**r)
|
return self.request(**r)
|
||||||
|
|
||||||
def login(self, path, username, password, **extra):
|
def login(self, path, username, password, **extra):
|
||||||
"""
|
"""
|
||||||
A specialized sequence of GET and POST to log into a view that
|
A specialized sequence of GET and POST to log into a view that
|
||||||
is protected by a @login_required access decorator.
|
is protected by a @login_required access decorator.
|
||||||
|
|
||||||
path should be the URL of the page that is login protected.
|
path should be the URL of the page that is login protected.
|
||||||
|
|
||||||
Returns the response from GETting the requested URL after
|
Returns the response from GETting the requested URL after
|
||||||
login is complete. Returns False if login process failed.
|
login is complete. Returns False if login process failed.
|
||||||
"""
|
"""
|
||||||
# First, GET the page that is login protected.
|
# First, GET the page that is login protected.
|
||||||
# This page will redirect to the login page.
|
# This page will redirect to the login page.
|
||||||
response = self.get(path)
|
response = self.get(path)
|
||||||
if response.status_code != 302:
|
if response.status_code != 302:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
login_path, data = response['Location'].split('?')
|
login_path, data = response['Location'].split('?')
|
||||||
next = data.split('=')[1]
|
next = data.split('=')[1]
|
||||||
|
|
||||||
@ -199,7 +199,7 @@ class Client:
|
|||||||
response = self.get(login_path, **extra)
|
response = self.get(login_path, **extra)
|
||||||
if response.status_code != 200:
|
if response.status_code != 200:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Last, POST the login data.
|
# Last, POST the login data.
|
||||||
form_data = {
|
form_data = {
|
||||||
'username': username,
|
'username': username,
|
||||||
|
31
django/utils/itercompat.py
Normal file
31
django/utils/itercompat.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
"""
|
||||||
|
Providing iterator functions that are not in all version of Python we support.
|
||||||
|
Where possible, we try to use the system-native version and only fall back to
|
||||||
|
these implementations if necessary.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import itertools
|
||||||
|
|
||||||
|
def compat_tee(iterable):
|
||||||
|
"""Return two independent iterators from a single iterable.
|
||||||
|
|
||||||
|
Based on http://www.python.org/doc/2.3.5/lib/itertools-example.html
|
||||||
|
"""
|
||||||
|
# Note: Using a dictionary and a list as the default arguments here is
|
||||||
|
# deliberate and safe in this instance.
|
||||||
|
def gen(next, data={}, cnt=[0]):
|
||||||
|
dpop = data.pop
|
||||||
|
for i in itertools.count():
|
||||||
|
if i == cnt[0]:
|
||||||
|
item = data[i] = next()
|
||||||
|
cnt[0] += 1
|
||||||
|
else:
|
||||||
|
item = dpop(i)
|
||||||
|
yield item
|
||||||
|
next = iter(iterable).next
|
||||||
|
return gen(next), gen(next)
|
||||||
|
|
||||||
|
if hasattr(itertools, 'tee'):
|
||||||
|
tee = itertools.tee
|
||||||
|
else:
|
||||||
|
tee = compat_tee
|
12
docs/faq.txt
12
docs/faq.txt
@ -313,6 +313,18 @@ PostgreSQL fans, and MySQL_ and `SQLite 3`_ are also supported.
|
|||||||
.. _MySQL: http://www.mysql.com/
|
.. _MySQL: http://www.mysql.com/
|
||||||
.. _`SQLite 3`: http://www.sqlite.org/
|
.. _`SQLite 3`: http://www.sqlite.org/
|
||||||
|
|
||||||
|
Do I lose anything by using Python 2.3 versus newer Python versions, such as Python 2.5?
|
||||||
|
----------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
No. Django itself is guaranteed to work with any version of Python from 2.3
|
||||||
|
and higher.
|
||||||
|
|
||||||
|
If you use a Python version newer than 2.3, you will, of course, be able to
|
||||||
|
take advantage of newer Python features in your own code, along with the speed
|
||||||
|
improvements and other optimizations that have been made to the Python language
|
||||||
|
itself. But the Django framework itself should work equally well on 2.3 as it
|
||||||
|
does on 2.4 or 2.5.
|
||||||
|
|
||||||
Do I have to use mod_python?
|
Do I have to use mod_python?
|
||||||
----------------------------
|
----------------------------
|
||||||
|
|
||||||
|
@ -366,6 +366,18 @@ If ``TEMPLATE_CONTEXT_PROCESSORS`` contains this processor, every
|
|||||||
`HttpRequest object`_. Note that this processor is not enabled by default;
|
`HttpRequest object`_. Note that this processor is not enabled by default;
|
||||||
you'll have to activate it.
|
you'll have to activate it.
|
||||||
|
|
||||||
|
Writing your own context processors
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
A context processor has a very simple interface: It's just a Python function
|
||||||
|
that takes one argument, an ``HttpRequest`` object, and returns a dictionary
|
||||||
|
that gets added to the template context. Each context processor *must* return
|
||||||
|
a dictionary.
|
||||||
|
|
||||||
|
Custom context processors can live anywhere in your code base. All Django cares
|
||||||
|
about is that your custom context processors are pointed-to by your
|
||||||
|
``TEMPLATE_CONTEXT_PROCESSORS`` setting.
|
||||||
|
|
||||||
Loading templates
|
Loading templates
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
|
@ -21,22 +21,22 @@ r"""
|
|||||||
'7'
|
'7'
|
||||||
>>> format(my_birthday, 'N')
|
>>> format(my_birthday, 'N')
|
||||||
'July'
|
'July'
|
||||||
>>> format(my_birthday, 'O')
|
>>> no_tz or format(my_birthday, 'O') == '+0100'
|
||||||
'+0100'
|
True
|
||||||
>>> format(my_birthday, 'P')
|
>>> format(my_birthday, 'P')
|
||||||
'10 p.m.'
|
'10 p.m.'
|
||||||
>>> format(my_birthday, 'r')
|
>>> no_tz or format(my_birthday, 'r') == 'Sun, 8 Jul 1979 22:00:00 +0100'
|
||||||
'Sun, 8 Jul 1979 22:00:00 +0100'
|
True
|
||||||
>>> format(my_birthday, 's')
|
>>> format(my_birthday, 's')
|
||||||
'00'
|
'00'
|
||||||
>>> format(my_birthday, 'S')
|
>>> format(my_birthday, 'S')
|
||||||
'th'
|
'th'
|
||||||
>>> format(my_birthday, 't')
|
>>> format(my_birthday, 't')
|
||||||
'31'
|
'31'
|
||||||
>>> format(my_birthday, 'T')
|
>>> no_tz or format(my_birthday, 'T') == 'CET'
|
||||||
'CET'
|
True
|
||||||
>>> format(my_birthday, 'U')
|
>>> no_tz or format(my_birthday, 'U') == '300531600'
|
||||||
'300531600'
|
True
|
||||||
>>> format(my_birthday, 'w')
|
>>> format(my_birthday, 'w')
|
||||||
'0'
|
'0'
|
||||||
>>> format(my_birthday, 'W')
|
>>> format(my_birthday, 'W')
|
||||||
@ -47,17 +47,17 @@ r"""
|
|||||||
'1979'
|
'1979'
|
||||||
>>> format(my_birthday, 'z')
|
>>> format(my_birthday, 'z')
|
||||||
'189'
|
'189'
|
||||||
>>> format(my_birthday, 'Z')
|
>>> no_tz or format(my_birthday, 'Z') == '3600'
|
||||||
'3600'
|
True
|
||||||
|
|
||||||
>>> format(summertime, 'I')
|
>>> no_tz or format(summertime, 'I') == '1'
|
||||||
'1'
|
True
|
||||||
>>> format(summertime, 'O')
|
>>> no_tz or format(summertime, 'O') == '+0200'
|
||||||
'+0200'
|
True
|
||||||
>>> format(wintertime, 'I')
|
>>> no_tz or format(wintertime, 'I') == '0'
|
||||||
'0'
|
True
|
||||||
>>> format(wintertime, 'O')
|
>>> no_tz or format(wintertime, 'O') == '+0100'
|
||||||
'+0100'
|
True
|
||||||
|
|
||||||
>>> format(my_birthday, r'Y z \C\E\T')
|
>>> format(my_birthday, r'Y z \C\E\T')
|
||||||
'1979 189 CET'
|
'1979 189 CET'
|
||||||
@ -73,7 +73,11 @@ format = dateformat.format
|
|||||||
os.environ['TZ'] = 'Europe/Copenhagen'
|
os.environ['TZ'] = 'Europe/Copenhagen'
|
||||||
translation.activate('en-us')
|
translation.activate('en-us')
|
||||||
|
|
||||||
time.tzset()
|
try:
|
||||||
|
time.tzset()
|
||||||
|
no_tz = False
|
||||||
|
except AttributeError:
|
||||||
|
no_tz = True
|
||||||
|
|
||||||
my_birthday = datetime.datetime(1979, 7, 8, 22, 00)
|
my_birthday = datetime.datetime(1979, 7, 8, 22, 00)
|
||||||
summertime = datetime.datetime(2005, 10, 30, 1, 00)
|
summertime = datetime.datetime(2005, 10, 30, 1, 00)
|
||||||
|
@ -170,6 +170,9 @@ class Templates(unittest.TestCase):
|
|||||||
# Escaped backslash using known escape char
|
# Escaped backslash using known escape char
|
||||||
'basic-syntax35': (r'{{ var|default_if_none:"foo\now" }}', {"var": None}, r'foo\now'),
|
'basic-syntax35': (r'{{ var|default_if_none:"foo\now" }}', {"var": None}, r'foo\now'),
|
||||||
|
|
||||||
|
# Empty strings can be passed as arguments to filters
|
||||||
|
'basic-syntax36': (r'{{ var|join:"" }}', {'var': ['a', 'b', 'c']}, 'abc'),
|
||||||
|
|
||||||
### COMMENT TAG ###########################################################
|
### COMMENT TAG ###########################################################
|
||||||
'comment-tag01': ("{% comment %}this is hidden{% endcomment %}hello", {}, "hello"),
|
'comment-tag01': ("{% comment %}this is hidden{% endcomment %}hello", {}, "hello"),
|
||||||
'comment-tag02': ("{% comment %}this is hidden{% endcomment %}hello{% comment %}foo{% endcomment %}", {}, "hello"),
|
'comment-tag02': ("{% comment %}this is hidden{% endcomment %}hello{% comment %}foo{% endcomment %}", {}, "hello"),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user