1
0
mirror of https://github.com/django/django.git synced 2025-07-06 18:59:13 +00:00

queryset-refactor: Merged from trunk up to [6623].

git-svn-id: http://code.djangoproject.com/svn/django/branches/queryset-refactor@6637 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Malcolm Tredinnick 2007-11-03 02:15:27 +00:00
parent f951d97d99
commit f189280eb3
15 changed files with 203 additions and 91 deletions

View File

@ -1016,7 +1016,7 @@ msgstr "Escoja %s para modificar"
#: contrib/admin/views/main.py:780 #: contrib/admin/views/main.py:780
msgid "Database error" msgid "Database error"
msgstr "Erorr en la base de datos" msgstr "Error en la base de datos"
#: contrib/auth/forms.py:17 #: contrib/auth/forms.py:17
#: contrib/auth/forms.py:138 #: contrib/auth/forms.py:138

View File

@ -104,7 +104,7 @@ class PasswordResetForm(oldforms.Manipulator):
'site_name': site_name, 'site_name': site_name,
'user': user, 'user': user,
} }
send_mail('Password reset on %s' % site_name, t.render(Context(c)), None, [user.email]) send_mail(_('Password reset on %s') % site_name, t.render(Context(c)), None, [user.email])
class PasswordChangeForm(oldforms.Manipulator): class PasswordChangeForm(oldforms.Manipulator):
"A form that lets a user change his password." "A form that lets a user change his password."

View File

@ -242,6 +242,8 @@ def setup_environ(settings_mod):
""" """
Configures the runtime environment. This can also be used by external Configures the runtime environment. This can also be used by external
scripts wanting to set up a similar environment to manage.py. scripts wanting to set up a similar environment to manage.py.
Returns the project directory (assuming the passed settings module is
directly in the project directory).
""" """
# Add this project to sys.path so that it's importable in the conventional # Add this project to sys.path so that it's importable in the conventional
# way. For example, if this file (manage.py) lives in a directory # way. For example, if this file (manage.py) lives in a directory

View File

@ -1,8 +1,10 @@
from django.core.management.base import copy_helper, CommandError, LabelCommand
import os import os
from django.core.management.base import copy_helper, CommandError, LabelCommand
class Command(LabelCommand): class Command(LabelCommand):
help = "Creates a Django app directory structure for the given app name in the current directory." help = ("Creates a Django app directory structure for the given app name"
" in the current directory.")
args = "[appname]" args = "[appname]"
label = 'application name' label = 'application name'
@ -14,17 +16,18 @@ class Command(LabelCommand):
def handle_label(self, app_name, directory=None, **options): def handle_label(self, app_name, directory=None, **options):
if directory is None: if directory is None:
directory = os.getcwd() directory = os.getcwd()
# Determine the project_name a bit naively -- by looking at the name of # Determine the project_name by using the basename of directory,
# the parent directory. # which should be the full path of the project directory (or the
project_dir = os.path.normpath(os.path.join(directory, os.pardir)) # current directory if no directory was passed).
parent_dir = os.path.basename(project_dir)
project_name = os.path.basename(directory) project_name = os.path.basename(directory)
if app_name == project_name: if app_name == project_name:
raise CommandError("You cannot create an app with the same name (%r) as your project." % app_name) raise CommandError("You cannot create an app with the same name"
copy_helper(self.style, 'app', app_name, directory, parent_dir) " (%r) as your project." % app_name)
copy_helper(self.style, 'app', app_name, directory, project_name)
class ProjectCommand(Command): class ProjectCommand(Command):
help = "Creates a Django app directory structure for the given app name in this project's directory." help = ("Creates a Django app directory structure for the given app name"
" in this project's directory.")
def __init__(self, project_directory): def __init__(self, project_directory):
super(ProjectCommand, self).__init__() super(ProjectCommand, self).__init__()

View File

