summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGeorg Bauer <gb@hugo.westfalen.de>2005-10-23 10:18:03 +0000
committerGeorg Bauer <gb@hugo.westfalen.de>2005-10-23 10:18:03 +0000
commit174c8a0295c7e3f4782b5a0769de92e3f94bc801 (patch)
treef3904f5b731f6314c55341bd5f859484d8cd8ec1
parent15f1da053220394061d0f37447e545597cee6cd9 (diff)
parent17f62269c2168549b76ee6204a1423a6e6f4e5f5 (diff)
i18n: merged to [992] from trunk
git-svn-id: http://code.djangoproject.com/svn/django/branches/i18n@993 bcc190cf-cafb-0310-a4f2-bffc1f526a37
-rw-r--r--django/core/management.py2
-rw-r--r--django/core/meta/__init__.py5
-rw-r--r--django/core/template/loaders/app_directories.py14
-rw-r--r--django/utils/dateformat.py37
-rw-r--r--django/utils/timesince.py18
-rw-r--r--django/utils/tzinfo.py52
-rw-r--r--django/views/decorators/auth.py19
-rw-r--r--docs/authentication.txt288
-rw-r--r--docs/templates.txt16
-rw-r--r--tests/othertests/dateformat.py75
-rw-r--r--tests/testapp/models/custom_pk.py4
11 files changed, 501 insertions, 29 deletions
diff --git a/django/core/management.py b/django/core/management.py
index 564697776d..3b973c28da 100644
--- a/django/core/management.py
+++ b/django/core/management.py
@@ -299,7 +299,7 @@ def init():
cursor = db.db.cursor()
for sql in get_sql_create(core) + get_sql_create(auth) + get_sql_initial_data(core) + get_sql_initial_data(auth):
cursor.execute(sql)
- cursor.execute("INSERT INTO %s (domain, name) VALUES ('mysite.com', 'My Django site')" % core.Site._meta.db_table)
+ cursor.execute("INSERT INTO %s (domain, name) VALUES ('example.com', 'Example site')" % core.Site._meta.db_table)
except Exception, e:
sys.stderr.write("Error: The database couldn't be initialized.\n%s\n" % e)
try:
diff --git a/django/core/meta/__init__.py b/django/core/meta/__init__.py
index f934f9dd6c..2e39f72e95 100644
--- a/django/core/meta/__init__.py
+++ b/django/core/meta/__init__.py
@@ -1360,9 +1360,10 @@ def function_get_sql_clause(opts, **kwargs):
def function_get_in_bulk(opts, klass, *args, **kwargs):
id_list = args and args[0] or kwargs['id_list']
assert id_list != [], "get_in_bulk() cannot be passed an empty list."
- kwargs['where'] = ["%s.id IN (%s)" % (opts.db_table, ",".join(map(str, id_list)))]
+ kwargs['where'] = ["%s.%s IN (%s)" % (opts.db_table, opts.pk.column, ",".join(['%s'] * len(id_list)))]
+ kwargs['params'] = id_list
obj_list = function_get_list(opts, klass, **kwargs)
- return dict([(o.id, o) for o in obj_list])
+ return dict([(getattr(o, opts.pk.column), o) for o in obj_list])
def function_get_latest(opts, klass, does_not_exist_exception, **kwargs):
kwargs['order_by'] = ('-' + opts.get_latest_by,)
diff --git a/django/core/template/loaders/app_directories.py b/django/core/template/loaders/app_directories.py
index 5afb18e2f5..b8bd0d6169 100644
--- a/django/core/template/loaders/app_directories.py
+++ b/django/core/template/loaders/app_directories.py
@@ -1,6 +1,7 @@
# Wrapper for loading templates from "template" directories in installed app packages.
from django.conf.settings import INSTALLED_APPS, TEMPLATE_FILE_EXTENSION
+from django.core.exceptions import ImproperlyConfigured
from django.core.template import TemplateDoesNotExist
import os
@@ -8,8 +9,17 @@ import os
app_template_dirs = []
for app in INSTALLED_APPS:
i = app.rfind('.')
- m, a = app[:i], app[i+1:]
- mod = getattr(__import__(m, '', '', [a]), a)
+ if i == -1:
+ m, a = app, None
+ else:
+ m, a = app[:i], app[i+1:]
+ try:
+ if a is None:
+ mod = __import__(m, '', '', [])
+ else:
+ mod = getattr(__import__(m, '', '', [a]), a)
+ except ImportError, e:
+ raise ImproperlyConfigured, 'ImportError %s: %s' % (app, e.args[0])
template_dir = os.path.join(os.path.dirname(mod.__file__), 'templates')
if os.path.isdir(template_dir):
app_template_dirs.append(template_dir)
diff --git a/django/utils/dateformat.py b/django/utils/dateformat.py
index 3620558f09..9913c3f8c4 100644
--- a/django/utils/dateformat.py
+++ b/django/utils/dateformat.py
@@ -12,8 +12,9 @@ Usage:
"""
from django.utils.dates import MONTHS, MONTHS_AP, WEEKDAYS
+from django.utils.tzinfo import LocalTimezone
from calendar import isleap
-import re
+import re, time
re_formatchars = re.compile(r'(?<!\\)([aABdDfFgGhHiIjlLmMnNOPrsStTUwWyYzZ])')
re_escaped = re.compile(r'\\(.)')
@@ -40,7 +41,9 @@ class TimeFormat(Formatter):
def A(self):
"'AM' or 'PM'"
- return self.a().upper()
+ if self.data.hour > 11:
+ return 'PM'
+ return 'AM'
def B(self):
"Swatch Internet time"
@@ -100,8 +103,12 @@ class TimeFormat(Formatter):
class DateFormat(TimeFormat):
year_days = [None, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334]
- def __init__(self, d):
- self.data = d
+ def __init__(self, dt):
+ # Accepts either a datetime or date object.
+ self.data = dt
+ self.timezone = getattr(dt, 'tzinfo', None)
+ if hasattr(self.data, 'hour') and not self.timezone:
+ self.timezone = LocalTimezone(dt)
def d(self):
"Day of the month, 2 digits with leading zeros; i.e. '01' to '31'"
@@ -119,6 +126,13 @@ class DateFormat(TimeFormat):
"'1' if Daylight Savings Time, '0' otherwise."
raise NotImplementedError
+ def I(self):
+ "'1' if Daylight Savings Time, '0' otherwise."
+ if self.timezone.dst(self.data):
+ return '1'
+ else:
+ return '0'
+
def j(self):
"Day of the month without leading zeros; i.e. '1' to '31'"
return self.data.day
@@ -149,11 +163,12 @@ class DateFormat(TimeFormat):
def O(self):
"Difference to Greenwich time in hours; e.g. '+0200'"
- raise NotImplementedError
+ tz = self.timezone.utcoffset(self.data)
+ return "%+03d%02d" % (tz.seconds // 3600, (tz.seconds // 60) % 60)
def r(self):
"RFC 822 formatted date; e.g. 'Thu, 21 Dec 2000 16:01:07 +0200'"
- raise NotImplementedError
+ return self.format('D, j M Y H:i:s O')
def S(self):
"English ordinal suffix for the day of the month, 2 characters; i.e. 'st', 'nd', 'rd' or 'th'"
@@ -174,11 +189,15 @@ class DateFormat(TimeFormat):
def T(self):
"Time zone of this machine; e.g. 'EST' or 'MDT'"
- raise NotImplementedError
+ name = self.timezone.tzname(self.data)
+ if name is None:
+ name = self.format('O')
+ return name
def U(self):
"Seconds since the Unix epoch (January 1 1970 00:00:00 GMT)"
- raise NotImplementedError
+ off = self.timezone.utcoffset(self.data)
+ return int(time.mktime(self.data.timetuple())) + off.seconds * 60
def w(self):
"Day of the week, numeric, i.e. '0' (Sunday) to '6' (Saturday)"
@@ -229,7 +248,7 @@ class DateFormat(TimeFormat):
"""Time zone offset in seconds (i.e. '-43200' to '43200'). The offset
for timezones west of UTC is always negative, and for those east of UTC
is always positive."""
- raise NotImplementedError
+ return self.timezone.utcoffset(self.data).seconds
def format(value, format_string):
"Convenience function"
diff --git a/django/utils/timesince.py b/django/utils/timesince.py
index c11cef0342..5b22fde58c 100644
--- a/django/utils/timesince.py
+++ b/django/utils/timesince.py
@@ -1,4 +1,5 @@
-import time, math, datetime
+import datetime, math, time
+from django.utils.tzinfo import LocalTimezone
def timesince(d, now=None):
"""
@@ -6,7 +7,6 @@ def timesince(d, now=None):
as a nicely formatted string, e.g "10 minutes"
Adapted from http://blog.natbat.co.uk/archive/2003/Jun/14/time_since
"""
- original = time.mktime(d.timetuple())
chunks = (
(60 * 60 * 24 * 365, 'year'),
(60 * 60 * 24 * 30, 'month'),
@@ -14,9 +14,17 @@ def timesince(d, now=None):
(60 * 60, 'hour'),
(60, 'minute')
)
- if not now:
- now = time.time()
- since = now - original
+ if now:
+ t = time.mktime(now)
+ else:
+ t = time.localtime()
+ if d.tzinfo:
+ tz = LocalTimezone()
+ else:
+ tz = None
+ now = datetime.datetime(t[0], t[1], t[2], t[3], t[4], t[5], tzinfo=tz)
+ delta = now - d
+ since = delta.days * 24 * 60 * 60 + delta.seconds
# Crazy iteration syntax because we need i to be current index
for i, (seconds, name) in zip(range(len(chunks)), chunks):
count = math.floor(since / seconds)
diff --git a/django/utils/tzinfo.py b/django/utils/tzinfo.py
new file mode 100644
index 0000000000..cc9f028e91
--- /dev/null
+++ b/django/utils/tzinfo.py
@@ -0,0 +1,52 @@
+"Implementation of tzinfo classes for use with datetime.datetime."
+
+import time
+from datetime import timedelta, tzinfo
+
+class FixedOffset(tzinfo):
+ "Fixed offset in minutes east from UTC."
+ def __init__(self, offset):
+ self.__offset = timedelta(minutes=offset)
+ self.__name = "%+03d%02d" % (offset // 60, offset % 60)
+
+ def __repr__(self):
+ return self.__name
+
+ def utcoffset(self, dt):
+ return self.__offset
+
+ def tzname(self, dt):
+ return self.__name
+
+ def dst(self, dt):
+ return timedelta(0)
+
+class LocalTimezone(tzinfo):
+ "Proxy timezone information from time module."
+ def __init__(self, dt):
+ tzinfo.__init__(self, dt)
+ self._tzname = time.tzname[self._isdst(dt)]
+
+ def __repr__(self):
+ return self._tzname
+
+ def utcoffset(self, dt):
+ if self._isdst(dt):
+ return timedelta(seconds=-time.altzone)
+ else:
+ return timedelta(seconds=-time.timezone)
+
+ def dst(self, dt):
+ if self._isdst(dt):
+ return timedelta(seconds=-time.altzone) - timedelta(seconds=-time.timezone)
+ else:
+ return timedelta(0)
+
+ def tzname(self, dt):
+ return time.tzname[self._isdst(dt)]
+
+ def _isdst(self, dt):
+ tt = (dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, dt.weekday(), 0, -1)
+ stamp = time.mktime(tt)
+ tt = time.localtime(stamp)
+ return tt.tm_isdst > 0
diff --git a/django/views/decorators/auth.py b/django/views/decorators/auth.py
index ae27fe33a1..f543a6aa48 100644
--- a/django/views/decorators/auth.py
+++ b/django/views/decorators/auth.py
@@ -1,12 +1,19 @@
-def login_required(view_func):
+def user_passes_test(view_func, test_func):
"""
- Decorator for views that checks that the user is logged in, redirecting
- to the log-in page if necessary.
+ Decorator for views that checks that the user passes the given test,
+ redirecting to the log-in page if necessary. The test should be a callable
+ that takes the user object and returns True if the user passes.
"""
from django.views.auth.login import redirect_to_login
def _checklogin(request, *args, **kwargs):
- if request.user.is_anonymous():
- return redirect_to_login(request.path)
- else:
+ if test_func(request.user):
return view_func(request, *args, **kwargs)
+ return redirect_to_login(request.path)
return _checklogin
+
+def login_required(view_func):
+ """
+ Decorator for views that checks that the user is logged in, redirecting
+ to the log-in page if necessary.
+ """
+ return user_passes_test(lambda u: not u.is_anonymous())
diff --git a/docs/authentication.txt b/docs/authentication.txt
new file mode 100644
index 0000000000..eaf3cc2b99
--- /dev/null
+++ b/docs/authentication.txt
@@ -0,0 +1,288 @@
+=============================
+User authentication in Django
+=============================
+
+Django comes with a user authentication system. It handles user accounts,
+groups, permissions and cookie-based user sessions. This document explains how
+things work.
+
+The basics
+==========
+
+Django supports authentication out of the box. The ``django-admin.py init``
+command, used to initialize a database with Django's core database tables,
+creates the infrastructure for the auth system. You don't have to do anything
+else to use authentication.
+
+The auth system consists of:
+
+ * Users
+ * Permissions: Binary (yes/no) flags designating whether a user may perform
+ a certain task.
+ * Groups: A generic way of applying labels and permissions to more than one
+ user.
+ * Messages: A simple way to queue messages for given users.
+
+Users
+=====
+
+Users are represented by a standard Django model, which lives in
+`django/models/auth.py`_.
+
+.. _django/models/auth.py: http://code.djangoproject.com/browser/django/trunk/django/models/auth.py
+
+API reference
+-------------
+
+Fields
+~~~~~~
+
+``User`` objects have the following fields:
+
+ * ``username`` -- Required. 30 characters or fewer. Alphanumeric characters
+ only (letters, digits and underscores).
+ * ``first_name`` -- Optional. 30 characters or fewer.
+ * ``last_name`` -- Optional. 30 characters or fewer.
+ * ``email`` -- Optional. E-mail address.
+ * ``password_md5`` -- Required. An MD5 hash of the password. (Django
+ doesn't store the raw password.) Raw passwords can be arbitrarily long
+ and can contain any character.
+ * ``is_staff`` -- Boolean. Designates whether this user can access the
+ admin site.
+ * ``is_active`` -- Boolean. Designates whether this user account is valid.
+ Set this to ``False`` instead of deleting accounts.
+ * ``is_superuser`` -- Boolean. Designates whether this user has permission
+ to do anything (according to the permission system).
+ * ``last_login`` -- A datetime of the user's last login. Is set to the
+ current date/time by default.
+ * ``date_joined`` -- A datetime designating when the account was created.
+ Is set to the current date/time by default when the account is created.
+
+Methods
+~~~~~~~
+
+``User`` objects have two many-to-many fields: ``groups`` and
+``user_permissions``. Because of those relationships, ``User`` objects get
+data-access methods like any other `Django model`_:
+
+ * ``get_group_list(**kwargs)``
+ * ``set_groups(id_list)``
+ * ``get_permission_list(**kwargs)``
+ * ``set_user_permissions(id_list)``
+
+In addition to those automatic API methods, ``User`` objects have the following
+methods:
+
+ * ``is_anonymous()`` -- Always returns ``False``. This is a way of
+ comparing ``User`` objects to anonymous users.
+
+ * ``get_full_name()`` -- Returns the ``first_name`` plus the ``last_name``,
+ with a space in between.
+
+ * ``set_password(raw_password)`` -- Sets the user's password to the given
+ raw string, taking care of the MD5 hashing. Doesn't save the ``User``
+ object.
+
+ * ``check_password(raw_password)`` -- Returns ``True`` if the given raw
+ string is the correct password for the user.
+
+ * ``get_group_permissions()`` -- Returns a list of permission strings that
+ the user has, through his/her groups.
+
+ * ``get_all_permissions()`` -- Returns a list of permission strings that
+ the user has, both through group and user permissions.
+
+ * ``has_perm(perm)`` -- Returns ``True`` if the user has the specified
+ permission.
+
+ * ``has_perms(perm_list)`` -- Returns ``True`` if the user has each of the
+ specified permissions.
+
+ * ``has_module_perms(package_name)`` -- Returns ``True`` if the user has
+ any permissions in the given package (the Django app label).
+
+ * ``get_and_delete_messages()`` -- Returns a list of ``Message`` objects in
+ the user's queue and deletes the messages from the queue.
+
+ * ``email_user(subject, message, from_email=None)`` -- Sends an e-mail to
+ the user. If ``from_email`` is ``None``, Django uses the
+ `DEFAULT_FROM_EMAIL`_ setting.
+
+ * ``get_profile()`` -- Returns a site-specific profile for this user.
+ Raises ``django.models.auth.SiteProfileNotAvailable`` if the current site
+ doesn't allow profiles.
+
+.. _Django model: http://www.djangoproject.com/documentation/model_api/
+.. _DEFAULT_FROM_EMAIL: http://www.djangoproject.com/documentation/settings/#default-from-email
+
+Module functions
+~~~~~~~~~~~~~~~~
+
+The ``django.models.auth.users`` module has the following helper functions:
+
+ * ``create_user(username, email, password)`` -- Creates, saves and returns
+ a ``User``. The ``username``, ``email`` and ``password`` are set as
+ given, and the ``User`` gets ``is_active=True``.
+
+ * ``make_random_password(length=10, allowed_chars='abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789')``
+ -- Returns a random password with the given length and given string of
+ allowed characters. (Note that the default value of ``allowed_chars``
+ doesn't contain ``"I"`` or letters that look like it, to avoid user
+ confusion.
+
+Basic usage
+-----------
+
+Creating users
+~~~~~~~~~~~~~~
+
+The most basic way to create users is to use the standard Django
+`database API`_. Just create and save a ``User`` object::
+
+ >>> from django.models.auth import users
+ >>> import md5
+ >>> p = md5.new('johnpassword').hexdigest()
+ >>> u = users.User(username='john', first_name='John', last_name='lennon',
+ ... email='lennon@thebeatles.com', password_md5=p, is_staff=True,
+ ... is_active=True, is_superuser=False)
+ >>> u.save()
+
+Note that ``password_md5`` requires the raw MD5 hash. Because that's a pain,
+there's a ``create_user`` helper function::
+
+ >>> from django.models.auth import users
+ >>> u = users.create_user('john', 'lennon@thebeatles.com', 'johnpassword')
+
+.. _database API: http://www.djangoproject.com/documentation/db_api/
+
+Changing passwords
+~~~~~~~~~~~~~~~~~~
+
+Change a password with ``set_password()``::
+
+ >>> from django.models.auth import users
+ >>> u = users.get_object(username__exact='john')
+ >>> u.set_password('new password')
+ >>> u.save()
+
+Anonymous users
+---------------
+
+``django.parts.auth.anonymoususers.AnonymousUser`` is a class that implements
+the ``django.models.auth.users.User`` interface, with these differences:
+
+ * ``is_anonymous()`` returns ``True`` instead of ``False``.
+ * ``has_perm()`` always returns ``False``.
+ * ``set_password()``, ``check_password()``, ``set_groups()`` and
+ ``set_permissions()`` raise ``NotImplementedError``.
+
+In practice, you probably won't need to use ``AnonymousUser`` objects on your
+own, but they're used by Web requests, as explained in the next section.
+
+Authentication in Web requests
+==============================
+
+Until now, this document has dealt with the low-level APIs for manipulating
+authentication-related objects. On a higher level, Django hooks this
+authentication framework into its system of `request objects`_.
+
+In any Django view, ``request.user`` will give you a ``User`` object
+representing the currently logged-in user. If a user isn't currently logged in,
+``request.user`` will be set to an instance of ``AnonymousUser`` (see the
+previous section). You can tell them apart with ``is_anonymous()``, like so::
+
+ if request.user.is_anonymous():
+ # Do something for anonymous users.
+ else:
+ # Do something for logged-in users.
+
+.. _request objects: http://www.djangoproject.com/documentation/request_response/#httprequest-objects
+
+Limiting access to logged-in users
+----------------------------------
+
+The raw way
+~~~~~~~~~~~
+
+The simple, raw way to limit access to pages is to check
+``request.user.is_anonymous()`` and either redirect to a login page::
+
+ from django.utils.httpwrappers import HttpResponseRedirect
+
+ def my_view(request):
+ if request.user.is_anonymous():
+ return HttpResponseRedirect('/login/?next=%s' % request.path)
+ # ...
+
+...or display an error message::
+
+ def my_view(request):
+ if request.user.is_anonymous():
+ return render_to_response('myapp/login_error')
+ # ...
+
+The login_required decorator
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+As a shortcut, you can use the convenient ``login_required`` decorator::
+
+ from django.views.decorators.auth import login_required
+
+ def my_view(request):
+ # ...
+ my_view = login_required(my_view)
+
+Here's the same thing, using Python 2.4's decorator syntax::
+
+ from django.views.decorators.auth import login_required
+
+ @login_required
+ def my_view(request):
+ # ...
+
+``login_required`` does the following:
+
+ * If the user isn't logged in, redirect to ``/accounts/login/``, passing
+ the current absolute URL in the query string as ``next``. For example:
+ ``/accounts/login/?next=/polls/3/``.
+ * If the user is logged in, execute the view normally. The view code is
+ free to assume the user is logged in.
+
+Limiting access to logged-in users that pass a test
+---------------------------------------------------
+
+To limit access based on certain permissions or another test, you'd do the same
+thing as described in the previous section.
+
+The simple way is to run your test on ``request.user`` in the view directly.
+For example, this view checks to make sure the user is logged in and has the
+permission ``polls.can_vote``::
+
+ def my_view(request):
+ if request.user.is_anonymous() or not request.user.has_perm('polls.can_vote'):
+ return HttpResponse("You can't vote in this poll.")
+ # ...
+
+As a shortcut, you can use the convenient ``user_passes_test`` decorator::
+
+ from django.views.decorators.auth import user_passes_test
+
+ @user_passes_test(lambda u: u.has_perm('polls.can_vote'))
+ def my_view(request):
+ # ...
+
+``user_passes_test`` takes a required argument: a callable that takes a
+``User`` object and returns ``True`` if the user is allowed to view the page.
+Note that ``user_passes_test`` does not automatically check that the ``User``
+is not anonymous.
+
+
+
+Permissions
+===========
+
+Groups
+======
+
+Messages
+========
diff --git a/docs/templates.txt b/docs/templates.txt
index e543b59763..debec88a74 100644
--- a/docs/templates.txt
+++ b/docs/templates.txt
@@ -517,18 +517,18 @@ Built-in tag reference
n Month without leading zeros. ``'1'`` to ``'12'``
N Month abbreviation in Associated Press ``'Jan.'``, ``'Feb.'``, ``'March'``, ``'May'``
style. Proprietary extension.
- O Not implemented.
+ O Difference to Greenwich time in hours. ``'+0200'``
P Time, in 12-hour hours, minutes and ``'1 a.m.'``, ``'1:30 p.m.'``, ``'midnight'``, ``'noon'``, ``'12:30 p.m.'``
'a.m.'/'p.m.', with minutes left off
if they're zero and the special-case
strings 'midnight' and 'noon' if
appropriate. Proprietary extension.
- r Not implemented.
+ r RFC 822 formatted date. ``'Thu, 21 Dec 2000 16:01:07 +0200'``
s Seconds, 2 digits with leading zeros. ``'00'`` to ``'59'``
S English ordinal suffix for day of the ``'st'``, ``'nd'``, ``'rd'`` or ``'th'``
month, 2 characters.
t Not implemented.
- T Not implemented.
+ T Time zone of this machine. ``'EST'``, ``'MDT'``
U Not implemented.
w Day of the week, digits without ``'0'`` (Sunday) to ``'6'`` (Saturday)
leading zeros.
@@ -537,7 +537,10 @@ Built-in tag reference
y Year, 2 digits. ``'99'``
Y Year, 4 digits. ``'1999'``
z Day of the year. ``0`` to ``365``
- Z Not implemented.
+ Z Time zone offset in seconds. The ``-43200`` to ``43200``
+ offset for timezones west of UTC is
+ always negative, and for those east of
+ UTC is always positive.
================ ====================================== =====================
Example::
@@ -610,6 +613,11 @@ Built-in tag reference
{% ssi /home/html/ljworld.com/includes/right_generic.html parsed %}
+ Note that if you use ``{% ssi %}``, you'll need to define
+ `ALLOWED_INCLUDE_ROOTS`_ in your Django settings, as a security measure.
+
+.. _ALLOWED_INCLUDE_ROOTS: http://www.djangoproject.com/documentation/settings/#allowed-include-roots
+
``templatetag``
Output one of the bits used to compose template tags.
diff --git a/tests/othertests/dateformat.py b/tests/othertests/dateformat.py
new file mode 100644
index 0000000000..fe8bc8256d
--- /dev/null
+++ b/tests/othertests/dateformat.py
@@ -0,0 +1,75 @@
+"""
+>>> format(my_birthday, '')
+''
+>>> format(my_birthday, 'a')
+'p.m.'
+>>> format(my_birthday, 'A')
+'PM'
+>>> format(my_birthday, 'j')
+'7'
+>>> format(my_birthday, 'l')
+'Saturday'
+>>> format(my_birthday, 'L')
+'False'
+>>> format(my_birthday, 'm')
+'07'
+>>> format(my_birthday, 'M')
+'Jul'
+>>> format(my_birthday, 'n')
+'7'
+>>> format(my_birthday, 'N')
+'July'
+>>> format(my_birthday, 'O')
+'+0100'
+>>> format(my_birthday, 'P')
+'10 p.m.'
+>>> format(my_birthday, 'r')
+'Sat, 7 Jul 1979 22:00:00 +0100'
+>>> format(my_birthday, 's')
+'00'
+>>> format(my_birthday, 'S')
+'th'
+>>> format(my_birthday, 't')
+Traceback (most recent call last):
+ ...
+NotImplementedError
+>>> format(my_birthday, 'T')
+'CET'
+>>> format(my_birthday, 'U')
+'300445200'
+>>> format(my_birthday, 'w')
+'6'
+>>> format(my_birthday, 'W')
+'27'
+>>> format(my_birthday, 'y')
+'79'
+>>> format(my_birthday, 'Y')
+'1979'
+>>> format(my_birthday, 'z')
+'188'
+>>> format(my_birthday, 'Z')
+'3600'
+
+>>> format(summertime, 'I')
+'1'
+>>> format(summertime, 'O')
+'+0200'
+>>> format(wintertime, 'I')
+'0'
+>>> format(wintertime, 'O')
+'+0100'
+
+>>> format(my_birthday, 'Y z \\C\\E\\T')
+'1979 188 CET'
+"""
+
+from django.utils import dateformat
+format = dateformat.format
+import datetime, os, time
+
+os.environ['TZ'] = 'Europe/Copenhagen'
+time.tzset()
+
+my_birthday = datetime.datetime(1979, 7, 7, 22, 00)
+summertime = datetime.datetime(2005, 10, 30, 1, 00)
+wintertime = datetime.datetime(2005, 10, 30, 4, 00)
diff --git a/tests/testapp/models/custom_pk.py b/tests/testapp/models/custom_pk.py
index 234b5c3308..5b0eb45462 100644
--- a/tests/testapp/models/custom_pk.py
+++ b/tests/testapp/models/custom_pk.py
@@ -53,6 +53,8 @@ EmployeeDoesNotExist: Employee does not exist for {'pk': 'foo'}
>>> fran.save()
>>> employees.get_list(last_name__exact='Jones')
[Dan Jones, Fran Jones]
+>>> employees.get_in_bulk(['ABC123', 'XYZ456'])
+{'XYZ456': Fran Jones, 'ABC123': Dan Jones}
>>> b = businesses.Business(name='Sears')
>>> b.save()
@@ -62,4 +64,6 @@ True
[Dan Jones, Fran Jones]
>>> fran.get_business_list()
[Sears]
+>>> businesses.get_in_bulk(['Sears'])
+{'Sears': Sears}
"""