1
0
mirror of https://github.com/django/django.git synced 2025-07-04 09:49:12 +00:00

newforms-admin: Merged to [6370].

git-svn-id: http://code.djangoproject.com/svn/django/branches/newforms-admin@6371 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Joseph Kocherhans 2007-09-17 15:49:03 +00:00
parent 019e3c61e4
commit 0de9a64905
58 changed files with 1108 additions and 175 deletions

View File

@ -84,10 +84,12 @@ answer newbie questions, and generally made Django that much better:
Russell Cloran <russell@rucus.net>
colin@owlfish.com
crankycoder@gmail.com
Paul Collier <paul@paul-collier.com>
Pete Crosier <pete.crosier@gmail.com>
Matt Croydon <http://www.postneo.com/>
flavio.curella@gmail.com
Jure Cuhalev <gandalf@owca.info>
John D'Agostino <john.dagostino@gmail.com>
dackze+django@gmail.com
David Danier <goliath.mailinglist@gmx.de>
Dirk Datzert <dummy@habmalnefrage.de>
@ -121,6 +123,7 @@ answer newbie questions, and generally made Django that much better:
Afonso Fernández Nogueira <fonzzo.django@gmail.com>
Matthew Flanagan <http://wadofstuff.blogspot.com>
Eric Floehr <eric@intellovations.com>
Vincent Foley <vfoleybourgon@yahoo.ca>
Jorge Gajon <gajon@gajon.org>
gandalf@owca.info
Marc Garcia <marc.garcia@accopensys.com>
@ -195,7 +198,11 @@ answer newbie questions, and generally made Django that much better:
Waylan Limberg <waylan@gmail.com>
limodou
Philip Lindborg <philip.lindborg@gmail.com>
<<<<<<< .working
Simon Litchfield <simon@quo.com.au>
=======
msaelices <msaelices@gmail.com>
>>>>>>> .merge-right.r6370
Matt McClanahan <http://mmcc.cx/>
Martin Maney <http://www.chipy.org/Martin_Maney>
Petr Marhoun <petr.marhoun@gmail.com>
@ -262,6 +269,7 @@ answer newbie questions, and generally made Django that much better:
Brian Rosner <brosner@gmail.com>
Oliver Rutherfurd <http://rutherfurd.net/>
ryankanno
Manuel Saelices <msaelices@yaco.es>
Ivan Sagalaev (Maniac) <http://www.softwaremaniacs.org/>
Vinay Sajip <vinay_sajip@yahoo.co.uk>
David Schein

View File

@ -4,20 +4,31 @@ import optparse
import os
import sys
def compile_messages(locale=None):
basedir = None
try:
set
except NameError:
from sets import Set as set # For Python 2.3
if os.path.isdir(os.path.join('conf', 'locale')):
basedir = os.path.abspath(os.path.join('conf', 'locale'))
elif os.path.isdir('locale'):
basedir = os.path.abspath('locale')
else:
print "This script should be run from the Django SVN tree or your project or app tree."
def compile_messages(locale=None):
basedirs = [os.path.join('conf', 'locale'), 'locale']
if os.environ.get('DJANGO_SETTINGS_MODULE'):
from django.conf import settings
basedirs += settings.LOCALE_PATHS
# Gather existing directories.
basedirs = set(map(os.path.abspath, filter(os.path.isdir, basedirs)))
if not basedirs:
print "This script should be run from the Django SVN tree or your project or app tree, or with the settings module specified."
sys.exit(1)
if locale is not None:
basedir = os.path.join(basedir, locale, 'LC_MESSAGES')
for basedir in basedirs:
if locale:
basedir = os.path.join(basedir, locale, 'LC_MESSAGES')
compile_messages_in_dir(basedir)
def compile_messages_in_dir(basedir):
for dirpath, dirnames, filenames in os.walk(basedir):
for f in filenames:
if f.endswith('.po'):
@ -40,9 +51,13 @@ def main():
parser = optparse.OptionParser()
parser.add_option('-l', '--locale', dest='locale',
help="The locale to process. Default is to process all.")
parser.add_option('--settings',
help='Python path to settings module, e.g. "myproject.settings". If provided, all LOCALE_PATHS will be processed. If this isn\'t provided, the DJANGO_SETTINGS_MODULE environment variable will be checked as well.')
options, args = parser.parse_args()
if len(args):
parser.error("This program takes no arguments")
if options.settings:
os.environ['DJANGO_SETTINGS_MODULE'] = options.settings
compile_messages(options.locale)
if __name__ == "__main__":

View File

@ -271,12 +271,14 @@ MIDDLEWARE_CLASSES = (
# SESSIONS #
############
SESSION_COOKIE_NAME = 'sessionid' # Cookie name. This can be whatever you want.
SESSION_COOKIE_AGE = 60 * 60 * 24 * 7 * 2 # Age of cookie, in seconds (default: 2 weeks).
SESSION_COOKIE_DOMAIN = None # A string like ".lawrence.com", or None for standard domain cookie.
SESSION_COOKIE_SECURE = False # Whether the session cookie should be secure (https:// only).
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_COOKIE_NAME = 'sessionid' # Cookie name. This can be whatever you want.
SESSION_COOKIE_AGE = 60 * 60 * 24 * 7 * 2 # Age of cookie, in seconds (default: 2 weeks).
SESSION_COOKIE_DOMAIN = None # A string like ".lawrence.com", or None for standard domain cookie.
SESSION_COOKIE_SECURE = False # Whether the session cookie should be secure (https:// only).
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_ENGINE = 'django.contrib.sessions.backends.db' # The module to store session data
SESSION_FILE_PATH = '/tmp/' # Directory to store session files if using the file session module
#########
# CACHE #

View File

@ -85,7 +85,7 @@ class AdminBoundField(object):
def original_value(self):
if self.original:
return self.original.__dict__[self.field.column]
return self.original.__dict__[self.field.attname]
def existing_display(self):
try:

View File

@ -0,0 +1,143 @@
import base64
import md5
import os
import random
import sys
import time
from django.conf import settings
from django.core.exceptions import SuspiciousOperation
try:
import cPickle as pickle
except ImportError:
import pickle
class SessionBase(object):
"""
Base class for all Session classes.
"""
TEST_COOKIE_NAME = 'testcookie'
TEST_COOKIE_VALUE = 'worked'
def __init__(self, session_key=None):
self._session_key = session_key
self.accessed = False
self.modified = False
def __contains__(self, key):
return key in self._session
def __getitem__(self, key):
return self._session[key]
def __setitem__(self, key, value):
self._session[key] = value
self.modified = True
def __delitem__(self, key):
del self._session[key]
self.modified = True
def keys(self):
return self._session.keys()
def items(self):
return self._session.items()
def get(self, key, default=None):
return self._session.get(key, default)
def pop(self, key, *args):
return self._session.pop(key, *args)
def set_test_cookie(self):
self[self.TEST_COOKIE_NAME] = self.TEST_COOKIE_VALUE
def test_cookie_worked(self):
return self.get(self.TEST_COOKIE_NAME) == self.TEST_COOKIE_VALUE
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)
pickled_md5 = md5.new(pickled + settings.SECRET_KEY).hexdigest()
return base64.encodestring(pickled + pickled_md5)
def decode(self, session_data):
encoded_data = base64.decodestring(session_data)
pickled, tamper_check = encoded_data[:-32], encoded_data[-32:]
if md5.new(pickled + settings.SECRET_KEY).hexdigest() != tamper_check:
raise SuspiciousOperation("User tampered with session cookie.")
try:
return pickle.loads(pickled)
# Unpickling can cause a variety of exceptions. If something happens,
# 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.
while 1:
session_key = md5.new("%s%s%s%s" % (random.randint(0, sys.maxint - 1),
os.getpid(), 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):
# Lazily loads session from storage.
self.accessed = True
try:
return self._session_cache
except AttributeError:
if self._session_key is None:
self._session_cache = {}
else:
self._session_cache = self.load()
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.
"""
raise NotImplementedError
def save(self):
"""
Saves the session data.
"""
raise NotImplementedError
def delete(self, session_key):
"""
Clears out the session data under this key.
"""
raise NotImplementedError
def load(self):
"""
Loads the session data and returns a dictionary.
"""
raise NotImplementedError

View File

@ -0,0 +1,26 @@
from django.conf import settings
from django.contrib.sessions.backends.base import SessionBase
from django.core.cache import cache
class SessionStore(SessionBase):
"""
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)
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)