@ -127,6 +127,27 @@ class BaseDatabaseOperations(object):
""" """
raise NotImplementedError('Full-text search is not implemented for this database backend') raise NotImplementedError('Full-text search is not implemented for this database backend')
def last_executed_query(self, cursor, sql, params):
"""
Returns a string of the query last executed by the given cursor, with
placeholders replaced with actual values.
`sql` is the raw query containing placeholders, and `params` is the
sequence of parameters. These are used by default, but this method
exists for database backends to provide a better implementation
according to their own quoting schemes.
"""
from django.utils.encoding import smart_unicode, force_unicode
# Convert params to contain Unicode values.
to_unicode = lambda s: force_unicode(s, strings_only=True)
if isinstance(params, (list, tuple)):
u_params = tuple([to_unicode(val) for val in params])
else:
u_params = dict([(to_unicode(k), to_unicode(v)) for k, v in params.items()])
return smart_unicode(sql) % u_params
def last_insert_id(self, cursor, table_name, pk_name): def last_insert_id(self, cursor, table_name, pk_name):
""" """
Given a cursor object that has just performed an INSERT statement into Given a cursor object that has just performed an INSERT statement into

View File

@ -5,7 +5,7 @@ Requires psycopg 2: http://initd.org/projects/psycopg2
""" """
from django.db.backends import BaseDatabaseWrapper, BaseDatabaseFeatures from django.db.backends import BaseDatabaseWrapper, BaseDatabaseFeatures
from django.db.backends.postgresql.operations import DatabaseOperations from django.db.backends.postgresql.operations import DatabaseOperations as PostgresqlDatabaseOperations
try: try:
import psycopg2 as Database import psycopg2 as Database
import psycopg2.extensions import psycopg2.extensions
@ -21,6 +21,13 @@ psycopg2.extensions.register_type(psycopg2.extensions.UNICODE)
class DatabaseFeatures(BaseDatabaseFeatures): class DatabaseFeatures(BaseDatabaseFeatures):
needs_datetime_string_cast = False needs_datetime_string_cast = False
class DatabaseOperations(PostgresqlDatabaseOperations):
def last_executed_query(self, cursor, sql, params):
# With psycopg2, cursor objects have a "query" attribute that is the
# exact query sent to the database. See docs here:
# http://www.initd.org/tracker/psycopg/wiki/psycopg2_documentation#postgresql-status-message-and-executed-query
return cursor.query
class DatabaseWrapper(BaseDatabaseWrapper): class DatabaseWrapper(BaseDatabaseWrapper):
features = DatabaseFeatures() features = DatabaseFeatures()
ops = DatabaseOperations() ops = DatabaseOperations()

View File

@ -1,7 +1,6 @@
import datetime import datetime
import md5 import md5
from time import time from time import time
from django.utils.encoding import smart_unicode, force_unicode
try: try:
import decimal import decimal
@ -11,7 +10,7 @@ except ImportError:
class CursorDebugWrapper(object): class CursorDebugWrapper(object):
def __init__(self, cursor, db): def __init__(self, cursor, db):
self.cursor = cursor self.cursor = cursor
self.db = db self.db = db # Instance of a BaseDatabaseWrapper subclass
def execute(self, sql, params=()): def execute(self, sql, params=()):
start = time() start = time()
@ -19,8 +18,9 @@ class CursorDebugWrapper(object):
return self.cursor.execute(sql, params) return self.cursor.execute(sql, params)
finally: finally:
stop = time() stop = time()
sql = self.db.ops.last_executed_query(self.cursor, sql, params)
self.db.queries.append({ self.db.queries.append({
'sql': smart_unicode(sql) % convert_args(params), 'sql': sql,
'time': "%.3f" % (stop - start), 'time': "%.3f" % (stop - start),
}) })
@ -31,7 +31,7 @@ class CursorDebugWrapper(object):
finally: finally:
stop = time() stop = time()
self.db.queries.append({ self.db.queries.append({
'sql': 'MANY: ' + sql + ' ' + smart_unicode(tuple(param_list)), 'sql': '%s times: %s' % (len(param_list), sql),
'time': "%.3f" % (stop - start), 'time': "%.3f" % (stop - start),
}) })
@ -41,16 +41,6 @@ class CursorDebugWrapper(object):
else: else:
return getattr(self.cursor, attr) return getattr(self.cursor, attr)
def convert_args(args):
"""
Convert sequence or dictionary to contain unicode values.
"""
to_unicode = lambda s: force_unicode(s, strings_only=True)
if isinstance(args, (list, tuple)):
return tuple([to_unicode(val) for val in args])
else:
return dict([(to_unicode(k), to_unicode(v)) for k, v in args.items()])
############################################### ###############################################
# Converters from database (string) to Python # # Converters from database (string) to Python #
############################################### ###############################################

