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

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
This commit is contained in:
Brian Rosner 2008-06-10 04:03:09 +00:00
parent 3b92ced518
commit 4530a408c4
36 changed files with 885 additions and 143 deletions

View File

@ -57,6 +57,7 @@ answer newbie questions, and generally made Django that much better:
David Ascher <http://ascher.ca/> David Ascher <http://ascher.ca/>
Jökull Sólberg Auðunsson <jokullsolberg@gmail.com> Jökull Sólberg Auðunsson <jokullsolberg@gmail.com>
Arthur <avandorp@gmail.com> Arthur <avandorp@gmail.com>
av0000@mail.ru
David Avsajanishvili <avsd05@gmail.com> David Avsajanishvili <avsd05@gmail.com>
axiak@mit.edu axiak@mit.edu
Niran Babalola <niran@niran.org> Niran Babalola <niran@niran.org>
@ -79,6 +80,7 @@ answer newbie questions, and generally made Django that much better:
brut.alll@gmail.com brut.alll@gmail.com
btoll@bestweb.net btoll@bestweb.net
Jonathan Buchanan <jonathan.buchanan@gmail.com> Jonathan Buchanan <jonathan.buchanan@gmail.com>
Keith Bussell <kbussell@gmail.com>
Juan Manuel Caicedo <juan.manuel.caicedo@gmail.com> Juan Manuel Caicedo <juan.manuel.caicedo@gmail.com>
Trevor Caira <trevor@caira.com> Trevor Caira <trevor@caira.com>
Ricardo Javier Cárdenes Medina <ricardo.cardenes@gmail.com> Ricardo Javier Cárdenes Medina <ricardo.cardenes@gmail.com>
@ -368,7 +370,7 @@ answer newbie questions, and generally made Django that much better:
Makoto Tsuyuki <mtsuyuki@gmail.com> Makoto Tsuyuki <mtsuyuki@gmail.com>
tt@gurgle.no tt@gurgle.no
David Tulig <david.tulig@gmail.com> David Tulig <david.tulig@gmail.com>
Amit Upadhyay Amit Upadhyay <http://www.amitu.com/blog/>
Geert Vanderkelen Geert Vanderkelen
I.S. van Oostveen <v.oostveen@idca.nl> I.S. van Oostveen <v.oostveen@idca.nl>
viestards.lists@gmail.com viestards.lists@gmail.com

View File

@ -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_SECURE = False # Whether the session cookie should be secure (https:// only).
SESSION_COOKIE_PATH = '/' # The path of the session cookie. SESSION_COOKIE_PATH = '/' # The path of the session cookie.
SESSION_SAVE_EVERY_REQUEST = False # Whether to save the session data on every request. 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_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. SESSION_FILE_PATH = None # Directory to store session files if using the file session module. If None, the backend will use a sensible default.

View File

@ -1,94 +1,8 @@
""" """
Helper function for creating superusers in the authentication system. Create a superuser from the command line. Deprecated; use manage.py
createsuperuser instead.
If run from the command line, this module lets you create a superuser
interactively.
""" """
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__": if __name__ == "__main__":
createsuperuser() from django.core.management import call_command
call_command("createsuperuser")

View File

@ -32,7 +32,7 @@ def create_permissions(app, created_models, verbosity):
def create_superuser(app, created_models, verbosity, **kwargs): def create_superuser(app, created_models, verbosity, **kwargs):
from django.contrib.auth.models import User 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): if User in created_models and kwargs.get('interactive', True):
msg = "\nYou just installed Django's auth system, which means you don't have " \ 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): " "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": ') confirm = raw_input('Please enter either "yes" or "no": ')
continue continue
if confirm == 'yes': if confirm == 'yes':
do_create() call_command("createsuperuser", interactive=True)
break break
dispatcher.connect(create_permissions, signal=signals.post_syncdb) if 'create_permissions' not in [i.__name__ for i in dispatcher.getAllReceivers(signal=signals.post_syncdb)]:
dispatcher.connect(create_superuser, sender=auth_app, 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)

View File

@ -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."

View File

@ -113,6 +113,13 @@ class UserManager(models.Manager):
user.save() user.save()
return user 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'): def make_random_password(self, length=10, allowed_chars='abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789'):
"Generates a random password with the given length and given allowed_chars" "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 # Note that default value of allowed_chars does not have "I" or letters

View File

@ -36,4 +36,21 @@ False
[] []
>>> a.user_permissions.all() >>> 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'!'
""" """

View File

@ -4,6 +4,7 @@ import os
import random import random
import sys import sys
import time import time
from datetime import datetime, timedelta
from django.conf import settings from django.conf import settings
from django.core.exceptions import SuspiciousOperation from django.core.exceptions import SuspiciousOperation
@ -128,6 +129,62 @@ class SessionBase(object):
_session = property(_get_session) _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. # Methods that child classes must implement.
def exists(self, session_key): def exists(self, session_key):

View File

@ -15,7 +15,7 @@ class SessionStore(SessionBase):
return session_data or {} return session_data or {}
def save(self): 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): def exists(self, session_key):
if self._cache.get(session_key): if self._cache.get(session_key):

View File

