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

Synced to trunk. Fixed views/decorators/auth.py. Added extra customisation points to admin change_forms.

git-svn-id: http://code.djangoproject.com/svn/django/branches/new-admin@998 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Robert Wittams 2005-10-23 15:45:02 +00:00
commit a4ce9cf2ce
11 changed files with 530 additions and 70 deletions

View File

@ -19,8 +19,7 @@
{% if has_absolute_url %}<li><a href="/r/{{ content_type_id }}/{{ object_id }}/" class="viewsitelink">View on site</a></li>{% endif%}
</ul>
{% endif %}{% endif %}
<form {{ form_enc_attrib }} action='{{ form_url }}' method="post">
<form {{ form_enc_attrib }} action='{{ form_url }}' method="post">{% block form_top %}{%endblock%}
{% if is_popup %}<input type="hidden" name="_popup" value="1">{% endif %}
{% if save_on_top %}{% submit_row %}{% endif %}
{% if form.error_dict %}<p class="errornote">Please correct the error{{ form.error_dict.items|pluralize }} below.</p>{% endif %}
@ -35,6 +34,7 @@
{% endfor %}
</fieldset>
{% endfor %}
{% block after_field_sets %}{% endblock %}
{% if change %}
{% if ordered_objects %}
<fieldset class="module"><h2>Ordering</h2>
@ -44,7 +44,9 @@
</div></fieldset>
{% endif %}
{% endif %}
{% for related_object in inline_related_objects %}{% edit_inline related_object %}{% endfor %}
{% block after_related_objects%}{%endblock%}
{% submit_row %}
{% if add %}
<script type="text/javascript">document.getElementById("{{first_form_field_id}}").focus();</script>

View File