View File

@ -0,0 +1,49 @@
from django.conf import settings
from django.contrib.sessions.models import Session
from django.contrib.sessions.backends.base import SessionBase
from django.core.exceptions import SuspiciousOperation
import datetime
class SessionStore(SessionBase):
"""
Implements database session store
"""
def __init__(self, session_key=None):
super(SessionStore, self).__init__(session_key)
def load(self):
try:
s = Session.objects.get(
session_key = self.session_key,
expire_date__gt=datetime.datetime.now()
)
return self.decode(s.session_data)
except (Session.DoesNotExist, SuspiciousOperation):
# Create a new session_key for extra security.
self.session_key = self._get_new_session_key()
self._session_cache = {}
# Save immediately to minimize collision
self.save()
return {}
def exists(self, session_key):
try:
Session.objects.get(session_key=session_key)
except Session.DoesNotExist:
return False
return True
def save(self):
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)
)
def delete(self, session_key):
try:
Session.objects.get(session_key=session_key).delete()
except Session.DoesNotExist:
pass

View File

@ -0,0 +1,68 @@
import os
from django.conf import settings
from django.contrib.sessions.backends.base import SessionBase
from django.core.exceptions import SuspiciousOperation
class SessionStore(SessionBase):
"""
Implements a file based session store.
"""
def __init__(self, session_key=None):
self.storage_path = settings.SESSION_FILE_PATH
self.file_prefix = settings.SESSION_COOKIE_NAME
super(SessionStore, self).__init__(session_key)
def _key_to_file(self, session_key=None):
"""
Get the file associated with this session key.
"""
if session_key is None:
session_key = self.session_key
# Make sure we're not vulnerable to directory traversal. Session keys
# should always be md5s, so they should never contain directory components.
if os.path.sep in session_key:
raise SuspiciousOperation("Invalid characters (directory components) in session key")
return os.path.join(self.storage_path, self.file_prefix + session_key)
def load(self):
session_data = {}
try:
session_file = open(self._key_to_file(), "rb")
try:
try:
session_data = self.decode(session_file.read())
except(EOFError, SuspiciousOperation):
self._session_key = self._get_new_session_key()
self._session_cache = {}
self.save()
finally:
session_file.close()
except(IOError):
pass
return session_data
def save(self):
try:
f = open(self._key_to_file(self.session_key), "wb")
try:
f.write(self.encode(self._session))
finally:
f.close()
except(IOError, EOFError):
pass
def exists(self, session_key):
if os.path.exists(self._key_to_file(session_key)):
return True
return False
def delete(self, session_key):
try:
os.unlink(self._key_to_file(session_key))
except OSError:
pass
def clean(self):
pass

View File