@ -41,7 +41,7 @@ class SessionStore(SessionBase):
Session.objects.create( Session.objects.create(
session_key = self.session_key, session_key = self.session_key,
session_data = self.encode(self._session), 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): def delete(self, session_key):

View File

@ -26,14 +26,14 @@ class SessionMiddleware(object):
if accessed: if accessed:
patch_vary_headers(response, ('Cookie',)) patch_vary_headers(response, ('Cookie',))
if modified or settings.SESSION_SAVE_EVERY_REQUEST: 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 max_age = None
expires = None expires = None
else: else:
max_age = settings.SESSION_COOKIE_AGE max_age = request.session.get_expiry_age()
expires_time = time.time() + settings.SESSION_COOKIE_AGE expires_time = time.time() + max_age
expires = cookie_date(expires_time) 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() request.session.save()
response.set_cookie(settings.SESSION_COOKIE_NAME, response.set_cookie(settings.SESSION_COOKIE_NAME,
request.session.session_key, max_age=max_age, request.session.session_key, max_age=max_age,

View File

@ -88,6 +88,100 @@ False
>>> s.pop('some key', 'does not exist') >>> s.pop('some key', 'does not exist')
'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__': if __name__ == '__main__':

View File

@ -32,6 +32,7 @@ class Command(BaseCommand):
# Keep a count of the installed objects and fixtures # Keep a count of the installed objects and fixtures
fixture_count = 0 fixture_count = 0
object_count = 0 object_count = 0
objects_per_fixture = []
models = set() models = set()
humanize = lambda dirname: dirname and "'%s'" % dirname or 'absolute path' humanize = lambda dirname: dirname and "'%s'" % dirname or 'absolute path'
@ -60,11 +61,16 @@ class Command(BaseCommand):
else: else:
formats = [] formats = []
if verbosity >= 2: if formats:
if formats: if verbosity > 1:
print "Loading '%s' fixtures..." % fixture_name print "Loading '%s' fixtures..." % fixture_name
else: else:
print "Skipping fixture '%s': %s is not a known serialization format" % (fixture_name, format) 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): if os.path.isabs(fixture_name):
fixture_dirs = [fixture_name] fixture_dirs = [fixture_name]
@ -93,6 +99,7 @@ class Command(BaseCommand):
return return
else: else:
fixture_count += 1 fixture_count += 1
objects_per_fixture.append(0)
if verbosity > 0: if verbosity > 0:
print "Installing %s fixture '%s' from %s." % \ print "Installing %s fixture '%s' from %s." % \
(format, fixture_name, humanize(fixture_dir)) (format, fixture_name, humanize(fixture_dir))
@ -100,6 +107,7 @@ class Command(BaseCommand):
objects = serializers.deserialize(format, fixture) objects = serializers.deserialize(format, fixture)
for obj in objects: for obj in objects:
object_count += 1 object_count += 1
objects_per_fixture[-1] += 1
models.add(obj.object.__class__) models.add(obj.object.__class__)
obj.save() obj.save()
label_found = True label_found = True
@ -117,10 +125,23 @@ class Command(BaseCommand):
return return
fixture.close() fixture.close()
except: except:
if verbosity >= 2: if verbosity > 1:
print "No %s fixture '%s' in %s." % \ print "No %s fixture '%s' in %s." % \
(format, fixture_name, humanize(fixture_dir)) (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: if object_count > 0:
sequence_sql = connection.ops.sequence_reset_sql(self.style, models) sequence_sql = connection.ops.sequence_reset_sql(self.style, models)
if sequence_sql: if sequence_sql:
@ -133,7 +154,7 @@ class Command(BaseCommand):
transaction.leave_transaction_management() transaction.leave_transaction_management()
if object_count == 0: if object_count == 0:
if verbosity >= 2: if verbosity > 1:
print "No fixtures found." print "No fixtures found."
else: else:
if verbosity > 0: if verbosity > 0:

View File

@ -446,7 +446,7 @@ def custom_sql_for_model(model):
fp = open(sql_file, 'U') fp = open(sql_file, 'U')
for statement in statements.split(fp.read().decode(settings.FILE_CHARSET)): for statement in statements.split(fp.read().decode(settings.FILE_CHARSET)):
# Remove any comments from the file # Remove any comments from the file
statement = re.sub(ur"--.*[\n\Z]", "", statement) statement = re.sub(ur"--.*([\n\Z]|$)", "", statement)
if statement.strip(): if statement.strip():
output.append(statement + u";") output.append(statement + u";")
fp.close() fp.close()

View File

@ -38,7 +38,7 @@ class Serializer(object):
self.start_serialization() self.start_serialization()
for obj in queryset: for obj in queryset:
self.start_object(obj) self.start_object(obj)
for field in obj._meta.fields: for field in obj._meta.local_fields:
if field.serialize: if field.serialize:
if field.rel is None: if field.rel is None:
if self.selected_fields is None or field.attname in self.selected_fields: if self.selected_fields is None or field.attname in self.selected_fields:

View File

@ -287,9 +287,14 @@ class Model(object):
meta = cls._meta meta = cls._meta
signal = False signal = False
for parent, field in meta.parents.items(): # If we are in a raw save, save the object exactly as presented.
self.save_base(raw, parent) # That means that we don't try to be smart about saving attributes
setattr(self, field.attname, self._get_pk_val(parent._meta)) # 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] non_pks = [f for f in meta.local_fields if not f.primary_key]

View File

@ -55,8 +55,12 @@ class Options(object):
# Next, apply any overridden values from 'class Meta'. # Next, apply any overridden values from 'class Meta'.
if self.meta: if self.meta:
meta_attrs = self.meta.__dict__.copy() meta_attrs = self.meta.__dict__.copy()
del meta_attrs['__module__'] for name in self.meta.__dict__:
del meta_attrs['__doc__'] # 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: for attr_name in DEFAULT_NAMES:
if attr_name in meta_attrs: if attr_name in meta_attrs:
setattr(self, attr_name, meta_attrs.pop(attr_name)) setattr(self, attr_name, meta_attrs.pop(attr_name))
@ -97,7 +101,7 @@ class Options(object):
# field. # field.
field = self.parents.value_for_index(0) field = self.parents.value_for_index(0)
field.primary_key = True field.primary_key = True
self.pk = field self.setup_pk(field)
else: else:
auto = AutoField(verbose_name='ID', primary_key=True, auto = AutoField(verbose_name='ID', primary_key=True,
auto_created=True) auto_created=True)

View File

@ -292,6 +292,8 @@ class QuerySet(object):
Updates all elements in the current QuerySet, setting all the given Updates all elements in the current QuerySet, setting all the given
fields to the appropriate values. 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 = self.query.clone(sql.UpdateQuery)
query.add_update_values(kwargs) query.add_update_values(kwargs)
query.execute_sql(None) query.execute_sql(None)
@ -306,6 +308,8 @@ class QuerySet(object):
code (it requires too much poking around at model internals to be code (it requires too much poking around at model internals to be
useful at that level). 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 = self.query.clone(sql.UpdateQuery)
query.add_update_fields(values) query.add_update_fields(values)
query.execute_sql(None) query.execute_sql(None)

View File

@ -851,7 +851,7 @@ class Query(object):
return alias return alias
def fill_related_selections(self, opts=None, root_alias=None, cur_depth=1, 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 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 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): (not restricted and f.null) or f.rel.parent_link):
continue continue
table = f.rel.to._meta.db_table table = f.rel.to._meta.db_table
if nullable or f.null:
promote = True
else:
promote = False
if model: if model:
int_opts = opts int_opts = opts
alias = root_alias alias = root_alias
@ -891,12 +895,12 @@ class Query(object):
int_opts = int_model._meta int_opts = int_model._meta
alias = self.join((alias, int_opts.db_table, lhs_col, alias = self.join((alias, int_opts.db_table, lhs_col,
int_opts.pk.column), exclusions=used, int_opts.pk.column), exclusions=used,
promote=f.null) promote=promote)
else: else:
alias = root_alias alias = root_alias
alias = self.join((alias, table, f.column, alias = self.join((alias, table, f.column,
f.rel.get_related_field().column), exclusions=used, f.rel.get_related_field().column), exclusions=used,
promote=f.null) promote=promote)
used.add(alias) used.add(alias)
self.related_select_cols.extend([(alias, f2.column) self.related_select_cols.extend([(alias, f2.column)
for f2 in f.rel.to._meta.fields]) for f2 in f.rel.to._meta.fields])
@ -905,8 +909,12 @@ class Query(object):
next = requested.get(f.name, {}) next = requested.get(f.name, {})
else: else:
next = False 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, 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, def add_filter(self, filter_expr, connector=AND, negate=False, trim=False,
can_reuse=None): can_reuse=None):

View File

@ -263,14 +263,25 @@ Creating superusers
------------------- -------------------
``manage.py syncdb`` prompts you to create a superuser the first time you run ``manage.py syncdb`` prompts you to create a superuser the first time you run
it after adding ``'django.contrib.auth'`` to your ``INSTALLED_APPS``. But if it after adding ``'django.contrib.auth'`` to your ``INSTALLED_APPS``. If you need
you need to create a superuser after that via the command line, you can use the to create a superuser at a later date, you can use a command line utility.
``create_superuser.py`` utility. Just run this command::
**New in Django development version.**::
manage.py createsuperuser --username=joe --email=joe@example.com
You will be prompted for a password. Once entered, the user is created. If you
leave off the ``--username`` or the ``--email`` option, It will prompt you for
those values as well.
If you're using an older release of Django, the old way of creating a superuser
on the command line still works::
python /path/to/django/contrib/auth/create_superuser.py python /path/to/django/contrib/auth/create_superuser.py
Make sure to substitute ``/path/to/`` with the path to the Django codebase on Where ``/path/to`` is the path to the Django codebase on your filesystem. The
your filesystem. ``manage.py`` command is prefered since it'll figure out the correct path and
environment for you.
Storing additional information about users Storing additional information about users
------------------------------------------ ------------------------------------------

View File

@ -109,6 +109,31 @@ the program name (``psql``, ``mysql``, ``sqlite3``) will find the program in
the right place. There's no way to specify the location of the program the right place. There's no way to specify the location of the program
manually. manually.
createsuperuser
---------------
**New in Django development version**
Creates a superuser account (a user who has all permissions). This is
useful if you need to create an initial superuser account but did not
do so during ``syncdb``, or if you need to programmatically generate
superuser accounts for your site(s).
When run interactively, this command will prompt for a password for
the new superuser account; when run non-interactively, no password
will be set and the superuser account will not be able to log in until
a password has been manually set for it.
The username and e-mail address for the new account can be supplied by
using the ``--username`` and ``--email`` arguments on the command
line; if not supplied, ``createsuperuser`` will prompt for them when
running interactively.
This command is only available if Django's `authentication system`_
(``django.contrib.auth``) is installed.
.. _authentication system: ../authentication/
diffsettings diffsettings
------------ ------------

View File

@ -228,13 +228,14 @@ Short answer: When we're comfortable with Django's APIs, have added all
features that we feel are necessary to earn a "1.0" status, and are ready to features that we feel are necessary to earn a "1.0" status, and are ready to
begin maintaining backwards compatibility. begin maintaining backwards compatibility.
The merging of Django's `magic-removal branch`_ went a long way toward Django The merging of Django's `Queryset Refactor branch`_ went a long way toward Django
1.0. 1.0. Merging the `Newforms Admin branch` will be another important step.
Of course, you should note that `quite a few production sites`_ use Django in Of course, you should note that `quite a few production sites`_ use Django in
its current status. Don't let the lack of a 1.0 turn you off. its current status. Don't let the lack of a 1.0 turn you off.
.. _magic-removal branch: http://code.djangoproject.com/wiki/RemovingTheMagic .. _Queryset Refactor branch: http://code.djangoproject.com/wiki/QuerysetRefactorBranch
.. _Newforms Admin branch: http://code.djangoproject.com/wiki/NewformsAdminBranch
.. _quite a few production sites: http://code.djangoproject.com/wiki/DjangoPoweredSites .. _quite a few production sites: http://code.djangoproject.com/wiki/DjangoPoweredSites
How can I download the Django documentation to read it offline? How can I download the Django documentation to read it offline?
@ -259,7 +260,9 @@ Where can I find Django developers for hire?
Consult our `developers for hire page`_ for a list of Django developers who Consult our `developers for hire page`_ for a list of Django developers who
would be happy to help you. would be happy to help you.
You might also be interested in posting a job to http://www.gypsyjobs.com/ . You might also be interested in posting a job to http://djangogigs.com/ .
If you want to find Django-capable people in your local area, try
http://djangopeople.net/ .
.. _developers for hire page: http://code.djangoproject.com/wiki/DevelopersForHire .. _developers for hire page: http://code.djangoproject.com/wiki/DevelopersForHire
@ -643,6 +646,81 @@ You can also use the Python API. See `creating users`_ for full info.
.. _creating users: ../authentication/#creating-users .. _creating users: ../authentication/#creating-users
Getting Help
============
How do I do X? Why doesn't Y work? Where can I go to get help?
--------------------------------------------------------------
If this FAQ doesn't contain an answer to your question, you might want to
try the `django-users mailing list`_. Feel free to ask any question related
to installing, using, or debugging Django.
If you prefer IRC, the `#django IRC channel`_ on freenode is an active
community of helpful individuals who may be able to solve your problem.
.. _`django-users mailing list`: http://groups.google.com/group/django-users
.. _`#django IRC channel`: irc://irc.freenode.net/django
Why hasn't my message appeared on django-users?
-----------------------------------------------
django-users_ has a lot of subscribers. This is good for the community, as
there are lot of people that can contribute answers to questions.
Unfortunately, it also means that django-users_ is an attractive target for
spammers.
In order to combat the spam problem, when you join the django-users_ mailing
list, we manually moderate the first message you send to the list. This means
that spammers get caught, but it also means that your first question to the
list might take a little longer to get answered. We apologize for any
inconvenience that this policy may cause.
.. _django-users: http://groups.google.com/group/django-users
Nobody on django-users answered my question? What should I do?
--------------------------------------------------------------
Wait. Ask again later. Try making your question more specific, or provide
a better example of your problem.
Remember, the readers of django-users_ are all volunteers. If nobody has
answered your question, it may be because nobody knows the answer, it may
be because nobody can understand the question, or it may be that everybody
that can help is extremely busy.
Resist any temptation to mail the `django-developers mailing list`_ in an
attempt to get an answer to your question. django-developers_ is for discussing
the development of Django itself. Attempts to use django-developers_ as
a second-tier support mechanism will not be met an enthusiastic response.
.. _`django-developers mailing list`: http://groups.google.com/group/django-developers
.. _django-developers: http://groups.google.com/group/django-developers
I think I've found a bug! What should I do?
-------------------------------------------
Detailed instructions on how to handle a potential bug can be found in our
`Guide to contributing to Django`_.
.. _`Guide to contributing to Django`: ../contributing/#reporting-bugs
I think I've found a security problem! What should I do?
--------------------------------------------------------
If you think you have found a security problem with Django, please send
a message to security@djangoproject.com. This is a private list only
open to long-time, highly trusted Django developers, and its archives
are not publicly readable.
Due to the sensitive nature of security issues, we ask that if you think you
have found a security problem, *please* don't send a message to one of the
public mailing lists. Django has a `policy for handling security issues`_;
while a defect is outstanding, we would like to minimize any damage that
could be inflicted through public knowledge of that defect.
.. _`policy for handling security issues`: ../contributing/#reporting-security-issues
Contributing code Contributing code
================= =================
@ -652,7 +730,7 @@ How can I get started contributing code to Django?
Thanks for asking! We've written an entire document devoted to this question. Thanks for asking! We've written an entire document devoted to this question.
It's titled `Contributing to Django`_. It's titled `Contributing to Django`_.
.. _Contributing to Django: ../contributing/ .. _`Contributing to Django`: ../contributing/
I submitted a bug fix in the ticket system several weeks ago. Why are you ignoring my patch? I submitted a bug fix in the ticket system several weeks ago. Why are you ignoring my patch?
-------------------------------------------------------------------------------------------- --------------------------------------------------------------------------------------------
@ -664,6 +742,11 @@ ignored" and "a ticket has not been attended to yet." Django's ticket system
contains hundreds of open tickets, of various degrees of impact on end-user contains hundreds of open tickets, of various degrees of impact on end-user
functionality, and Django's developers have to review and prioritize. functionality, and Django's developers have to review and prioritize.
On top of that - the team working on Django are all volunteers. As a result,
the amount of time that we have to work on Django is limited, and will vary
from week to week depending on how much spare time we have. If we are busy, we
may not be able to spend as much time on Django as we might want.
Besides, if your feature request stands no chance of inclusion in Django, we Besides, if your feature request stands no chance of inclusion in Django, we
won't ignore it -- we'll just close the ticket. So if your ticket is still won't ignore it -- we'll just close the ticket. So if your ticket is still
open, it doesn't mean we're ignoring you; it just means we haven't had time to open, it doesn't mean we're ignoring you; it just means we haven't had time to

View File

@ -63,6 +63,41 @@ be serialized.
doesn't specify all the fields that are required by a model, the deserializer doesn't specify all the fields that are required by a model, the deserializer
will not be able to save deserialized instances. will not be able to save deserialized instances.
Inherited Models
~~~~~~~~~~~~~~~~
If you have a model that is defined using an `abstract base class`_, you don't
have to do anything special to serialize that model. Just call the serializer
on the object (or objects) that you want to serialize, and the output will be
a complete representation of the serialized object.
However, if you have a model that uses `multi-table inheritance`_, you also
need to serialize all of the base classes for the model. This is because only
the fields that are locally defined on the model will be serialized. For
example, consider the following models::
class Place(models.Model):
name = models.CharField(max_length=50)
class Restaurant(Place):
serves_hot_dogs = models.BooleanField()
If you only serialize the Restaurant model::
data = serializers.serialize('xml', Restaurant.objects.all())
the fields on the serialized output will only contain the `serves_hot_dogs`
attribute. The `name` attribute of the base class will be ignored.
In order to fully serialize your Restaurant instances, you will need to
serialize the Place models as well::
all_objects = list(Restaurant.objects.all()) + list(Place.objects.all())
data = serializers.serialize('xml', all_objects)
.. _abstract base class: http://www.djangoproject.com/documentation/model-api/#abstract-base-classes
.. _multi-table inheritance: http://www.djangoproject.com/documentation/model-api/#multi-table-inheritance
Deserializing data Deserializing data
------------------ ------------------

View File

@ -80,19 +80,24 @@ attribute, which is a dictionary-like object. You can read it and write to it.
It implements the following standard dictionary methods: It implements the following standard dictionary methods:
* ``__getitem__(key)`` * ``__getitem__(key)``
Example: ``fav_color = request.session['fav_color']`` Example: ``fav_color = request.session['fav_color']``
* ``__setitem__(key, value)`` * ``__setitem__(key, value)``
Example: ``request.session['fav_color'] = 'blue'`` Example: ``request.session['fav_color'] = 'blue'``
* ``__delitem__(key)`` * ``__delitem__(key)``
Example: ``del request.session['fav_color']``. This raises ``KeyError`` Example: ``del request.session['fav_color']``. This raises ``KeyError``
if the given ``key`` isn't already in the session. if the given ``key`` isn't already in the session.
* ``__contains__(key)`` * ``__contains__(key)``
Example: ``'fav_color' in request.session`` Example: ``'fav_color' in request.session``
* ``get(key, default=None)`` * ``get(key, default=None)``
Example: ``fav_color = request.session.get('fav_color', 'red')`` Example: ``fav_color = request.session.get('fav_color', 'red')``
* ``keys()`` * ``keys()``
@ -101,23 +106,70 @@ It implements the following standard dictionary methods:
* ``setdefault()`` (**New in Django development version**) * ``setdefault()`` (**New in Django development version**)
It also has these three methods: It also has these methods:
* ``set_test_cookie()`` * ``set_test_cookie()``
Sets a test cookie to determine whether the user's browser supports Sets a test cookie to determine whether the user's browser supports
cookies. Due to the way cookies work, you won't be able to test this cookies. Due to the way cookies work, you won't be able to test this
until the user's next page request. See "Setting test cookies" below for until the user's next page request. See "Setting test cookies" below for
more information. more information.
* ``test_cookie_worked()`` * ``test_cookie_worked()``
Returns either ``True`` or ``False``, depending on whether the user's Returns either ``True`` or ``False``, depending on whether the user's
browser accepted the test cookie. Due to the way cookies work, you'll browser accepted the test cookie. Due to the way cookies work, you'll
have to call ``set_test_cookie()`` on a previous, separate page request. have to call ``set_test_cookie()`` on a previous, separate page request.
See "Setting test cookies" below for more information. See "Setting test cookies" below for more information.
* ``delete_test_cookie()`` * ``delete_test_cookie()``
Deletes the test cookie. Use this to clean up after yourself. Deletes the test cookie. Use this to clean up after yourself.
* ``set_expiry(value)``
**New in Django development version**
Sets the expiration time for the session. You can pass a number of
different values:
* If ``value`` is an integer, the session will expire after that
many seconds of inactivity. For example, calling
``request.session.set_expiry(300)`` would make the session expire
in 5 minutes.
* If ``value`` is a ``datetime`` or ``timedelta`` object, the
session will expire at that specific time.
* If ``value`` is ``0`` then the user's session cookie will expire
when their browser is closed.
* If ``value`` is ``None``, the session reverts to using the global
session expiry policy.
* ``get_expiry_age()``
**New in Django development version**
Returns the number of seconds until this session expires. For sessions
with no custom expiration (or those set to expire at browser close), this
will equal ``settings.SESSION_COOKIE_AGE``.
* ``get_expiry_date()``
**New in Django development version**
Returns the date this session will expire. For sessions with no custom
expiration (or those set to expire at browser close), this will equal the
date ``settings.SESSION_COOKIE_AGE`` seconds from now.
* ``get_expire_at_browser_close()``
**New in Django development version**
Returns either ``True`` or ``False``, depending on whether the user's
session cookie will expire when their browser is closed.
You can edit ``request.session`` at any point in your view. You can edit it You can edit ``request.session`` at any point in your view. You can edit it
multiple times. multiple times.
@ -278,6 +330,12 @@ browser-length cookies -- cookies that expire as soon as the user closes his or
her browser. Use this if you want people to have to log in every time they open her browser. Use this if you want people to have to log in every time they open
a browser. a browser.
**New in Django development version**
This setting is a global default and can be overwritten at a per-session level
by explicitly calling ``request.session.set_expiry()`` as described above in
`using sessions in views`_.
Clearing the session table Clearing the session table
========================== ==========================

View File

@ -147,8 +147,13 @@ Test constructor for Restaurant.
>>> c.save() >>> c.save()
>>> ir = ItalianRestaurant(name='Ristorante Miron', address='1234 W. Ash', serves_hot_dogs=False, serves_pizza=False, serves_gnocchi=True, rating=4, chef=c) >>> ir = ItalianRestaurant(name='Ristorante Miron', address='1234 W. Ash', serves_hot_dogs=False, serves_pizza=False, serves_gnocchi=True, rating=4, chef=c)
>>> ir.save() >>> ir.save()
>>> ItalianRestaurant.objects.filter(address='1234 W. Ash')
[<ItalianRestaurant: Ristorante Miron the italian restaurant>]
>>> ir.address = '1234 W. Elm' >>> ir.address = '1234 W. Elm'
>>> ir.save() >>> ir.save()
>>> ItalianRestaurant.objects.filter(address='1234 W. Elm')
[<ItalianRestaurant: Ristorante Miron the italian restaurant>]
# Make sure Restaurant and ItalianRestaurant have the right fields in the right # Make sure Restaurant and ItalianRestaurant have the right fields in the right
# order. # order.

View File

@ -63,5 +63,12 @@ a manager method.
>>> DataPoint.objects.values('value').distinct() >>> DataPoint.objects.values('value').distinct()
[{'value': u'thing'}] [{'value': u'thing'}]
We do not support update on already sliced query sets.
>>> DataPoint.objects.all()[:2].update(another_value='another thing')
Traceback (most recent call last):
...
AssertionError: Cannot update a query once a slice has been taken.
""" """
} }