@ -607,7 +607,7 @@ class AdminBoundFieldSet(BoundFieldSet):
super(AdminBoundFieldSet, self).__init__(field_set, field_mapping, original, AdminBoundFieldLine)
def fill_extra_context(opts, app_label, context, add=False, change=False, show_delete=False, form_url=''):
def render_change_form(opts, app_label, context, add=False, change=False, show_delete=False, form_url=''):
ordered_objects = opts.get_ordered_objects()[:]
auto_populated_fields = [f for f in opts.fields if f.prepopulate_from]
coltype = ordered_objects and 'colMS' or 'colM'
@ -650,9 +650,17 @@ def fill_extra_context(opts, app_label, context, add=False, change=False, show_d
'has_delete_permission' : context['perms'][app_label][opts.get_delete_permission()]
}
context.update(extra_context)
context.update(extra_context)
return render_to_response(["admin/%s/%s/change_form" % (app_label, opts.object_name.lower() ),
"admin/%s/change_form" % app_label ,
"admin/change_form"],
context_instance=context)
def log_add_message(user, opts,manipulator,new_object):
pk_value = getattr(new_object, opts.pk.column)
log.log_action(user.id, opts.get_content_type_id(), pk_value, repr(new_object), log.ADDITION)
def add_stage(request, app_label, module_name, show_delete=False, form_url='', post_url='../', post_url_continue='../%s/', object_id_override=None):
mod, opts = _get_mod_opts(app_label, module_name)
if not request.user.has_perm(app_label + '.' + opts.get_add_permission()):
@ -666,13 +674,10 @@ def add_stage(request, app_label, module_name, show_delete=False, form_url='', p
manipulator.do_html2python(new_data)
if not errors and not request.POST.has_key("_preview"):
for f in opts.many_to_many:
if f.rel.raw_id_admin:
new_data.setlist(f.name, new_data[f.name].split(","))
new_object = manipulator.save(new_data)
pk_value = getattr(new_object, opts.pk.column)
log.log_action(request.user.id, opts.get_content_type_id(), pk_value, repr(new_object), log.ADDITION)
log_add_message(request.user, opts,manipulator,new_object)
msg = 'The %s "%s" was added successfully.' % (opts.verbose_name, new_object)
# Here, we distinguish between different save types by checking for
# the presence of keys in request.POST.
if request.POST.has_key("_continue"):
@ -689,8 +694,6 @@ def add_stage(request, app_label, module_name, show_delete=False, form_url='', p
else:
request.user.add_message(msg)
return HttpResponseRedirect(post_url)
# if request.POST.has_key("_preview"): # Always happens anyway.
# manipulator.do_html2python(new_data)
else:
# Add default data.
new_data = manipulator.flatten_data()
@ -710,13 +713,24 @@ def add_stage(request, app_label, module_name, show_delete=False, form_url='', p
if object_id_override is not None:
c['object_id'] = object_id_override
fill_extra_context(opts, app_label, c, add=True)
return render_to_response("admin/change_form", context_instance=c)
return render_change_form(opts, app_label, c, add=True)
add_stage = staff_member_required(add_stage)
def log_change_message(user, opts,manipulator,new_object):
pk_value = getattr(new_object, opts.pk.column)
# Construct the change message.
change_message = []
if manipulator.fields_added:
change_message.append('Added %s.' % get_text_list(manipulator.fields_added, 'and'))
if manipulator.fields_changed:
change_message.append('Changed %s.' % get_text_list(manipulator.fields_changed, 'and'))
if manipulator.fields_deleted:
change_message.append('Deleted %s.' % get_text_list(manipulator.fields_deleted, 'and'))
change_message = ' '.join(change_message)
if not change_message:
change_message = 'No fields changed.'
log.log_action(user.id, opts.get_content_type_id(), pk_value, repr(new_object), log.CHANGE, change_message)
def change_stage(request, app_label, module_name, object_id):
mod, opts = _get_mod_opts(app_label, module_name)
if not request.user.has_perm(app_label + '.' + opts.get_change_permission()):
@ -737,26 +751,8 @@ def change_stage(request, app_label, module_name, object_id):
manipulator.do_html2python(new_data)
if not errors and not request.POST.has_key("_preview"):
# Now done in commaseparatedint
# for f in opts.many_to_many:
# if f.rel.raw_id_admin:
# new_data.setlist(f.name, new_data[f.name].split(","))
new_object = manipulator.save(new_data)
pk_value = getattr(new_object, opts.pk.column)
# Construct the change message.
change_message = []
if manipulator.fields_added:
change_message.append('Added %s.' % get_text_list(manipulator.fields_added, 'and'))
if manipulator.fields_changed:
change_message.append('Changed %s.' % get_text_list(manipulator.fields_changed, 'and'))
if manipulator.fields_deleted:
change_message.append('Deleted %s.' % get_text_list(manipulator.fields_deleted, 'and'))
change_message = ' '.join(change_message)
if not change_message:
change_message = 'No fields changed.'
log.log_action(request.user.id, opts.get_content_type_id(), pk_value, repr(new_object), log.CHANGE, change_message)
log_change_message(request.user,opts,manipulator,new_object)
msg = 'The %s "%s" was changed successfully.' % (opts.verbose_name, new_object)
if request.POST.has_key("_continue"):
request.user.add_message("%s You may edit it again below." % msg)
@ -773,15 +769,14 @@ def change_stage(request, app_label, module_name, object_id):
else:
request.user.add_message(msg)
return HttpResponseRedirect("../")
# if request.POST.has_key("_preview"): # always happens
# manipulator.do_html2python(new_data)
else:
# Populate new_data with a "flattened" version of the current data.
new_data = manipulator.flatten_data()
# TODO: do this in flatten_data...
# If the object has ordered objects on its admin page, get the existing
# order and flatten it into a comma-separated list of IDs.
id_order_list = []
for rel_obj in opts.get_ordered_objects():
id_order_list.extend(getattr(obj, 'get_%s_order' % rel_obj.object_name.lower())())
@ -794,6 +789,7 @@ def change_stage(request, app_label, module_name, object_id):
form.original = manipulator.original_object
form.order_objects = []
#TODO Should be done in flatten_data / FormWrapper construction
for related in opts.get_followed_related_objects():
wrt = related.opts.order_with_respect_to
if wrt and wrt.rel and wrt.rel.to == opts:
@ -810,10 +806,9 @@ def change_stage(request, app_label, module_name, object_id):
'is_popup' : request.REQUEST.has_key('_popup')
})
fill_extra_context(opts, app_label, c, change=True)
return render_change_form(opts, app_label, c, change=True)
return render_to_response('admin/change_form', context_instance=c)
change_stage = staff_member_required(change_stage)
def _nest_help(obj, depth, val):
current = obj

View File

@ -1498,9 +1498,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,)

View File

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

View File

@ -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)

52
django/utils/tzinfo.py Normal file
View File

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

View File

@ -1,12 +1,25 @@
def login_required(view_func):
def user_passes_test(test_func):
"""
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.
"""
def _dec(view_func):
def _checklogin(request, *args, **kwargs):
from django.views.auth.login import redirect_to_login
if test_func(request.user):
return view_func(request, *args, **kwargs)
return redirect_to_login(request.path)
return _checklogin
return _dec
login_required = user_passes_test(lambda u: not u.is_anonymous())
login_required.__doc__ = (
"""
Decorator for views that checks that the user is logged in, redirecting
to the log-in page if necessary.
"""
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:
return view_func(request, *args, **kwargs)
return _checklogin
)

288
docs/authentication.txt Normal file
View File

@ -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
========

View File

@ -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::

View File

@ -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)

View File

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