@ -1,6 +1,4 @@
from django.conf import settings
from django.contrib.sessions.models import Session
from django.core.exceptions import SuspiciousOperation
from django.utils.cache import patch_vary_headers
from email.Utils import formatdate
import datetime
@ -9,73 +7,11 @@ import time
TEST_COOKIE_NAME = 'testcookie'
TEST_COOKIE_VALUE = 'worked'
class SessionWrapper(object):
def __init__(self, session_key):
self.session_key = session_key
self.accessed = False
self.modified = False
def __contains__(self, key):
return key in self._session
def __getitem__(self, key):
return self._session[key]
def __setitem__(self, key, value):
self._session[key] = value
self.modified = True
def __delitem__(self, key):
del self._session[key]
self.modified = True
def keys(self):
return self._session.keys()
def items(self):
return self._session.items()
def get(self, key, default=None):
return self._session.get(key, default)
def pop(self, key, *args):
self.modified = self.modified or key in self._session
return self._session.pop(key, *args)
def set_test_cookie(self):
self[TEST_COOKIE_NAME] = TEST_COOKIE_VALUE
def test_cookie_worked(self):
return self.get(TEST_COOKIE_NAME) == TEST_COOKIE_VALUE
def delete_test_cookie(self):
del self[TEST_COOKIE_NAME]
def _get_session(self):
# Lazily loads session from storage.
self.accessed = True
try:
return self._session_cache
except AttributeError:
if self.session_key is None:
self._session_cache = {}
else:
try:
s = Session.objects.get(session_key=self.session_key,
expire_date__gt=datetime.datetime.now())
self._session_cache = s.get_decoded()
except (Session.DoesNotExist, SuspiciousOperation):
self._session_cache = {}
# Set the session_key to None to force creation of a new
# key, for extra security.
self.session_key = None
return self._session_cache
_session = property(_get_session)
class SessionMiddleware(object):
def process_request(self, request):
request.session = SessionWrapper(request.COOKIES.get(settings.SESSION_COOKIE_NAME, None))
engine = __import__(settings.SESSION_ENGINE, {}, {}, [''])
request.session = engine.SessionStore(request.COOKIES.get(settings.SESSION_COOKIE_NAME, None))
def process_response(self, request, response):
# If request.session was modified, or if response.session was set, save
@ -89,25 +25,22 @@ class SessionMiddleware(object):
if accessed:
patch_vary_headers(response, ('Cookie',))
if modified or settings.SESSION_SAVE_EVERY_REQUEST:
if request.session.session_key:
session_key = request.session.session_key
else:
obj = Session.objects.get_new_session_object()
session_key = obj.session_key
if settings.SESSION_EXPIRE_AT_BROWSER_CLOSE:
max_age = None
expires = None
else:
max_age = settings.SESSION_COOKIE_AGE
rfcdate = formatdate(time.time() + settings.SESSION_COOKIE_AGE)
# Fixed length date must have '-' separation in the format
# DD-MMM-YYYY for compliance with Netscape cookie standard
expires = (rfcdate[:7] + "-" + rfcdate[8:11]
+ "-" + rfcdate[12:26] + "GMT")
new_session = Session.objects.save(session_key, request.session._session,
datetime.datetime.now() + datetime.timedelta(seconds=settings.SESSION_COOKIE_AGE))
response.set_cookie(settings.SESSION_COOKIE_NAME, session_key,
expires = datetime.datetime.strftime(datetime.datetime.utcnow() + \
datetime.timedelta(seconds=settings.SESSION_COOKIE_AGE), "%a, %d-%b-%Y %H:%M:%S GMT")
# Save the seesion data and refresh the client cookie.
request.session.save()
response.set_cookie(settings.SESSION_COOKIE_NAME, request.session.session_key,
max_age=max_age, expires=expires, domain=settings.SESSION_COOKIE_DOMAIN,
secure=settings.SESSION_COOKIE_SECURE or None)
return response

View File

@ -1,4 +1,4 @@
import base64, md5, random, sys, datetime, os, time
import base64, md5, random, sys, datetime
import cPickle as pickle
from django.db import models
from django.utils.translation import ugettext_lazy as _
@ -74,6 +74,7 @@ class Session(models.Model):
session_data = models.TextField(_('session data'))
expire_date = models.DateTimeField(_('expire date'))
objects = SessionManager()
class Meta:
db_table = 'django_session'
verbose_name = _('session')

View File

@ -1,35 +1,59 @@
r"""
>>> s = SessionWrapper(None)
Inject data into the session cache.
>>> s._session_cache = {}
>>> s._session_cache['some key'] = 'exists'
>>> from django.contrib.sessions.backends.db import SessionStore as DatabaseSession
>>> from django.contrib.sessions.backends.cache import SessionStore as CacheSession
>>> from django.contrib.sessions.backends.file import SessionStore as FileSession
>>> s.accessed
>>> db_session = DatabaseSession()
>>> db_session.modified
False
>>> s.modified
False
>>> s.pop('non existant key', 'does not exist')
>>> db_session['cat'] = "dog"
>>> db_session.modified
True
>>> db_session.pop('cat')
'dog'
>>> db_session.pop('some key', 'does not exist')
'does not exist'
>>> s.accessed
>>> db_session.save()
>>> db_session.exists(db_session.session_key)
True
>>> s.modified
>>> db_session.delete(db_session.session_key)
>>> db_session.exists(db_session.session_key)
False
>>> s.pop('some key')
'exists'
>>> s.accessed
>>> file_session = FileSession()
>>> file_session.modified
False
>>> file_session['cat'] = "dog"
>>> file_session.modified
True
>>> s.modified
True
>>> s.pop('some key', 'does not exist')
>>> file_session.pop('cat')
'dog'
>>> file_session.pop('some key', 'does not exist')
'does not exist'
>>> file_session.save()
>>> file_session.exists(file_session.session_key)
True
>>> file_session.delete(file_session.session_key)
>>> file_session.exists(file_session.session_key)
False
>>> cache_session = CacheSession()
>>> cache_session.modified
False
>>> cache_session['cat'] = "dog"
>>> cache_session.modified
True
>>> cache_session.pop('cat')
'dog'
>>> cache_session.pop('some key', 'does not exist')
'does not exist'
>>> cache_session.save()
>>> cache_session.delete(cache_session.session_key)
>>> cache_session.exists(cache_session.session_key)
False
"""
from django.contrib.sessions.middleware import SessionWrapper
if __name__ == '__main__':
import doctest
doctest.testmod()

View File

@ -13,11 +13,19 @@ def auth(request):
"""
Returns context variables required by apps that use Django's authentication
system.
If there is no 'user' attribute in the request, uses AnonymousUser (from
django.contrib.auth).
"""
if hasattr(request, 'user'):
user = request.user
else:
from django.contrib.auth.models import AnonymousUser
user = AnonymousUser()
return {
'user': request.user,
'messages': request.user.get_and_delete_messages(),
'perms': PermWrapper(request.user),
'user': user,
'messages': user.get_and_delete_messages(),
'perms': PermWrapper(user),
}
def debug(request):

View File

@ -42,8 +42,11 @@ class ModPythonRequest(http.HttpRequest):
return '%s%s' % (self.path, self._req.args and ('?' + self._req.args) or '')
def is_secure(self):
# Note: modpython 3.2.10+ has req.is_https(), but we need to support previous versions
return 'HTTPS' in self._req.subprocess_env and self._req.subprocess_env['HTTPS'] == 'on'
try:
return self._req.is_https()
except AttributeError:
# mod_python < 3.2.10 doesn't have req.is_https().
return self._req.subprocess_env.get('HTTPS', '').lower() in ('on', '1')
def _load_post_and_files(self):
"Populates self._post and self._files"

View File

@ -83,6 +83,11 @@ class Model(object):
def _get_pk_val(self):
return getattr(self, self._meta.pk.attname)
def _set_pk_val(self, value):
return setattr(self, self._meta.pk.attname, value)
pk = property(_get_pk_val, _set_pk_val)
def __repr__(self):
return smart_str(u'<%s: %s>' % (self.__class__.__name__, unicode(self)))

View File

@ -878,6 +878,11 @@ class IPAddressField(Field):
def validate(self, field_data, all_data):
validators.isValidIPAddress4(field_data, None)
def formfield(self, **kwargs):
defaults = {'form_class': forms.IPAddressField}
defaults.update(kwargs)
return super(IPAddressField, self).formfield(**defaults)
class NullBooleanField(Field):
empty_strings_allowed = False
def __init__(self, *args, **kwargs):

View File

@ -55,6 +55,7 @@ class SetRemoteAddrFromForwardedFor(object):
return None
else:
# HTTP_X_FORWARDED_FOR can be a comma-separated list of IPs.
# Take just the first one.
real_ip = real_ip.split(",")[0]
# 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()
request.META['REMOTE_ADDR'] = real_ip

View File

@ -26,7 +26,7 @@ __all__ = (
'RegexField', 'EmailField', 'FileField', 'ImageField', 'URLField', 'BooleanField',
'ChoiceField', 'NullBooleanField', 'MultipleChoiceField',
'ComboField', 'MultiValueField', 'FloatField', 'DecimalField',
'SplitDateTimeField',
'SplitDateTimeField', 'IPAddressField',
)
# These values, if given to to_python(), will trigger the self.required check.
@ -635,3 +635,11 @@ class SplitDateTimeField(MultiValueField):
raise ValidationError(ugettext(u'Enter a valid time.'))
return datetime.datetime.combine(*data_list)
return None
ipv4_re = re.compile(r'^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$')
class IPAddressField(RegexField):
def __init__(self, *args, **kwargs):
RegexField.__init__(self, ipv4_re,
error_message=ugettext(u'Enter a valid IPv4 address.'),
*args, **kwargs)

View File

@ -63,7 +63,7 @@ class BaseForm(StrAndUnicode):
# information. Any improvements to the form API should be made to *this*
# class, not to the Form class.
def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
initial=None, error_class=ErrorList):
initial=None, error_class=ErrorList, label_suffix=':'):
self.is_bound = data is not None or files is not None
self.data = data or {}
self.files = files or {}
@ -71,6 +71,7 @@ class BaseForm(StrAndUnicode):
self.prefix = prefix
self.initial = initial or {}
self.error_class = error_class
self.label_suffix = label_suffix
self._errors = None # Stores the errors after clean() has been called.
# The base_fields class attribute is the *class-wide* definition of
@ -134,9 +135,10 @@ class BaseForm(StrAndUnicode):
output.append(error_row % force_unicode(bf_errors))
if bf.label:
label = escape(force_unicode(bf.label))
# Only add a colon if the label does not end in punctuation.
if label[-1] not in ':?.!':
label += ':'
# Only add the suffix if the label does not end in punctuation.
if self.label_suffix:
if label[-1] not in ':?.!':
label += self.label_suffix
label = bf.label_tag(label) or ''
else:
label = ''

View File