View File

@ -1,11 +1,20 @@
""" """
Field classes Field classes.
""" """
import copy import copy
import datetime import datetime
import re import re
import time import time
# Python 2.3 fallbacks
try:
from decimal import Decimal, DecimalException
except ImportError:
from django.utils._decimal import Decimal, DecimalException
try:
set
except NameError:
from sets import Set as set
from django.utils.translation import ugettext from django.utils.translation import ugettext
from django.utils.encoding import StrAndUnicode, smart_unicode from django.utils.encoding import StrAndUnicode, smart_unicode
@ -13,18 +22,14 @@ from django.utils.encoding import StrAndUnicode, smart_unicode
from util import ErrorList, ValidationError from util import ErrorList, ValidationError
from widgets import TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, FileInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple, DateTimeInput from widgets import TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, FileInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple, DateTimeInput
try:
from decimal import Decimal, DecimalException
except ImportError:
from django.utils._decimal import Decimal, DecimalException
__all__ = ( __all__ = (
'Field', 'CharField', 'IntegerField', 'Field', 'CharField', 'IntegerField',
'DEFAULT_DATE_INPUT_FORMATS', 'DateField', 'DEFAULT_DATE_INPUT_FORMATS', 'DateField',
'DEFAULT_TIME_INPUT_FORMATS', 'TimeField', 'DEFAULT_TIME_INPUT_FORMATS', 'TimeField',
'DEFAULT_DATETIME_INPUT_FORMATS', 'DateTimeField', 'DEFAULT_DATETIME_INPUT_FORMATS', 'DateTimeField',
'RegexField', 'EmailField', 'FileField', 'ImageField', 'URLField', 'BooleanField', 'RegexField', 'EmailField', 'FileField', 'ImageField', 'URLField',
'ChoiceField', 'NullBooleanField', 'MultipleChoiceField', 'BooleanField', 'NullBooleanField', 'ChoiceField', 'MultipleChoiceField',
'ComboField', 'MultiValueField', 'FloatField', 'DecimalField', 'ComboField', 'MultiValueField', 'FloatField', 'DecimalField',
'SplitDateTimeField', 'IPAddressField', 'SplitDateTimeField', 'IPAddressField',
) )
@ -32,15 +37,6 @@ __all__ = (
# These values, if given to to_python(), will trigger the self.required check. # These values, if given to to_python(), will trigger the self.required check.
EMPTY_VALUES = (None, '') EMPTY_VALUES = (None, '')
try:
set
except NameError:
from sets import Set as set # Python 2.3 fallback
try:
from decimal import Decimal
except ImportError:
from django.utils._decimal import Decimal # Python 2.3 fallback
class Field(object): class Field(object):
widget = TextInput # Default widget to use when rendering this type of Field. widget = TextInput # Default widget to use when rendering this type of Field.

View File

@ -1,8 +1,12 @@
"Translation helper functions" "Translation helper functions"
import os, re, sys import locale
import os
import re
import sys
import gettext as gettext_module import gettext as gettext_module
from cStringIO import StringIO from cStringIO import StringIO
from django.utils.encoding import force_unicode from django.utils.encoding import force_unicode
try: try:
@ -25,15 +29,25 @@ _active = {}
# The default translation is based on the settings file. # The default translation is based on the settings file.
_default = None _default = None
# This is a cache for accept-header to translation object mappings to prevent # This is a cache for normalised accept-header languages to prevent multiple
# the accept parser to run multiple times for one user. # file lookups when checking the same locale on repeated requests.
_accepted = {} _accepted = {}
def to_locale(language): # Format of Accept-Language header values. From RFC 2616, section 14.4 and 3.9.
accept_language_re = re.compile(r'''
([A-Za-z]{1,8}(?:-[A-Za-z]{1,8})*|\*) # "en", "en-au", "x-y-z", "*"
(?:;q=(0(?:\.\d{,3})?|1(?:.0{,3})?))? # Optional "q=1.00", "q=0.8"
(?:\s*,\s*|$) # Multiple accepts per header.
''', re.VERBOSE)
def to_locale(language, to_lower=False):
"Turns a language name (en-us) into a locale name (en_US)." "Turns a language name (en-us) into a locale name (en_US)."
p = language.find('-') p = language.find('-')
if p >= 0: if p >= 0:
return language[:p].lower()+'_'+language[p+1:].upper() if to_lower:
return language[:p].lower()+'_'+language[p+1:].lower()
else:
return language[:p].lower()+'_'+language[p+1:].upper()
else: else:
return language.lower() return language.lower()
@ -334,46 +348,40 @@ def get_language_from_request(request):
if lang_code in supported and lang_code is not None and check_for_language(lang_code): if lang_code in supported and lang_code is not None and check_for_language(lang_code):
return lang_code return lang_code
lang_code = request.COOKIES.get('django_language', None) lang_code = request.COOKIES.get('django_language')
if lang_code in supported and lang_code is not None and check_for_language(lang_code): if lang_code and lang_code in supported and check_for_language(lang_code):
return lang_code return lang_code
accept = request.META.get('HTTP_ACCEPT_LANGUAGE', None) accept = request.META.get('HTTP_ACCEPT_LANGUAGE', '')
if accept is not None: for lang, unused in parse_accept_lang_header(accept):
if lang == '*':
break
t = _accepted.get(accept, None) # We have a very restricted form for our language files (no encoding
if t is not None: # specifier, since they all must be UTF-8 and only one possible
return t # language each time. So we avoid the overhead of gettext.find() and
# look up the MO file manually.
def _parsed(el): normalized = locale.locale_alias.get(to_locale(lang, True))
p = el.find(';q=') if not normalized:
if p >= 0: continue
lang = el[:p].strip()
order = int(float(el[p+3:].strip())*100)
else:
lang = el
order = 100
p = lang.find('-')
if p >= 0:
mainlang = lang[:p]
else:
mainlang = lang
return (lang, mainlang, order)
langs = [_parsed(el) for el in accept.split(',')] # Remove the default encoding from locale_alias
langs.sort(lambda a,b: -1*cmp(a[2], b[2])) normalized = normalized.split('.')[0]
for lang, mainlang, order in langs: if normalized in _accepted:
if lang in supported or mainlang in supported: # We've seen this locale before and have an MO file for it, so no
langfile = gettext_module.find('django', globalpath, [to_locale(lang)]) # need to check again.
if langfile: return _accepted[normalized]
# reconstruct the actual language from the language
# filename, because otherwise we might incorrectly for lang in (normalized, normalized.split('_')[0]):
# report de_DE if we only have de available, but if lang not in supported:
# did find de_DE because of language normalization continue
lang = langfile[len(globalpath):].split(os.path.sep)[1] langfile = os.path.join(globalpath, lang, 'LC_MESSAGES',
_accepted[accept] = lang 'django.mo')
return lang if os.path.exists(langfile):
_accepted[normalized] = lang
return lang
return settings.LANGUAGE_CODE return settings.LANGUAGE_CODE
@ -505,3 +513,23 @@ def templatize(src):
out.write(blankout(t.contents, 'X')) out.write(blankout(t.contents, 'X'))
return out.getvalue() return out.getvalue()
def parse_accept_lang_header(lang_string):
"""
Parses the lang_string, which is the body of an HTTP Accept-Language
header, and returns a list of (lang, q-value), ordered by 'q' values.
Any format errors in lang_string results in an empty list being returned.
"""
result = []
pieces = accept_language_re.split(lang_string)
if pieces[-1]:
return []
for i in range(0, len(pieces) - 1, 3):
first, lang, priority = pieces[i : i + 3]
if first:
return []
priority = priority and float(priority) or 1.0
result.append((lang, priority))
result.sort(lambda x, y: -cmp(x[1], y[1]))
return result

View File

@ -741,22 +741,25 @@ Customized actions
**New in Django development version** **New in Django development version**
If you want to add an action of your own to ``manage.py``, you can. Applications can register their own actions with ``manage.py``. For example,
Simply add a ``management/commands`` directory to your application. you might want to add a ``manage.py`` action for a Django app that you're
Each python module in that directory will be discovered and registered as distributing.
To do this, just add a ``management/commands`` directory to your application.
Each Python module in that directory will be auto-discovered and registered as
a command that can be executed as an action when you run ``manage.py``:: a command that can be executed as an action when you run ``manage.py``::
/fancy_blog blog/
__init__.py __init__.py
models.py models.py
/management management/
__init__.py __init__.py
/commands commands/
__init__.py __init__.py
explode.py explode.py
views.py views.py
In this example, ``explode`` command will be made available to any project In this example, the ``explode`` command will be made available to any project
that includes the ``fancy_blog`` application in ``settings.INSTALLED_APPS``. that includes the ``fancy_blog`` application in ``settings.INSTALLED_APPS``.
The ``explode.py`` module has only one requirement -- it must define a class The ``explode.py`` module has only one requirement -- it must define a class

View File

@ -26,7 +26,6 @@ There were some problems with form translations in #3600
Translations are done at rendering time, so multi-lingual apps can define forms Translations are done at rendering time, so multi-lingual apps can define forms
early and still send back the right translation. early and still send back the right translation.
# XFAIL
>>> activate('de') >>> activate('de')
>>> print f.as_p() >>> print f.as_p()
<p><label for="id_username">Benutzername:</label> <input id="id_username" type="text" name="username" maxlength="10" /></p> <p><label for="id_username">Benutzername:</label> <input id="id_username" type="text" name="username" maxlength="10" /></p>

View File

@ -0,0 +1,57 @@
tests = """
>>> from django.utils.translation.trans_real import parse_accept_lang_header
>>> p = parse_accept_lang_header
Good headers.
>>> p('de')
[('de', 1.0)]
>>> p('en-AU')
[('en-AU', 1.0)]
>>> p('*;q=1.00')
[('*', 1.0)]
>>> p('en-AU;q=0.123')
[('en-AU', 0.123)]
>>> p('en-au;q=0.1')
[('en-au', 0.10000000000000001)]
>>> p('en-au;q=1.0')
[('en-au', 1.0)]
>>> p('da, en-gb;q=0.25, en;q=0.5')
[('da', 1.0), ('en', 0.5), ('en-gb', 0.25)]
>>> p('en-au-xx')
[('en-au-xx', 1.0)]
>>> p('de,en-au;q=0.75,en-us;q=0.5,en;q=0.25,es;q=0.125,fa;q=0.125')
[('de', 1.0), ('en-au', 0.75), ('en-us', 0.5), ('en', 0.25), ('es', 0.125), ('fa', 0.125)]
>>> p('*')
[('*', 1.0)]
>>> p('de;q=0.')
[('de', 1.0)]
>>> p('')
[]
Bad headers; should always return [].
>>> p('en-gb;q=1.0000')
[]
>>> p('en;q=0.1234')
[]
>>> p('en;q=.2')
[]
>>> p('abcdefghi-au')
[]
>>> p('**')
[]
>>> p('en,,gb')
[]
>>> p('en-au;q=0.1.0')
[]
>>> p('XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXZ,en')
[]
>>> p('da, en-gb;q=0.8, en;q=0.7,#')
[]
>>> p('de;q=2.0')
[]
>>> p('de;q=0.a')
[]
>>> p('')
[]
"""

View File

@ -1,6 +1,7 @@
# coding: utf-8 # coding: utf-8
import misc
ur""" regressions = ur"""
Format string interpolation should work with *_lazy objects. Format string interpolation should work with *_lazy objects.
>>> from django.utils.translation import ugettext_lazy, activate, deactivate, gettext_lazy >>> from django.utils.translation import ugettext_lazy, activate, deactivate, gettext_lazy
@ -39,3 +40,8 @@ unicode(string_concat(...)) should not raise a TypeError - #4796
>>> unicode(django.utils.translation.string_concat("dja", "ngo")) >>> unicode(django.utils.translation.string_concat("dja", "ngo"))
u'django' u'django'
""" """
__test__ = {
'regressions': regressions,
'misc': misc.tests,
}

View File

@ -7,7 +7,7 @@ urlpatterns = patterns('',
# Test urls for testing reverse lookups # Test urls for testing reverse lookups
(r'^$', views.index), (r'^$', views.index),
(r'^client/(\d+)/$', views.client), (r'^client/(\d+)/$', views.client),
(r'^client/(\d+)/(?P<action>[^/]+)/$', views.client_action), (r'^client/(?P<id>\d+)/(?P<action>[^/]+)/$', views.client_action),
url(r'^named-client/(\d+)/$', views.client, name="named.client"), url(r'^named-client/(\d+)/$', views.client, name="named.client"),
# Unicode strings are permitted everywhere. # Unicode strings are permitted everywhere.