mirror of
https://github.com/django/django.git
synced 2025-07-04 17:59:13 +00:00
[multi-db] Merged trunk to [4050]. Some tests still failing.
git-svn-id: http://code.djangoproject.com/svn/django/branches/multiple-db-support@4157 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
b7a897eebb
commit
478ebadec7
@ -101,6 +101,7 @@ DATABASE_USER = '' # Not used with sqlite3.
|
||||
DATABASE_PASSWORD = '' # Not used with sqlite3.
|
||||
DATABASE_HOST = '' # Set to empty string for localhost. Not used with sqlite3.
|
||||
DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3.
|
||||
DATABASE_OPTIONS = {} # Set to empy dictionary for default.
|
||||
|
||||
# Optional named database connections in addition to the default.
|
||||
OTHER_DATABASES = {}
|
||||
@ -231,6 +232,10 @@ MONTH_DAY_FORMAT = 'F j'
|
||||
# Hint: you really don't!
|
||||
TRANSACTIONS_MANAGED = False
|
||||
|
||||
# The User-Agent string to use when checking for URL validity through the
|
||||
# isExistingURL validator.
|
||||
URL_VALIDATOR_USER_AGENT = "Django/0.96pre (http://www.djangoproject.com)"
|
||||
|
||||
##############
|
||||
# MIDDLEWARE #
|
||||
##############
|
||||
|
@ -169,8 +169,8 @@ var dateParsePatterns = [
|
||||
handler: function(bits) {
|
||||
var d = new Date();
|
||||
d.setYear(parseInt(bits[1]));
|
||||
d.setDate(parseInt(bits[3], 10));
|
||||
d.setMonth(parseInt(bits[2], 10) - 1);
|
||||
d.setDate(parseInt(bits[3], 10));
|
||||
return d;
|
||||
}
|
||||
},
|
||||
|
@ -177,8 +177,8 @@ def output_all(form_fields):
|
||||
output_all = register.simple_tag(output_all)
|
||||
|
||||
def auto_populated_field_script(auto_pop_fields, change = False):
|
||||
t = []
|
||||
for field in auto_pop_fields:
|
||||
t = []
|
||||
if change:
|
||||
t.append('document.getElementById("id_%s")._changed = true;' % field.name)
|
||||
else:
|
||||
|
@ -34,7 +34,7 @@ class CommentManager(models.Manager):
|
||||
"""
|
||||
Given a rating_string, this returns a tuple of (rating_range, options).
|
||||
>>> s = "scale:1-10|First_category|Second_category"
|
||||
>>> get_rating_options(s)
|
||||
>>> Comment.objects.get_rating_options(s)
|
||||
([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], ['First category', 'Second category'])
|
||||
"""
|
||||
rating_range, options = rating_string.split('|', 1)
|
||||
|
@ -186,6 +186,33 @@ def get_sql_reset(app):
|
||||
get_sql_reset.help_doc = "Prints the DROP TABLE SQL, then the CREATE TABLE SQL, for the given app name(s)."
|
||||
get_sql_reset.args = APP_ARGS
|
||||
|
||||
def get_sql_initial_data_for_model(model):
|
||||
from django.db import models
|
||||
from django.conf import settings
|
||||
|
||||
opts = model._meta
|
||||
app_dir = os.path.normpath(os.path.join(os.path.dirname(models.get_app(model._meta.app_label).__file__), 'sql'))
|
||||
output = []
|
||||
|
||||
# Some backends can't execute more than one SQL statement at a time,
|
||||
# so split into separate statements.
|
||||
statements = re.compile(r";[ \t]*$", re.M)
|
||||
|
||||
# Find custom SQL, if it's available.
|
||||
sql_files = [os.path.join(app_dir, "%s.%s.sql" % (opts.object_name.lower(), settings.DATABASE_ENGINE)),
|
||||
os.path.join(app_dir, "%s.sql" % opts.object_name.lower())]
|
||||
for sql_file in sql_files:
|
||||
if os.path.exists(sql_file):
|
||||
fp = open(sql_file, 'U')
|
||||
for statement in statements.split(fp.read()):
|
||||
# Remove any comments from the file
|
||||
statement = re.sub(r"--.*[\n\Z]", "", statement)
|
||||
if statement.strip():
|
||||
output.append(statement + ";")
|
||||
fp.close()
|
||||
|
||||
return output
|
||||
|
||||
def get_sql_initial_data(app):
|
||||
"Returns a list of the initial INSERT SQL statements for the given app."
|
||||
from django.db import model_connection_name
|
||||
|
@ -1,54 +1,46 @@
|
||||
from math import ceil
|
||||
|
||||
class InvalidPage(Exception):
|
||||
pass
|
||||
|
||||
class ObjectPaginator(object):
|
||||
"""
|
||||
This class makes pagination easy. Feed it a QuerySet, plus the number of
|
||||
objects you want on each page. Then read the hits and pages properties to
|
||||
This class makes pagination easy. Feed it a QuerySet or list, plus the number
|
||||
of objects you want on each page. Then read the hits and pages properties to
|
||||
see how many pages it involves. Call get_page with a page number (starting
|
||||
at 0) to get back a list of objects for that page.
|
||||
|
||||
Finally, check if a page number has a next/prev page using
|
||||
has_next_page(page_number) and has_previous_page(page_number).
|
||||
|
||||
Use orphans to avoid small final pages. For example:
|
||||
13 records, num_per_page=10, orphans=2 --> pages==2, len(self.get_page(0))==10
|
||||
12 records, num_per_page=10, orphans=2 --> pages==1, len(self.get_page(0))==12
|
||||
"""
|
||||
def __init__(self, query_set, num_per_page):
|
||||
def __init__(self, query_set, num_per_page, orphans=0):
|
||||
self.query_set = query_set
|
||||
self.num_per_page = num_per_page
|
||||
self._hits, self._pages = None, None
|
||||
self._has_next = {} # Caches page_number -> has_next_boolean
|
||||
self.orphans = orphans
|
||||
self._hits = self._pages = None
|
||||
|
||||
def get_page(self, page_number):
|
||||
def validate_page_number(self, page_number):
|
||||
try:
|
||||
page_number = int(page_number)
|
||||
except ValueError:
|
||||
raise InvalidPage
|
||||
if page_number < 0:
|
||||
if page_number < 0 or page_number > self.pages - 1:
|
||||
raise InvalidPage
|
||||
return page_number
|
||||
|
||||
# Retrieve one extra record, and check for the existence of that extra
|
||||
# record to determine whether there's a next page.
|
||||
limit = self.num_per_page + 1
|
||||
offset = page_number * self.num_per_page
|
||||
|
||||
object_list = list(self.query_set[offset:offset+limit])
|
||||
|
||||
if not object_list:
|
||||
raise InvalidPage
|
||||
|
||||
self._has_next[page_number] = (len(object_list) > self.num_per_page)
|
||||
return object_list[:self.num_per_page]
|
||||
def get_page(self, page_number):
|
||||
page_number = self.validate_page_number(page_number)
|
||||
bottom = page_number * self.num_per_page
|
||||
top = bottom + self.num_per_page
|
||||
if top + self.orphans >= self.hits:
|
||||
top = self.hits
|
||||
return self.query_set[bottom:top]
|
||||
|
||||
def has_next_page(self, page_number):
|
||||
"Does page $page_number have a 'next' page?"
|
||||
if not self._has_next.has_key(page_number):
|
||||
if self._pages is None:
|
||||
offset = (page_number + 1) * self.num_per_page
|
||||
self._has_next[page_number] = len(self.query_set[offset:offset+1]) > 0
|
||||
else:
|
||||
self._has_next[page_number] = page_number < (self.pages - 1)
|
||||
return self._has_next[page_number]
|
||||
return page_number < self.pages - 1
|
||||
|
||||
def has_previous_page(self, page_number):
|
||||
return page_number > 0
|
||||
@ -58,8 +50,7 @@ class ObjectPaginator(object):
|
||||
Returns the 1-based index of the first object on the given page,
|
||||
relative to total objects found (hits).
|
||||
"""
|
||||
if page_number == 0:
|
||||
return 1
|
||||
page_number = self.validate_page_number(page_number)
|
||||
return (self.num_per_page * page_number) + 1
|
||||
|
||||
def last_on_page(self, page_number):
|
||||
@ -67,20 +58,30 @@ class ObjectPaginator(object):
|
||||
Returns the 1-based index of the last object on the given page,
|
||||
relative to total objects found (hits).
|
||||
"""
|
||||
if page_number == 0 and self.num_per_page >= self._hits:
|
||||
return self._hits
|
||||
elif page_number == (self._pages - 1) and (page_number + 1) * self.num_per_page > self._hits:
|
||||
return self._hits
|
||||
return (page_number + 1) * self.num_per_page
|
||||
page_number = self.validate_page_number(page_number)
|
||||
page_number += 1 # 1-base
|
||||
if page_number == self.pages:
|
||||
return self.hits
|
||||
return page_number * self.num_per_page
|
||||
|
||||
def _get_hits(self):
|
||||
if self._hits is None:
|
||||
self._hits = self.query_set.count()
|
||||
# Try .count() or fall back to len().
|
||||
try:
|
||||
self._hits = int(self.query_set.count())
|
||||
except (AttributeError, TypeError, ValueError):
|
||||
# AttributeError if query_set has no object count.
|
||||
# TypeError if query_set.count() required arguments.
|
||||
# ValueError if int() fails.
|
||||
self._hits = len(self.query_set)
|
||||
return self._hits
|
||||
|
||||
def _get_pages(self):
|
||||
if self._pages is None:
|
||||
self._pages = int(ceil(self.hits / float(self.num_per_page)))
|
||||
hits = (self.hits - 1 - self.orphans)
|
||||
if hits < 1:
|
||||
hits = 0
|
||||
self._pages = hits // self.num_per_page + 1
|
||||
return self._pages
|
||||
|
||||
hits = property(_get_hits)
|
||||
|
@ -28,6 +28,7 @@ class Serializer(object):
|
||||
self.options = options
|
||||
|
||||
self.stream = options.get("stream", StringIO())
|
||||
self.selected_fields = options.get("fields")
|
||||
|
||||
self.start_serialization()
|
||||
for obj in queryset:
|
||||
@ -36,11 +37,14 @@ class Serializer(object):
|
||||
if field is obj._meta.pk:
|
||||
continue
|
||||
elif field.rel is None:
|
||||
self.handle_field(obj, field)
|
||||
if self.selected_fields is None or field.attname in self.selected_fields:
|
||||
self.handle_field(obj, field)
|
||||
else:
|
||||
self.handle_fk_field(obj, field)
|
||||
if self.selected_fields is None or field.attname[:-3] in self.selected_fields:
|
||||
self.handle_fk_field(obj, field)
|
||||
for field in obj._meta.many_to_many:
|
||||
self.handle_m2m_field(obj, field)
|
||||
if self.selected_fields is None or field.attname in self.selected_fields:
|
||||
self.handle_m2m_field(obj, field)
|
||||
self.end_object(obj)
|
||||
self.end_serialization()
|
||||
return self.getvalue()
|
||||
|
@ -76,7 +76,7 @@ def Deserializer(object_list, **options):
|
||||
m2m_data[field.name] = field.rel.to._default_manager.in_bulk(field_value).values()
|
||||
|
||||
# Handle FK fields
|
||||
elif field.rel and isinstance(field.rel, models.ManyToOneRel):
|
||||
elif field.rel and isinstance(field.rel, models.ManyToOneRel) and field_value is not None:
|
||||
try:
|
||||
data[field.name] = field.rel.to._default_manager.get(pk=field_value)
|
||||
except field.rel.to.DoesNotExist:
|
||||
|
@ -166,7 +166,11 @@ class Deserializer(base.Deserializer):
|
||||
# If it doesn't exist, set the field to None (which might trigger
|
||||
# validation error, but that's expected).
|
||||
RelatedModel = self._get_model_from_node(node, "to")
|
||||
return RelatedModel.objects.get(pk=getInnerText(node).strip().encode(self.encoding))
|
||||
# Check if there is a child node named 'None', returning None if so.
|
||||
if len(node.childNodes) == 1 and node.childNodes[0].nodeName == 'None':
|
||||
return None
|
||||
else:
|
||||
return RelatedModel.objects.get(pk=getInnerText(node).strip().encode(self.encoding))
|
||||
|
||||
def _handle_m2m_field_node(self, node):
|
||||
"""
|
||||
|
@ -33,9 +33,9 @@ Optional Fcgi settings: (setting=value)
|
||||
method=IMPL prefork or threaded (default prefork)
|
||||
maxrequests=NUMBER number of requests a child handles before it is
|
||||
killed and a new child is forked (0 = no limit).
|
||||
maxspare=NUMBER max number of spare processes to keep running.
|
||||
minspare=NUMBER min number of spare processes to prefork.
|
||||
maxchildren=NUMBER hard limit number of processes in prefork mode.
|
||||
maxspare=NUMBER max number of spare processes / threads
|
||||
minspare=NUMBER min number of spare processes / threads.
|
||||
maxchildren=NUMBER hard limit number of processes / threads
|
||||
daemonize=BOOL whether to detach from terminal.
|
||||
pidfile=FILE write the spawned process-id to this file.
|
||||
workdir=DIRECTORY change to this directory when daemonizing
|
||||
@ -110,7 +110,11 @@ def runfastcgi(argset=[], **kwargs):
|
||||
}
|
||||
elif options['method'] in ('thread', 'threaded'):
|
||||
from flup.server.fcgi import WSGIServer
|
||||
wsgi_opts = {}
|
||||
wsgi_opts = {
|
||||
'maxSpare': int(options["maxspare"]),
|
||||
'minSpare': int(options["minspare"]),
|
||||
'maxThreads': int(options["maxchildren"]),
|
||||
}
|
||||
else:
|
||||
return fastcgi_help("ERROR: Implementation must be one of prefork or thread.")
|
||||
|
||||
|
@ -21,7 +21,10 @@ class NoReverseMatch(Exception):
|
||||
def get_mod_func(callback):
|
||||
# Converts 'django.views.news.stories.story_detail' to
|
||||
# ['django.views.news.stories', 'story_detail']
|
||||
dot = callback.rindex('.')
|
||||
try:
|
||||
dot = callback.rindex('.')
|
||||
except ValueError:
|
||||
return callback, ''
|
||||
return callback[:dot], callback[dot+1:]
|
||||
|
||||
def reverse_helper(regex, *args, **kwargs):
|
||||
|
@ -8,6 +8,7 @@ validator will *always* be run, regardless of whether its associated
|
||||
form field is required.
|
||||
"""
|
||||
|
||||
import urllib2
|
||||
from django.conf import settings
|
||||
from django.utils.translation import gettext, gettext_lazy, ngettext
|
||||
from django.utils.functional import Promise, lazy
|
||||
@ -223,17 +224,25 @@ def isWellFormedXmlFragment(field_data, all_data):
|
||||
isWellFormedXml('<root>%s</root>' % field_data, all_data)
|
||||
|
||||
def isExistingURL(field_data, all_data):
|
||||
import urllib2
|
||||
try:
|
||||
u = urllib2.urlopen(field_data)
|
||||
headers = {
|
||||
"Accept" : "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5",
|
||||
"Accept-Language" : "en-us,en;q=0.5",
|
||||
"Accept-Charset": "ISO-8859-1,utf-8;q=0.7,*;q=0.7",
|
||||
"Connection" : "close",
|
||||
"User-Agent": settings.URL_VALIDATOR_USER_AGENT
|
||||
}
|
||||
req = urllib2.Request(field_data,None, headers)
|
||||
u = urllib2.urlopen(req)
|
||||
except ValueError:
|
||||
raise ValidationError, gettext("Invalid URL: %s") % field_data
|
||||
raise ValidationError, _("Invalid URL: %s") % field_data
|
||||
except urllib2.HTTPError, e:
|
||||
# 401s are valid; they just mean authorization is required.
|
||||
if e.code not in ('401',):
|
||||
raise ValidationError, gettext("The URL %s is a broken link.") % field_data
|
||||
# 301 and 302 are redirects; they just mean look somewhere else.
|
||||
if str(e.code) not in ('401','301','302'):
|
||||
raise ValidationError, _("The URL %s is a broken link.") % field_data
|
||||
except: # urllib2.URLError, httplib.InvalidURL, etc.
|
||||
raise ValidationError, gettext("The URL %s is a broken link.") % field_data
|
||||
raise ValidationError, _("The URL %s is a broken link.") % field_data
|
||||
|
||||
def isValidUSState(field_data, all_data):
|
||||
"Checks that the given string is a valid two-letter U.S. state abbreviation"
|
||||
@ -344,6 +353,38 @@ class UniqueAmongstFieldsWithPrefix(object):
|
||||
if field_name != self.field_name and value == field_data:
|
||||
raise ValidationError, self.error_message
|
||||
|
||||
class NumberIsInRange(object):
|
||||
"""
|
||||
Validator that tests if a value is in a range (inclusive).
|
||||
"""
|
||||
def __init__(self, lower=None, upper=None, error_message=''):
|
||||
self.lower, self.upper = lower, upper
|
||||
if not error_message:
|
||||
if lower and upper:
|
||||
self.error_message = gettext("This value must be between %s and %s.") % (lower, upper)
|
||||
elif lower:
|
||||
self.error_message = gettext("This value must be at least %s.") % lower
|
||||
elif upper:
|
||||
self.error_message = gettext("This value must be no more than %s.") % upper
|
||||
else:
|
||||
self.error_message = error_message
|
||||
|
||||
def __call__(self, field_data, all_data):
|
||||
# Try to make the value numeric. If this fails, we assume another
|
||||
# validator will catch the problem.
|
||||
try:
|
||||
val = float(field_data)
|
||||
except ValueError:
|
||||
return
|
||||
|
||||
# Now validate
|
||||
if self.lower and self.upper and (val < self.lower or val > self.upper):
|
||||
raise ValidationError(self.error_message)
|
||||
elif self.lower and val < self.lower:
|
||||
raise ValidationError(self.error_message)
|
||||
elif self.upper and val > self.upper:
|
||||
raise ValidationError(self.error_message)
|
||||
|
||||
class IsAPowerOf(object):
|
||||
"""
|
||||
>>> v = IsAPowerOf(2)
|
||||
|
@ -48,6 +48,8 @@ class ConnectionInfo(object):
|
||||
super(ConnectionInfo, self).__init__(**kw)
|
||||
if settings is None:
|
||||
from django.conf import settings
|
||||
if not settings.DATABASE_OPTIONS:
|
||||
settings.DATABASE_OPTIONS = {}
|
||||
self.settings = settings
|
||||
self.backend = self.load_backend()
|
||||
self.connection = self.backend.DatabaseWrapper(settings)
|
||||
|
@ -50,6 +50,7 @@ Database.convertVariantToPython = variantToPython
|
||||
class DatabaseWrapper(object):
|
||||
def __init__(self, settings):
|
||||
self.settings = settings
|
||||
self.options = settings.DATABASE_OPTIONS
|
||||
self.connection = None
|
||||
self.queries = []
|
||||
|
||||
|
@ -291,6 +291,8 @@ class SchemaBuilder(object):
|
||||
output.append(BoundStatement(fp.read(), db.connection))
|
||||
else:
|
||||
for statement in statements.split(fp.read()):
|
||||
# Remove any comments from the file
|
||||
statement = re.sub(r"--.*[\n\Z]", "", statement)
|
||||
if statement.strip():
|
||||
output.append(BoundStatement(statement + ";",
|
||||
db.connection))
|
||||
|
@ -64,6 +64,7 @@ class DatabaseWrapper(object):
|
||||
self.connection = None
|
||||
self.queries = []
|
||||
self.server_version = None
|
||||
self.options = settings.DATABASE_OPTIONS
|
||||
|
||||
def _valid_connection(self):
|
||||
if self.connection is not None:
|
||||
@ -90,6 +91,7 @@ class DatabaseWrapper(object):
|
||||
kwargs['host'] = settings.DATABASE_HOST
|
||||
if settings.DATABASE_PORT:
|
||||
kwargs['port'] = int(settings.DATABASE_PORT)
|
||||
kwargs.update(self.options)
|
||||
self.connection = Database.connect(**kwargs)
|
||||
cursor = self.connection.cursor()
|
||||
if self.connection.get_server_info() >= '4.1':
|
||||
|
@ -18,6 +18,7 @@ class DatabaseWrapper(object):
|
||||
self.settings = settings
|
||||
self.connection = None
|
||||
self.queries = []
|
||||
self.options = settings.DATABASE_OPTIONS
|
||||
|
||||
def _valid_connection(self):
|
||||
return self.connection is not None
|
||||
@ -29,10 +30,10 @@ class DatabaseWrapper(object):
|
||||
settings.DATABASE_HOST = 'localhost'
|
||||
if len(settings.DATABASE_PORT.strip()) != 0:
|
||||
dsn = Database.makedsn(settings.DATABASE_HOST, int(settings.DATABASE_PORT), settings.DATABASE_NAME)
|
||||
self.connection = Database.connect(settings.DATABASE_USER, settings.DATABASE_PASSWORD, dsn)
|
||||
self.connection = Database.connect(settings.DATABASE_USER, settings.DATABASE_PASSWORD, dsn, **self.options)
|
||||
else:
|
||||
conn_string = "%s/%s@%s" % (settings.DATABASE_USER, settings.DATABASE_PASSWORD, settings.DATABASE_NAME)
|
||||
self.connection = Database.connect(conn_string)
|
||||
self.connection = Database.connect(conn_string, **self.options)
|
||||
return FormatStylePlaceholderCursor(self.connection)
|
||||
|
||||
def _commit(self):
|
||||
|
@ -18,6 +18,7 @@ class DatabaseWrapper(object):
|
||||
self.settings = settings
|
||||
self.connection = None
|
||||
self.queries = []
|
||||
self.options = settings.DATABASE_OPTIONS
|
||||
|
||||
def cursor(self):
|
||||
settings = self.settings
|
||||
@ -34,7 +35,7 @@ class DatabaseWrapper(object):
|
||||
conn_string += " host=%s" % settings.DATABASE_HOST
|
||||
if settings.DATABASE_PORT:
|
||||
conn_string += " port=%s" % settings.DATABASE_PORT
|
||||
self.connection = Database.connect(conn_string)
|
||||
self.connection = Database.connect(conn_string, **self.options)
|
||||
self.connection.set_isolation_level(1) # make transactions transparent to all cursors
|
||||
cursor = self.connection.cursor()
|
||||
cursor.execute("SET TIME ZONE %s", [settings.TIME_ZONE])
|
||||
|
@ -18,6 +18,7 @@ class DatabaseWrapper(object):
|
||||
self.settings = settings
|
||||
self.connection = None
|
||||
self.queries = []
|
||||
self.options = settings.DATABASE_OPTIONS
|
||||
|
||||
def cursor(self):
|
||||
settings = self.settings
|
||||
@ -34,7 +35,7 @@ class DatabaseWrapper(object):
|
||||
conn_string += " host=%s" % settings.DATABASE_HOST
|
||||
if settings.DATABASE_PORT:
|
||||
conn_string += " port=%s" % settings.DATABASE_PORT
|
||||
self.connection = Database.connect(conn_string)
|
||||
self.connection = Database.connect(conn_string, **self.options)
|
||||
self.connection.set_isolation_level(1) # make transactions transparent to all cursors
|
||||
cursor = self.connection.cursor()
|
||||
cursor.tzinfo_factory = None
|
||||
|
@ -46,13 +46,17 @@ class DatabaseWrapper(local):
|
||||
self.settings = settings
|
||||
self.connection = None
|
||||
self.queries = []
|
||||
self.options = settings.DATABASE_OPTIONS
|
||||
|
||||
def cursor(self):
|
||||
settings = self.settings
|
||||
if self.connection is None:
|
||||
self.connection = Database.connect(settings.DATABASE_NAME,
|
||||
detect_types=Database.PARSE_DECLTYPES | Database.PARSE_COLNAMES)
|
||||
|
||||
kwargs = {
|
||||
'database': settings.DATABASE_NAME,
|
||||
'detect_types': Database.PARSE_DECLTYPES | Database.PARSE_COLNAMES,
|
||||
}
|
||||
kwargs.update(self.options)
|
||||
self.connection = Database.connect(**kwargs)
|
||||
# Register extract and date_trunc functions.
|
||||
self.connection.create_function("django_extract", 2, _sqlite_extract)
|
||||
self.connection.create_function("django_date_trunc", 2, _sqlite_date_trunc)
|
||||
|
@ -17,7 +17,7 @@ class CursorDebugWrapper(object):
|
||||
if not isinstance(params, (tuple, dict)):
|
||||
params = tuple(params)
|
||||
self.db.queries.append({
|
||||
'sql': sql % tuple(params),
|
||||
'sql': sql % params,
|
||||
'time': "%.3f" % (stop - start),
|
||||
})
|
||||
|
||||
|
@ -591,7 +591,7 @@ class FileField(Field):
|
||||
# If the raw path is passed in, validate it's under the MEDIA_ROOT.
|
||||
def isWithinMediaRoot(field_data, all_data):
|
||||
f = os.path.abspath(os.path.join(settings.MEDIA_ROOT, field_data))
|
||||
if not f.startswith(os.path.normpath(settings.MEDIA_ROOT)):
|
||||
if not f.startswith(os.path.abspath(os.path.normpath(settings.MEDIA_ROOT))):
|
||||
raise validators.ValidationError, _("Enter a valid filename.")
|
||||
field_list[1].validator_list.append(isWithinMediaRoot)
|
||||
return field_list
|
||||
|
@ -286,7 +286,7 @@ def manipulator_validator_unique_together(field_name_list, opts, self, field_dat
|
||||
# This is really not going to work for fields that have different
|
||||
# form fields, e.g. DateTime.
|
||||
# This validation needs to occur after html2python to be effective.
|
||||
field_val = all_data.get(f.attname, None)
|
||||
field_val = all_data.get(f.name, None)
|
||||
if field_val is None:
|
||||
# This will be caught by another validator, assuming the field
|
||||
# doesn't have blank=True.
|
||||
|
@ -108,8 +108,13 @@ class FormWrapper(object):
|
||||
This allows dictionary-style lookups of formfields. It also handles feeding
|
||||
prepopulated data and validation error messages to the formfield objects.
|
||||
"""
|
||||
def __init__(self, manipulator, data, error_dict, edit_inline=True):
|
||||
self.manipulator, self.data = manipulator, data
|
||||
def __init__(self, manipulator, data=None, error_dict=None, edit_inline=True):
|
||||
self.manipulator = manipulator
|
||||
if data is None:
|
||||
data = {}
|
||||
if error_dict is None:
|
||||
error_dict = {}
|
||||
self.data = data
|
||||
self.error_dict = error_dict
|
||||
self._inline_collections = None
|
||||
self.edit_inline = edit_inline
|
||||
|
@ -188,17 +188,35 @@ url_re = re.compile(
|
||||
r'(?::\d+)?' # optional port
|
||||
r'(?:/?|/\S+)$', re.IGNORECASE)
|
||||
|
||||
try:
|
||||
from django.conf import settings
|
||||
URL_VALIDATOR_USER_AGENT = settings.URL_VALIDATOR_USER_AGENT
|
||||
except ImportError:
|
||||
# It's OK if Django settings aren't configured.
|
||||
URL_VALIDATOR_USER_AGENT = 'Django (http://www.djangoproject.com/)'
|
||||
|
||||
class URLField(RegexField):
|
||||
def __init__(self, required=True, verify_exists=False, widget=None):
|
||||
def __init__(self, required=True, verify_exists=False, widget=None,
|
||||
validator_user_agent=URL_VALIDATOR_USER_AGENT):
|
||||
RegexField.__init__(self, url_re, u'Enter a valid URL.', required, widget)
|
||||
self.verify_exists = verify_exists
|
||||
self.user_agent = validator_user_agent
|
||||
|
||||
def clean(self, value):
|
||||
value = RegexField.clean(self, value)
|
||||
if self.verify_exists:
|
||||
import urllib2
|
||||
from django.conf import settings
|
||||
headers = {
|
||||
"Accept": "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5",
|
||||
"Accept-Language": "en-us,en;q=0.5",
|
||||
"Accept-Charset": "ISO-8859-1,utf-8;q=0.7,*;q=0.7",
|
||||
"Connection": "close",
|
||||
"User-Agent": self.user_agent,
|
||||
}
|
||||
try:
|
||||
u = urllib2.urlopen(value)
|
||||
req = urllib2.Request(field_data, None, headers)
|
||||
u = urllib2.urlopen(req)
|
||||
except ValueError:
|
||||
raise ValidationError(u'Enter a valid URL.')
|
||||
except: # urllib2.URLError, httplib.InvalidURL, etc.
|
||||
|
@ -868,8 +868,11 @@ class Library(object):
|
||||
dict = func(*args)
|
||||
|
||||
if not getattr(self, 'nodelist', False):
|
||||
from django.template.loader import get_template
|
||||
t = get_template(file_name)
|
||||
from django.template.loader import get_template, select_template
|
||||
if hasattr(file_name, '__iter__'):
|
||||
t = select_template(file_name)
|
||||
else:
|
||||
t = get_template(file_name)
|
||||
self.nodelist = t.nodelist
|
||||
return self.nodelist.render(context_class(dict))
|
||||
|
||||
|
@ -421,7 +421,11 @@ def filesizeformat(bytes):
|
||||
Format the value like a 'human-readable' file size (i.e. 13 KB, 4.1 MB, 102
|
||||
bytes, etc).
|
||||
"""
|
||||
bytes = float(bytes)
|
||||
try:
|
||||
bytes = float(bytes)
|
||||
except TypeError:
|
||||
return "0 bytes"
|
||||
|
||||
if bytes < 1024:
|
||||
return "%d byte%s" % (bytes, bytes != 1 and 's' or '')
|
||||
if bytes < 1024 * 1024:
|
||||
|
@ -124,17 +124,27 @@ class ForNode(Node):
|
||||
return nodelist.render(context)
|
||||
|
||||
class IfChangedNode(Node):
|
||||
def __init__(self, nodelist):
|
||||
def __init__(self, nodelist, *varlist):
|
||||
self.nodelist = nodelist
|
||||
self._last_seen = None
|
||||
self._varlist = varlist
|
||||
|
||||
def render(self, context):
|
||||
if context.has_key('forloop') and context['forloop']['first']:
|
||||
self._last_seen = None
|
||||
content = self.nodelist.render(context)
|
||||
if content != self._last_seen:
|
||||
try:
|
||||
if self._varlist:
|
||||
# Consider multiple parameters.
|
||||
# This automatically behaves like a OR evaluation of the multiple variables.
|
||||
compare_to = [resolve_variable(var, context) for var in self._varlist]
|
||||
else:
|
||||
compare_to = self.nodelist.render(context)
|
||||
except VariableDoesNotExist:
|
||||
compare_to = None
|
||||
|
||||
if compare_to != self._last_seen:
|
||||
firstloop = (self._last_seen == None)
|
||||
self._last_seen = content
|
||||
self._last_seen = compare_to
|
||||
context.push()
|
||||
context['ifchanged'] = {'firstloop': firstloop}
|
||||
content = self.nodelist.render(context)
|
||||
@ -634,23 +644,34 @@ def ifchanged(parser, token):
|
||||
"""
|
||||
Check if a value has changed from the last iteration of a loop.
|
||||
|
||||
The 'ifchanged' block tag is used within a loop. It checks its own rendered
|
||||
contents against its previous state and only displays its content if the
|
||||
value has changed::
|
||||
The 'ifchanged' block tag is used within a loop. It has two possible uses.
|
||||
|
||||
<h1>Archive for {{ year }}</h1>
|
||||
1. Checks its own rendered contents against its previous state and only
|
||||
displays the content if it has changed. For example, this displays a list of
|
||||
days, only displaying the month if it changes::
|
||||
|
||||
{% for date in days %}
|
||||
{% ifchanged %}<h3>{{ date|date:"F" }}</h3>{% endifchanged %}
|
||||
<a href="{{ date|date:"M/d"|lower }}/">{{ date|date:"j" }}</a>
|
||||
{% endfor %}
|
||||
<h1>Archive for {{ year }}</h1>
|
||||
|
||||
{% for date in days %}
|
||||
{% ifchanged %}<h3>{{ date|date:"F" }}</h3>{% endifchanged %}
|
||||
<a href="{{ date|date:"M/d"|lower }}/">{{ date|date:"j" }}</a>
|
||||
{% endfor %}
|
||||
|
||||
2. If given a variable, check if that variable has changed. For example, the
|
||||
following shows the date every time it changes, but only shows the hour if both
|
||||
the hour and the date has changed::
|
||||
|
||||
{% for date in days %}
|
||||
{% ifchanged date.date %} {{date.date}} {% endifchanged %}
|
||||
{% ifchanged date.hour date.date %}
|
||||
{{date.hour}}
|
||||
{% endifchanged %}
|
||||
{% endfor %}
|
||||
"""
|
||||
bits = token.contents.split()
|
||||
if len(bits) != 1:
|
||||
raise TemplateSyntaxError, "'ifchanged' tag takes no arguments"
|
||||
nodelist = parser.parse(('endifchanged',))
|
||||
parser.delete_first_token()
|
||||
return IfChangedNode(nodelist)
|
||||
return IfChangedNode(nodelist, *bits[1:])
|
||||
ifchanged = register.tag(ifchanged)
|
||||
|
||||
#@register.tag
|
||||
|
@ -610,6 +610,15 @@ fails. If no message is passed in, a default message is used.
|
||||
string "123" is less than the string "2", for example. If you don't want
|
||||
string comparison here, you will need to write your own validator.
|
||||
|
||||
``NumberIsInRange``
|
||||
Takes two boundary number, ``lower`` and ``upper`` and checks that the
|
||||
field is greater than ``lower`` (if given) and less than ``upper`` (if
|
||||
given).
|
||||
|
||||
Both checks are inclusive; that is, ``NumberIsInRange(10, 20)`` will allow
|
||||
values of both 10 and 20. This validator only checks numeric fields
|
||||
(i.e. floats and integer fields).
|
||||
|
||||
``IsAPowerOf``
|
||||
Takes an integer argument and when called as a validator, checks that the
|
||||
field being validated is a power of the integer.
|
||||
|
@ -265,6 +265,14 @@ Default: ``''`` (Empty string)
|
||||
The name of the database to use. For SQLite, it's the full path to the database
|
||||
file.
|
||||
|
||||
DATABASE_OPTIONS
|
||||
----------------
|
||||
|
||||
Default: ``{}`` (Empty dictionary)
|
||||
|
||||
Extra parameters to use when connecting to the database. Consult backend
|
||||
module's document for available keywords.
|
||||
|
||||
DATABASE_PASSWORD
|
||||
-----------------
|
||||
|
||||
@ -821,6 +829,16 @@ manual configuration option (see below), Django will *not* touch the ``TZ``
|
||||
environment variable, and it'll be up to you to ensure your processes are
|
||||
running in the correct environment.
|
||||
|
||||
URL_VALIDATOR_USER_AGENT
|
||||
------------------------
|
||||
|
||||
Default: ``Django/<version> (http://www.djangoproject.com/)``
|
||||
|
||||
The string to use as the ``User-Agent`` header when checking to see if URLs
|
||||
exist (see the ``verify_exists`` option on URLField_).
|
||||
|
||||
.. URLField: ../model_api/#urlfield
|
||||
|
||||
USE_ETAGS
|
||||
---------
|
||||
|
||||
|
@ -95,7 +95,7 @@ latest five news items::
|
||||
from django.contrib.syndication.feeds import Feed
|
||||
from chicagocrime.models import NewsItem
|
||||
|
||||
class SiteNewsFeed(Feed):
|
||||
class LatestEntries(Feed):
|
||||
title = "Chicagocrime.org site news"
|
||||
link = "/sitenews/"
|
||||
description = "Updates on changes and additions to chicagocrime.org."
|
||||
@ -120,8 +120,8 @@ One thing's left to do. In an RSS feed, each ``<item>`` has a ``<title>``,
|
||||
put into those elements.
|
||||
|
||||
* To specify the contents of ``<title>`` and ``<description>``, create
|
||||
`Django templates`_ called ``feeds/sitenews_title.html`` and
|
||||
``feeds/sitenews_description.html``, where ``sitenews`` is the ``slug``
|
||||
`Django templates`_ called ``feeds/latest_title.html`` and
|
||||
``feeds/latest_description.html``, where ``latest`` is the ``slug``
|
||||
specified in the URLconf for the given feed. Note the ``.html`` extension
|
||||
is required. The RSS system renders that template for each item, passing
|
||||
it two template context variables:
|
||||
@ -145,6 +145,16 @@ put into those elements.
|
||||
Both ``get_absolute_url()`` and ``item_link()`` should return the item's
|
||||
URL as a normal Python string.
|
||||
|
||||
* For the LatestEntries example above, we could have very simple feed templates:
|
||||
|
||||
* latest_title.html::
|
||||
|
||||
{{ obj.title }}
|
||||
|
||||
* latest_description.html::
|
||||
|
||||
{{ obj.description }}
|
||||
|
||||
.. _chicagocrime.org: http://www.chicagocrime.org/
|
||||
.. _object-relational mapper: http://www.djangoproject.com/documentation/db_api/
|
||||
.. _Django templates: http://www.djangoproject.com/documentation/templates/
|
||||
|
@ -525,16 +525,29 @@ ifchanged
|
||||
|
||||
Check if a value has changed from the last iteration of a loop.
|
||||
|
||||
The ``ifchanged`` block tag is used within a loop. It checks its own rendered
|
||||
contents against its previous state and only displays its content if the value
|
||||
has changed::
|
||||
The 'ifchanged' block tag is used within a loop. It has two possible uses.
|
||||
|
||||
<h1>Archive for {{ year }}</h1>
|
||||
1. Checks its own rendered contents against its previous state and only
|
||||
displays the content if it has changed. For example, this displays a list of
|
||||
days, only displaying the month if it changes::
|
||||
|
||||
{% for day in days %}
|
||||
{% ifchanged %}<h3>{{ day|date:"F" }}</h3>{% endifchanged %}
|
||||
<a href="{{ day|date:"M/d"|lower }}/">{{ day|date:"j" }}</a>
|
||||
{% endfor %}
|
||||
<h1>Archive for {{ year }}</h1>
|
||||
|
||||
{% for date in days %}
|
||||
{% ifchanged %}<h3>{{ date|date:"F" }}</h3>{% endifchanged %}
|
||||
<a href="{{ date|date:"M/d"|lower }}/">{{ date|date:"j" }}</a>
|
||||
{% endfor %}
|
||||
|
||||
2. If given a variable, check if that variable has changed. For example, the
|
||||
following shows the date every time it changes, but only shows the hour if both
|
||||
the hour and the date has changed::
|
||||
|
||||
{% for date in days %}
|
||||
{% ifchanged date.date %} {{date.date}} {% endifchanged %}
|
||||
{% ifchanged date.hour date.date %}
|
||||
{{date.hour}}
|
||||
{% endifchanged %}
|
||||
{% endfor %}
|
||||
|
||||
ifequal
|
||||
~~~~~~~
|
||||
|
@ -64,4 +64,17 @@ True
|
||||
>>> paginator.last_on_page(1)
|
||||
9
|
||||
|
||||
# Add a few more records to test out the orphans feature.
|
||||
>>> for x in range(10, 13):
|
||||
... Article(headline="Article %s" % x, pub_date=datetime(2006, 10, 6)).save()
|
||||
|
||||
# With orphans set to 3 and 10 items per page, we should get all 12 items on a single page:
|
||||
>>> paginator = ObjectPaginator(Article.objects.all(), 10, orphans=3)
|
||||
>>> paginator.pages
|
||||
1
|
||||
|
||||
# With orphans only set to 1, we should get two pages:
|
||||
>>> paginator = ObjectPaginator(Article.objects.all(), 10, orphans=1)
|
||||
>>> paginator.pages
|
||||
2
|
||||
"""}
|
||||
|
@ -329,6 +329,21 @@ class Templates(unittest.TestCase):
|
||||
'ifchanged06': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% for x in numx %}{% ifchanged %}{{ x }}{% endifchanged %}{% endfor %}{% endfor %}', { 'num': (1, 1, 1), 'numx': (2, 2, 2)}, '1222'),
|
||||
'ifchanged07': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% for x in numx %}{% ifchanged %}{{ x }}{% endifchanged %}{% for y in numy %}{% ifchanged %}{{ y }}{% endifchanged %}{% endfor %}{% endfor %}{% endfor %}', { 'num': (1, 1, 1), 'numx': (2, 2, 2), 'numy': (3, 3, 3)}, '1233323332333'),
|
||||
|
||||
# Test one parameter given to ifchanged.
|
||||
'ifchanged-param01': ('{% for n in num %}{% ifchanged n %}..{% endifchanged %}{{ n }}{% endfor %}', { 'num': (1,2,3) }, '..1..2..3'),
|
||||
'ifchanged-param02': ('{% for n in num %}{% for x in numx %}{% ifchanged n %}..{% endifchanged %}{{ x }}{% endfor %}{% endfor %}', { 'num': (1,2,3), 'numx': (5,6,7) }, '..567..567..567'),
|
||||
|
||||
# Test multiple parameters to ifchanged.
|
||||
'ifchanged-param03': ('{% for n in num %}{{ n }}{% for x in numx %}{% ifchanged x n %}{{ x }}{% endifchanged %}{% endfor %}{% endfor %}', { 'num': (1,1,2), 'numx': (5,6,6) }, '156156256'),
|
||||
|
||||
# Test a date+hour like construct, where the hour of the last day
|
||||
# is the same but the date had changed, so print the hour anyway.
|
||||
'ifchanged-param04': ('{% for d in days %}{% ifchanged %}{{ d.day }}{% endifchanged %}{% for h in d.hours %}{% ifchanged d h %}{{ h }}{% endifchanged %}{% endfor %}{% endfor %}', {'days':[{'day':1, 'hours':[1,2,3]},{'day':2, 'hours':[3]},] }, '112323'),
|
||||
|
||||
# Logically the same as above, just written with explicit
|
||||
# ifchanged for the day.
|
||||
'ifchanged-param04': ('{% for d in days %}{% ifchanged d.day %}{{ d.day }}{% endifchanged %}{% for h in d.hours %}{% ifchanged d.day h %}{{ h }}{% endifchanged %}{% endfor %}{% endfor %}', {'days':[{'day':1, 'hours':[1,2,3]},{'day':2, 'hours':[3]},] }, '112323'),
|
||||
|
||||
### IFEQUAL TAG ###########################################################
|
||||
'ifequal01': ("{% ifequal a b %}yes{% endifequal %}", {"a": 1, "b": 2}, ""),
|
||||
'ifequal02': ("{% ifequal a b %}yes{% endifequal %}", {"a": 1, "b": 1}, "yes"),
|
||||
|
Loading…
x
Reference in New Issue
Block a user