@ -4,8 +4,6 @@ from cStringIO import StringIO
from urlparse import urlparse
from django.conf import settings
from django.contrib.auth import authenticate, login
from django.contrib.sessions.models import Session
from django.contrib.sessions.middleware import SessionWrapper
from django.core.handlers.base import BaseHandler
from django.core.handlers.wsgi import WSGIRequest
from django.core.signals import got_request_exception
@ -132,9 +130,10 @@ class Client:
def _session(self):
"Obtain the current session variables"
if 'django.contrib.sessions' in settings.INSTALLED_APPS:
engine = __import__(settings.SESSION_ENGINE, {}, {}, [''])
cookie = self.cookies.get(settings.SESSION_COOKIE_NAME, None)
if cookie:
return SessionWrapper(cookie.value)
return engine.SessionStore(cookie.value)
return {}
session = property(_session)
@ -247,24 +246,23 @@ class Client:
"""
user = authenticate(**credentials)
if user and user.is_active and 'django.contrib.sessions' in settings.INSTALLED_APPS:
obj = Session.objects.get_new_session_object()
engine = __import__(settings.SESSION_ENGINE, {}, {}, [''])
# Create a fake request to store login details
request = HttpRequest()
request.session = SessionWrapper(obj.session_key)
request.session = engine.SessionStore()
login(request, user)
# Set the cookie to represent the session
self.cookies[settings.SESSION_COOKIE_NAME] = obj.session_key
self.cookies[settings.SESSION_COOKIE_NAME] = request.session.session_key
self.cookies[settings.SESSION_COOKIE_NAME]['max-age'] = None
self.cookies[settings.SESSION_COOKIE_NAME]['path'] = '/'
self.cookies[settings.SESSION_COOKIE_NAME]['domain'] = settings.SESSION_COOKIE_DOMAIN
self.cookies[settings.SESSION_COOKIE_NAME]['secure'] = settings.SESSION_COOKIE_SECURE or None
self.cookies[settings.SESSION_COOKIE_NAME]['expires'] = None
# Set the session values
Session.objects.save(obj.session_key, request.session._session,
datetime.datetime.now() + datetime.timedelta(seconds=settings.SESSION_COOKIE_AGE))
# Save the session values
request.session.save()
return True
else:
@ -275,9 +273,6 @@ class Client:
Causes the authenticated user to be logged out.
"""
try:
Session.objects.get(session_key=self.cookies['sessionid'].value).delete()
except KeyError:
pass
session = __import__(settings.SESSION_ENGINE, {}, {}, ['']).SessionStore()
session.delete(session_key=self.cookies['sessionid'].value)
self.cookies = SimpleCookie()

View File

@ -72,12 +72,23 @@ class SortedDict(dict):
def items(self):
return zip(self.keyOrder, self.values())
def iteritems(self):
for key in self.keyOrder:
yield key, dict.__getitem__(self, key)
def keys(self):
return self.keyOrder[:]
def iterkeys(self):
return iter(self.keyOrder)
def values(self):
return [dict.__getitem__(self, k) for k in self.keyOrder]
def itervalues(self):
for key in self.keyOrder:
yield dict.__getitem__(self, key)
def update(self, dict):
for k, v in dict.items():
self.__setitem__(k, v)
@ -91,6 +102,15 @@ class SortedDict(dict):
"Returns the value of the item at the given zero-based index."
return self[self.keyOrder[index]]
def insert(self, index, key, value):
"Inserts the key, value pair before the item with the given index."
if key in self.keyOrder:
n = self.keyOrder.index(key)
del self.keyOrder[n]
if n < index: index -= 1
self.keyOrder.insert(index, key)
dict.__setitem__(self, key, value)
def copy(self):
"Returns a copy of this object."
# This way of initializing the copy means it works for subclasses, too.

View File

