diff options
| author | Joseph Kocherhans <joseph@jkocherhans.com> | 2007-09-25 00:08:38 +0000 |
|---|---|---|
| committer | Joseph Kocherhans <joseph@jkocherhans.com> | 2007-09-25 00:08:38 +0000 |
| commit | 609eaf130d2d9eac109aefa59bfced32b67b9eb1 (patch) | |
| tree | b8bf4fd18c0fe75a418525baefa485c5ce31ce2f /django | |
| parent | afb8f0a61969dff6450cd296be5eac2a5693ecd9 (diff) | |
newforms-admin: Merged to [6416]
git-svn-id: http://code.djangoproject.com/svn/django/branches/newforms-admin@6417 bcc190cf-cafb-0310-a4f2-bffc1f526a37
Diffstat (limited to 'django')
24 files changed, 426 insertions, 190 deletions
diff --git a/django/contrib/admin/templatetags/admin_modify.py b/django/contrib/admin/templatetags/admin_modify.py index 44ee2e55dd..f9cd20966e 100644 --- a/django/contrib/admin/templatetags/admin_modify.py +++ b/django/contrib/admin/templatetags/admin_modify.py @@ -40,7 +40,7 @@ class FieldWidgetNode(template.Node): default = None def __init__(self, bound_field_var): - self.bound_field_var = bound_field_var + self.bound_field_var = template.Variable(bound_field_var) def get_nodelist(cls, klass): if klass not in cls.nodelists: @@ -64,7 +64,7 @@ class FieldWidgetNode(template.Node): get_nodelist = classmethod(get_nodelist) def render(self, context): - bound_field = template.resolve_variable(self.bound_field_var, context) + bound_field = self.bound_field_var.resolve(context) context.push() context['bound_field'] = bound_field diff --git a/django/contrib/auth/backends.py b/django/contrib/auth/backends.py index 4b8efcca46..be6cfede11 100644 --- a/django/contrib/auth/backends.py +++ b/django/contrib/auth/backends.py @@ -1,3 +1,4 @@ +from django.db import connection from django.contrib.auth.models import User class ModelBackend: @@ -14,6 +15,49 @@ class ModelBackend: except User.DoesNotExist: return None + def get_group_permissions(self, user_obj): + "Returns a list of permission strings that this user has through his/her groups." + if not hasattr(user_obj, '_group_perm_cache'): + cursor = connection.cursor() + # The SQL below works out to the following, after DB quoting: + # cursor.execute(""" + # SELECT ct."app_label", p."codename" + # FROM "auth_permission" p, "auth_group_permissions" gp, "auth_user_groups" ug, "django_content_type" ct + # WHERE p."id" = gp."permission_id" + # AND gp."group_id" = ug."group_id" + # AND ct."id" = p."content_type_id" + # AND ug."user_id" = %s, [self.id]) + qn = connection.ops.quote_name + sql = """ + SELECT ct.%s, p.%s + FROM %s p, %s gp, %s ug, %s ct + WHERE p.%s = gp.%s + AND gp.%s = ug.%s + AND ct.%s = p.%s + AND ug.%s = %%s""" % ( + qn('app_label'), qn('codename'), + qn('auth_permission'), qn('auth_group_permissions'), + qn('auth_user_groups'), qn('django_content_type'), + qn('id'), qn('permission_id'), + qn('group_id'), qn('group_id'), + qn('id'), qn('content_type_id'), + qn('user_id'),) + cursor.execute(sql, [user_obj.id]) + user_obj._group_perm_cache = set(["%s.%s" % (row[0], row[1]) for row in cursor.fetchall()]) + return user_obj._group_perm_cache + + def get_all_permissions(self, user_obj): + if not hasattr(user_obj, '_perm_cache'): + user_obj._perm_cache = set([u"%s.%s" % (p.content_type.app_label, p.codename) for p in user_obj.user_permissions.select_related()]) + user_obj._perm_cache.update(self.get_group_permissions(user_obj)) + return user_obj._perm_cache + + def has_perm(self, user_obj, perm): + return perm in self.get_all_permissions(user_obj) + + def has_module_perms(self, user_obj, app_label): + return bool(len([p for p in self.get_all_permissions(user_obj) if p[:p.index('.')] == app_label])) + def get_user(self, user_id): try: return User.objects.get(pk=user_id) diff --git a/django/contrib/auth/models.py b/django/contrib/auth/models.py index baf7e6e210..62e4a8bc59 100644 --- a/django/contrib/auth/models.py +++ b/django/contrib/auth/models.py @@ -1,6 +1,7 @@ +from django.contrib import auth from django.core import validators from django.core.exceptions import ImproperlyConfigured -from django.db import connection, models +from django.db import models from django.db.models.manager import EmptyManager from django.contrib.contenttypes.models import ContentType from django.utils.encoding import smart_str @@ -195,64 +196,68 @@ class User(models.Model): return self.password != UNUSABLE_PASSWORD def get_group_permissions(self): - "Returns a list of permission strings that this user has through his/her groups." - if not hasattr(self, '_group_perm_cache'): - cursor = connection.cursor() - # The SQL below works out to the following, after DB quoting: - # cursor.execute(""" - # SELECT ct."app_label", p."codename" - # FROM "auth_permission" p, "auth_group_permissions" gp, "auth_user_groups" ug, "django_content_type" ct - # WHERE p."id" = gp."permission_id" - # AND gp."group_id" = ug."group_id" - # AND ct."id" = p."content_type_id" - # AND ug."user_id" = %s, [self.id]) - qn = connection.ops.quote_name - sql = """ - SELECT ct.%s, p.%s - FROM %s p, %s gp, %s ug, %s ct - WHERE p.%s = gp.%s - AND gp.%s = ug.%s - AND ct.%s = p.%s - AND ug.%s = %%s""" % ( - qn('app_label'), qn('codename'), - qn('auth_permission'), qn('auth_group_permissions'), - qn('auth_user_groups'), qn('django_content_type'), - qn('id'), qn('permission_id'), - qn('group_id'), qn('group_id'), - qn('id'), qn('content_type_id'), - qn('user_id'),) - cursor.execute(sql, [self.id]) - self._group_perm_cache = set(["%s.%s" % (row[0], row[1]) for row in cursor.fetchall()]) - return self._group_perm_cache + """ + Returns a list of permission strings that this user has through + his/her groups. This method queries all available auth backends. + """ + permissions = set() + for backend in auth.get_backends(): + if hasattr(backend, "get_group_permissions"): + permissions.update(backend.get_group_permissions(self)) + return permissions def get_all_permissions(self): - if not hasattr(self, '_perm_cache'): - self._perm_cache = set([u"%s.%s" % (p.content_type.app_label, p.codename) for p in self.user_permissions.select_related()]) - self._perm_cache.update(self.get_group_permissions()) - return self._perm_cache + permissions = set() + for backend in auth.get_backends(): + if hasattr(backend, "get_all_permissions"): + permissions.update(backend.get_all_permissions(self)) + return permissions def has_perm(self, perm): - "Returns True if the user has the specified permission." + """ + Returns True if the user has the specified permission. This method + queries all available auth backends, but returns immediately if any + backend returns True. Thus, a user who has permission from a single + auth backend is assumed to have permission in general. + """ + # Inactive users have no permissions. if not self.is_active: return False + + # Superusers have all permissions. if self.is_superuser: return True - return perm in self.get_all_permissions() + + # Otherwise we need to check the backends. + for backend in auth.get_backends(): + if hasattr(backend, "has_perm"): + if backend.has_perm(self, perm): + return True + return False def has_perms(self, perm_list): - "Returns True if the user has each of the specified permissions." + """Returns True if the user has each of the specified permissions.""" for perm in perm_list: if not self.has_perm(perm): return False return True def has_module_perms(self, app_label): - "Returns True if the user has any permissions in the given app label." + """ + Returns True if the user has any permissions in the given app + label. Uses pretty much the same logic as has_perm, above. + """ if not self.is_active: return False + if self.is_superuser: return True - return bool(len([p for p in self.get_all_permissions() if p[:p.index('.')] == app_label])) + + for backend in auth.get_backends(): + if hasattr(backend, "has_module_perms"): + if backend.has_module_perms(self, app_label): + return True + return False def get_and_delete_messages(self): messages = [] @@ -285,7 +290,12 @@ class User(models.Model): class Message(models.Model): """ - The message system is a lightweight way to queue messages for given users. A message is associated with a User instance (so it is only applicable for registered users). There's no concept of expiration or timestamps. Messages are created by the Django admin after successful actions. For example, "The poll Foo was created successfully." is a message. + The message system is a lightweight way to queue messages for given + users. A message is associated with a User instance (so it is only + applicable for registered users). There's no concept of expiration or + timestamps. Messages are created by the Django admin after successful + actions. For example, "The poll Foo was created successfully." is a + message. """ user = models.ForeignKey(User) message = models.TextField(_('message')) diff --git a/django/contrib/comments/templatetags/comments.py b/django/contrib/comments/templatetags/comments.py index 1d4628978d..959cec4c7f 100644 --- a/django/contrib/comments/templatetags/comments.py +++ b/django/contrib/comments/templatetags/comments.py @@ -19,6 +19,8 @@ class CommentFormNode(template.Node): ratings_optional=False, ratings_required=False, rating_options='', is_public=True): self.content_type = content_type + if obj_id_lookup_var is not None: + obj_id_lookup_var = template.Variable(obj_id_lookup_var) self.obj_id_lookup_var, self.obj_id, self.free = obj_id_lookup_var, obj_id, free self.photos_optional, self.photos_required = photos_optional, photos_required self.ratings_optional, self.ratings_required = ratings_optional, ratings_required @@ -32,7 +34,7 @@ class CommentFormNode(template.Node): context.push() if self.obj_id_lookup_var is not None: try: - self.obj_id = template.resolve_variable(self.obj_id_lookup_var, context) + self.obj_id = self.obj_id_lookup_var.resolve(context) except template.VariableDoesNotExist: return '' # Validate that this object ID is valid for this content-type. @@ -75,6 +77,8 @@ class CommentFormNode(template.Node): class CommentCountNode(template.Node): def __init__(self, package, module, context_var_name, obj_id, var_name, free): self.package, self.module = package, module + if context_var_name is not None: + context_var_name = template.Variable(context_var_name) self.context_var_name, self.obj_id = context_var_name, obj_id self.var_name, self.free = var_name, free @@ -82,7 +86,7 @@ class CommentCountNode(template.Node): from django.conf import settings manager = self.free and FreeComment.objects or Comment.objects if self.context_var_name is not None: - self.obj_id = template.resolve_variable(self.context_var_name, context) + self.obj_id = self.context_var_name.resolve(context) comment_count = manager.filter(object_id__exact=self.obj_id, content_type__app_label__exact=self.package, content_type__model__exact=self.module, site__id__exact=settings.SITE_ID).count() @@ -92,6 +96,8 @@ class CommentCountNode(template.Node): class CommentListNode(template.Node): def __init__(self, package, module, context_var_name, obj_id, var_name, free, ordering, extra_kwargs=None): self.package, self.module = package, module + if context_var_name is not None: + context_var_name = template.Variable(context_var_name) self.context_var_name, self.obj_id = context_var_name, obj_id self.var_name, self.free = var_name, free self.ordering = ordering @@ -102,7 +108,7 @@ class CommentListNode(template.Node): get_list_function = self.free and FreeComment.objects.filter or Comment.objects.get_list_with_karma if self.context_var_name is not None: try: - self.obj_id = template.resolve_variable(self.context_var_name, context) + self.obj_id = self.context_var_name.resolve(context) except template.VariableDoesNotExist: return '' kwargs = { diff --git a/django/contrib/sessions/backends/base.py b/django/contrib/sessions/backends/base.py index 382212bb70..e6be0482de 100644 --- a/django/contrib/sessions/backends/base.py +++ b/django/contrib/sessions/backends/base.py @@ -16,7 +16,6 @@ class SessionBase(object): """ Base class for all Session classes. """ - TEST_COOKIE_NAME = 'testcookie' TEST_COOKIE_VALUE = 'worked' @@ -59,7 +58,7 @@ class SessionBase(object): def delete_test_cookie(self): del self[self.TEST_COOKIE_NAME] - + def encode(self, session_dict): "Returns the given session dictionary pickled and encoded as a string." pickled = pickle.dumps(session_dict, pickle.HIGHEST_PROTOCOL) @@ -77,28 +76,33 @@ class SessionBase(object): # just return an empty dictionary (an empty session). except: return {} - + def _get_new_session_key(self): "Returns session key that isn't being used." # The random module is seeded when this Apache child is created. # Use settings.SECRET_KEY as added salt. + try: + pid = os.getpid() + except AttributeError: + # No getpid() in Jython, for example + pid = 1 while 1: - session_key = md5.new("%s%s%s%s" % (random.randint(0, sys.maxint - 1), - os.getpid(), time.time(), settings.SECRET_KEY)).hexdigest() + session_key = md5.new("%s%s%s%s" % (random.randint(0, sys.maxint - 1), + pid, time.time(), settings.SECRET_KEY)).hexdigest() if not self.exists(session_key): break return session_key - + def _get_session_key(self): if self._session_key: return self._session_key else: self._session_key = self._get_new_session_key() return self._session_key - + def _set_session_key(self, session_key): self._session_key = session_key - + session_key = property(_get_session_key, _set_session_key) def _get_session(self): @@ -114,9 +118,9 @@ class SessionBase(object): return self._session_cache _session = property(_get_session) - + # Methods that child classes must implement. - + def exists(self, session_key): """ Returns True if the given session_key already exists. @@ -140,4 +144,3 @@ class SessionBase(object): Loads the session data and returns a dictionary. """ raise NotImplementedError - diff --git a/django/core/management/__init__.py b/django/core/management/__init__.py index a4731652f5..e15dfccdf2 100644 --- a/django/core/management/__init__.py +++ b/django/core/management/__init__.py @@ -1,18 +1,104 @@ import django +from django.core.management.base import BaseCommand, CommandError, handle_default_options from optparse import OptionParser import os import sys +from imp import find_module # For backwards compatibility: get_version() used to be in this module. get_version = django.get_version -def load_command_class(name): +# A cache of loaded commands, so that call_command +# doesn't have to reload every time it is called +_commands = None + +def find_commands(management_dir): + """ + Given a path to a management directory, return a list of all the command names + that are available. Returns an empty list if no commands are defined. + """ + command_dir = os.path.join(management_dir,'commands') + try: + return [f[:-3] for f in os.listdir(command_dir) if not f.startswith('_') and f.endswith('.py')] + except OSError: + return [] + +def find_management_module(app_name): """ - Given a command name, returns the Command class instance. Raises - ImportError if it doesn't exist. + Determine the path to the management module for the application named, + without acutally importing the application or the management module. + + Raises ImportError if the management module cannot be found for any reason. + """ + parts = app_name.split('.') + parts.append('management') + parts.reverse() + path = None + while parts: + part = parts.pop() + f,path,descr = find_module(part, path and [path] or None) + return path + +def load_command_class(app_name, name): """ - # Let the ImportError propogate. - return getattr(__import__('django.core.management.commands.%s' % name, {}, {}, ['Command']), 'Command')() + Given a command name and an application name, returns the Command + class instance. All errors raised by the importation process + (ImportError, AttributeError) are allowed to propagate. + """ + return getattr(__import__('%s.management.commands.%s' % (app_name, name), + {}, {}, ['Command']), 'Command')() + +def get_commands(load_user_commands=True, project_directory=None): + """ + Returns a dictionary of commands against the application in which + those commands can be found. This works by looking for a + management.commands package in django.core, and in each installed + application -- if a commands package exists, all commands in that + package are registered. + + Core commands are always included; user-defined commands will also + be included if ``load_user_commands`` is True. If a project directory + is provided, the startproject command will be disabled, and the + startapp command will be modified to use that directory. + + The dictionary is in the format {command_name: app_name}. Key-value + pairs from this dictionary can then be used in calls to + load_command_class(app_name, command_name) + + If a specific version of a command must be loaded (e.g., with the + startapp command), the instantiated module can be placed in the + dictionary in place of the application name. + + The dictionary is cached on the first call, and reused on subsequent + calls. + """ + global _commands + if _commands is None: + _commands = dict([(name, 'django.core') + for name in find_commands(__path__[0])]) + if load_user_commands: + # Get commands from all installed apps + from django.conf import settings + for app_name in settings.INSTALLED_APPS: + try: + path = find_management_module(app_name) + _commands.update(dict([(name, app_name) + for name in find_commands(path)])) + except ImportError: + pass # No management module - ignore this app + + if project_directory: + # Remove the "startproject" command from self.commands, because + # that's a django-admin.py command, not a manage.py command. + del _commands['startproject'] + + # Override the startapp command so that it always uses the + # project_directory, not the current working directory + # (which is default). + from django.core.management.commands.startapp import ProjectCommand + _commands['startapp'] = ProjectCommand(project_directory) + + return _commands def call_command(name, *args, **options): """ @@ -25,8 +111,26 @@ def call_command(name, *args, **options): call_command('shell', plain=True) call_command('sqlall', 'myapp') """ - klass = load_command_class(name) + try: + app_name = get_commands()[name] + if isinstance(app_name, BaseCommand): + # If the command is already loaded, use it directly. + klass = app_name + else: + klass = load_command_class(app_name, name) + except KeyError: + raise CommandError, "Unknown command: %r" % name return klass.execute(*args, **options) + +class LaxOptionParser(OptionParser): + """ + An option parser that doesn't raise any errors on unknown options. + + This is needed because the --settings and --pythonpath options affect + the commands (and thus the options) that are available to the user. + """ + def error(self, msg): + pass class ManagementUtility(object): """ @@ -38,21 +142,9 @@ class ManagementUtility(object): def __init__(self, argv=None): self.argv = argv or sys.argv[:] self.prog_name = os.path.basename(self.argv[0]) - self.commands = self.default_commands() - - def default_commands(self): - """ - Returns a dictionary of instances of all available Command classes. - - This works by looking for and loading all Python modules in the - django.core.management.commands package. - - The dictionary is in the format {name: command_instance}. - """ - command_dir = os.path.join(__path__[0], 'commands') - names = [f[:-3] for f in os.listdir(command_dir) if not f.startswith('_') and f.endswith('.py')] - return dict([(name, load_command_class(name)) for name in names]) - + self.project_directory = None + self.user_commands = False + def main_help_text(self): """ Returns the script's main help text, as a string. @@ -61,7 +153,7 @@ class ManagementUtility(object): usage.append('Django command line tool, version %s' % django.get_version()) usage.append("Type '%s help <subcommand>' for help on a specific subcommand." % self.prog_name) usage.append('Available subcommands:') - commands = self.commands.keys() + commands = get_commands(self.user_commands, self.project_directory).keys() commands.sort() for cmd in commands: usage.append(' %s' % cmd) @@ -74,16 +166,33 @@ class ManagementUtility(object): django-admin.py or manage.py) if it can't be found. """ try: - return self.commands[subcommand] + app_name = get_commands(self.user_commands, self.project_directory)[subcommand] + if isinstance(app_name, BaseCommand): + # If the command is already loaded, use it directly. + klass = app_name + else: + klass = load_command_class(app_name, subcommand) except KeyError: sys.stderr.write("Unknown command: %r\nType '%s help' for usage.\n" % (subcommand, self.prog_name)) sys.exit(1) - + return klass + def execute(self): """ Given the command-line arguments, this figures out which subcommand is being run, creates a parser appropriate to that command, and runs it. """ + # Preprocess options to extract --settings and --pythonpath. These options + # could affect the commands that are available, so they must be processed + # early + parser = LaxOptionParser(version=get_version(), + option_list=BaseCommand.option_list) + try: + options, args = parser.parse_args(self.argv) + handle_default_options(options) + except: + pass # Ignore any option errors at this point. + try: subcommand = self.argv[1] except IndexError: @@ -91,8 +200,8 @@ class ManagementUtility(object): sys.exit(1) if subcommand == 'help': - if len(self.argv) > 2: - self.fetch_command(self.argv[2]).print_help(self.prog_name, self.argv[2]) + if len(args) > 2: + self.fetch_command(args[2]).print_help(self.prog_name, args[2]) else: sys.stderr.write(self.main_help_text() + '\n') sys.exit(1) @@ -116,16 +225,9 @@ class ProjectManagementUtility(ManagementUtility): """ def __init__(self, argv, project_directory): super(ProjectManagementUtility, self).__init__(argv) - - # Remove the "startproject" command from self.commands, because - # that's a django-admin.py command, not a manage.py command. - del self.commands['startproject'] - - # Override the startapp command so that it always uses the - # project_directory, not the current working directory (which is default). - from django.core.management.commands.startapp import ProjectCommand - self.commands['startapp'] = ProjectCommand(project_directory) - + self.project_directory = project_directory + self.user_commands = True + def setup_environ(settings_mod): """ Configure the runtime environment. This can also be used by external diff --git a/django/core/management/base.py b/django/core/management/base.py index d883fe23dc..26ecef4ff5 100644 --- a/django/core/management/base.py +++ b/django/core/management/base.py @@ -9,6 +9,17 @@ import os class CommandError(Exception): pass +def handle_default_options(options): + """ + Include any default options that all commands should accept + here so that ManagementUtility can handle them before searching + for user commands. + """ + if options.settings: + os.environ['DJANGO_SETTINGS_MODULE'] = options.settings + if options.pythonpath: + sys.path.insert(0, options.pythonpath) + class BaseCommand(object): # Metadata about this command. option_list = ( @@ -55,10 +66,7 @@ class BaseCommand(object): def run_from_argv(self, argv): parser = self.create_parser(argv[0], argv[1]) options, args = parser.parse_args(argv[2:]) - if options.settings: - os.environ['DJANGO_SETTINGS_MODULE'] = options.settings - if options.pythonpath: - sys.path.insert(0, options.pythonpath) + handle_default_options(options) self.execute(*args, **options.__dict__) def execute(self, *args, **options): diff --git a/django/db/backends/ado_mssql/creation.py b/django/db/backends/ado_mssql/creation.py index 1411ca4d6a..d4ba8f2897 100644 --- a/django/db/backends/ado_mssql/creation.py +++ b/django/db/backends/ado_mssql/creation.py @@ -6,10 +6,10 @@ DATA_TYPES = { 'DateField': 'smalldatetime', 'DateTimeField': 'smalldatetime', 'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)', - 'FileField': 'varchar(100)', - 'FilePathField': 'varchar(100)', + 'FileField': 'varchar(%(max_length)s)', + 'FilePathField': 'varchar(%(max_length)s)', 'FloatField': 'double precision', - 'ImageField': 'varchar(100)', + 'ImageField': 'varchar(%(max_length)s)', 'IntegerField': 'int', 'IPAddressField': 'char(15)', 'NullBooleanField': 'bit', diff --git a/django/db/backends/mysql/creation.py b/django/db/backends/mysql/creation.py index b2b3992651..efb351c07e 100644 --- a/django/db/backends/mysql/creation.py +++ b/django/db/backends/mysql/creation.py @@ -10,10 +10,10 @@ DATA_TYPES = { 'DateField': 'date', 'DateTimeField': 'datetime', 'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)', - 'FileField': 'varchar(100)', - 'FilePathField': 'varchar(100)', + 'FileField': 'varchar(%(max_length)s)', + 'FilePathField': 'varchar(%(max_length)s)', 'FloatField': 'double precision', - 'ImageField': 'varchar(100)', + 'ImageField': 'varchar(%(max_length)s)', 'IntegerField': 'integer', 'IPAddressField': 'char(15)', 'NullBooleanField': 'bool', diff --git a/django/db/backends/mysql_old/creation.py b/django/db/backends/mysql_old/creation.py index b2b3992651..efb351c07e 100644 --- a/django/db/backends/mysql_old/creation.py +++ b/django/db/backends/mysql_old/creation.py @@ -10,10 +10,10 @@ DATA_TYPES = { 'DateField': 'date', 'DateTimeField': 'datetime', 'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)', - 'FileField': 'varchar(100)', - 'FilePathField': 'varchar(100)', + 'FileField': 'varchar(%(max_length)s)', + 'FilePathField': 'varchar(%(max_length)s)', 'FloatField': 'double precision', - 'ImageField': 'varchar(100)', + 'ImageField': 'varchar(%(max_length)s)', 'IntegerField': 'integer', 'IPAddressField': 'char(15)', 'NullBooleanField': 'bool', diff --git a/django/db/backends/oracle/creation.py b/django/db/backends/oracle/creation.py index d080b5d283..f4ada55ac6 100644 --- a/django/db/backends/oracle/creation.py +++ b/django/db/backends/oracle/creation.py @@ -13,10 +13,10 @@ DATA_TYPES = { 'DateField': 'DATE', 'DateTimeField': 'TIMESTAMP', 'DecimalField': 'NUMBER(%(max_digits)s, %(decimal_places)s)', - 'FileField': 'NVARCHAR2(100)', - 'FilePathField': 'NVARCHAR2(100)', + 'FileField': 'NVARCHAR2(%(max_length)s)', + 'FilePathField': 'NVARCHAR2(%(max_length)s)', 'FloatField': 'DOUBLE PRECISION', - 'ImageField': 'NVARCHAR2(100)', + 'ImageField': 'NVARCHAR2(%(max_length)s)', 'IntegerField': 'NUMBER(11)', 'IPAddressField': 'VARCHAR2(15)', 'NullBooleanField': 'NUMBER(1) CHECK ((%(column)s IN (0,1)) OR (%(column)s IS NULL))', @@ -28,7 +28,7 @@ DATA_TYPES = { 'SmallIntegerField': 'NUMBER(11)', 'TextField': 'NCLOB', 'TimeField': 'TIMESTAMP', - 'URLField': 'VARCHAR2(200)', + 'URLField': 'VARCHAR2(%(max_length)s)', 'USStateField': 'CHAR(2)', } diff --git a/django/db/backends/postgresql/creation.py b/django/db/backends/postgresql/creation.py index ceffea19e6..b3e374da27 100644 --- a/django/db/backends/postgresql/creation.py +++ b/django/db/backends/postgresql/creation.py @@ -10,10 +10,10 @@ DATA_TYPES = { 'DateField': 'date', 'DateTimeField': 'timestamp with time zone', 'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)', - 'FileField': 'varchar(100)', - 'FilePathField': 'varchar(100)', + 'FileField': 'varchar(%(max_length)s)', + 'FilePathField': 'varchar(%(max_length)s)', 'FloatField': 'double precision', - 'ImageField': 'varchar(100)', + 'ImageField': 'varchar(%(max_length)s)', 'IntegerField': 'integer', 'IPAddressField': 'inet', 'NullBooleanField': 'boolean', diff --git a/django/db/backends/sqlite3/creation.py b/django/db/backends/sqlite3/creation.py index eccb19a160..54b75f23be 100644 --- a/django/db/backends/sqlite3/creation.py +++ b/django/db/backends/sqlite3/creation.py @@ -9,10 +9,10 @@ DATA_TYPES = { 'DateField': 'date', 'DateTimeField': 'datetime', 'DecimalField': 'decimal', - 'FileField': 'varchar(100)', - 'FilePathField': 'varchar(100)', + 'FileField': 'varchar(%(max_length)s)', + 'FilePathField': 'varchar(%(max_length)s)', 'FloatField': 'real', - 'ImageField': 'varchar(100)', + 'ImageField': 'varchar(%(max_length)s)', 'IntegerField': 'integer', 'IPAddressField': 'char(15)', 'NullBooleanField': 'bool', diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index 597271c997..67f14f9baf 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -684,8 +684,7 @@ class DecimalField(Field): class EmailField(CharField): def __init__(self, *args, **kwargs): - if 'max_length' not in kwargs: - kwargs['max_length'] = 75 + kwargs['max_length'] = kwargs.get('max_length', 75) CharField.__init__(self, *args, **kwargs) def get_internal_type(self): @@ -705,6 +704,7 @@ class EmailField(CharField): class FileField(Field): def __init__(self, verbose_name=None, name=None, upload_to='', **kwargs): self.upload_to = upload_to + kwargs['max_length'] = kwargs.get('max_length', 100) Field.__init__(self, verbose_name, name, **kwargs) def get_db_prep_save(self, value): @@ -806,6 +806,7 @@ class FileField(Field): class FilePathField(Field): def __init__(self, verbose_name=None, name=None, path='', match=None, recursive=False, **kwargs): self.path, self.match, self.recursive = path, match, recursive + kwargs['max_length'] = kwargs.get('max_length', 100) Field.__init__(self, verbose_name, name, **kwargs) def get_manipulator_field_objs(self): diff --git a/django/db/models/query.py b/django/db/models/query.py index 23d0bac6c8..4d0d295e97 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -1180,7 +1180,7 @@ def delete_objects(seen_objs): if field.rel and field.null and field.rel.to in seen_objs: setattr(instance, field.attname, None) - setattr(instance, cls._meta.pk.attname, None) dispatcher.send(signal=signals.post_delete, sender=cls, instance=instance) + setattr(instance, cls._meta.pk.attname, None) transaction.commit_unless_managed() diff --git a/django/middleware/http.py b/django/middleware/http.py index 78e066c67b..71cdf7aa5d 100644 --- a/django/middleware/http.py +++ b/django/middleware/http.py @@ -54,8 +54,7 @@ class SetRemoteAddrFromForwardedFor(object): except KeyError: return None else: - # HTTP_X_FORWARDED_FOR can be a comma-separated list of IPs. - # Take just the last one. - # See http://bob.pythonmac.org/archives/2005/09/23/apache-x-forwarded-for-caveat/ - real_ip = real_ip.split(",")[-1].strip() + # HTTP_X_FORWARDED_FOR can be a comma-separated list of IPs. The + # client's IP will be the first one. + real_ip = real_ip.split(",")[0].strip() request.META['REMOTE_ADDR'] = real_ip diff --git a/django/oldforms/__init__.py b/django/oldforms/__init__.py index 93cfa1d8fa..9bb90416c4 100644 --- a/django/oldforms/__init__.py +++ b/django/oldforms/__init__.py @@ -447,7 +447,7 @@ class LargeTextField(TextField): self.field_name, self.rows, self.cols, escape(data)) class HiddenField(FormField): - def __init__(self, field_name, is_required=False, validator_list=None): + def __init__(self, field_name, is_required=False, validator_list=None, max_length=None): if validator_list is None: validator_list = [] self.field_name, self.is_required = field_name, is_required self.validator_list = validator_list[:] @@ -674,7 +674,7 @@ class CheckboxSelectMultipleField(SelectMultipleField): #################### class FileUploadField(FormField): - def __init__(self, field_name, is_required=False, validator_list=None): + def __init__(self, field_name, is_required=False, validator_list=None, max_length=None): if validator_list is None: validator_list = [] self.field_name, self.is_required = field_name, is_required self.validator_list = [self.isNonEmptyFile] + validator_list @@ -946,7 +946,7 @@ class IPAddressField(TextField): class FilePathField(SelectField): "A SelectField whose choices are the files in a given directory." - def __init__(self, field_name, path, match=None, recursive=False, is_required=False, validator_list=None): + def __init__(self, field_name, path, match=None, recursive=False, is_required=False, validator_list=None, max_length=None): import os from django.db.models import BLANK_CHOICE_DASH if match is not None: diff --git a/django/template/__init__.py b/django/template/__init__.py index 449e0d0c28..1cfd85be06 100644 --- a/django/template/__init__.py +++ b/django/template/__init__.py @@ -88,8 +88,6 @@ UNKNOWN_SOURCE="<unknown source>" tag_re = re.compile('(%s.*?%s|%s.*?%s|%s.*?%s)' % (re.escape(BLOCK_TAG_START), re.escape(BLOCK_TAG_END), re.escape(VARIABLE_TAG_START), re.escape(VARIABLE_TAG_END), re.escape(COMMENT_TAG_START), re.escape(COMMENT_TAG_END))) -# matches if the string is valid number -number_re = re.compile(r'[-+]?(\d+|\d*\.\d+)$') # global dictionary of libraries that have been loaded using get_library libraries = {} @@ -564,18 +562,19 @@ class FilterExpression(object): elif constant_arg is not None: args.append((False, constant_arg.replace(r'\"', '"'))) elif var_arg: - args.append((True, var_arg)) + args.append((True, Variable(var_arg))) filter_func = parser.find_filter(filter_name) self.args_check(filter_name,filter_func, args) filters.append( (filter_func,args)) upto = match.end() if upto != len(token): raise TemplateSyntaxError, "Could not parse the remainder: '%s' from '%s'" % (token[upto:], token) - self.var, self.filters = var, filters + self.filters = filters + self.var = Variable(var) def resolve(self, context, ignore_failures=False): try: - obj = resolve_variable(self.var, context) + obj = self.var.resolve(context) except VariableDoesNotExist: if ignore_failures: obj = None @@ -595,7 +594,7 @@ class FilterExpression(object): if not lookup: arg_vals.append(arg) else: - arg_vals.append(resolve_variable(arg, context)) + arg_vals.append(arg.resolve(context)) obj = func(obj, *arg_vals) return obj @@ -637,37 +636,98 @@ class FilterExpression(object): def resolve_variable(path, context): """ Returns the resolved variable, which may contain attribute syntax, within - the given context. The variable may be a hard-coded string (if it begins - and ends with single or double quote marks). + the given context. + + Deprecated; use the Variable class instead. + """ + return Variable(path).resolve(context) - >>> c = {'article': {'section':'News'}} - >>> resolve_variable('article.section', c) - u'News' - >>> resolve_variable('article', c) - {'section': 'News'} - >>> class AClass: pass - >>> c = AClass() - >>> c.article = AClass() - >>> c.article.section = 'News' - >>> resolve_variable('article.section', c) - u'News' +class Variable(object): + """ + A template variable, resolvable against a given context. The variable may be + a hard-coded string (if it begins and ends with single or double quote + marks):: + + >>> c = {'article': {'section':'News'}} + >>> Variable('article.section').resolve(c) + u'News' + >>> Variable('article').resolve(c) + {'section': 'News'} + >>> class AClass: pass + >>> c = AClass() + >>> c.article = AClass() + >>> c.article.section = 'News' + >>> Variable('article.section').resolve(c) + u'News' (The example assumes VARIABLE_ATTRIBUTE_SEPARATOR is '.') """ - if number_re.match(path): - number_type = '.' in path and float or int - current = number_type(path) - elif path[0] in ('"', "'") and path[0] == path[-1]: - current = path[1:-1] - else: + + def __init__(self, var): + self.var = var + self.literal = None + self.lookups = None + + try: + # First try to treat this variable as a number. + # + # Note that this could cause an OverflowError here that we're not + # catching. Since this should only happen at compile time, that's + # probably OK. + self.literal = float(var) + + # So it's a float... is it an int? If the original value contained a + # dot or an "e" then it was a float, not an int. + if '.' not in var and 'e' not in var.lower(): + self.literal = int(self.literal) + + # "2." is invalid + if var.endswith('.'): + raise ValueError + + except ValueError: + # A ValueError means that the variable isn't a number. + # If it's wrapped with quotes (single or double), then + # we're also dealing with a literal. + if var[0] in "\"'" and var[0] == var[-1]: + self.literal = var[1:-1] + + else: + # Otherwise we'll set self.lookups so that resolve() knows we're + # dealing with a bonafide variable + self.lookups = tuple(var.split(VARIABLE_ATTRIBUTE_SEPARATOR)) + + def resolve(self, context): + """Resolve this variable against a given context.""" + if self.lookups is not None: + # We're dealing with a variable that needs to be resolved + return self._resolve_lookup(context) + else: + # We're dealing with a literal, so it's already been "resolved" + return self.literal + + def __repr__(self): + return "<%s: %r>" % (self.__class__.__name__, self.var) + + def __str__(self): + return self.var + + def _resolve_lookup(self, context): + """ + Performs resolution of a real variable (i.e. not a literal) against the + given context. + + As indicated by the method's name, this method is an implementation + detail and shouldn't be called by external code. Use Variable.resolve() + instead. + """ current = context - bits = path.split(VARIABLE_ATTRIBUTE_SEPARATOR) - while bits: + for bit in self.lookups: try: # dictionary lookup - current = current[bits[0]] + current = current[bit] except (TypeError, AttributeError, KeyError): try: # attribute lookup - current = getattr(current, bits[0]) + current = getattr(current, bit) if callable(current): if getattr(current, 'alters_data', False): current = settings.TEMPLATE_STRING_IF_INVALID @@ -685,27 +745,27 @@ def resolve_variable(path, context): raise except (TypeError, AttributeError): try: # list-index lookup - current = current[int(bits[0])] + current = current[int(bit)] except (IndexError, # list index out of range ValueError, # invalid literal for int() - KeyError, # current is a dict without `int(bits[0])` key + KeyError, # current is a dict without `int(bit)` key TypeError, # unsubscriptable object ): - raise VariableDoesNotExist("Failed lookup for key [%s] in %r", (bits[0], current)) # missing attribute + raise VariableDoesNotExist("Failed lookup for key [%s] in %r", (bit, current)) # missing attribute except Exception, e: if getattr(e, 'silent_variable_failure', False): current = settings.TEMPLATE_STRING_IF_INVALID else: raise - del bits[0] - if isinstance(current, (basestring, Promise)): - try: - current = force_unicode(current) - except UnicodeDecodeError: - # Failing to convert to unicode can happen sometimes (e.g. debug - # tracebacks). So we allow it in this particular instance. - pass - return current + + if isinstance(current, (basestring, Promise)): + try: + current = force_unicode(current) + except UnicodeDecodeError: + # Failing to convert to unicode can happen sometimes (e.g. debug + # tracebacks). So we allow it in this particular instance. + pass + return current class Node(object): def render(self, context): @@ -861,10 +921,10 @@ class Library(object): class SimpleNode(Node): def __init__(self, vars_to_resolve): - self.vars_to_resolve = vars_to_resolve + self.vars_to_resolve = map(Variable, vars_to_resolve) def render(self, context): - resolved_vars = [resolve_variable(var, context) for var in self.vars_to_resolve] + resolved_vars = [var.resolve(context) for var in self.vars_to_resolve] return func(*resolved_vars) compile_func = curry(generic_tag_compiler, params, defaults, getattr(func, "_decorated_function", func).__name__, SimpleNode) @@ -883,10 +943,10 @@ class Library(object): class InclusionNode(Node): def __init__(self, vars_to_resolve): - self.vars_to_resolve = vars_to_resolve + self.vars_to_resolve = map(Variable, vars_to_resolve) def render(self, context): - resolved_vars = [resolve_variable(var, context) for var in self.vars_to_resolve] + resolved_vars = [var.resolve(context) for var in self.vars_to_resolve] if takes_context: args = [context] + resolved_vars else: diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py index 1fd6d02c70..f1fcf5fe90 100644 --- a/django/template/defaultfilters.py +++ b/django/template/defaultfilters.py @@ -1,6 +1,6 @@ "Default variable filters" -from django.template import resolve_variable, Library +from django.template import Variable, Library from django.conf import settings from django.utils.translation import ugettext, ungettext from django.utils.encoding import force_unicode, smart_str, iri_to_uri @@ -297,7 +297,8 @@ def dictsort(value, arg): Takes a list of dicts, returns that list sorted by the property given in the argument. """ - decorated = [(resolve_variable(u'var.' + arg, {u'var' : item}), item) for item in value] + var_resolve = Variable(arg).resolve + decorated = [(var_resolve(item), item) for item in value] decorated.sort() return [item[1] for item in decorated] @@ -306,7 +307,8 @@ def dictsortreversed(value, arg): Takes a list of dicts, returns that list sorted in reverse order by the property given in the argument. """ - decorated = [(resolve_variable(u'var.' + arg, {u'var' : item}), item) for item in value] + var_resolve = Variable(arg).resolve + decorated = [(var_resolve(item), item) for item in value] decorated.sort() decorated.reverse() return [item[1] for item in decorated] diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py index e23295f732..151985bfdd 100644 --- a/django/template/defaulttags.py +++ b/django/template/defaulttags.py @@ -1,6 +1,6 @@ "Default tags used by the template system, available to all templates." -from django.template import Node, NodeList, Template, Context, resolve_variable +from django.template import Node, NodeList, Template, Context, Variable from django.template import TemplateSyntaxError, VariableDoesNotExist, BLOCK_TAG_START, BLOCK_TAG_END, VARIABLE_TAG_START, VARIABLE_TAG_END, SINGLE_BRACE_START, SINGLE_BRACE_END, COMMENT_TAG_START, COMMENT_TAG_END from django.template import get_library, Library, InvalidTemplateLibrary from django.conf import settings @@ -30,7 +30,7 @@ class CycleNode(Node): def render(self, context): self.counter += 1 value = self.cyclevars[self.counter % self.cyclevars_len] - value = resolve_variable(value, context) + value = Variable(value).resolve(context) if self.variable_name: context[self.variable_name] = value return value @@ -57,12 +57,12 @@ class FilterNode(Node): class FirstOfNode(Node): def __init__(self, vars): - self.vars = vars + self.vars = map(Variable, vars) def render(self, context): for var in self.vars: try: - value = resolve_variable(var, context) + value = var.resolve(context) except VariableDoesNotExist: continue if value: @@ -147,7 +147,7 @@ class IfChangedNode(Node): def __init__(self, nodelist, *varlist): self.nodelist = nodelist self._last_seen = None - self._varlist = varlist + self._varlist = map(Variable, varlist) def render(self, context): if 'forloop' in context and context['forloop']['first']: @@ -156,7 +156,7 @@ class IfChangedNode(Node): 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] + compare_to = [var.resolve(context) for var in self._varlist] else: compare_to = self.nodelist.render(context) except VariableDoesNotExist: @@ -175,7 +175,7 @@ class IfChangedNode(Node): class IfEqualNode(Node): def __init__(self, var1, var2, nodelist_true, nodelist_false, negate): - self.var1, self.var2 = var1, var2 + self.var1, self.var2 = Variable(var1), Variable(var2) self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false self.negate = negate @@ -184,11 +184,11 @@ class IfEqualNode(Node): def render(self, context): try: - val1 = resolve_variable(self.var1, context) + val1 = self.var1.resolve(context) except VariableDoesNotExist: val1 = None try: - val2 = resolve_variable(self.var2, context) + val2 = self.var2.resolve(context) except VariableDoesNotExist: val2 = None if (self.negate and val1 != val2) or (not self.negate and val1 == val2): diff --git a/django/template/loader_tags.py b/django/template/loader_tags.py index 19f368711c..652fda11ce 100644 --- a/django/template/loader_tags.py +++ b/django/template/loader_tags.py @@ -1,4 +1,4 @@ -from django.template import TemplateSyntaxError, TemplateDoesNotExist, resolve_variable +from django.template import TemplateSyntaxError, TemplateDoesNotExist, Variable from django.template import Library, Node from django.template.loader import get_template, get_template_from_string, find_template_source from django.conf import settings @@ -99,11 +99,11 @@ class ConstantIncludeNode(Node): class IncludeNode(Node): def __init__(self, template_name): - self.template_name = template_name + self.template_name = Variable(template_name) def render(self, context): try: - template_name = resolve_variable(self.template_name, context) + template_name = self.template_name.resolve(context) t = get_template(template_name) return t.render(context) except TemplateSyntaxError, e: diff --git a/django/templatetags/i18n.py b/django/templatetags/i18n.py index 1e85c6b5d1..d5b0741a61 100644 --- a/django/templatetags/i18n.py +++ b/django/templatetags/i18n.py @@ -1,4 +1,4 @@ -from django.template import Node, resolve_variable +from django.template import Node, Variable from django.template import TemplateSyntaxError, TokenParser, Library from django.template import TOKEN_TEXT, TOKEN_VAR from django.utils import translation @@ -32,11 +32,11 @@ class GetCurrentLanguageBidiNode(Node): class TranslateNode(Node): def __init__(self, value, noop): - self.value = value + self.value = Variable(value) self.noop = noop def render(self, context): - value = resolve_variable(self.value, context) + value = self.value.resolve(context) if self.noop: return value else: diff --git a/django/utils/datastructures.py b/django/utils/datastructures.py index 40e99c3962..2f3c9bb568 100644 --- a/django/utils/datastructures.py +++ b/django/utils/datastructures.py @@ -149,7 +149,7 @@ class MultiValueDict(dict): dict.__init__(self, key_to_list_mapping) def __repr__(self): - return "<MultiValueDict: %s>" % dict.__repr__(self) + return "<%s: %s>" % (self.__class__.__name__, dict.__repr__(self)) def __getitem__(self, key): """ diff --git a/django/utils/encoding.py b/django/utils/encoding.py index 69c3e9c28b..6daae4386d 100644 --- a/django/utils/encoding.py +++ b/django/utils/encoding.py @@ -1,5 +1,6 @@ import types import urllib +import datetime from django.utils.functional import Promise class StrAndUnicode(object): @@ -30,7 +31,7 @@ def force_unicode(s, encoding='utf-8', strings_only=False, errors='strict'): If strings_only is True, don't convert (some) non-string-like objects. """ - if strings_only and isinstance(s, (types.NoneType, int, long)): + if strings_only and isinstance(s, (types.NoneType, int, long, datetime.datetime, datetime.time, float)): return s if not isinstance(s, basestring,): if hasattr(s, '__unicode__'): |
