diff options
| author | Brian Rosner <brosner@gmail.com> | 2008-06-10 04:03:09 +0000 |
|---|---|---|
| committer | Brian Rosner <brosner@gmail.com> | 2008-06-10 04:03:09 +0000 |
| commit | 4530a408c40647c195f27f81da1315e98e956f96 (patch) | |
| tree | ed8290751a7e284b9f030f4da97b422067ca9c95 /django | |
| parent | 3b92ced51856e740cd6fb3d3e5bb8ea932bd276e (diff) | |
newforms-admin: Merged from trunk up to [7602].
git-svn-id: http://code.djangoproject.com/svn/django/branches/newforms-admin@7604 bcc190cf-cafb-0310-a4f2-bffc1f526a37
Diffstat (limited to 'django')
| -rw-r--r-- | django/conf/global_settings.py | 2 | ||||
| -rw-r--r-- | django/contrib/auth/create_superuser.py | 94 | ||||
| -rw-r--r-- | django/contrib/auth/management/__init__.py (renamed from django/contrib/auth/management.py) | 10 | ||||
| -rw-r--r-- | django/contrib/auth/management/commands/__init__.py | 0 | ||||
| -rw-r--r-- | django/contrib/auth/management/commands/createsuperuser.py | 123 | ||||
| -rw-r--r-- | django/contrib/auth/models.py | 7 | ||||
| -rw-r--r-- | django/contrib/auth/tests/basic.py | 17 | ||||
| -rw-r--r-- | django/contrib/sessions/backends/base.py | 57 | ||||
| -rw-r--r-- | django/contrib/sessions/backends/cache.py | 8 | ||||
| -rw-r--r-- | django/contrib/sessions/backends/db.py | 2 | ||||
| -rw-r--r-- | django/contrib/sessions/middleware.py | 8 | ||||
| -rw-r--r-- | django/contrib/sessions/tests.py | 94 | ||||
| -rw-r--r-- | django/core/management/commands/loaddata.py | 35 | ||||
| -rw-r--r-- | django/core/management/sql.py | 2 | ||||
| -rw-r--r-- | django/core/serializers/base.py | 2 | ||||
| -rw-r--r-- | django/db/models/base.py | 13 | ||||
| -rw-r--r-- | django/db/models/options.py | 10 | ||||
| -rw-r--r-- | django/db/models/query.py | 4 | ||||
| -rw-r--r-- | django/db/models/sql/query.py | 16 |
19 files changed, 380 insertions, 124 deletions
diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py index 8eea31b0db..cdf71c00dc 100644 --- a/django/conf/global_settings.py +++ b/django/conf/global_settings.py @@ -289,7 +289,7 @@ SESSION_COOKIE_DOMAIN = None # A string like ".lawren SESSION_COOKIE_SECURE = False # Whether the session cookie should be secure (https:// only). SESSION_COOKIE_PATH = '/' # The path of the session cookie. SESSION_SAVE_EVERY_REQUEST = False # Whether to save the session data on every request. -SESSION_EXPIRE_AT_BROWSER_CLOSE = False # Whether sessions expire when a user closes his browser. +SESSION_EXPIRE_AT_BROWSER_CLOSE = False # Whether a user's session cookie expires when they close their browser. SESSION_ENGINE = 'django.contrib.sessions.backends.db' # The module to store session data SESSION_FILE_PATH = None # Directory to store session files if using the file session module. If None, the backend will use a sensible default. diff --git a/django/contrib/auth/create_superuser.py b/django/contrib/auth/create_superuser.py index 7b6cefd268..7b58678b78 100644 --- a/django/contrib/auth/create_superuser.py +++ b/django/contrib/auth/create_superuser.py @@ -1,94 +1,8 @@ """ -Helper function for creating superusers in the authentication system. - -If run from the command line, this module lets you create a superuser -interactively. +Create a superuser from the command line. Deprecated; use manage.py +createsuperuser instead. """ -from django.core import validators -from django.contrib.auth.models import User -import getpass -import os -import sys -import re - -RE_VALID_USERNAME = re.compile('\w+$') - -def createsuperuser(username=None, email=None, password=None): - """ - Helper function for creating a superuser from the command line. All - arguments are optional and will be prompted-for if invalid or not given. - """ - try: - import pwd - except ImportError: - default_username = '' - else: - # Determine the current system user's username, to use as a default. - default_username = pwd.getpwuid(os.getuid())[0].replace(' ', '').lower() - - # Determine whether the default username is taken, so we don't display - # it as an option. - if default_username: - try: - User.objects.get(username=default_username) - except User.DoesNotExist: - pass - else: - default_username = '' - - try: - while 1: - if not username: - input_msg = 'Username' - if default_username: - input_msg += ' (Leave blank to use %r)' % default_username - username = raw_input(input_msg + ': ') - if default_username and username == '': - username = default_username - if not RE_VALID_USERNAME.match(username): - sys.stderr.write("Error: That username is invalid. Use only letters, digits and underscores.\n") - username = None - continue - try: - User.objects.get(username=username) - except User.DoesNotExist: - break - else: - sys.stderr.write("Error: That username is already taken.\n") - username = None - while 1: - if not email: - email = raw_input('E-mail address: ') - try: - validators.isValidEmail(email, None) - except validators.ValidationError: - sys.stderr.write("Error: That e-mail address is invalid.\n") - email = None - else: - break - while 1: - if not password: - password = getpass.getpass() - password2 = getpass.getpass('Password (again): ') - if password != password2: - sys.stderr.write("Error: Your passwords didn't match.\n") - password = None - continue - if password.strip() == '': - sys.stderr.write("Error: Blank passwords aren't allowed.\n") - password = None - continue - break - except KeyboardInterrupt: - sys.stderr.write("\nOperation cancelled.\n") - sys.exit(1) - u = User.objects.create_user(username, email, password) - u.is_staff = True - u.is_active = True - u.is_superuser = True - u.save() - print "Superuser created successfully." - if __name__ == "__main__": - createsuperuser() + from django.core.management import call_command + call_command("createsuperuser") diff --git a/django/contrib/auth/management.py b/django/contrib/auth/management/__init__.py index 2b4cb8bd19..8394bee5cd 100644 --- a/django/contrib/auth/management.py +++ b/django/contrib/auth/management/__init__.py @@ -32,7 +32,7 @@ def create_permissions(app, created_models, verbosity): def create_superuser(app, created_models, verbosity, **kwargs): from django.contrib.auth.models import User - from django.contrib.auth.create_superuser import createsuperuser as do_create + from django.core.management import call_command if User in created_models and kwargs.get('interactive', True): msg = "\nYou just installed Django's auth system, which means you don't have " \ "any superusers defined.\nWould you like to create one now? (yes/no): " @@ -42,8 +42,10 @@ def create_superuser(app, created_models, verbosity, **kwargs): confirm = raw_input('Please enter either "yes" or "no": ') continue if confirm == 'yes': - do_create() + call_command("createsuperuser", interactive=True) break -dispatcher.connect(create_permissions, signal=signals.post_syncdb) -dispatcher.connect(create_superuser, sender=auth_app, signal=signals.post_syncdb) +if 'create_permissions' not in [i.__name__ for i in dispatcher.getAllReceivers(signal=signals.post_syncdb)]: + dispatcher.connect(create_permissions, signal=signals.post_syncdb) +if 'create_superuser' not in [i.__name__ for i in dispatcher.getAllReceivers(signal=signals.post_syncdb, sender=auth_app)]: + dispatcher.connect(create_superuser, sender=auth_app, signal=signals.post_syncdb)
\ No newline at end of file diff --git a/django/contrib/auth/management/commands/__init__.py b/django/contrib/auth/management/commands/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/django/contrib/auth/management/commands/__init__.py diff --git a/django/contrib/auth/management/commands/createsuperuser.py b/django/contrib/auth/management/commands/createsuperuser.py new file mode 100644 index 0000000000..4299762c74 --- /dev/null +++ b/django/contrib/auth/management/commands/createsuperuser.py @@ -0,0 +1,123 @@ +""" +Management utility to create superusers. +""" + +import getpass +import os +import re +import sys +from optparse import make_option +from django.contrib.auth.models import User, UNUSABLE_PASSWORD +from django.core import validators +from django.core.management.base import BaseCommand, CommandError + +RE_VALID_USERNAME = re.compile('\w+$') + +class Command(BaseCommand): + option_list = BaseCommand.option_list + ( + make_option('--username', dest='username', default=None, + help='Specifies the username for the superuser.'), + make_option('--email', dest='email', default=None, + help='Specifies the email address for the superuser.'), + make_option('--noinput', action='store_false', dest='interactive', default=True, + help='Tells Django to NOT prompt the user for input of any kind. ' \ + 'You must use --username and --email with --noinput, and ' \ + 'superusers created with --noinput will not be able to log in ' \ + 'until they\'re given a valid password.'), + ) + help = 'Used to create a superuser.' + + def handle(self, *args, **options): + username = options.get('username', None) + email = options.get('email', None) + interactive = options.get('interactive') + + # Do quick and dirty validation if --noinput + if not interactive: + if not username or not email: + raise CommandError("You must use --username and --email with --noinput.") + if not RE_VALID_USERNAME.match(username): + raise CommandError("Invalid username. Use only letters, digits, and underscores") + try: + validators.isValidEmail(email, None) + except validators.ValidationError: + raise CommandError("Invalid email address.") + + password = '' + + # Try to determine the current system user's username to use as a default. + try: + import pwd + except ImportError: + default_username = '' + else: + default_username = pwd.getpwuid(os.getuid())[0].replace(' ', '').lower() + + # Determine whether the default username is taken, so we don't display + # it as an option. + if default_username: + try: + User.objects.get(username=default_username) + except User.DoesNotExist: + pass + else: + default_username = '' + + # Prompt for username/email/password. Enclose this whole thing in a + # try/except to trap for a keyboard interrupt and exit gracefully. + if interactive: + try: + + # Get a username + while 1: + if not username: + input_msg = 'Username' + if default_username: + input_msg += ' (Leave blank to use %r)' % default_username + username = raw_input(input_msg + ': ') + if default_username and username == '': + username = default_username + if not RE_VALID_USERNAME.match(username): + sys.stderr.write("Error: That username is invalid. Use only letters, digits and underscores.\n") + username = None + continue + try: + User.objects.get(username=username) + except User.DoesNotExist: + break + else: + sys.stderr.write("Error: That username is already taken.\n") + username = None + + # Get an email + while 1: + if not email: + email = raw_input('E-mail address: ') + try: + validators.isValidEmail(email, None) + except validators.ValidationError: + sys.stderr.write("Error: That e-mail address is invalid.\n") + email = None + else: + break + + # Get a password + while 1: + if not password: + password = getpass.getpass() + password2 = getpass.getpass('Password (again): ') + if password != password2: + sys.stderr.write("Error: Your passwords didn't match.\n") + password = None + continue + if password.strip() == '': + sys.stderr.write("Error: Blank passwords aren't allowed.\n") + password = None + continue + break + except KeyboardInterrupt: + sys.stderr.write("\nOperation cancelled.\n") + sys.exit(1) + + User.objects.create_superuser(username, email, password) + print "Superuser created successfully." diff --git a/django/contrib/auth/models.py b/django/contrib/auth/models.py index e3f7541dda..56b4cbc082 100644 --- a/django/contrib/auth/models.py +++ b/django/contrib/auth/models.py @@ -113,6 +113,13 @@ class UserManager(models.Manager): user.save() return user + def create_superuser(self, username, email, password): + u = self.create_user(username, email, password) + u.is_staff = True + u.is_active = True + u.is_superuser = True + u.save() + def make_random_password(self, length=10, allowed_chars='abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789'): "Generates a random password with the given length and given allowed_chars" # Note that default value of allowed_chars does not have "I" or letters diff --git a/django/contrib/auth/tests/basic.py b/django/contrib/auth/tests/basic.py index 950815e791..8667ca9e5a 100644 --- a/django/contrib/auth/tests/basic.py +++ b/django/contrib/auth/tests/basic.py @@ -36,4 +36,21 @@ False [] >>> a.user_permissions.all() [] + +# +# Tests for createsuperuser management command. +# It's nearly impossible to test the interactive mode -- a command test helper +# would be needed (and *awesome*) -- so just test the non-interactive mode. +# This covers most of the important validation, but not all. +# +>>> from django.core.management import call_command + +>>> call_command("createsuperuser", noinput=True, username="joe", email="joe@somewhere.org") +Superuser created successfully. + +>>> u = User.objects.get(username="joe") +>>> u.email +u'joe@somewhere.org' +>>> u.password +u'!' """
\ No newline at end of file diff --git a/django/contrib/sessions/backends/base.py b/django/contrib/sessions/backends/base.py index b8726fd2bd..1063760915 100644 --- a/django/contrib/sessions/backends/base.py +++ b/django/contrib/sessions/backends/base.py @@ -4,6 +4,7 @@ import os import random import sys import time +from datetime import datetime, timedelta from django.conf import settings from django.core.exceptions import SuspiciousOperation @@ -128,6 +129,62 @@ class SessionBase(object): _session = property(_get_session) + def get_expiry_age(self): + """Get the number of seconds until the session expires.""" + expiry = self.get('_session_expiry') + if not expiry: # Checks both None and 0 cases + return settings.SESSION_COOKIE_AGE + if not isinstance(expiry, datetime): + return expiry + delta = expiry - datetime.now() + return delta.days * 86400 + delta.seconds + + def get_expiry_date(self): + """Get session the expiry date (as a datetime object).""" + expiry = self.get('_session_expiry') + if isinstance(expiry, datetime): + return expiry + if not expiry: # Checks both None and 0 cases + expiry = settings.SESSION_COOKIE_AGE + return datetime.now() + timedelta(seconds=expiry) + + def set_expiry(self, value): + """ + Sets a custom expiration for the session. ``value`` can be an integer, a + Python ``datetime`` or ``timedelta`` object or ``None``. + + If ``value`` is an integer, the session will expire after that many + seconds of inactivity. If set to ``0`` then the session will expire on + browser close. + + If ``value`` is a ``datetime`` or ``timedelta`` object, the session + will expire at that specific future time. + + If ``value`` is ``None``, the session uses the global session expiry + policy. + """ + if value is None: + # Remove any custom expiration for this session. + try: + del self['_session_expiry'] + except KeyError: + pass + return + if isinstance(value, timedelta): + value = datetime.now() + value + self['_session_expiry'] = value + + def get_expire_at_browser_close(self): + """ + Returns ``True`` if the session is set to expire when the browser + closes, and ``False`` if there's an expiry date. Use + ``get_expiry_date()`` or ``get_expiry_age()`` to find the actual expiry + date/age, if there is one. + """ + if self.get('_session_expiry') is None: + return settings.SESSION_EXPIRE_AT_BROWSER_CLOSE + return self.get('_session_expiry') == 0 + # Methods that child classes must implement. def exists(self, session_key): diff --git a/django/contrib/sessions/backends/cache.py b/django/contrib/sessions/backends/cache.py index c3e641e691..7626163a13 100644 --- a/django/contrib/sessions/backends/cache.py +++ b/django/contrib/sessions/backends/cache.py @@ -4,23 +4,23 @@ from django.core.cache import cache class SessionStore(SessionBase): """ - A cache-based session store. + A cache-based session store. """ def __init__(self, session_key=None): self._cache = cache super(SessionStore, self).__init__(session_key) - + def load(self): session_data = self._cache.get(self.session_key) return session_data or {} def save(self): - self._cache.set(self.session_key, self._session, settings.SESSION_COOKIE_AGE) + self._cache.set(self.session_key, self._session, self.get_expiry_age()) def exists(self, session_key): if self._cache.get(session_key): return True return False - + def delete(self, session_key): self._cache.delete(session_key)
\ No newline at end of file diff --git a/django/contrib/sessions/backends/db.py b/django/contrib/sessions/backends/db.py index 0f79d9ee1a..b1c1097865 100644 --- a/django/contrib/sessions/backends/db.py +++ b/django/contrib/sessions/backends/db.py @@ -41,7 +41,7 @@ class SessionStore(SessionBase): Session.objects.create( session_key = self.session_key, session_data = self.encode(self._session), - expire_date = datetime.datetime.now() + datetime.timedelta(seconds=settings.SESSION_COOKIE_AGE) + expire_date = self.get_expiry_date() ) def delete(self, session_key): diff --git a/django/contrib/sessions/middleware.py b/django/contrib/sessions/middleware.py index 2af2312e76..a7b376dde0 100644 --- a/django/contrib/sessions/middleware.py +++ b/django/contrib/sessions/middleware.py @@ -26,14 +26,14 @@ class SessionMiddleware(object): if accessed: patch_vary_headers(response, ('Cookie',)) if modified or settings.SESSION_SAVE_EVERY_REQUEST: - if settings.SESSION_EXPIRE_AT_BROWSER_CLOSE: + if request.session.get_expire_at_browser_close(): max_age = None expires = None else: - max_age = settings.SESSION_COOKIE_AGE - expires_time = time.time() + settings.SESSION_COOKIE_AGE + max_age = request.session.get_expiry_age() + expires_time = time.time() + max_age expires = cookie_date(expires_time) - # Save the seesion data and refresh the client cookie. + # Save the session data and refresh the client cookie. request.session.save() response.set_cookie(settings.SESSION_COOKIE_NAME, request.session.session_key, max_age=max_age, diff --git a/django/contrib/sessions/tests.py b/django/contrib/sessions/tests.py index b2c664ce7b..0f162b211f 100644 --- a/django/contrib/sessions/tests.py +++ b/django/contrib/sessions/tests.py @@ -88,6 +88,100 @@ False >>> s.pop('some key', 'does not exist') 'does not exist' + +######################### +# Custom session expiry # +######################### + +>>> from django.conf import settings +>>> from datetime import datetime, timedelta + +>>> td10 = timedelta(seconds=10) + +# A normal session has a max age equal to settings +>>> s.get_expiry_age() == settings.SESSION_COOKIE_AGE +True + +# So does a custom session with an idle expiration time of 0 (but it'll expire +# at browser close) +>>> s.set_expiry(0) +>>> s.get_expiry_age() == settings.SESSION_COOKIE_AGE +True + +# Custom session idle expiration time +>>> s.set_expiry(10) +>>> delta = s.get_expiry_date() - datetime.now() +>>> delta.seconds in (9, 10) +True +>>> age = s.get_expiry_age() +>>> age in (9, 10) +True + +# Custom session fixed expiry date (timedelta) +>>> s.set_expiry(td10) +>>> delta = s.get_expiry_date() - datetime.now() +>>> delta.seconds in (9, 10) +True +>>> age = s.get_expiry_age() +>>> age in (9, 10) +True + +# Custom session fixed expiry date (fixed datetime) +>>> s.set_expiry(datetime.now() + td10) +>>> delta = s.get_expiry_date() - datetime.now() +>>> delta.seconds in (9, 10) +True +>>> age = s.get_expiry_age() +>>> age in (9, 10) +True + +# Set back to default session age +>>> s.set_expiry(None) +>>> s.get_expiry_age() == settings.SESSION_COOKIE_AGE +True + +# Allow to set back to default session age even if no alternate has been set +>>> s.set_expiry(None) + + +# We're changing the setting then reverting back to the original setting at the +# end of these tests. +>>> original_expire_at_browser_close = settings.SESSION_EXPIRE_AT_BROWSER_CLOSE +>>> settings.SESSION_EXPIRE_AT_BROWSER_CLOSE = False + +# Custom session age +>>> s.set_expiry(10) +>>> s.get_expire_at_browser_close() +False + +# Custom expire-at-browser-close +>>> s.set_expiry(0) +>>> s.get_expire_at_browser_close() +True + +# Default session age +>>> s.set_expiry(None) +>>> s.get_expire_at_browser_close() +False + +>>> settings.SESSION_EXPIRE_AT_BROWSER_CLOSE = True + +# Custom session age +>>> s.set_expiry(10) +>>> s.get_expire_at_browser_close() +False + +# Custom expire-at-browser-close +>>> s.set_expiry(0) +>>> s.get_expire_at_browser_close() +True + +# Default session age +>>> s.set_expiry(None) +>>> s.get_expire_at_browser_close() +True + +>>> settings.SESSION_EXPIRE_AT_BROWSER_CLOSE = original_expire_at_browser_close """ if __name__ == '__main__': diff --git a/django/core/management/commands/loaddata.py b/django/core/management/commands/loaddata.py index d06b131d6f..193bb26ccf 100644 --- a/django/core/management/commands/loaddata.py +++ b/django/core/management/commands/loaddata.py @@ -32,6 +32,7 @@ class Command(BaseCommand): # Keep a count of the installed objects and fixtures fixture_count = 0 object_count = 0 + objects_per_fixture = [] models = set() humanize = lambda dirname: dirname and "'%s'" % dirname or 'absolute path' @@ -60,11 +61,16 @@ class Command(BaseCommand): else: formats = [] - if verbosity >= 2: - if formats: + if formats: + if verbosity > 1: print "Loading '%s' fixtures..." % fixture_name - else: - print "Skipping fixture '%s': %s is not a known serialization format" % (fixture_name, format) + else: + sys.stderr.write( + self.style.ERROR("Problem installing fixture '%s': %s is not a known serialization format." % + (fixture_name, format))) + transaction.rollback() + transaction.leave_transaction_management() + return if os.path.isabs(fixture_name): fixture_dirs = [fixture_name] @@ -93,6 +99,7 @@ class Command(BaseCommand): return else: fixture_count += 1 + objects_per_fixture.append(0) if verbosity > 0: print "Installing %s fixture '%s' from %s." % \ (format, fixture_name, humanize(fixture_dir)) @@ -100,6 +107,7 @@ class Command(BaseCommand): objects = serializers.deserialize(format, fixture) for obj in objects: object_count += 1 + objects_per_fixture[-1] += 1 models.add(obj.object.__class__) obj.save() label_found = True @@ -117,10 +125,23 @@ class Command(BaseCommand): return fixture.close() except: - if verbosity >= 2: + if verbosity > 1: print "No %s fixture '%s' in %s." % \ (format, fixture_name, humanize(fixture_dir)) + + # If any of the fixtures we loaded contain 0 objects, assume that an + # error was encountered during fixture loading. + if 0 in objects_per_fixture: + sys.stderr.write( + self.style.ERROR("No fixture data found for '%s'. (File format may be invalid.)" % + (fixture_name))) + transaction.rollback() + transaction.leave_transaction_management() + return + + # If we found even one object in a fixture, we need to reset the + # database sequences. if object_count > 0: sequence_sql = connection.ops.sequence_reset_sql(self.style, models) if sequence_sql: @@ -128,12 +149,12 @@ class Command(BaseCommand): print "Resetting sequences" for line in sequence_sql: cursor.execute(line) - + transaction.commit() transaction.leave_transaction_management() if object_count == 0: - if verbosity >= 2: + if verbosity > 1: print "No fixtures found." else: if verbosity > 0: diff --git a/django/core/management/sql.py b/django/core/management/sql.py index 574be5a1ee..c635fcab8a 100644 --- a/django/core/management/sql.py +++ b/django/core/management/sql.py @@ -446,7 +446,7 @@ def custom_sql_for_model(model): fp = open(sql_file, 'U') for statement in statements.split(fp.read().decode(settings.FILE_CHARSET)): # Remove any comments from the file - statement = re.sub(ur"--.*[\n\Z]", "", statement) + statement = re.sub(ur"--.*([\n\Z]|$)", "", statement) if statement.strip(): output.append(statement + u";") fp.close() diff --git a/django/core/serializers/base.py b/django/core/serializers/base.py index a79497ecec..e22a35815b 100644 --- a/django/core/serializers/base.py +++ b/django/core/serializers/base.py @@ -38,7 +38,7 @@ class Serializer(object): self.start_serialization() for obj in queryset: self.start_object(obj) - for field in obj._meta.fields: + for field in obj._meta.local_fields: if field.serialize: if field.rel is None: if self.selected_fields is None or field.attname in self.selected_fields: diff --git a/django/db/models/base.py b/django/db/models/base.py index 01c2f31794..a253f38f47 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -287,12 +287,17 @@ class Model(object): meta = cls._meta signal = False - for parent, field in meta.parents.items(): - self.save_base(raw, parent) - setattr(self, field.attname, self._get_pk_val(parent._meta)) + # If we are in a raw save, save the object exactly as presented. + # That means that we don't try to be smart about saving attributes + # that might have come from the parent class - we just save the + # attributes we have been given to the class we have been given. + if not raw: + for parent, field in meta.parents.items(): + self.save_base(raw, parent) + setattr(self, field.attname, self._get_pk_val(parent._meta)) non_pks = [f for f in meta.local_fields if not f.primary_key] - + # First, try an UPDATE. If that doesn't update anything, do an INSERT. pk_val = self._get_pk_val(meta) # Note: the comparison with '' is required for compatibility with diff --git a/django/db/models/options.py b/django/db/models/options.py index 3948a5fd10..c78ab1cd48 100644 --- a/django/db/models/options.py +++ b/django/db/models/options.py @@ -55,8 +55,12 @@ class Options(object): # Next, apply any overridden values from 'class Meta'. if self.meta: meta_attrs = self.meta.__dict__.copy() - del meta_attrs['__module__'] - del meta_attrs['__doc__'] + for name in self.meta.__dict__: + # Ignore any private attributes that Django doesn't care about. + # NOTE: We can't modify a dictionary's contents while looping + # over it, so we loop over the *original* dictionary instead. + if name.startswith('_'): + del meta_attrs[name] for attr_name in DEFAULT_NAMES: if attr_name in meta_attrs: setattr(self, attr_name, meta_attrs.pop(attr_name)) @@ -97,7 +101,7 @@ class Options(object): # field. field = self.parents.value_for_index(0) field.primary_key = True - self.pk = field + self.setup_pk(field) else: auto = AutoField(verbose_name='ID', primary_key=True, auto_created=True) diff --git a/django/db/models/query.py b/django/db/models/query.py index 6b341ba9ab..12731caa94 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -292,6 +292,8 @@ class QuerySet(object): Updates all elements in the current QuerySet, setting all the given fields to the appropriate values. """ + assert self.query.can_filter(), \ + "Cannot update a query once a slice has been taken." query = self.query.clone(sql.UpdateQuery) query.add_update_values(kwargs) query.execute_sql(None) @@ -306,6 +308,8 @@ class QuerySet(object): code (it requires too much poking around at model internals to be useful at that level). """ + assert self.query.can_filter(), \ + "Cannot update a query once a slice has been taken." query = self.query.clone(sql.UpdateQuery) query.add_update_fields(values) query.execute_sql(None) diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index a6957bab7b..3044882a86 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -851,7 +851,7 @@ class Query(object): return alias def fill_related_selections(self, opts=None, root_alias=None, cur_depth=1, - used=None, requested=None, restricted=None): + used=None, requested=None, restricted=None, nullable=None): """ Fill in the information needed for a select_related query. The current depth is measured as the number of connections away from the root model @@ -883,6 +883,10 @@ class Query(object): (not restricted and f.null) or f.rel.parent_link): continue table = f.rel.to._meta.db_table + if nullable or f.null: + promote = True + else: + promote = False if model: int_opts = opts alias = root_alias @@ -891,12 +895,12 @@ class Query(object): int_opts = int_model._meta alias = self.join((alias, int_opts.db_table, lhs_col, int_opts.pk.column), exclusions=used, - promote=f.null) + promote=promote) else: alias = root_alias alias = self.join((alias, table, f.column, f.rel.get_related_field().column), exclusions=used, - promote=f.null) + promote=promote) used.add(alias) self.related_select_cols.extend([(alias, f2.column) for f2 in f.rel.to._meta.fields]) @@ -905,8 +909,12 @@ class Query(object): next = requested.get(f.name, {}) else: next = False + if f.null is not None: + new_nullable = f.null + else: + new_nullable = None self.fill_related_selections(f.rel.to._meta, alias, cur_depth + 1, - used, next, restricted) + used, next, restricted, new_nullable) def add_filter(self, filter_expr, connector=AND, negate=False, trim=False, can_reuse=None): |