View File

@ -0,0 +1 @@
This data shouldn't load, as it's of an unknown file format.

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<django-objcts version="1.0">
<objct pk="2" model="fixtures.article">
<field type="CharField" name="headline">Poker on TV is great!</field>
<field type="DateTimeField" name="pub_date">2006-06-16 11:00:00</field>
</objct>
</django-objcts>

View File

@ -71,4 +71,27 @@ __test__ = {'API_TESTS':"""
>>> Absolute.load_count >>> Absolute.load_count
1 1
###############################################
# Test for ticket #4371 -- fixture loading fails silently in testcases
# Validate that error conditions are caught correctly
# redirect stderr for the next few tests...
>>> import sys
>>> savestderr = sys.stderr
>>> sys.stderr = sys.stdout
# Loading data of an unknown format should fail
>>> management.call_command('loaddata', 'bad_fixture1.unkn', verbosity=0)
Problem installing fixture 'bad_fixture1': unkn is not a known serialization format.
# Loading a fixture file with invalid data using explicit filename
>>> management.call_command('loaddata', 'bad_fixture2.xml', verbosity=0)
No fixture data found for 'bad_fixture2'. (File format may be invalid.)
# Loading a fixture file with invalid data without file extension
>>> management.call_command('loaddata', 'bad_fixture2', verbosity=0)
No fixture data found for 'bad_fixture2'. (File format may be invalid.)
>>> sys.stderr = savestderr
"""} """}

View File

@ -0,0 +1,120 @@
"""
Regression tests for Model inheritance behaviour.
"""
from django.db import models
class Place(models.Model):
name = models.CharField(max_length=50)
address = models.CharField(max_length=80)
class Meta:
ordering = ('name',)
def __unicode__(self):
return u"%s the place" % self.name
class Restaurant(Place):
serves_hot_dogs = models.BooleanField()
serves_pizza = models.BooleanField()
def __unicode__(self):
return u"%s the restaurant" % self.name
class ItalianRestaurant(Restaurant):
serves_gnocchi = models.BooleanField()
def __unicode__(self):
return u"%s the italian restaurant" % self.name
class ParkingLot(Place):
# An explicit link to the parent (we can control the attribute name).
parent = models.OneToOneField(Place, primary_key=True, parent_link=True)
capacity = models.IntegerField()
def __unicode__(self):
return u"%s the parking lot" % self.name
__test__ = {'API_TESTS':"""
# Regression for #7350, #7202
# Check that when you create a Parent object with a specific reference to an existent
# child instance, saving the Parent doesn't duplicate the child.
# This behaviour is only activated during a raw save - it is mostly relevant to
# deserialization, but any sort of CORBA style 'narrow()' API would require a
# similar approach.
# Create a child-parent-grandparent chain
>>> place1 = Place(name="Guido's House of Pasta", address='944 W. Fullerton')
>>> place1.save_base(raw=True)
>>> restaurant = Restaurant(place_ptr=place1, serves_hot_dogs=True, serves_pizza=False)
>>> restaurant.save_base(raw=True)
>>> italian_restaurant = ItalianRestaurant(restaurant_ptr=restaurant, serves_gnocchi=True)
>>> italian_restaurant.save_base(raw=True)
# Create a child-parent chain with an explicit parent link
>>> place2 = Place(name='Main St', address='111 Main St')
>>> place2.save_base(raw=True)
>>> park = ParkingLot(parent=place2, capacity=100)
>>> park.save_base(raw=True)
# Check that no extra parent objects have been created.
>>> Place.objects.all()
[<Place: Guido's House of Pasta the place>, <Place: Main St the place>]
>>> dicts = Restaurant.objects.values('name','serves_hot_dogs')
>>> [sorted(d.items()) for d in dicts]
[[('name', u"Guido's House of Pasta"), ('serves_hot_dogs', True)]]
>>> dicts = ItalianRestaurant.objects.values('name','serves_hot_dogs','serves_gnocchi')
>>> [sorted(d.items()) for d in dicts]
[[('name', u"Guido's House of Pasta"), ('serves_gnocchi', True), ('serves_hot_dogs', True)]]
>>> dicts = ParkingLot.objects.values('name','capacity')
>>> [sorted(d.items()) for d in dicts]
[[('capacity', 100), ('name', u'Main St')]]
# You can also update objects when using a raw save.
>>> place1.name = "Guido's All New House of Pasta"
>>> place1.save_base(raw=True)
>>> restaurant.serves_hot_dogs = False
>>> restaurant.save_base(raw=True)
>>> italian_restaurant.serves_gnocchi = False
>>> italian_restaurant.save_base(raw=True)
>>> place2.name='Derelict lot'
>>> place2.save_base(raw=True)
>>> park.capacity = 50
>>> park.save_base(raw=True)
# No extra parent objects after an update, either.
>>> Place.objects.all()
[<Place: Derelict lot the place>, <Place: Guido's All New House of Pasta the place>]
>>> dicts = Restaurant.objects.values('name','serves_hot_dogs')
>>> [sorted(d.items()) for d in dicts]
[[('name', u"Guido's All New House of Pasta"), ('serves_hot_dogs', False)]]
>>> dicts = ItalianRestaurant.objects.values('name','serves_hot_dogs','serves_gnocchi')
>>> [sorted(d.items()) for d in dicts]
[[('name', u"Guido's All New House of Pasta"), ('serves_gnocchi', False), ('serves_hot_dogs', False)]]
>>> dicts = ParkingLot.objects.values('name','capacity')
>>> [sorted(d.items()) for d in dicts]
[[('capacity', 50), ('name', u'Derelict lot')]]
# If you try to raw_save a parent attribute onto a child object,
# the attribute will be ignored.
>>> italian_restaurant.name = "Lorenzo's Pasta Hut"
>>> italian_restaurant.save_base(raw=True)
# Note that the name has not changed
# - name is an attribute of Place, not ItalianRestaurant
>>> dicts = ItalianRestaurant.objects.values('name','serves_hot_dogs','serves_gnocchi')
>>> [sorted(d.items()) for d in dicts]
[[('name', u"Guido's All New House of Pasta"), ('serves_gnocchi', False), ('serves_hot_dogs', False)]]
"""}

View File

@ -0,0 +1,55 @@
"""
Regression tests for proper working of ForeignKey(null=True). Tests these bugs:
* #7369: FK non-null after null relationship on select_related() generates an invalid query
"""
from django.db import models
class SystemInfo(models.Model):
system_name = models.CharField(max_length=32)
class Forum(models.Model):
system_info = models.ForeignKey(SystemInfo)
forum_name = models.CharField(max_length=32)
class Post(models.Model):
forum = models.ForeignKey(Forum, null=True)
title = models.CharField(max_length=32)
def __unicode__(self):
return self.title
class Comment(models.Model):
post = models.ForeignKey(Post, null=True)
comment_text = models.CharField(max_length=250)
def __unicode__(self):
return self.comment_text
__test__ = {'API_TESTS':"""
>>> s = SystemInfo.objects.create(system_name='First forum')
>>> f = Forum.objects.create(system_info=s, forum_name='First forum')
>>> p = Post.objects.create(forum=f, title='First Post')
>>> c1 = Comment.objects.create(post=p, comment_text='My first comment')
>>> c2 = Comment.objects.create(comment_text='My second comment')
# Starting from comment, make sure that a .select_related(...) with a specified
# set of fields will properly LEFT JOIN multiple levels of NULLs (and the things
# that come after the NULLs, or else data that should exist won't).
>>> c = Comment.objects.select_related().get(id=1)
>>> c.post
<Post: First Post>
>>> c = Comment.objects.select_related().get(id=2)
>>> print c.post
None
>>> comments = Comment.objects.select_related('post__forum__system_info').all()
>>> [(c.id, c.post.id) for c in comments]
[(1, 1), (2, None)]
>>> [(c.comment_text, c.post.title) for c in comments]
[(u'My first comment', u'First Post'), (u'My second comment', None)]
"""}

View File

@ -223,3 +223,23 @@ class ModifyingSaveData(models.Model):
"A save method that modifies the data in the object" "A save method that modifies the data in the object"
self.data = 666 self.data = 666
super(ModifyingSaveData, self).save(raw) super(ModifyingSaveData, self).save(raw)
# Tests for serialization of models using inheritance.
# Regression for #7202, #7350
class AbstractBaseModel(models.Model):
parent_data = models.IntegerField()
class Meta:
abstract = True
class InheritAbstractModel(AbstractBaseModel):
child_data = models.IntegerField()
class BaseModel(models.Model):
parent_data = models.IntegerField()
class InheritBaseModel(BaseModel):
child_data = models.IntegerField()
class ExplicitInheritBaseModel(BaseModel):
parent = models.OneToOneField(BaseModel)
child_data = models.IntegerField()

View File

@ -32,7 +32,7 @@ def data_create(pk, klass, data):
instance = klass(id=pk) instance = klass(id=pk)
instance.data = data instance.data = data
models.Model.save_base(instance, raw=True) models.Model.save_base(instance, raw=True)
return instance return [instance]
def generic_create(pk, klass, data): def generic_create(pk, klass, data):
instance = klass(id=pk) instance = klass(id=pk)
@ -40,31 +40,44 @@ def generic_create(pk, klass, data):
models.Model.save_base(instance, raw=True) models.Model.save_base(instance, raw=True)
for tag in data[1:]: for tag in data[1:]:
instance.tags.create(data=tag) instance.tags.create(data=tag)
return instance return [instance]
def fk_create(pk, klass, data): def fk_create(pk, klass, data):
instance = klass(id=pk) instance = klass(id=pk)
setattr(instance, 'data_id', data) setattr(instance, 'data_id', data)
models.Model.save_base(instance, raw=True) models.Model.save_base(instance, raw=True)
return instance return [instance]
def m2m_create(pk, klass, data): def m2m_create(pk, klass, data):
instance = klass(id=pk) instance = klass(id=pk)
models.Model.save_base(instance, raw=True) models.Model.save_base(instance, raw=True)
instance.data = data instance.data = data
return instance return [instance]
def o2o_create(pk, klass, data): def o2o_create(pk, klass, data):
instance = klass() instance = klass()
instance.data_id = data instance.data_id = data
models.Model.save_base(instance, raw=True) models.Model.save_base(instance, raw=True)
return instance return [instance]
def pk_create(pk, klass, data): def pk_create(pk, klass, data):
instance = klass() instance = klass()
instance.data = data instance.data = data
models.Model.save_base(instance, raw=True) models.Model.save_base(instance, raw=True)
return instance return [instance]
def inherited_create(pk, klass, data):
instance = klass(id=pk,**data)
# This isn't a raw save because:
# 1) we're testing inheritance, not field behaviour, so none
# of the field values need to be protected.
# 2) saving the child class and having the parent created
# automatically is easier than manually creating both.
models.Model.save(instance)
created = [instance]
for klass,field in instance._meta.parents.items():
created.append(klass.objects.get(id=pk))
return created
# A set of functions that can be used to compare # A set of functions that can be used to compare
# test data objects of various kinds # test data objects of various kinds
@ -94,6 +107,11 @@ def pk_compare(testcase, pk, klass, data):
instance = klass.objects.get(data=data) instance = klass.objects.get(data=data)
testcase.assertEqual(data, instance.data) testcase.assertEqual(data, instance.data)
def inherited_compare(testcase, pk, klass, data):
instance = klass.objects.get(id=pk)
for key,value in data.items():
testcase.assertEqual(value, getattr(instance,key))
# Define some data types. Each data type is # Define some data types. Each data type is
# actually a pair of functions; one to create # actually a pair of functions; one to create
# and one to compare objects of that type # and one to compare objects of that type
@ -103,6 +121,7 @@ fk_obj = (fk_create, fk_compare)
m2m_obj = (m2m_create, m2m_compare) m2m_obj = (m2m_create, m2m_compare)
o2o_obj = (o2o_create, o2o_compare) o2o_obj = (o2o_create, o2o_compare)
pk_obj = (pk_create, pk_compare) pk_obj = (pk_create, pk_compare)
inherited_obj = (inherited_create, inherited_compare)
test_data = [ test_data = [
# Format: (data type, PK value, Model Class, data) # Format: (data type, PK value, Model Class, data)
@ -255,6 +274,10 @@ The end."""),
(data_obj, 800, AutoNowDateTimeData, datetime.datetime(2006,6,16,10,42,37)), (data_obj, 800, AutoNowDateTimeData, datetime.datetime(2006,6,16,10,42,37)),
(data_obj, 810, ModifyingSaveData, 42), (data_obj, 810, ModifyingSaveData, 42),
(inherited_obj, 900, InheritAbstractModel, {'child_data':37,'parent_data':42}),
(inherited_obj, 910, ExplicitInheritBaseModel, {'child_data':37,'parent_data':42}),
(inherited_obj, 920, InheritBaseModel, {'child_data':37,'parent_data':42}),
] ]
# Because Oracle treats the empty string as NULL, Oracle is expected to fail # Because Oracle treats the empty string as NULL, Oracle is expected to fail
@ -277,13 +300,19 @@ def serializerTest(format, self):
# Create all the objects defined in the test data # Create all the objects defined in the test data
objects = [] objects = []
instance_count = {}
transaction.enter_transaction_management() transaction.enter_transaction_management()
transaction.managed(True) transaction.managed(True)
for (func, pk, klass, datum) in test_data: for (func, pk, klass, datum) in test_data:
objects.append(func[0](pk, klass, datum)) objects.extend(func[0](pk, klass, datum))
instance_count[klass] = 0
transaction.commit() transaction.commit()
transaction.leave_transaction_management() transaction.leave_transaction_management()
# Get a count of the number of objects created for each class
for klass in instance_count:
instance_count[klass] = klass.objects.count()
# Add the generic tagged objects to the object list # Add the generic tagged objects to the object list
objects.extend(Tag.objects.all()) objects.extend(Tag.objects.all())
@ -304,6 +333,11 @@ def serializerTest(format, self):
for (func, pk, klass, datum) in test_data: for (func, pk, klass, datum) in test_data:
func[1](self, pk, klass, datum) func[1](self, pk, klass, datum)
# Assert that the number of objects deserialized is the
# same as the number that was serialized.
for klass, count in instance_count.items():
self.assertEquals(count, klass.objects.count())
def fieldsTest(format, self): def fieldsTest(format, self):
# Clear the database first # Clear the database first
management.call_command('flush', verbosity=0, interactive=False) management.call_command('flush', verbosity=0, interactive=False)