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:
parent
019e3c61e4
commit
0de9a64905
8
AUTHORS
8
AUTHORS
@ -84,10 +84,12 @@ answer newbie questions, and generally made Django that much better:
|
|||||||
Russell Cloran <russell@rucus.net>
|
Russell Cloran <russell@rucus.net>
|
||||||
colin@owlfish.com
|
colin@owlfish.com
|
||||||
crankycoder@gmail.com
|
crankycoder@gmail.com
|
||||||
|
Paul Collier <paul@paul-collier.com>
|
||||||
Pete Crosier <pete.crosier@gmail.com>
|
Pete Crosier <pete.crosier@gmail.com>
|
||||||
Matt Croydon <http://www.postneo.com/>
|
Matt Croydon <http://www.postneo.com/>
|
||||||
flavio.curella@gmail.com
|
flavio.curella@gmail.com
|
||||||
Jure Cuhalev <gandalf@owca.info>
|
Jure Cuhalev <gandalf@owca.info>
|
||||||
|
John D'Agostino <john.dagostino@gmail.com>
|
||||||
dackze+django@gmail.com
|
dackze+django@gmail.com
|
||||||
David Danier <goliath.mailinglist@gmx.de>
|
David Danier <goliath.mailinglist@gmx.de>
|
||||||
Dirk Datzert <dummy@habmalnefrage.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>
|
Afonso Fernández Nogueira <fonzzo.django@gmail.com>
|
||||||
Matthew Flanagan <http://wadofstuff.blogspot.com>
|
Matthew Flanagan <http://wadofstuff.blogspot.com>
|
||||||
Eric Floehr <eric@intellovations.com>
|
Eric Floehr <eric@intellovations.com>
|
||||||
|
Vincent Foley <vfoleybourgon@yahoo.ca>
|
||||||
Jorge Gajon <gajon@gajon.org>
|
Jorge Gajon <gajon@gajon.org>
|
||||||
gandalf@owca.info
|
gandalf@owca.info
|
||||||
Marc Garcia <marc.garcia@accopensys.com>
|
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>
|
Waylan Limberg <waylan@gmail.com>
|
||||||
limodou
|
limodou
|
||||||
Philip Lindborg <philip.lindborg@gmail.com>
|
Philip Lindborg <philip.lindborg@gmail.com>
|
||||||
|
<<<<<<< .working
|
||||||
Simon Litchfield <simon@quo.com.au>
|
Simon Litchfield <simon@quo.com.au>
|
||||||
|
=======
|
||||||
|
msaelices <msaelices@gmail.com>
|
||||||
|
>>>>>>> .merge-right.r6370
|
||||||
Matt McClanahan <http://mmcc.cx/>
|
Matt McClanahan <http://mmcc.cx/>
|
||||||
Martin Maney <http://www.chipy.org/Martin_Maney>
|
Martin Maney <http://www.chipy.org/Martin_Maney>
|
||||||
Petr Marhoun <petr.marhoun@gmail.com>
|
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>
|
Brian Rosner <brosner@gmail.com>
|
||||||
Oliver Rutherfurd <http://rutherfurd.net/>
|
Oliver Rutherfurd <http://rutherfurd.net/>
|
||||||
ryankanno
|
ryankanno
|
||||||
|
Manuel Saelices <msaelices@yaco.es>
|
||||||
Ivan Sagalaev (Maniac) <http://www.softwaremaniacs.org/>
|
Ivan Sagalaev (Maniac) <http://www.softwaremaniacs.org/>
|
||||||
Vinay Sajip <vinay_sajip@yahoo.co.uk>
|
Vinay Sajip <vinay_sajip@yahoo.co.uk>
|
||||||
David Schein
|
David Schein
|
||||||
|
@ -4,20 +4,31 @@ import optparse
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
def compile_messages(locale=None):
|
try:
|
||||||
basedir = None
|
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'))
|
def compile_messages(locale=None):
|
||||||
elif os.path.isdir('locale'):
|
basedirs = [os.path.join('conf', 'locale'), 'locale']
|
||||||
basedir = os.path.abspath('locale')
|
if os.environ.get('DJANGO_SETTINGS_MODULE'):
|
||||||
else:
|
from django.conf import settings
|
||||||
print "This script should be run from the Django SVN tree or your project or app tree."
|
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)
|
sys.exit(1)
|
||||||
|
|
||||||
if locale is not None:
|
for basedir in basedirs:
|
||||||
basedir = os.path.join(basedir, locale, 'LC_MESSAGES')
|
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 dirpath, dirnames, filenames in os.walk(basedir):
|
||||||
for f in filenames:
|
for f in filenames:
|
||||||
if f.endswith('.po'):
|
if f.endswith('.po'):
|
||||||
@ -40,9 +51,13 @@ def main():
|
|||||||
parser = optparse.OptionParser()
|
parser = optparse.OptionParser()
|
||||||
parser.add_option('-l', '--locale', dest='locale',
|
parser.add_option('-l', '--locale', dest='locale',
|
||||||
help="The locale to process. Default is to process all.")
|
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()
|
options, args = parser.parse_args()
|
||||||
if len(args):
|
if len(args):
|
||||||
parser.error("This program takes no arguments")
|
parser.error("This program takes no arguments")
|
||||||
|
if options.settings:
|
||||||
|
os.environ['DJANGO_SETTINGS_MODULE'] = options.settings
|
||||||
compile_messages(options.locale)
|
compile_messages(options.locale)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
@ -271,12 +271,14 @@ MIDDLEWARE_CLASSES = (
|
|||||||
# SESSIONS #
|
# SESSIONS #
|
||||||
############
|
############
|
||||||
|
|
||||||
SESSION_COOKIE_NAME = 'sessionid' # Cookie name. This can be whatever you want.
|
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_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_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_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_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 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 #
|
# CACHE #
|
||||||
|
@ -85,7 +85,7 @@ class AdminBoundField(object):
|
|||||||
|
|
||||||
def original_value(self):
|
def original_value(self):
|
||||||
if self.original:
|
if self.original:
|
||||||
return self.original.__dict__[self.field.column]
|
return self.original.__dict__[self.field.attname]
|
||||||
|
|
||||||
def existing_display(self):
|
def existing_display(self):
|
||||||
try:
|
try:
|
||||||
|
0
django/contrib/sessions/backends/__init__.py
Normal file
0
django/contrib/sessions/backends/__init__.py
Normal file
143
django/contrib/sessions/backends/base.py
Normal file
143
django/contrib/sessions/backends/base.py
Normal 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
|
||||||
|
|
26
django/contrib/sessions/backends/cache.py
Normal file
26
django/contrib/sessions/backends/cache.py
Normal 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)
|
49
django/contrib/sessions/backends/db.py
Normal file
49
django/contrib/sessions/backends/db.py
Normal 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
|
68
django/contrib/sessions/backends/file.py
Normal file
68
django/contrib/sessions/backends/file.py
Normal 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
|
@ -1,6 +1,4 @@
|
|||||||
from django.conf import settings
|
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 django.utils.cache import patch_vary_headers
|
||||||
from email.Utils import formatdate
|
from email.Utils import formatdate
|
||||||
import datetime
|
import datetime
|
||||||
@ -9,73 +7,11 @@ import time
|
|||||||
TEST_COOKIE_NAME = 'testcookie'
|
TEST_COOKIE_NAME = 'testcookie'
|
||||||
TEST_COOKIE_VALUE = 'worked'
|
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):
|
class SessionMiddleware(object):
|
||||||
|
|
||||||
def process_request(self, request):
|
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):
|
def process_response(self, request, response):
|
||||||
# If request.session was modified, or if response.session was set, save
|
# If request.session was modified, or if response.session was set, save
|
||||||
@ -89,25 +25,22 @@ 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 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:
|
if settings.SESSION_EXPIRE_AT_BROWSER_CLOSE:
|
||||||
max_age = None
|
max_age = None
|
||||||
expires = None
|
expires = None
|
||||||
else:
|
else:
|
||||||
max_age = settings.SESSION_COOKIE_AGE
|
max_age = settings.SESSION_COOKIE_AGE
|
||||||
rfcdate = formatdate(time.time() + settings.SESSION_COOKIE_AGE)
|
rfcdate = formatdate(time.time() + settings.SESSION_COOKIE_AGE)
|
||||||
|
|
||||||
# Fixed length date must have '-' separation in the format
|
# Fixed length date must have '-' separation in the format
|
||||||
# DD-MMM-YYYY for compliance with Netscape cookie standard
|
# DD-MMM-YYYY for compliance with Netscape cookie standard
|
||||||
expires = (rfcdate[:7] + "-" + rfcdate[8:11]
|
expires = datetime.datetime.strftime(datetime.datetime.utcnow() + \
|
||||||
+ "-" + rfcdate[12:26] + "GMT")
|
datetime.timedelta(seconds=settings.SESSION_COOKIE_AGE), "%a, %d-%b-%Y %H:%M:%S GMT")
|
||||||
new_session = Session.objects.save(session_key, request.session._session,
|
|
||||||
datetime.datetime.now() + datetime.timedelta(seconds=settings.SESSION_COOKIE_AGE))
|
# Save the seesion data and refresh the client cookie.
|
||||||
response.set_cookie(settings.SESSION_COOKIE_NAME, session_key,
|
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,
|
max_age=max_age, expires=expires, domain=settings.SESSION_COOKIE_DOMAIN,
|
||||||
secure=settings.SESSION_COOKIE_SECURE or None)
|
secure=settings.SESSION_COOKIE_SECURE or None)
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import base64, md5, random, sys, datetime, os, time
|
import base64, md5, random, sys, datetime
|
||||||
import cPickle as pickle
|
import cPickle as pickle
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
@ -74,6 +74,7 @@ class Session(models.Model):
|
|||||||
session_data = models.TextField(_('session data'))
|
session_data = models.TextField(_('session data'))
|
||||||
expire_date = models.DateTimeField(_('expire date'))
|
expire_date = models.DateTimeField(_('expire date'))
|
||||||
objects = SessionManager()
|
objects = SessionManager()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
db_table = 'django_session'
|
db_table = 'django_session'
|
||||||
verbose_name = _('session')
|
verbose_name = _('session')
|
||||||
|
@ -1,35 +1,59 @@
|
|||||||
r"""
|
r"""
|
||||||
>>> s = SessionWrapper(None)
|
|
||||||
|
|
||||||
Inject data into the session cache.
|
>>> from django.contrib.sessions.backends.db import SessionStore as DatabaseSession
|
||||||
>>> s._session_cache = {}
|
>>> from django.contrib.sessions.backends.cache import SessionStore as CacheSession
|
||||||
>>> s._session_cache['some key'] = 'exists'
|
>>> from django.contrib.sessions.backends.file import SessionStore as FileSession
|
||||||
|
|
||||||
>>> s.accessed
|
>>> db_session = DatabaseSession()
|
||||||
|
>>> db_session.modified
|
||||||
False
|
False
|
||||||
>>> s.modified
|
>>> db_session['cat'] = "dog"
|
||||||
False
|
>>> db_session.modified
|
||||||
|
True
|
||||||
>>> s.pop('non existant key', 'does not exist')
|
>>> db_session.pop('cat')
|
||||||
|
'dog'
|
||||||
|
>>> db_session.pop('some key', 'does not exist')
|
||||||
'does not exist'
|
'does not exist'
|
||||||
>>> s.accessed
|
>>> db_session.save()
|
||||||
|
>>> db_session.exists(db_session.session_key)
|
||||||
True
|
True
|
||||||
>>> s.modified
|
>>> db_session.delete(db_session.session_key)
|
||||||
|
>>> db_session.exists(db_session.session_key)
|
||||||
False
|
False
|
||||||
|
|
||||||
>>> s.pop('some key')
|
>>> file_session = FileSession()
|
||||||
'exists'
|
>>> file_session.modified
|
||||||
>>> s.accessed
|
False
|
||||||
|
>>> file_session['cat'] = "dog"
|
||||||
|
>>> file_session.modified
|
||||||
True
|
True
|
||||||
>>> s.modified
|
>>> file_session.pop('cat')
|
||||||
True
|
'dog'
|
||||||
|
>>> file_session.pop('some key', 'does not exist')
|
||||||
>>> s.pop('some key', 'does not exist')
|
|
||||||
'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__':
|
if __name__ == '__main__':
|
||||||
import doctest
|
import doctest
|
||||||
doctest.testmod()
|
doctest.testmod()
|
||||||
|
@ -13,11 +13,19 @@ def auth(request):
|
|||||||
"""
|
"""
|
||||||
Returns context variables required by apps that use Django's authentication
|
Returns context variables required by apps that use Django's authentication
|
||||||
system.
|
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 {
|
return {
|
||||||
'user': request.user,
|
'user': user,
|
||||||
'messages': request.user.get_and_delete_messages(),
|
'messages': user.get_and_delete_messages(),
|
||||||
'perms': PermWrapper(request.user),
|
'perms': PermWrapper(user),
|
||||||
}
|
}
|
||||||
|
|
||||||
def debug(request):
|
def debug(request):
|
||||||
|
@ -42,8 +42,11 @@ class ModPythonRequest(http.HttpRequest):
|
|||||||
return '%s%s' % (self.path, self._req.args and ('?' + self._req.args) or '')
|
return '%s%s' % (self.path, self._req.args and ('?' + self._req.args) or '')
|
||||||
|
|
||||||
def is_secure(self):
|
def is_secure(self):
|
||||||
# Note: modpython 3.2.10+ has req.is_https(), but we need to support previous versions
|
try:
|
||||||
return 'HTTPS' in self._req.subprocess_env and self._req.subprocess_env['HTTPS'] == 'on'
|
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):
|
def _load_post_and_files(self):
|
||||||
"Populates self._post and self._files"
|
"Populates self._post and self._files"
|
||||||
|
@ -83,6 +83,11 @@ class Model(object):
|
|||||||
def _get_pk_val(self):
|
def _get_pk_val(self):
|
||||||
return getattr(self, self._meta.pk.attname)
|
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):
|
def __repr__(self):
|
||||||
return smart_str(u'<%s: %s>' % (self.__class__.__name__, unicode(self)))
|
return smart_str(u'<%s: %s>' % (self.__class__.__name__, unicode(self)))
|
||||||
|
|
||||||
|
@ -878,6 +878,11 @@ class IPAddressField(Field):
|
|||||||
def validate(self, field_data, all_data):
|
def validate(self, field_data, all_data):
|
||||||
validators.isValidIPAddress4(field_data, None)
|
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):
|
class NullBooleanField(Field):
|
||||||
empty_strings_allowed = False
|
empty_strings_allowed = False
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
@ -55,6 +55,7 @@ class SetRemoteAddrFromForwardedFor(object):
|
|||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
# HTTP_X_FORWARDED_FOR can be a comma-separated list of IPs.
|
# HTTP_X_FORWARDED_FOR can be a comma-separated list of IPs.
|
||||||
# Take just the first one.
|
# Take just the last one.
|
||||||
real_ip = real_ip.split(",")[0]
|
# 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
|
request.META['REMOTE_ADDR'] = real_ip
|
||||||
|
@ -26,7 +26,7 @@ __all__ = (
|
|||||||
'RegexField', 'EmailField', 'FileField', 'ImageField', 'URLField', 'BooleanField',
|
'RegexField', 'EmailField', 'FileField', 'ImageField', 'URLField', 'BooleanField',
|
||||||
'ChoiceField', 'NullBooleanField', 'MultipleChoiceField',
|
'ChoiceField', 'NullBooleanField', 'MultipleChoiceField',
|
||||||
'ComboField', 'MultiValueField', 'FloatField', 'DecimalField',
|
'ComboField', 'MultiValueField', 'FloatField', 'DecimalField',
|
||||||
'SplitDateTimeField',
|
'SplitDateTimeField', 'IPAddressField',
|
||||||
)
|
)
|
||||||
|
|
||||||
# These values, if given to to_python(), will trigger the self.required check.
|
# These values, if given to to_python(), will trigger the self.required check.
|
||||||
@ -635,3 +635,11 @@ class SplitDateTimeField(MultiValueField):
|
|||||||
raise ValidationError(ugettext(u'Enter a valid time.'))
|
raise ValidationError(ugettext(u'Enter a valid time.'))
|
||||||
return datetime.datetime.combine(*data_list)
|
return datetime.datetime.combine(*data_list)
|
||||||
return None
|
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)
|
||||||
|
@ -63,7 +63,7 @@ class BaseForm(StrAndUnicode):
|
|||||||
# information. Any improvements to the form API should be made to *this*
|
# information. Any improvements to the form API should be made to *this*
|
||||||
# class, not to the Form class.
|
# class, not to the Form class.
|
||||||
def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
|
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.is_bound = data is not None or files is not None
|
||||||
self.data = data or {}
|
self.data = data or {}
|
||||||
self.files = files or {}
|
self.files = files or {}
|
||||||
@ -71,6 +71,7 @@ class BaseForm(StrAndUnicode):
|
|||||||
self.prefix = prefix
|
self.prefix = prefix
|
||||||
self.initial = initial or {}
|
self.initial = initial or {}
|
||||||
self.error_class = error_class
|
self.error_class = error_class
|
||||||
|
self.label_suffix = label_suffix
|
||||||
self._errors = None # Stores the errors after clean() has been called.
|
self._errors = None # Stores the errors after clean() has been called.
|
||||||
|
|
||||||
# The base_fields class attribute is the *class-wide* definition of
|
# 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))
|
output.append(error_row % force_unicode(bf_errors))
|
||||||
if bf.label:
|
if bf.label:
|
||||||
label = escape(force_unicode(bf.label))
|
label = escape(force_unicode(bf.label))
|
||||||
# Only add a colon if the label does not end in punctuation.
|
# Only add the suffix if the label does not end in punctuation.
|
||||||
if label[-1] not in ':?.!':
|
if self.label_suffix:
|
||||||
label += ':'
|
if label[-1] not in ':?.!':
|
||||||
|
label += self.label_suffix
|
||||||
label = bf.label_tag(label) or ''
|
label = bf.label_tag(label) or ''
|
||||||
else:
|
else:
|
||||||
label = ''
|
label = ''
|
||||||
|
@ -4,8 +4,6 @@ from cStringIO import StringIO
|
|||||||
from urlparse import urlparse
|
from urlparse import urlparse
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth import authenticate, login
|
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.base import BaseHandler
|
||||||
from django.core.handlers.wsgi import WSGIRequest
|
from django.core.handlers.wsgi import WSGIRequest
|
||||||
from django.core.signals import got_request_exception
|
from django.core.signals import got_request_exception
|
||||||
@ -132,9 +130,10 @@ class Client:
|
|||||||
def _session(self):
|
def _session(self):
|
||||||
"Obtain the current session variables"
|
"Obtain the current session variables"
|
||||||
if 'django.contrib.sessions' in settings.INSTALLED_APPS:
|
if 'django.contrib.sessions' in settings.INSTALLED_APPS:
|
||||||
|
engine = __import__(settings.SESSION_ENGINE, {}, {}, [''])
|
||||||
cookie = self.cookies.get(settings.SESSION_COOKIE_NAME, None)
|
cookie = self.cookies.get(settings.SESSION_COOKIE_NAME, None)
|
||||||
if cookie:
|
if cookie:
|
||||||
return SessionWrapper(cookie.value)
|
return engine.SessionStore(cookie.value)
|
||||||
return {}
|
return {}
|
||||||
session = property(_session)
|
session = property(_session)
|
||||||
|
|
||||||
@ -247,24 +246,23 @@ class Client:
|
|||||||
"""
|
"""
|
||||||
user = authenticate(**credentials)
|
user = authenticate(**credentials)
|
||||||
if user and user.is_active and 'django.contrib.sessions' in settings.INSTALLED_APPS:
|
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
|
# Create a fake request to store login details
|
||||||
request = HttpRequest()
|
request = HttpRequest()
|
||||||
request.session = SessionWrapper(obj.session_key)
|
request.session = engine.SessionStore()
|
||||||
login(request, user)
|
login(request, user)
|
||||||
|
|
||||||
# Set the cookie to represent the session
|
# 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]['max-age'] = None
|
||||||
self.cookies[settings.SESSION_COOKIE_NAME]['path'] = '/'
|
self.cookies[settings.SESSION_COOKIE_NAME]['path'] = '/'
|
||||||
self.cookies[settings.SESSION_COOKIE_NAME]['domain'] = settings.SESSION_COOKIE_DOMAIN
|
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]['secure'] = settings.SESSION_COOKIE_SECURE or None
|
||||||
self.cookies[settings.SESSION_COOKIE_NAME]['expires'] = None
|
self.cookies[settings.SESSION_COOKIE_NAME]['expires'] = None
|
||||||
|
|
||||||
# Set the session values
|
# Save the session values
|
||||||
Session.objects.save(obj.session_key, request.session._session,
|
request.session.save()
|
||||||
datetime.datetime.now() + datetime.timedelta(seconds=settings.SESSION_COOKIE_AGE))
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
@ -275,9 +273,6 @@ class Client:
|
|||||||
|
|
||||||
Causes the authenticated user to be logged out.
|
Causes the authenticated user to be logged out.
|
||||||
"""
|
"""
|
||||||
try:
|
session = __import__(settings.SESSION_ENGINE, {}, {}, ['']).SessionStore()
|
||||||
Session.objects.get(session_key=self.cookies['sessionid'].value).delete()
|
session.delete(session_key=self.cookies['sessionid'].value)
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
self.cookies = SimpleCookie()
|
self.cookies = SimpleCookie()
|
||||||
|
@ -72,12 +72,23 @@ class SortedDict(dict):
|
|||||||
def items(self):
|
def items(self):
|
||||||
return zip(self.keyOrder, self.values())
|
return zip(self.keyOrder, self.values())
|
||||||
|
|
||||||
|
def iteritems(self):
|
||||||
|
for key in self.keyOrder:
|
||||||
|
yield key, dict.__getitem__(self, key)
|
||||||
|
|
||||||
def keys(self):
|
def keys(self):
|
||||||
return self.keyOrder[:]
|
return self.keyOrder[:]
|
||||||
|
|
||||||
|
def iterkeys(self):
|
||||||
|
return iter(self.keyOrder)
|
||||||
|
|
||||||
def values(self):
|
def values(self):
|
||||||
return [dict.__getitem__(self, k) for k in self.keyOrder]
|
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):
|
def update(self, dict):
|
||||||
for k, v in dict.items():
|
for k, v in dict.items():
|
||||||
self.__setitem__(k, v)
|
self.__setitem__(k, v)
|
||||||
@ -91,6 +102,15 @@ class SortedDict(dict):
|
|||||||
"Returns the value of the item at the given zero-based index."
|
"Returns the value of the item at the given zero-based index."
|
||||||
return self[self.keyOrder[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):
|
def copy(self):
|
||||||
"Returns a copy of this object."
|
"Returns a copy of this object."
|
||||||
# This way of initializing the copy means it works for subclasses, too.
|
# This way of initializing the copy means it works for subclasses, too.
|
||||||
|
@ -166,8 +166,8 @@ class DateFormat(TimeFormat):
|
|||||||
|
|
||||||
def O(self):
|
def O(self):
|
||||||
"Difference to Greenwich time in hours; e.g. '+0200'"
|
"Difference to Greenwich time in hours; e.g. '+0200'"
|
||||||
tz = self.timezone.utcoffset(self.data)
|
seconds = self.Z()
|
||||||
return u"%+03d%02d" % (tz.seconds // 3600, (tz.seconds // 60) % 60)
|
return u"%+03d%02d" % (seconds // 3600, (seconds // 60) % 60)
|
||||||
|
|
||||||
def r(self):
|
def r(self):
|
||||||
"RFC 822 formatted date; e.g. 'Thu, 21 Dec 2000 16:01:07 +0200'"
|
"RFC 822 formatted date; e.g. 'Thu, 21 Dec 2000 16:01:07 +0200'"
|
||||||
|
@ -1,11 +1,20 @@
|
|||||||
import datetime, math, time
|
import datetime
|
||||||
|
import time
|
||||||
|
|
||||||
from django.utils.tzinfo import LocalTimezone
|
from django.utils.tzinfo import LocalTimezone
|
||||||
from django.utils.translation import ungettext, ugettext
|
from django.utils.translation import ungettext, ugettext
|
||||||
|
|
||||||
def timesince(d, now=None):
|
def timesince(d, now=None):
|
||||||
"""
|
"""
|
||||||
Takes two datetime objects and returns the time between then and now
|
Takes two datetime objects and returns the time between d and now
|
||||||
as a nicely formatted string, e.g "10 minutes"
|
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
|
Adapted from http://blog.natbat.co.uk/archive/2003/Jun/14/time_since
|
||||||
"""
|
"""
|
||||||
chunks = (
|
chunks = (
|
||||||
@ -32,6 +41,9 @@ def timesince(d, now=None):
|
|||||||
# ignore microsecond part of 'd' since we removed it from 'now'
|
# ignore microsecond part of 'd' since we removed it from 'now'
|
||||||
delta = now - (d - datetime.timedelta(0, 0, d.microsecond))
|
delta = now - (d - datetime.timedelta(0, 0, d.microsecond))
|
||||||
since = delta.days * 24 * 60 * 60 + delta.seconds
|
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):
|
for i, (seconds, name) in enumerate(chunks):
|
||||||
count = since // seconds
|
count = since // seconds
|
||||||
if count != 0:
|
if count != 0:
|
||||||
|
@ -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.
|
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``
|
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
|
need to use ``LoadModule`` to load them dynamically (as shown in the
|
||||||
example at the bottom of this note).
|
example at the bottom of this note).
|
||||||
|
|
||||||
You'll also need to insert configuration directives that prevent Apache
|
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
|
authentication modules you have loaded, you might need one or more of
|
||||||
the following directives::
|
the following directives::
|
||||||
|
|
||||||
|
@ -645,11 +645,11 @@ Examples:
|
|||||||
To run the test server on port 7000 with ``fixture1`` and ``fixture2``::
|
To run the test server on port 7000 with ``fixture1`` and ``fixture2``::
|
||||||
|
|
||||||
django-admin.py testserver --addrport 7000 fixture1 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
|
(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
|
that it doesn't matter whether the options come before or after the fixture
|
||||||
``testserver`` command.)
|
arguments.)
|
||||||
|
|
||||||
To run on 1.2.3.4:7000 with a `test` fixture::
|
To run on 1.2.3.4:7000 with a `test` fixture::
|
||||||
|
|
||||||
|
@ -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
|
major type will always be ``"text"``, but you can change it to the subtype. For
|
||||||
example::
|
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.content_subtype = "html" # Main content is now text/html
|
||||||
msg.send()
|
msg.send()
|
||||||
|
|
||||||
|
@ -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
|
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/``
|
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
|
(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
|
use the same ``compile-messages.py`` to produce the binary ``django.mo`` files
|
||||||
are used by ``gettext``.
|
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
|
Application message files are a bit complicated to discover -- they need the
|
||||||
``LocaleMiddleware``. If you don't use the middleware, only the Django message
|
``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
|
After setting the language choice, Django redirects the user, following this
|
||||||
algorithm:
|
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
|
* If that doesn't exist, or is empty, Django tries the URL in the
|
||||||
``Referrer`` header.
|
``Referrer`` header.
|
||||||
* If that's empty -- say, if a user's browser suppresses that header --
|
* If that's empty -- say, if a user's browser suppresses that header --
|
||||||
|
@ -86,7 +86,7 @@ to create a temporary test database.
|
|||||||
.. _SQLite: http://www.sqlite.org/
|
.. _SQLite: http://www.sqlite.org/
|
||||||
.. _pysqlite: http://initd.org/tracker/pysqlite
|
.. _pysqlite: http://initd.org/tracker/pysqlite
|
||||||
.. _MySQL backend: ../databases/
|
.. _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/
|
.. _Oracle: http://www.oracle.com/
|
||||||
.. _testing framework: ../testing/
|
.. _testing framework: ../testing/
|
||||||
|
|
||||||
|
@ -1284,6 +1284,17 @@ won't add the automatic ``id`` column.
|
|||||||
|
|
||||||
Each model requires exactly one field to have ``primary_key=True``.
|
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
|
Admin options
|
||||||
=============
|
=============
|
||||||
|
|
||||||
|
@ -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'``.
|
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
|
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``.
|
Takes two optional arguments for validation, ``max_value`` and ``min_value``.
|
||||||
These control the range of values permitted in the field.
|
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``
|
``MultipleChoiceField``
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
@ -1693,7 +1722,7 @@ the full list of conversions:
|
|||||||
``ForeignKey`` ``ModelChoiceField`` (see below)
|
``ForeignKey`` ``ModelChoiceField`` (see below)
|
||||||
``ImageField`` ``ImageField``
|
``ImageField`` ``ImageField``
|
||||||
``IntegerField`` ``IntegerField``
|
``IntegerField`` ``IntegerField``
|
||||||
``IPAddressField`` ``CharField``
|
``IPAddressField`` ``IPAddressField``
|
||||||
``ManyToManyField`` ``ModelMultipleChoiceField`` (see
|
``ManyToManyField`` ``ModelMultipleChoiceField`` (see
|
||||||
below)
|
below)
|
||||||
``NullBooleanField`` ``CharField``
|
``NullBooleanField`` ``CharField``
|
||||||
|
@ -10,18 +10,21 @@ Cookies contain a session ID -- not the data itself.
|
|||||||
Enabling sessions
|
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
|
* Edit the ``MIDDLEWARE_CLASSES`` setting and make sure
|
||||||
``MIDDLEWARE_CLASSES`` contains ``'django.contrib.sessions.middleware.SessionMiddleware'``.
|
``MIDDLEWARE_CLASSES`` contains ``'django.contrib.sessions.middleware.SessionMiddleware'``.
|
||||||
The default ``settings.py`` created by ``django-admin.py startproject`` has
|
The default ``settings.py`` created by ``django-admin.py startproject`` has
|
||||||
``SessionMiddleware`` activated.
|
``SessionMiddleware`` activated.
|
||||||
|
|
||||||
* Add ``'django.contrib.sessions'`` to your ``INSTALLED_APPS`` setting, and
|
* Add ``'django.contrib.sessions'`` to your ``INSTALLED_APPS`` setting,
|
||||||
run ``manage.py syncdb`` to install the single database table that stores
|
and run ``manage.py syncdb`` to install the single database table
|
||||||
session data.
|
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
|
If you don't want to use sessions, you might as well remove the
|
||||||
``SessionMiddleware`` line from ``MIDDLEWARE_CLASSES`` and ``'django.contrib.sessions'``
|
``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/
|
.. _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
|
Using sessions in views
|
||||||
=======================
|
=======================
|
||||||
|
|
||||||
@ -153,9 +194,21 @@ Here's a typical usage example::
|
|||||||
Using sessions out of views
|
Using sessions out of views
|
||||||
===========================
|
===========================
|
||||||
|
|
||||||
Internally, each session is just a normal Django model. The ``Session`` model
|
**New in Django development version**
|
||||||
is defined in ``django/contrib/sessions/models.py``. Because it's a normal
|
|
||||||
model, you can access sessions using the normal Django database API::
|
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
|
>>> from django.contrib.sessions.models import Session
|
||||||
>>> s = Session.objects.get(pk='2b1189a188b44ad18c35e113ac6ceead')
|
>>> s = Session.objects.get(pk='2b1189a188b44ad18c35e113ac6ceead')
|
||||||
@ -245,6 +298,31 @@ Settings
|
|||||||
|
|
||||||
A few `Django settings`_ give you control over session behavior:
|
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
|
SESSION_COOKIE_AGE
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
@ -257,10 +257,11 @@ The database backend to use. The build-in database backends are
|
|||||||
``'postgresql_psycopg2'``, ``'postgresql'``, ``'mysql'``, ``'mysql_old'``,
|
``'postgresql_psycopg2'``, ``'postgresql'``, ``'mysql'``, ``'mysql_old'``,
|
||||||
``'sqlite3'``, ``'oracle'``, or ``'ado_mssql'``.
|
``'sqlite3'``, ``'oracle'``, or ``'ado_mssql'``.
|
||||||
|
|
||||||
You can also use a database backend that doesn't ship with Django by
|
In the Django development version, you can use a database backend that doesn't
|
||||||
setting ``DATABASE_ENGINE`` to a fully-qualified path (i.e.
|
ship with Django by setting ``DATABASE_ENGINE`` to a fully-qualified path (i.e.
|
||||||
``mypackage.backends.whatever``). Writing a whole new database backend from
|
``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
|
DATABASE_HOST
|
||||||
-------------
|
-------------
|
||||||
@ -733,6 +734,21 @@ Default: ``'root@localhost'``
|
|||||||
The e-mail address that error messages come from, such as those sent to
|
The e-mail address that error messages come from, such as those sent to
|
||||||
``ADMINS`` and ``MANAGERS``.
|
``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
|
SESSION_COOKIE_AGE
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
@ -775,6 +791,17 @@ Default: ``False``
|
|||||||
Whether to expire the session when the user closes his or her browser.
|
Whether to expire the session when the user closes his or her browser.
|
||||||
See the `session docs`_.
|
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
|
SESSION_SAVE_EVERY_REQUEST
|
||||||
--------------------------
|
--------------------------
|
||||||
|
|
||||||
|
@ -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,
|
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".
|
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
|
timeuntil
|
||||||
~~~~~~~~~
|
~~~~~~~~~
|
||||||
|
|
||||||
Similar to ``timesince``, except that it measures the time from now until the
|
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
|
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`` 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
|
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
|
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
|
title
|
||||||
~~~~~
|
~~~~~
|
||||||
|
@ -33,6 +33,11 @@ __test__ = {'API_TESTS': """
|
|||||||
>>> a.id
|
>>> a.id
|
||||||
1L
|
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.
|
# Access database columns via Python attributes.
|
||||||
>>> a.headline
|
>>> a.headline
|
||||||
'Area man programs in Python'
|
'Area man programs in Python'
|
||||||
|
@ -56,6 +56,15 @@ DoesNotExist: Employee matching query does not exist.
|
|||||||
>>> Employee.objects.filter(pk__in=['ABC123','XYZ456'])
|
>>> Employee.objects.filter(pk__in=['ABC123','XYZ456'])
|
||||||
[<Employee: Fran Bones>, <Employee: Dan Jones>]
|
[<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 got married and changed her last name.
|
||||||
>>> fran = Employee.objects.get(pk='XYZ456')
|
>>> fran = Employee.objects.get(pk='XYZ456')
|
||||||
>>> fran.last_name = 'Jones'
|
>>> fran.last_name = 'Jones'
|
||||||
|
@ -9,6 +9,7 @@ FIXTURE_DIRS setting.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
class Article(models.Model):
|
class Article(models.Model):
|
||||||
headline = models.CharField(max_length=100, default='Default headline')
|
headline = models.CharField(max_length=100, default='Default headline')
|
||||||
@ -53,7 +54,13 @@ __test__ = {'API_TESTS': """
|
|||||||
# object list is unaffected
|
# object list is unaffected
|
||||||
>>> Article.objects.all()
|
>>> 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>]
|
[<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.
|
# Reset the database representation of this app. This will delete all data.
|
||||||
>>> management.call_command('flush', verbosity=0, interactive=False)
|
>>> management.call_command('flush', verbosity=0, interactive=False)
|
||||||
>>> Article.objects.all()
|
>>> 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
|
# Dump the current contents of the database as a JSON fixture
|
||||||
>>> management.call_command('dumpdata', 'fixtures', format='json')
|
>>> 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"}}]
|
[{"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
|
from django.test import TestCase
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.db import connection
|
||||||
|
|
||||||
class Square(models.Model):
|
class Square(models.Model):
|
||||||
root = models.IntegerField()
|
root = models.IntegerField()
|
||||||
@ -7,18 +8,27 @@ class Square(models.Model):
|
|||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return "%s ** 2 == %s" % (self.root, self.square)
|
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': """
|
__test__ = {'API_TESTS': """
|
||||||
|
|
||||||
#4896: Test cursor.executemany
|
#4896: Test cursor.executemany
|
||||||
>>> from django.db import connection
|
>>> from django.db import connection
|
||||||
>>> cursor = connection.cursor()
|
>>> cursor = connection.cursor()
|
||||||
>>> cursor.executemany('INSERT INTO BACKENDS_SQUARE (ROOT, SQUARE) VALUES (%s, %s)',
|
>>> opts = Square._meta
|
||||||
... [(i, i**2) for i in range(-5, 6)]) and None or None
|
>>> 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.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>]
|
[<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
|
#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()
|
>>> Square.objects.count()
|
||||||
11
|
11
|
||||||
|
|
||||||
|
@ -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_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>
|
<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 ################################################################
|
# Initial data ################################################################
|
||||||
|
|
||||||
You can specify initial data for a field by using the 'initial' argument to a
|
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
|
>>> f.cleaned_data
|
||||||
{'field1': u'some text,JP,2007-04-25 06:24:00'}
|
{'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 #
|
# Tests of underlying functions #
|
||||||
#################################
|
#################################
|
||||||
|
@ -771,6 +771,10 @@ class Templates(unittest.TestCase):
|
|||||||
# Check that timezone is respected
|
# Check that timezone is respected
|
||||||
'timesince06' : ('{{ a|timesince:b }}', {'a':NOW_tz + timedelta(hours=8), 'b':NOW_tz}, '8 hours'),
|
'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 ##################################################
|
### TIMEUNTIL TAG ##################################################
|
||||||
# Default compare with datetime.now()
|
# Default compare with datetime.now()
|
||||||
'timeuntil01' : ('{{ a|timeuntil }}', {'a':datetime.now() + timedelta(minutes=2, seconds = 10)}, '2 minutes'),
|
'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'),
|
'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'),
|
'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 ########################################################
|
### URL TAG ########################################################
|
||||||
# Successes
|
# Successes
|
||||||
'url01' : ('{% url regressiontests.templates.views.client client.id %}', {'client': {'id': 1}}, '/url_tag/client/1/'),
|
'url01' : ('{% url regressiontests.templates.views.client client.id %}', {'client': {'id': 1}}, '/url_tag/client/1/'),
|
||||||
|
@ -6,6 +6,8 @@ from unittest import TestCase
|
|||||||
|
|
||||||
from django.utils import html
|
from django.utils import html
|
||||||
|
|
||||||
|
from timesince import timesince_tests
|
||||||
|
|
||||||
class TestUtilsHtml(TestCase):
|
class TestUtilsHtml(TestCase):
|
||||||
|
|
||||||
def check_output(self, function, value, output=None):
|
def check_output(self, function, value, output=None):
|
||||||
@ -113,3 +115,11 @@ class TestUtilsHtml(TestCase):
|
|||||||
)
|
)
|
||||||
for value, output in items:
|
for value, output in items:
|
||||||
self.check_output(f, value, output)
|
self.check_output(f, value, output)
|
||||||
|
|
||||||
|
__test__ = {
|
||||||
|
'timesince_tests': timesince_tests,
|
||||||
|
}
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import doctest
|
||||||
|
doctest.testmod()
|
||||||
|
77
tests/regressiontests/utils/timesince.py
Normal file
77
tests/regressiontests/utils/timesince.py
Normal 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'
|
||||||
|
"""
|
0
tests/regressiontests/views/__init__.py
Normal file
0
tests/regressiontests/views/__init__.py
Normal file
25
tests/regressiontests/views/fixtures/testdata.json
Normal file
25
tests/regressiontests/views/fixtures/testdata.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
BIN
tests/regressiontests/views/locale/en/LC_MESSAGES/djangojs.mo
Normal file
BIN
tests/regressiontests/views/locale/en/LC_MESSAGES/djangojs.mo
Normal file
Binary file not shown.
@ -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"
|
BIN
tests/regressiontests/views/locale/es/LC_MESSAGES/djangojs.mo
Normal file
BIN
tests/regressiontests/views/locale/es/LC_MESSAGES/djangojs.mo
Normal file
Binary file not shown.
@ -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"
|
BIN
tests/regressiontests/views/locale/fr/LC_MESSAGES/djangojs.mo
Normal file
BIN
tests/regressiontests/views/locale/fr/LC_MESSAGES/djangojs.mo
Normal file
Binary file not shown.
@ -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"
|
1
tests/regressiontests/views/media/file.txt
Normal file
1
tests/regressiontests/views/media/file.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
An example media file.
|
24
tests/regressiontests/views/models.py
Normal file
24
tests/regressiontests/views/models.py
Normal 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
|
||||||
|
|
3
tests/regressiontests/views/tests/__init__.py
Normal file
3
tests/regressiontests/views/tests/__init__.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from defaults import *
|
||||||
|
from i18n import *
|
||||||
|
from static import *
|
39
tests/regressiontests/views/tests/defaults.py
Normal file
39
tests/regressiontests/views/tests/defaults.py
Normal 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)
|
30
tests/regressiontests/views/tests/i18n.py
Normal file
30
tests/regressiontests/views/tests/i18n.py
Normal 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)
|
15
tests/regressiontests/views/tests/static.py
Normal file
15
tests/regressiontests/views/tests/static.py
Normal 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)
|
26
tests/regressiontests/views/urls.py
Normal file
26
tests/regressiontests/views/urls.py
Normal 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}),
|
||||||
|
)
|
12
tests/regressiontests/views/views.py
Normal file
12
tests/regressiontests/views/views.py
Normal 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', {})
|
@ -11,4 +11,7 @@ urlpatterns = patterns('',
|
|||||||
|
|
||||||
# test urlconf for {% url %} template tag
|
# test urlconf for {% url %} template tag
|
||||||
(r'^url_tag/', include('regressiontests.templates.urls')),
|
(r'^url_tag/', include('regressiontests.templates.urls')),
|
||||||
|
|
||||||
|
# django built-in views
|
||||||
|
(r'^views/', include('regressiontests.views.urls')),
|
||||||
)
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user