@ -166,8 +166,8 @@ class DateFormat(TimeFormat):
def O(self):
"Difference to Greenwich time in hours; e.g. '+0200'"
tz = self.timezone.utcoffset(self.data)
return u"%+03d%02d" % (tz.seconds // 3600, (tz.seconds // 60) % 60)
seconds = self.Z()
return u"%+03d%02d" % (seconds // 3600, (seconds // 60) % 60)
def r(self):
"RFC 822 formatted date; e.g. 'Thu, 21 Dec 2000 16:01:07 +0200'"

View File

@ -1,11 +1,20 @@
import datetime, math, time
import datetime
import time
from django.utils.tzinfo import LocalTimezone
from django.utils.translation import ungettext, ugettext
def timesince(d, now=None):
"""
Takes two datetime objects and returns the time between then and now
as a nicely formatted string, e.g "10 minutes"
Takes two datetime objects and returns the time between d and now
as a nicely formatted string, e.g. "10 minutes". If d occurs after now,
then "0 minutes" is returned.
Units used are years, months, weeks, days, hours, and minutes.
Seconds and microseconds are ignored. Up to two adjacent units will be
displayed. For example, "2 weeks, 3 days" and "1 year, 3 months" are
possible outputs, but "2 weeks, 3 hours" and "1 year, 5 days" are not.
Adapted from http://blog.natbat.co.uk/archive/2003/Jun/14/time_since
"""
chunks = (
@ -32,6 +41,9 @@ def timesince(d, now=None):
# ignore microsecond part of 'd' since we removed it from 'now'
delta = now - (d - datetime.timedelta(0, 0, d.microsecond))
since = delta.days * 24 * 60 * 60 + delta.seconds
if since <= 0:
# d is in the future compared to now, stop processing.
return u'0 ' + ugettext('minutes')
for i, (seconds, name) in enumerate(chunks):
count = since // seconds
if count != 0:

View File

@ -34,12 +34,12 @@ with the standard ``Auth*`` and ``Require`` directives::
If you're using Apache 2.2, you'll need to take a couple extra steps.
You'll need to ensure that ``mod_auth_basic`` and ``mod_authz_user``
are loaded. These might be compiled staticly into Apache, or you might
are loaded. These might be compiled statically into Apache, or you might
need to use ``LoadModule`` to load them dynamically (as shown in the
example at the bottom of this note).
You'll also need to insert configuration directives that prevent Apache
from trying to use other authentication modules. Depnding on which other
from trying to use other authentication modules. Depending on which other
authentication modules you have loaded, you might need one or more of
the following directives::

View File

@ -645,11 +645,11 @@ Examples:
To run the test server on port 7000 with ``fixture1`` and ``fixture2``::
django-admin.py testserver --addrport 7000 fixture1 fixture2
django-admin.py testserver fixture1 fixture2 --addrport 8080
django-admin.py testserver fixture1 fixture2 --addrport 7000
(The above statements are equivalent. We include both of them to demonstrate
that it doesn't matter whether the options come before or after the
``testserver`` command.)
that it doesn't matter whether the options come before or after the fixture
arguments.)
To run on 1.2.3.4:7000 with a `test` fixture::

View File

@ -328,7 +328,7 @@ attribute on the ``EmailMessage`` class to change the main content type. The
major type will always be ``"text"``, but you can change it to the subtype. For
example::
msg = EmailMessage(subject, html_content, from_email, to)
msg = EmailMessage(subject, html_content, from_email, [to])
msg.content_subtype = "html" # Main content is now text/html
msg.send()

View File

@ -669,8 +669,11 @@ To create message files, you use the same ``make-messages.py`` tool as with the
Django message files. You only need to be in the right place -- in the directory
where either the ``conf/locale`` (in case of the source tree) or the ``locale/``
(in case of app messages or project messages) directory are located. And you
use the same ``compile-messages.py`` to produce the binary ``django.mo`` files that
are used by ``gettext``.
use the same ``compile-messages.py`` to produce the binary ``django.mo`` files
that are used by ``gettext``.
You can also run ``compile-message.py --settings=path.to.settings`` to make
the compiler process all the directories in your ``LOCALE_PATHS`` setting.
Application message files are a bit complicated to discover -- they need the
``LocaleMiddleware``. If you don't use the middleware, only the Django message
@ -710,7 +713,7 @@ language choice in a ``django_language`` cookie.
After setting the language choice, Django redirects the user, following this
algorithm:
* Django looks for a ``next`` parameter in ``POST`` request.
* Django looks for a ``next`` parameter in the ``POST`` data.
* If that doesn't exist, or is empty, Django tries the URL in the
``Referrer`` header.
* If that's empty -- say, if a user's browser suppresses that header --

View File

@ -86,7 +86,7 @@ to create a temporary test database.
.. _SQLite: http://www.sqlite.org/
.. _pysqlite: http://initd.org/tracker/pysqlite
.. _MySQL backend: ../databases/
.. _cx_Oracle: http://www.python.net/crew/atuining/cx_Oracle/
.. _cx_Oracle: http://cx-oracle.sourceforge.net/
.. _Oracle: http://www.oracle.com/
.. _testing framework: ../testing/

View File

@ -1284,6 +1284,17 @@ won't add the automatic ``id`` column.
Each model requires exactly one field to have ``primary_key=True``.
The ``pk`` property
-------------------
**New in Django development version**
Regardless of whether you define a primary key field yourself, or let Django
supply one for you, each model will have a property called ``pk``. It behaves
like a normal attribute on the model, but is actually an alias for whichever
attribute is the primary key field for the model. You can read and set this
value, just as you would for any other attribute, and it will update the
correct field in the model.
Admin options
=============

View File

@ -516,6 +516,26 @@ include ``%s`` -- then the library will act as if ``auto_id`` is ``True``.
By default, ``auto_id`` is set to the string ``'id_%s'``.
Normally, a colon (``:``) will be appended after any label name when a form is
rendered. It's possible to change the colon to another character, or omit it
entirely, using the ``label_suffix`` parameter::
>>> f = ContactForm(auto_id='id_for_%s', label_suffix='')
>>> print f.as_ul()
<li><label for="id_for_subject">Subject</label> <input id="id_for_subject" type="text" name="subject" maxlength="100" /></li>
<li><label for="id_for_message">Message</label> <input type="text" name="message" id="id_for_message" /></li>
<li><label for="id_for_sender">Sender</label> <input type="text" name="sender" id="id_for_sender" /></li>
<li><label for="id_for_cc_myself">Cc myself</label> <input type="checkbox" name="cc_myself" id="id_for_cc_myself" /></li>
>>> f = ContactForm(auto_id='id_for_%s', label_suffix=' ->')
>>> print f.as_ul()
<li><label for="id_for_subject">Subject -></label> <input id="id_for_subject" type="text" name="subject" maxlength="100" /></li>
<li><label for="id_for_message">Message -></label> <input type="text" name="message" id="id_for_message" /></li>
<li><label for="id_for_sender">Sender -></label> <input type="text" name="sender" id="id_for_sender" /></li>
<li><label for="id_for_cc_myself">Cc myself -></label> <input type="checkbox" name="cc_myself" id="id_for_cc_myself" /></li>
Note that the label suffix is added only if the last character of the
label isn't a punctuation character (``.``, ``!``, ``?`` or ``:``)
Notes on field ordering
~~~~~~~~~~~~~~~~~~~~~~~
@ -1267,6 +1287,15 @@ When you use a ``FileField`` on a form, you must also remember to
Takes two optional arguments for validation, ``max_value`` and ``min_value``.
These control the range of values permitted in the field.
``IPAddressField``
~~~~~~~~~~~~~~~~~~
* Default widget: ``TextInput``
* Empty value: ``''`` (an empty string)
* Normalizes to: A Unicode object.
* Validates that the given value is a valid IPv4 address, using a regular
expression.
``MultipleChoiceField``
~~~~~~~~~~~~~~~~~~~~~~~
@ -1693,7 +1722,7 @@ the full list of conversions:
``ForeignKey`` ``ModelChoiceField`` (see below)
``ImageField`` ``ImageField``
``IntegerField`` ``IntegerField``
``IPAddressField`` ``CharField``
``IPAddressField`` ``IPAddressField``
``ManyToManyField`` ``ModelMultipleChoiceField`` (see
below)
``NullBooleanField`` ``CharField``

View File

@ -10,18 +10,21 @@ Cookies contain a session ID -- not the data itself.
Enabling sessions
=================
Sessions are implemented via a piece of middleware_ and a Django model.
Sessions are implemented via a piece of middleware_.
To enable session functionality, do these two things:
To enable session functionality, do the following:
* Edit the ``MIDDLEWARE_CLASSES`` setting and make sure
``MIDDLEWARE_CLASSES`` contains ``'django.contrib.sessions.middleware.SessionMiddleware'``.
The default ``settings.py`` created by ``django-admin.py startproject`` has
``SessionMiddleware`` activated.
* Add ``'django.contrib.sessions'`` to your ``INSTALLED_APPS`` setting, and
run ``manage.py syncdb`` to install the single database table that stores
session data.
* Add ``'django.contrib.sessions'`` to your ``INSTALLED_APPS`` setting,
and run ``manage.py syncdb`` to install the single database table
that stores session data.
**New in development version**: this step is optional if you're not using
the database session backend; see `configuring the session engine`_.
If you don't want to use sessions, you might as well remove the
``SessionMiddleware`` line from ``MIDDLEWARE_CLASSES`` and ``'django.contrib.sessions'``
@ -29,6 +32,44 @@ from your ``INSTALLED_APPS``. It'll save you a small bit of overhead.
.. _middleware: ../middleware/
Configuring the session engine
==============================
**New in development version**.
By default, Django stores sessions in your database (using the model
``django.contrib.sessions.models.Session``). Though this is convenient, in
some setups it's faster to store session data elsewhere, so Django can be
configured to store session data on your filesystem or in your cache.
Using file-based sessions
-------------------------
To use file-based sessions, set the ``SESSION_ENGINE`` setting to
``"django.contrib.sessions.backends.file"``.
You might also want to set the ``SESSION_FILE_PATH`` setting (which
defaults to ``/tmp``) to control where Django stores session files. Be
sure to check that your Web server has permissions to read and write to
this location.
Using cache-based sessions
--------------------------
To store session data using Django's cache system, set ``SESSION_ENGINE``
to ``"django.contrib.sessions.backends.cache"``. You'll want to make sure
you've configured your cache; see the `cache documentation`_ for details.
.. _cache documentation: ../cache/
.. note::
You should probably only use cache-based sessions if you're using the
memcached cache backend. The local memory and simple cache backends
don't retain data long enough to be good choices, and it'll be faster
to use file or database sessions directly instead of sending everything
through the file or database cache backends.
Using sessions in views
=======================
@ -153,9 +194,21 @@ Here's a typical usage example::
Using sessions out of views
===========================
Internally, each session is just a normal Django model. The ``Session`` model
is defined in ``django/contrib/sessions/models.py``. Because it's a normal
model, you can access sessions using the normal Django database API::
**New in Django development version**
An API is available to manipulate session data outside of a view::
>>> from django.contrib.sessions.engines.db import SessionStore
>>> s = SessionStore(session_key='2b1189a188b44ad18c35e113ac6ceead')
>>> s['last_login'] = datetime.datetime(2005, 8, 20, 13, 35, 10)
>>> s['last_login']
datetime.datetime(2005, 8, 20, 13, 35, 0)
>>> s.save()
If you're using the ``django.contrib.sessions.engine.db`` backend, each
session is just a normal Django model. The ``Session`` model is defined in
``django/contrib/sessions/models.py``. Because it's a normal model, you can
access sessions using the normal Django database API::
>>> from django.contrib.sessions.models import Session
>>> s = Session.objects.get(pk='2b1189a188b44ad18c35e113ac6ceead')
@ -245,6 +298,31 @@ Settings
A few `Django settings`_ give you control over session behavior:
SESSION_ENGINE
--------------
**New in Django development version**
Default: ``django.contrib.sessions.backends.db``
Controls where Django stores session data. Valid values are:
* ``'django.contrib.sessions.backends.db'``
* ``'django.contrib.sessions.backends.file'``
* ``'django.contrib.sessions.backends.cache'``
See `configuring the session engine`_ for more details.
SESSION_FILE_PATH
-----------------
**New in Django development version**
Default: ``/tmp/``
If you're using file-based session storage, this sets the directory in
which Django will store session data.
SESSION_COOKIE_AGE
------------------

View File

@ -257,10 +257,11 @@ The database backend to use. The build-in database backends are
``'postgresql_psycopg2'``, ``'postgresql'``, ``'mysql'``, ``'mysql_old'``,
``'sqlite3'``, ``'oracle'``, or ``'ado_mssql'``.
You can also use a database backend that doesn't ship with Django by
setting ``DATABASE_ENGINE`` to a fully-qualified path (i.e.
In the Django development version, you can use a database backend that doesn't
ship with Django by setting ``DATABASE_ENGINE`` to a fully-qualified path (i.e.
``mypackage.backends.whatever``). Writing a whole new database backend from
scratch is left as an exercise to the reader.
scratch is left as an exercise to the reader; see the other backends for
examples.
DATABASE_HOST
-------------
@ -733,6 +734,21 @@ Default: ``'root@localhost'``
The e-mail address that error messages come from, such as those sent to
``ADMINS`` and ``MANAGERS``.
SESSION_ENGINE
--------------
**New in Django development version**
Default: ``django.contrib.sessions.backends.db``
Controls where Django stores session data. Valid values are:
* ``'django.contrib.sessions.backends.db'``
* ``'django.contrib.sessions.backends.file'``
* ``'django.contrib.sessions.backends.cache'``
See the `session docs`_ for more details.
SESSION_COOKIE_AGE
------------------
@ -775,6 +791,17 @@ Default: ``False``
Whether to expire the session when the user closes his or her browser.
See the `session docs`_.
SESSION_FILE_PATH
-----------------
**New in Django development version**
Default: ``/tmp/``
If you're using file-based session storage, this sets the directory in
which Django will store session data. See the `session docs`_ for
more details.
SESSION_SAVE_EVERY_REQUEST
--------------------------

View File

@ -1275,17 +1275,23 @@ For example, if ``blog_date`` is a date instance representing midnight on 1
June 2006, and ``comment_date`` is a date instance for 08:00 on 1 June 2006,
then ``{{ comment_date|timesince:blog_date }}`` would return "8 hours".
Minutes is the smallest unit used, and "0 minutes" will be returned for any
date that is in the future relative to the comparison point.
timeuntil
~~~~~~~~~
Similar to ``timesince``, except that it measures the time from now until the
given date or datetime. For example, if today is 1 June 2006 and
``conference_date`` is a date instance holding 29 June 2006, then
``{{ conference_date|timeuntil }}`` will return "28 days".
``{{ conference_date|timeuntil }}`` will return "4 weeks".
Takes an optional argument that is a variable containing the date to use as
the comparison point (instead of *now*). If ``from_date`` contains 22 June
2006, then ``{{ conference_date|timeuntil:from_date }}`` will return "7 days".
2006, then ``{{ conference_date|timeuntil:from_date }}`` will return "1 week".
Minutes is the smallest unit used, and "0 minutes" will be returned for any
date that is in the past relative to the comparison point.
title
~~~~~

View File

@ -33,6 +33,11 @@ __test__ = {'API_TESTS': """
>>> a.id
1L
# Models have a pk property that is an alias for the primary key attribute (by
# default, the 'id' attribute).
>>> a.pk
1L
# Access database columns via Python attributes.
>>> a.headline
'Area man programs in Python'

View File

@ -56,6 +56,15 @@ DoesNotExist: Employee matching query does not exist.
>>> Employee.objects.filter(pk__in=['ABC123','XYZ456'])
[<Employee: Fran Bones>, <Employee: Dan Jones>]
# The primary key can be accessed via the pk property on the model.
>>> e = Employee.objects.get(pk='ABC123')
>>> e.pk
u'ABC123'
# Or we can use the real attribute name for the primary key:
>>> e.employee_code
u'ABC123'
# Fran got married and changed her last name.
>>> fran = Employee.objects.get(pk='XYZ456')
>>> fran.last_name = 'Jones'

View File

@ -9,6 +9,7 @@ FIXTURE_DIRS setting.
"""
from django.db import models
from django.conf import settings
class Article(models.Model):
headline = models.CharField(max_length=100, default='Default headline')
@ -53,7 +54,13 @@ __test__ = {'API_TESTS': """
# object list is unaffected
>>> Article.objects.all()
[<Article: XML identified as leading cause of cancer>, <Article: Django conquers world!>, <Article: Copyright is fine the way it is>, <Article: Poker on TV is great!>, <Article: Python program becomes self aware>]
"""}
# Database flushing does not work on MySQL with the default storage engine
# because it requires transaction support.
if settings.DATABASE_ENGINE not in ('mysql', 'mysql_old'):
__test__['API_TESTS'] += \
"""
# Reset the database representation of this app. This will delete all data.
>>> management.call_command('flush', verbosity=0, interactive=False)
>>> Article.objects.all()
@ -75,7 +82,7 @@ Multiple fixtures named 'fixture2' in '...fixtures'. Aborting.
# Dump the current contents of the database as a JSON fixture
>>> management.call_command('dumpdata', 'fixtures', format='json')
[{"pk": 3, "model": "fixtures.article", "fields": {"headline": "Time to reform copyright", "pub_date": "2006-06-16 13:00:00"}}, {"pk": 2, "model": "fixtures.article", "fields": {"headline": "Poker has no place on ESPN", "pub_date": "2006-06-16 12:00:00"}}, {"pk": 1, "model": "fixtures.article", "fields": {"headline": "Python program becomes self aware", "pub_date": "2006-06-16 11:00:00"}}]
"""}
"""
from django.test import TestCase

View File

@ -1,4 +1,5 @@
from django.db import models
from django.db import connection
class Square(models.Model):
root = models.IntegerField()
@ -7,18 +8,27 @@ class Square(models.Model):
def __unicode__(self):
return "%s ** 2 == %s" % (self.root, self.square)
if connection.features.uses_case_insensitive_names:
t_convert = lambda x: x.upper()
else:
t_convert = lambda x: x
qn = connection.ops.quote_name
__test__ = {'API_TESTS': """
#4896: Test cursor.executemany
>>> from django.db import connection
>>> cursor = connection.cursor()
>>> cursor.executemany('INSERT INTO BACKENDS_SQUARE (ROOT, SQUARE) VALUES (%s, %s)',
... [(i, i**2) for i in range(-5, 6)]) and None or None
>>> opts = Square._meta
>>> f1, f2 = opts.get_field('root'), opts.get_field('square')
>>> query = ('INSERT INTO %s (%s, %s) VALUES (%%s, %%s)'
... % (t_convert(opts.db_table), qn(f1.column), qn(f2.column)))
>>> cursor.executemany(query, [(i, i**2) for i in range(-5, 6)]) and None or None
>>> Square.objects.order_by('root')
[<Square: -5 ** 2 == 25>, <Square: -4 ** 2 == 16>, <Square: -3 ** 2 == 9>, <Square: -2 ** 2 == 4>, <Square: -1 ** 2 == 1>, <Square: 0 ** 2 == 0>, <Square: 1 ** 2 == 1>, <Square: 2 ** 2 == 4>, <Square: 3 ** 2 == 9>, <Square: 4 ** 2 == 16>, <Square: 5 ** 2 == 25>]
#4765: executemany with params=[] does nothing
>>> cursor.executemany('INSERT INTO BACKENDS_SQUARE (ROOT, SQUARE) VALUES (%s, %s)', []) and None or None
>>> cursor.executemany(query, []) and None or None
>>> Square.objects.count()
11

View File

@ -2945,6 +2945,37 @@ is default behavior.
<li><label for="id_username">Username:</label> <input id="id_username" type="text" name="username" maxlength="10" /></li>
<li><label for="id_password">Password:</label> <input type="password" name="password" id="id_password" /></li>
# Label Suffix ################################################################
You can specify the 'label_suffix' argument to a Form class to modify the
punctuation symbol used at the end of a label. By default, the colon (:) is
used, and is only appended to the label if the label doesn't already end with a
punctuation symbol: ., !, ? or :. If you specify a different suffix, it will
be appended regardless of the last character of the label.
>>> class FavoriteForm(Form):
... color = CharField(label='Favorite color?')
... animal = CharField(label='Favorite animal')
...
>>> f = FavoriteForm(auto_id=False)
>>> print f.as_ul()
<li>Favorite color? <input type="text" name="color" /></li>
<li>Favorite animal: <input type="text" name="animal" /></li>
>>> f = FavoriteForm(auto_id=False, label_suffix='?')
>>> print f.as_ul()
<li>Favorite color? <input type="text" name="color" /></li>
<li>Favorite animal? <input type="text" name="animal" /></li>
>>> f = FavoriteForm(auto_id=False, label_suffix='')
>>> print f.as_ul()
<li>Favorite color? <input type="text" name="color" /></li>
<li>Favorite animal <input type="text" name="animal" /></li>
>>> f = FavoriteForm(auto_id=False, label_suffix=u'\u2192')
>>> f.as_ul()
u'<li>Favorite color? <input type="text" name="color" /></li>\n<li>Favorite animal\u2192 <input type="text" name="animal" /></li>'
# Initial data ################################################################
You can specify initial data for a field by using the 'initial' argument to a
@ -3805,6 +3836,61 @@ ValidationError: [u'This field is required.']
>>> f.cleaned_data
{'field1': u'some text,JP,2007-04-25 06:24:00'}
# IPAddressField ##################################################################
>>> f = IPAddressField()
>>> f.clean('')
Traceback (most recent call last):
...
ValidationError: [u'This field is required.']
>>> f.clean(None)
Traceback (most recent call last):
...
ValidationError: [u'This field is required.']
>>> f.clean('127.0.0.1')
u'127.0.0.1'
>>> f.clean('foo')
Traceback (most recent call last):
...
ValidationError: [u'Enter a valid IPv4 address.']
>>> f.clean('127.0.0.')
Traceback (most recent call last):
...
ValidationError: [u'Enter a valid IPv4 address.']
>>> f.clean('1.2.3.4.5')
Traceback (most recent call last):
...
ValidationError: [u'Enter a valid IPv4 address.']
>>> f.clean('256.125.1.5')
Traceback (most recent call last):
...
ValidationError: [u'Enter a valid IPv4 address.']
>>> f = IPAddressField(required=False)
>>> f.clean('')
u''
>>> f.clean(None)
u''
>>> f.clean('127.0.0.1')
u'127.0.0.1'
>>> f.clean('foo')
Traceback (most recent call last):
...
ValidationError: [u'Enter a valid IPv4 address.']
>>> f.clean('127.0.0.')
Traceback (most recent call last):
...
ValidationError: [u'Enter a valid IPv4 address.']
>>> f.clean('1.2.3.4.5')
Traceback (most recent call last):
...
ValidationError: [u'Enter a valid IPv4 address.']
>>> f.clean('256.125.1.5')
Traceback (most recent call last):
...
ValidationError: [u'Enter a valid IPv4 address.']
#################################
# Tests of underlying functions #
#################################

View File

@ -771,6 +771,10 @@ class Templates(unittest.TestCase):
# Check that timezone is respected
'timesince06' : ('{{ a|timesince:b }}', {'a':NOW_tz + timedelta(hours=8), 'b':NOW_tz}, '8 hours'),
# Check times in the future.
'timesince07' : ('{{ a|timesince }}', {'a':datetime.now() + timedelta(minutes=1, seconds=10)}, '0 minutes'),
'timesince08' : ('{{ a|timesince }}', {'a':datetime.now() + timedelta(days=1, minutes=1)}, '0 minutes'),
### TIMEUNTIL TAG ##################################################
# Default compare with datetime.now()
'timeuntil01' : ('{{ a|timeuntil }}', {'a':datetime.now() + timedelta(minutes=2, seconds = 10)}, '2 minutes'),
@ -781,6 +785,10 @@ class Templates(unittest.TestCase):
'timeuntil04' : ('{{ a|timeuntil:b }}', {'a':NOW - timedelta(days=1), 'b':NOW - timedelta(days=2)}, '1 day'),
'timeuntil05' : ('{{ a|timeuntil:b }}', {'a':NOW - timedelta(days=2), 'b':NOW - timedelta(days=2, minutes=1)}, '1 minute'),
# Check times in the past.
'timeuntil07' : ('{{ a|timeuntil }}', {'a':datetime.now() - timedelta(minutes=1, seconds=10)}, '0 minutes'),
'timeuntil08' : ('{{ a|timeuntil }}', {'a':datetime.now() - timedelta(days=1, minutes=1)}, '0 minutes'),
### URL TAG ########################################################
# Successes
'url01' : ('{% url regressiontests.templates.views.client client.id %}', {'client': {'id': 1}}, '/url_tag/client/1/'),

View File

@ -6,6 +6,8 @@ from unittest import TestCase
from django.utils import html
from timesince import timesince_tests
class TestUtilsHtml(TestCase):
def check_output(self, function, value, output=None):
@ -113,3 +115,11 @@ class TestUtilsHtml(TestCase):
)
for value, output in items:
self.check_output(f, value, output)
__test__ = {
'timesince_tests': timesince_tests,
}
if __name__ == "__main__":
import doctest
doctest.testmod()

View File

@ -0,0 +1,77 @@
timesince_tests = """
>>> from datetime import datetime, timedelta
>>> from django.utils.timesince import timesince
>>> t = datetime(2007, 8, 14, 13, 46, 0)
>>> onemicrosecond = timedelta(microseconds=1)
>>> onesecond = timedelta(seconds=1)
>>> oneminute = timedelta(minutes=1)
>>> onehour = timedelta(hours=1)
>>> oneday = timedelta(days=1)
>>> oneweek = timedelta(days=7)
>>> onemonth = timedelta(days=30)
>>> oneyear = timedelta(days=365)
# equal datetimes.
>>> timesince(t, t)
u'0 minutes'
# Microseconds and seconds are ignored.
>>> timesince(t, t+onemicrosecond)
u'0 minutes'
>>> timesince(t, t+onesecond)
u'0 minutes'
# Test other units.
>>> timesince(t, t+oneminute)
u'1 minute'
>>> timesince(t, t+onehour)
u'1 hour'
>>> timesince(t, t+oneday)
u'1 day'
>>> timesince(t, t+oneweek)
u'1 week'
>>> timesince(t, t+onemonth)
u'1 month'
>>> timesince(t, t+oneyear)
u'1 year'
# Test multiple units.
>>> timesince(t, t+2*oneday+6*onehour)
u'2 days, 6 hours'
>>> timesince(t, t+2*oneweek+2*oneday)
u'2 weeks, 2 days'
# If the two differing units aren't adjacent, only the first unit is displayed.
>>> timesince(t, t+2*oneweek+3*onehour+4*oneminute)
u'2 weeks'
>>> timesince(t, t+4*oneday+5*oneminute)
u'4 days'
# When the second date occurs before the first, we should always get 0 minutes.
>>> timesince(t, t-onemicrosecond)
u'0 minutes'
>>> timesince(t, t-onesecond)
u'0 minutes'
>>> timesince(t, t-oneminute)
u'0 minutes'
>>> timesince(t, t-onehour)
u'0 minutes'
>>> timesince(t, t-oneday)
u'0 minutes'
>>> timesince(t, t-oneweek)
u'0 minutes'
>>> timesince(t, t-onemonth)
u'0 minutes'
>>> timesince(t, t-oneyear)
u'0 minutes'
>>> timesince(t, t-2*oneday-6*onehour)
u'0 minutes'
>>> timesince(t, t-2*oneweek-2*oneday)
u'0 minutes'
>>> timesince(t, t-2*oneweek-3*onehour-4*oneminute)
u'0 minutes'
>>> timesince(t, t-4*oneday-5*oneminute)
u'0 minutes'
"""

View File

View File

@ -0,0 +1,25 @@
[
{
"pk": 1,
"model": "views.article",
"fields": {
"author": 1,
"title": "An Article"
}
},
{
"pk": 1,
"model": "views.author",
"fields": {
"name": "Boris"
}
},
{
"pk": 1,
"model": "sites.site",
"fields": {
"domain": "testserver",
"name": "testserver"
}
}
]

View File

@ -0,0 +1,20 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2007-09-15 16:45+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "this is to be translated"
msgstr "this is to be translated in english"

View File

@ -0,0 +1,21 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2007-09-15 16:45+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: media/js/translate.js:1
msgid "this is to be translated"
msgstr "esto tiene que ser traducido"

View File

@ -0,0 +1,20 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2007-09-15 19:15+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "this is to be translated"
msgstr "il faut le traduire"

View File

@ -0,0 +1 @@
An example media file.

View File

@ -0,0 +1,24 @@
"""
Regression tests for Django built-in views
"""
from django.db import models
from django.conf import settings
class Author(models.Model):
name = models.CharField(max_length=100)
def __unicode__(self):
return self.name
def get_absolute_url(self):
return '/views/authors/%s/' % self.id
class Article(models.Model):
title = models.CharField(max_length=100)
author = models.ForeignKey(Author)
def __unicode__(self):
return self.title

View File

@ -0,0 +1,3 @@
from defaults import *
from i18n import *
from static import *

View File

@ -0,0 +1,39 @@
from os import path
from django.conf import settings
from django.test import TestCase
from django.contrib.contenttypes.models import ContentType
from regressiontests.views.models import Author, Article
class DefaultsTests(TestCase):
"""Test django views in django/views/defaults.py"""
fixtures = ['testdata.json']
def test_shorcut_with_absolute_url(self):
"Can view a shortcut an Author object that has with a get_absolute_url method"
for obj in Author.objects.all():
short_url = '/views/shortcut/%s/%s/' % (ContentType.objects.get_for_model(Author).id, obj.pk)
response = self.client.get(short_url)
self.assertRedirects(response, 'http://testserver%s' % obj.get_absolute_url(),
status_code=302, target_status_code=404)
def test_shortcut_no_absolute_url(self):
"Shortcuts for an object that has with a get_absolute_url method raises 404"
for obj in Article.objects.all():
short_url = '/views/shortcut/%s/%s/' % (ContentType.objects.get_for_model(Article).id, obj.pk)
response = self.client.get(short_url)
self.assertEquals(response.status_code, 404)
def test_page_not_found(self):
"A 404 status is returned by the page_not_found view"
non_existing_urls = ['/views/non_existing_url/', # this is in urls.py
'/views/other_non_existing_url/'] # this NOT in urls.py
for url in non_existing_urls:
response = self.client.get(url)
self.assertEquals(response.status_code, 404)
def test_server_error(self):
"The server_error view raises a 500 status"
response = self.client.get('/views/server_error/')
self.assertEquals(response.status_code, 500)

View File

@ -0,0 +1,30 @@
from os import path
import gettext
from django.conf import settings
from django.test import TestCase
from django.utils.translation import activate
from regressiontests.views.urls import locale_dir
class I18NTests(TestCase):
""" Tests django views in django/views/i18n.py """
def test_setlang(self):
"""The set_language view can be used to change the session language"""
for lang_code, lang_name in settings.LANGUAGES:
post_data = dict(language=lang_code, next='/views/')
response = self.client.post('/views/i18n/setlang/', data=post_data)
self.assertRedirects(response, 'http://testserver/views/')
self.assertEquals(self.client.session['django_language'], lang_code)
def test_jsi18n(self):
"""The javascript_catalog can be deployed with language settings"""
for lang_code in ['es', 'fr', 'en']:
activate(lang_code)
catalog = gettext.translation('djangojs', locale_dir, [lang_code])
trans_txt = catalog.ugettext('this is to be translated')
response = self.client.get('/views/jsi18n/')
# in response content must to be a line like that:
# catalog['this is to be translated'] = 'same_that_trans_txt'
self.assertContains(response, trans_txt, 1)

View File

@ -0,0 +1,15 @@
from os import path
from django.test import TestCase
from regressiontests.views.urls import media_dir
class StaticTests(TestCase):
"""Tests django views in django/views/static.py"""
def test_serve(self):
"The static view can serve static media"
media_files = ['file.txt',]
for filename in media_files:
response = self.client.get('/views/site_media/%s' % filename)
file = open(path.join(media_dir, filename))
self.assertEquals(file.read(), response.content)

View File

@ -0,0 +1,26 @@
from os import path
from django.conf.urls.defaults import *
import views
base_dir = path.dirname(path.abspath(__file__))
media_dir = path.join(base_dir, 'media')
locale_dir = path.join(base_dir, 'locale')
js_info_dict = {
'domain': 'djangojs',
'packages': ('regressiontests.views',),
}
urlpatterns = patterns('',
(r'^$', views.index_page),
(r'^shortcut/(\d+)/(.*)/$', 'django.views.defaults.shortcut'),
(r'^non_existing_url/', 'django.views.defaults.page_not_found'),
(r'^server_error/', 'django.views.defaults.server_error'),
(r'^i18n/', include('django.conf.urls.i18n')),
(r'^jsi18n/$', 'django.views.i18n.javascript_catalog', js_info_dict),
(r'^jsi18n_test/$', views.jsi18n_test),
(r'^site_media/(?P<path>.*)$', 'django.views.static.serve', {'document_root': media_dir}),
)

View File

@ -0,0 +1,12 @@
from django.http import HttpResponse
from django.template import RequestContext
from django.shortcuts import render_to_response
def index_page(request):
""" Dummy index page """
return HttpResponse('<html><body>Dummy page</body></html>')
def jsi18n_test(request):
""" View for testing javascript message files """
return render_to_response('js_i18n.html', {})

View File

@ -11,4 +11,7 @@ urlpatterns = patterns('',
# test urlconf for {% url %} template tag
(r'^url_tag/', include('regressiontests.templates.urls')),
# django built-in views
(r'^views/', include('regressiontests.views.urls')),
)