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

unicode: Merged from trunk up to [5150].

git-svn-id: http://code.djangoproject.com/svn/django/branches/unicode@5151 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Malcolm Tredinnick 2007-05-05 09:53:50 +00:00
parent 63d95548a7
commit dcc8577969
27 changed files with 1929 additions and 888 deletions

View File

@ -181,6 +181,7 @@ answer newbie questions, and generally made Django that much better:
Luke Plant <http://lukeplant.me.uk/> Luke Plant <http://lukeplant.me.uk/>
plisk plisk
Daniel Poelzleithner <http://poelzi.org/> Daniel Poelzleithner <http://poelzi.org/>
polpak@yahoo.com
J. Rademaker J. Rademaker
Michael Radziej <mir@noris.de> Michael Radziej <mir@noris.de>
ramiro ramiro
@ -224,6 +225,7 @@ answer newbie questions, and generally made Django that much better:
wam-djangobug@wamber.net wam-djangobug@wamber.net
Dan Watson <http://theidioteque.net/> Dan Watson <http://theidioteque.net/>
Chris Wesseling <Chris.Wesseling@cwi.nl> Chris Wesseling <Chris.Wesseling@cwi.nl>
charly.wilhelm@gmail.com
Rachel Willmer <http://www.willmer.com/kb/> Rachel Willmer <http://www.willmer.com/kb/>
Gary Wilson <gary.wilson@gmail.com> Gary Wilson <gary.wilson@gmail.com>
wojtek wojtek

View File

@ -122,6 +122,7 @@ EMAIL_PORT = 25
# Optional SMTP authentication information for EMAIL_HOST. # Optional SMTP authentication information for EMAIL_HOST.
EMAIL_HOST_USER = '' EMAIL_HOST_USER = ''
EMAIL_HOST_PASSWORD = '' EMAIL_HOST_PASSWORD = ''
EMAIL_USE_TLS = False
# List of strings representing installed apps. # List of strings representing installed apps.
INSTALLED_APPS = () INSTALLED_APPS = ()

File diff suppressed because it is too large Load Diff

View File

@ -22,5 +22,7 @@ def url(regex, view, kwargs=None, name=None, prefix=''):
# For include(...) processing. # For include(...) processing.
return RegexURLResolver(regex, view[0], kwargs) return RegexURLResolver(regex, view[0], kwargs)
else: else:
return RegexURLPattern(regex, prefix and (prefix + '.' + view) or view, kwargs, name) if prefix and isinstance(view, basestring):
view = prefix + '.' + view
return RegexURLPattern(regex, view, kwargs, name)

View File

@ -0,0 +1,31 @@
# -*- coding: utf-8 -*
from django.utils.translation import gettext_lazy as _
STATE_CHOICES = (
('AG', _('Aargau')),
('AI', _('Appenzell Innerrhoden')),
('AR', _('Appenzell Ausserrhoden')),
('BS', _('Basel-Stadt')),
('BL', _('Basel-Land')),
('BE', _('Berne')),
('FR', _('Fribourg')),
('GE', _('Geneva')),
('GL', _('Glarus')),
('GR', _('Graubuenden')),
('JU', _('Jura')),
('LU', _('Lucerne')),
('NE', _('Neuchatel')),
('NW', _('Nidwalden')),
('OW', _('Obwalden')),
('SH', _('Schaffhausen')),
('SZ', _('Schwyz')),
('SO', _('Solothurn')),
('SG', _('St. Gallen')),
('TG', _('Thurgau')),
('TI', _('Ticino')),
('UR', _('Uri')),
('VS', _('Valais')),
('VD', _('Vaud')),
('ZG', _('Zug')),
('ZH', _('Zurich'))
)

View File

@ -0,0 +1,109 @@
"""
Swiss-specific Form helpers
"""
from django.newforms import ValidationError
from django.newforms.fields import Field, RegexField, Select, EMPTY_VALUES
from django.utils.encoding import smart_unicode
from django.utils.translation import gettext
import re
id_re = re.compile(r"^(?P<idnumber>\w{8})(?P<pos9>(\d{1}|<))(?P<checksum>\d{1})$")
phone_digits_re = re.compile(r'^0([1-9]{1})\d{8}$')
class CHZipCodeField(RegexField):
def __init__(self, *args, **kwargs):
super(CHZipCodeField, self).__init__(r'^\d{4}$',
max_length=None, min_length=None,
error_message=gettext('Enter a zip code in the format XXXX.'),
*args, **kwargs)
class CHPhoneNumberField(Field):
"""
Validate local Swiss phone number (not international ones)
The correct format is '0XX XXX XX XX'.
'0XX.XXX.XX.XX' and '0XXXXXXXXX' validate but are corrected to
'0XX XXX XX XX'.
"""
def clean(self, value):
super(CHPhoneNumberField, self).clean(value)
if value in EMPTY_VALUES:
return u''
value = re.sub('(\.|\s|/|-)', '', smart_unicode(value))
m = phone_digits_re.search(value)
if m:
return u'%s %s %s %s' % (value[0:3], value[3:6], value[6:8], value[8:10])
raise ValidationError('Phone numbers must be in 0XX XXX XX XX format.')
class CHStateSelect(Select):
"""
A Select widget that uses a list of CH states as its choices.
"""
def __init__(self, attrs=None):
from ch_states import STATE_CHOICES # relative import
super(CHStateSelect, self).__init__(attrs, choices=STATE_CHOICES)
class CHIdentityCardNumberField(Field):
"""
A Swiss identity card number.
Checks the following rules to determine whether the number is valid:
* Conforms to the X1234567<0 or 1234567890 format.
* Included checksums match calculated checksums
Algorithm is documented at http://adi.kousz.ch/artikel/IDCHE.htm
"""
def has_valid_checksum(self, number):
given_number, given_checksum = number[:-1], number[-1]
new_number = given_number
calculated_checksum = 0
fragment = ""
parameter = 7
first = str(number[:1])
if first.isalpha():
num = ord(first.upper()) - 65
if num < 0 or num > 8:
return False
new_number = str(num) + new_number[1:]
new_number = new_number[:8] + '0'
if not new_number.isdigit():
return False
for i in range(len(new_number)):
fragment = int(new_number[i])*parameter
calculated_checksum += fragment
if parameter == 1:
parameter = 7
elif parameter == 3:
parameter = 1
elif parameter ==7:
parameter = 3
return str(calculated_checksum)[-1] == given_checksum
def clean(self, value):
super(CHIdentityCardNumberField, self).clean(value)
error_msg = gettext('Enter a valid Swiss identity or passport card number in X1234567<0 or 1234567890 format.')
if value in EMPTY_VALUES:
return u''
match = re.match(id_re, value)
if not match:
raise ValidationError(error_msg)
idnumber, pos9, checksum = match.groupdict()['idnumber'], match.groupdict()['pos9'], match.groupdict()['checksum']
if idnumber == '00000000' or \
idnumber == 'A0000000':
raise ValidationError(error_msg)
all_digits = "%s%s%s" % (idnumber, pos9, checksum)
if not self.has_valid_checksum(all_digits):
raise ValidationError(error_msg)
return u'%s%s%s' % (idnumber, pos9, checksum)

View File

@ -1,14 +1,22 @@
# Use this module for e-mailing. """
Tools for sending email.
"""
from django.conf import settings from django.conf import settings
from email.MIMEText import MIMEText from email.MIMEText import MIMEText
from email.Header import Header from email.Header import Header
from email.Utils import formatdate from email.Utils import formatdate
from email import Charset
import os
import smtplib import smtplib
import socket import socket
import time import time
import random import random
# Don't BASE64-encode UTF-8 messages so that we avoid unwanted attention from
# some spam filters.
Charset.add_charset('utf-8', Charset.SHORTEST, Charset.QP, 'utf-8')
# Cache the hostname, but do it lazily: socket.getfqdn() can take a couple of # Cache the hostname, but do it lazily: socket.getfqdn() can take a couple of
# seconds, which slows down the restart of the server. # seconds, which slows down the restart of the server.
class CachedDnsName(object): class CachedDnsName(object):
@ -22,6 +30,28 @@ class CachedDnsName(object):
DNS_NAME = CachedDnsName() DNS_NAME = CachedDnsName()
# Copied from Python standard library and modified to used the cached hostname
# for performance.
def make_msgid(idstring=None):
"""Returns a string suitable for RFC 2822 compliant Message-ID, e.g:
<20020201195627.33539.96671@nightshade.la.mastaler.com>
Optional idstring if given is a string used to strengthen the
uniqueness of the message id.
"""
timeval = time.time()
utcdate = time.strftime('%Y%m%d%H%M%S', time.gmtime(timeval))
pid = os.getpid()
randint = random.randrange(100000)
if idstring is None:
idstring = ''
else:
idstring = '.' + idstring
idhost = DNS_NAME
msgid = '<%s.%s.%s%s@%s>' % (utcdate, pid, randint, idstring, idhost)
return msgid
class BadHeaderError(ValueError): class BadHeaderError(ValueError):
pass pass
@ -34,6 +64,131 @@ class SafeMIMEText(MIMEText):
val = Header(val, settings.DEFAULT_CHARSET) val = Header(val, settings.DEFAULT_CHARSET)
MIMEText.__setitem__(self, name, val) MIMEText.__setitem__(self, name, val)
class SMTPConnection(object):
"""
A wrapper that manages the SMTP network connection.
"""
def __init__(self, host=None, port=None, username=None, password=None,
use_tls=None, fail_silently=False):
self.host = host or settings.EMAIL_HOST
self.port = port or settings.EMAIL_PORT
self.username = username or settings.EMAIL_HOST_USER
self.password = password or settings.EMAIL_HOST_PASSWORD
self.use_tls = (use_tls is not None) and use_tls or settings.EMAIL_USE_TLS
self.fail_silently = fail_silently
self.connection = None
def open(self):
"""
Ensure we have a connection to the email server. Returns whether or not
a new connection was required.
"""
if self.connection:
# Nothing to do if the connection is already open.
return False
try:
self.connection = smtplib.SMTP(self.host, self.port)
if self.use_tls:
self.connection.ehlo()
self.connection.starttls()
self.connection.ehlo()
if self.username and self.password:
self.connection.login(self.username, self.password)
return True
except:
if not self.fail_silently:
raise
def close(self):
"""Close the connection to the email server."""
try:
try:
self.connection.quit()
except socket.sslerror:
# This happens when calling quit() on a TLS connection
# sometimes.
self.connection.close()
except:
if self.fail_silently:
return
raise
finally:
self.connection = None
def send_messages(self, email_messages):
"""
Send one or more EmailMessage objects and return the number of email
messages sent.
"""
if not email_messages:
return
new_conn_created = self.open()
if not self.connection:
# We failed silently on open(). Trying to send would be pointless.
return
num_sent = 0
for message in email_messages:
sent = self._send(message)
if sent:
num_sent += 1
if new_conn_created:
self.close()
return num_sent
def _send(self, email_message):
"""A helper method that does the actual sending."""
if not email_message.to:
return False
try:
self.connection.sendmail(email_message.from_email,
email_message.recipients(),
email_message.message().as_string())
except:
if not self.fail_silently:
raise
return False
return True
class EmailMessage(object):
"""
A container for email information.
"""
def __init__(self, subject='', body='', from_email=None, to=None, bcc=None, connection=None):
self.to = to or []
self.bcc = bcc or []
self.from_email = from_email or settings.DEFAULT_FROM_EMAIL
self.subject = subject
self.body = body
self.connection = connection
def get_connection(self, fail_silently=False):
if not self.connection:
self.connection = SMTPConnection(fail_silently=fail_silently)
return self.connection
def message(self):
msg = SafeMIMEText(self.body, 'plain', settings.DEFAULT_CHARSET)
msg['Subject'] = self.subject
msg['From'] = self.from_email
msg['To'] = ', '.join(self.to)
msg['Date'] = formatdate()
msg['Message-ID'] = make_msgid()
if self.bcc:
msg['Bcc'] = ', '.join(self.bcc)
return msg
def recipients(self):
"""
Returns a list of all recipients of the email (includes direct
addressees as well as Bcc entries).
"""
return self.to + self.bcc
def send(self, fail_silently=False):
"""Send the email message."""
return self.get_connection(fail_silently).send_messages([self])
def send_mail(subject, message, from_email, recipient_list, fail_silently=False, auth_user=None, auth_password=None): def send_mail(subject, message, from_email, recipient_list, fail_silently=False, auth_user=None, auth_password=None):
""" """
Easy wrapper for sending a single message to a recipient list. All members Easy wrapper for sending a single message to a recipient list. All members
@ -41,8 +196,13 @@ def send_mail(subject, message, from_email, recipient_list, fail_silently=False,
If auth_user is None, the EMAIL_HOST_USER setting is used. If auth_user is None, the EMAIL_HOST_USER setting is used.
If auth_password is None, the EMAIL_HOST_PASSWORD setting is used. If auth_password is None, the EMAIL_HOST_PASSWORD setting is used.
NOTE: This method is deprecated. It exists for backwards compatibility.
New code should use the EmailMessage class directly.
""" """
return send_mass_mail([[subject, message, from_email, recipient_list]], fail_silently, auth_user, auth_password) connection = SMTPConnection(username=auth_user, password=auth_password,
fail_silently=fail_silently)
return EmailMessage(subject, message, from_email, recipient_list, connection=connection).send()
def send_mass_mail(datatuple, fail_silently=False, auth_user=None, auth_password=None): def send_mass_mail(datatuple, fail_silently=False, auth_user=None, auth_password=None):
""" """
@ -53,52 +213,24 @@ def send_mass_mail(datatuple, fail_silently=False, auth_user=None, auth_password
If auth_user and auth_password are set, they're used to log in. If auth_user and auth_password are set, they're used to log in.
If auth_user is None, the EMAIL_HOST_USER setting is used. If auth_user is None, the EMAIL_HOST_USER setting is used.
If auth_password is None, the EMAIL_HOST_PASSWORD setting is used. If auth_password is None, the EMAIL_HOST_PASSWORD setting is used.
NOTE: This method is deprecated. It exists for backwards compatibility.
New code should use the EmailMessage class directly.
""" """
if auth_user is None: connection = SMTPConnection(username=auth_user, password=auth_password,
auth_user = settings.EMAIL_HOST_USER fail_silently=fail_silently)
if auth_password is None: messages = [EmailMessage(subject, message, sender, recipient) for subject, message, sender, recipient in datatuple]
auth_password = settings.EMAIL_HOST_PASSWORD return connection.send_messages(messages)
try:
server = smtplib.SMTP(settings.EMAIL_HOST, settings.EMAIL_PORT)
if auth_user and auth_password:
server.login(auth_user, auth_password)
except:
if fail_silently:
return
raise
num_sent = 0
for subject, message, from_email, recipient_list in datatuple:
if not recipient_list:
continue
from_email = from_email or settings.DEFAULT_FROM_EMAIL
msg = SafeMIMEText(message, 'plain', settings.DEFAULT_CHARSET)
msg['Subject'] = subject
msg['From'] = from_email
msg['To'] = ', '.join(recipient_list)
msg['Date'] = formatdate()
try:
random_bits = str(random.getrandbits(64))
except AttributeError: # Python 2.3 doesn't have random.getrandbits().
random_bits = ''.join([random.choice('1234567890') for i in range(19)])
msg['Message-ID'] = "<%d.%s@%s>" % (time.time(), random_bits, DNS_NAME)
try:
server.sendmail(from_email, recipient_list, msg.as_string())
num_sent += 1
except:
if not fail_silently:
raise
try:
server.quit()
except:
if fail_silently:
return
raise
return num_sent
def mail_admins(subject, message, fail_silently=False): def mail_admins(subject, message, fail_silently=False):
"Sends a message to the admins, as defined by the ADMINS setting." "Sends a message to the admins, as defined by the ADMINS setting."
send_mail(settings.EMAIL_SUBJECT_PREFIX + subject, message, settings.SERVER_EMAIL, [a[1] for a in settings.ADMINS], fail_silently) EmailMessage(settings.EMAIL_SUBJECT_PREFIX + subject, message,
settings.SERVER_EMAIL, [a[1] for a in
settings.ADMINS]).send(fail_silently=fail_silently)
def mail_managers(subject, message, fail_silently=False): def mail_managers(subject, message, fail_silently=False):
"Sends a message to the managers, as defined by the MANAGERS setting." "Sends a message to the managers, as defined by the MANAGERS setting."
send_mail(settings.EMAIL_SUBJECT_PREFIX + subject, message, settings.SERVER_EMAIL, [a[1] for a in settings.MANAGERS], fail_silently) EmailMessage(settings.EMAIL_SUBJECT_PREFIX + subject, message,
settings.SERVER_EMAIL, [a[1] for a in
settings.MANAGERS]).send(fail_silently=fail_silently)

View File

@ -539,6 +539,7 @@ def syncdb(verbosity=1, interactive=True):
# Install custom SQL for the app (but only if this # Install custom SQL for the app (but only if this
# is a model we've just created) # is a model we've just created)
for app in models.get_apps(): for app in models.get_apps():
app_name = app.__name__.split('.')[-2]
for model in models.get_models(app): for model in models.get_models(app):
if model in created_models: if model in created_models:
custom_sql = get_custom_sql_for_model(model) custom_sql = get_custom_sql_for_model(model)

View File

@ -869,6 +869,12 @@ class USStateField(Field):
def get_manipulator_field_objs(self): def get_manipulator_field_objs(self):
return [oldforms.USStateField] return [oldforms.USStateField]
def formfield(self, **kwargs):
from django.contrib.localflavor.us.forms import USStateSelect
defaults = {'widget': USStateSelect}
defaults.update(kwargs)
return super(USStateField, self).formfield(**defaults)
class XMLField(TextField): class XMLField(TextField):
def __init__(self, verbose_name=None, name=None, schema_path=None, **kwargs): def __init__(self, verbose_name=None, name=None, schema_path=None, **kwargs):
self.schema_path = schema_path self.schema_path = schema_path

View File

@ -853,6 +853,13 @@ def find_field(name, field_list, related_query):
return None return None
return matches[0] return matches[0]
def field_choices(field_list, related_query):
if related_query:
choices = [f.field.related_query_name() for f in field_list]
else:
choices = [f.name for f in field_list]
return choices
def lookup_inner(path, lookup_type, value, opts, table, column): def lookup_inner(path, lookup_type, value, opts, table, column):
qn = backend.quote_name qn = backend.quote_name
joins, where, params = SortedDict(), [], [] joins, where, params = SortedDict(), [], []
@ -937,7 +944,11 @@ def lookup_inner(path, lookup_type, value, opts, table, column):
except FieldFound: # Match found, loop has been shortcut. except FieldFound: # Match found, loop has been shortcut.
pass pass
else: # No match found. else: # No match found.
raise TypeError, "Cannot resolve keyword '%s' into field" % name choices = field_choices(current_opts.many_to_many, False) + \
field_choices(current_opts.get_all_related_many_to_many_objects(), True) + \
field_choices(current_opts.get_all_related_objects(), True) + \
field_choices(current_opts.fields, False)
raise TypeError, "Cannot resolve keyword '%s' into field, choices are: %s" % (name, ", ".join(choices))
# Check whether an intermediate join is required between current_table # Check whether an intermediate join is required between current_table
# and new_table. # and new_table.

View File

@ -1,7 +1,9 @@
import re, doctest, unittest import re, doctest, unittest
from urlparse import urlparse
from django.db import transaction from django.db import transaction
from django.core import management from django.core import management
from django.db.models import get_apps from django.db.models import get_apps
from django.test.client import Client
normalize_long_ints = lambda s: re.sub(r'(?<![\w])(\d+)L(?![\w])', '\\1', s) normalize_long_ints = lambda s: re.sub(r'(?<![\w])(\d+)L(?![\w])', '\\1', s)
@ -46,5 +48,33 @@ class TestCase(unittest.TestCase):
super(). super().
""" """
self.client = Client()
self.install_fixtures() self.install_fixtures()
super(TestCase, self).run(result) super(TestCase, self).run(result)
def assertRedirects(self, response, expected_path):
"""Assert that a response redirected to a specific URL, and that the
redirect URL can be loaded.
"""
self.assertEqual(response.status_code, 302,
"Response didn't redirect: Reponse code was %d" % response.status_code)
scheme, netloc, path, params, query, fragment = urlparse(response['Location'])
self.assertEqual(path, expected_path,
"Response redirected to '%s', expected '%s'" % (path, expected_path))
redirect_response = self.client.get(path)
self.assertEqual(redirect_response.status_code, 200,
"Couldn't retrieve redirection page '%s'" % path)
def assertContains(self, response, text, count=1):
"""Assert that a response indicates that a page was retreived successfully,
(i.e., the HTTP status code was 200), and that ``text`` occurs ``count``
times in the content of the response.
"""
self.assertEqual(response.status_code, 200,
"Couldn't retrieve page'")
real_count = response.content.count(text)
self.assertEqual(real_count, count,
"Could only find %d of %d instances of '%s' in response" % (real_count, count, text))

View File

@ -94,12 +94,10 @@ Formatting
The text documentation is written in ReST (ReStructured Text) format. That The text documentation is written in ReST (ReStructured Text) format. That
means it's easy to read but is also formatted in a way that makes it easy to means it's easy to read but is also formatted in a way that makes it easy to
convert into other formats, such as HTML. If you're interested, the script that convert into other formats, such as HTML. If you have the `reStructuredText`_
converts the ReST text docs into djangoproject.com's HTML lives at library installed, you can use ``rst2html`` to generate your own HTML files.
`djangoproject.com/django_website/apps/docs/parts/build_documentation.py`_ in
the Django Subversion repository.
.. _djangoproject.com/django_website/apps/docs/parts/build_documentation.py: http://code.djangoproject.com/browser/djangoproject.com/django_website/apps/docs/parts/build_documentation.py .. _reStructuredText: http://docutils.sourceforge.net/rst.html
Differences between versions Differences between versions
============================ ============================

View File

@ -22,7 +22,8 @@ In two lines::
Mail will be sent using the SMTP host and port specified in the `EMAIL_HOST`_ Mail will be sent using the SMTP host and port specified in the `EMAIL_HOST`_
and `EMAIL_PORT`_ settings. The `EMAIL_HOST_USER`_ and `EMAIL_HOST_PASSWORD`_ and `EMAIL_PORT`_ settings. The `EMAIL_HOST_USER`_ and `EMAIL_HOST_PASSWORD`_
settings, if set, will be used to authenticate to the SMTP server. settings, if set, will be used to authenticate to the SMTP server and the
`EMAIL_USE_TLS`_ settings will control whether a secure connection is used.
.. note:: .. note::
@ -34,6 +35,7 @@ settings, if set, will be used to authenticate to the SMTP server.
.. _EMAIL_PORT: ../settings/#email-port .. _EMAIL_PORT: ../settings/#email-port
.. _EMAIL_HOST_USER: ../settings/#email-host-user .. _EMAIL_HOST_USER: ../settings/#email-host-user
.. _EMAIL_HOST_PASSWORD: ../settings/#email-host-password .. _EMAIL_HOST_PASSWORD: ../settings/#email-host-password
.. _EMAIL_USE_TLS: ../settings/#email-use-tls
send_mail() send_mail()
@ -183,3 +185,65 @@ from the request's POST data, sends that to admin@example.com and redirects to
return HttpResponse('Make sure all fields are entered and valid.') return HttpResponse('Make sure all fields are entered and valid.')
.. _Header injection: http://securephp.damonkohler.com/index.php/Email_Injection .. _Header injection: http://securephp.damonkohler.com/index.php/Email_Injection
The EmailMessage and SMTPConnection classes
===========================================
**New in Django development version**
Django's ``send_mail()`` and ``send_mass_mail()`` functions are actually thin
wrappers that make use of the ``EmailMessage`` and ``SMTPConnection`` classes
in ``django.mail``. If you ever need to customize the way Django sends email,
you can subclass these two classes to suit your needs.
.. note::
Not all features of the ``EmailMessage`` class are available through the
``send_mail()`` and related wrapper functions. If you wish to use advanced
features such as including BCC recipients or multi-part email, you will
need to create ``EmailMessage`` instances directly.
In general, ``EmailMessage`` is responsible for creating the email message
itself. ``SMTPConnection`` is responsible for the network connection side of
the operation. This means you can reuse the same connection (an
``SMTPConnection`` instance) for multiple messages.
The ``EmailMessage`` class is initialised as follows::
email = EmailMessage(subject, body, from_email, to, bcc, connection)
All of these parameters are optional. If ``from_email`` is omitted, the value
from ``settings.DEFAULT_FROM_EMAIL`` is used. Both the ``to`` and ``bcc``
parameters are lists of addresses.
The class has the following methods that you can use:
* ``send()`` sends the message, using either the connection that is specified
in the ``connection`` attribute, or creating a new connection if none already
exists.
* ``message()`` constructs a ``django.core.mail.SafeMIMEText`` object (a
sub-class of Python's ``email.MIMEText.MIMEText`` class) holding the
message to be sent. If you ever need to extend the `EmailMessage` class,
you will probably want to override this method to put the content you wish
into the MIME object.
* ``recipients()`` returns a lists of all the recipients of the message,
whether they are recorded in the ``to`` or ``bcc`` attributes. This is
another method you need to possibly override when sub-classing, since the
SMTP server needs to be told the full list of recipients when the message
is sent. If you add another way to specify recipients in your class, they
need to be returned from this method as well.
The ``SMTPConnection`` class is initialized with the host, port, username and
password for the SMTP server. If you don't specify one or more of those
options, they are read from your settings file.
If you are sending lots of messages at once, the ``send_messages()`` method of
the ``SMTPConnection`` class will be useful. It takes a list of ``EmailMessage``
instances (or sub-classes) and sends them over a single connection. For
example, if you have a function called ``get_notification_email()`` that returns a
list of ``EmailMessage`` objects representing some periodic email you wish to
send out, you could send this with::
connection = SMTPConnection() # Use default settings for connection
messages = get_notification_email()
connection.send_messages(messages)

View File

@ -107,7 +107,7 @@ posts a comment. It doesn't let a user post a comment more than once::
This simplistic view logs in a "member" of the site:: This simplistic view logs in a "member" of the site::
def login(request): def login(request):
m = members.get_object(username__exact=request.POST['username']) m = Member.objects.get(username=request.POST['username'])
if m.password == request.POST['password']: if m.password == request.POST['password']:
request.session['member_id'] = m.id request.session['member_id'] = m.id
return HttpResponse("You're logged in.") return HttpResponse("You're logged in.")

View File

@ -428,6 +428,15 @@ Subject-line prefix for e-mail messages sent with ``django.core.mail.mail_admins
or ``django.core.mail.mail_managers``. You'll probably want to include the or ``django.core.mail.mail_managers``. You'll probably want to include the
trailing space. trailing space.
EMAIL_USE_TLS
-------------
**New in Django development version**
Default: ``False``
Whether to use a TLS (secure) connection when talking to the SMTP server.
FILE_CHARSET FILE_CHARSET
------------ ------------
@ -508,44 +517,17 @@ in standard language format. For example, U.S. English is ``"en-us"``. See the
LANGUAGES LANGUAGES
--------- ---------
Default: A tuple of all available languages. Currently, this is:: Default: A tuple of all available languages. This list is continually growing
and including a copy here would inevitably become rapidly out of date. You can
see the current list of translated languages by looking in
``django/conf/global_settings.py`` (or view the `online source`_).
LANGUAGES = ( .. _online source: http://code.djangoproject.com/browser/django/trunk/django/conf/global_settings.py
('ar', _('Arabic')),
('bn', _('Bengali')),
('cs', _('Czech')),
('cy', _('Welsh')),
('da', _('Danish')),
('de', _('German')),
('el', _('Greek')),
('en', _('English')),
('es', _('Spanish')),
('es_AR', _('Argentinean Spanish')),
('fr', _('French')),
('gl', _('Galician')),
('hu', _('Hungarian')),
('he', _('Hebrew')),
('is', _('Icelandic')),
('it', _('Italian')),
('ja', _('Japanese')),
('nl', _('Dutch')),
('no', _('Norwegian')),
('pt-br', _('Brazilian')),
('ro', _('Romanian')),
('ru', _('Russian')),
('sk', _('Slovak')),
('sl', _('Slovenian')),
('sr', _('Serbian')),
('sv', _('Swedish')),
('ta', _('Tamil')),
('uk', _('Ukrainian')),
('zh-cn', _('Simplified Chinese')),
('zh-tw', _('Traditional Chinese')),
)
A tuple of two-tuples in the format (language code, language name). This The list is a tuple of two-tuples in the format (language code, language
specifies which languages are available for language selection. See the name) -- for example, ``('ja', 'Japanese')``. This specifies which languages
`internationalization docs`_ for details. are available for language selection. See the `internationalization docs`_ for
details.
Generally, the default value should suffice. Only set this setting if you want Generally, the default value should suffice. Only set this setting if you want
to restrict language selection to a subset of the Django-provided languages. to restrict language selection to a subset of the Django-provided languages.

View File

@ -646,15 +646,15 @@ This example illustrates all possible attributes and methods for a ``Feed`` clas
def item_enclosure_mime_type(self, item): def item_enclosure_mime_type(self, item):
""" """
Takes an item, as returned by items(), and returns the item's Takes an item, as returned by items(), and returns the item's
enclosure mime type. enclosure MIME type.
""" """
def item_enclosure_mime_type(self): def item_enclosure_mime_type(self):
""" """
Returns the enclosure length, in bytes, for every item in the feed. Returns the enclosure MIME type for every item in the feed.
""" """
item_enclosure_mime_type = "audio/mpeg" # Hard-coded enclosure mime-type. item_enclosure_mime_type = "audio/mpeg" # Hard-coded enclosure MIME type.
# ITEM PUBDATE -- It's optional to use one of these three. This is a # ITEM PUBDATE -- It's optional to use one of these three. This is a
# hook that specifies how to get the pubdate for a given item. # hook that specifies how to get the pubdate for a given item.

View File

@ -345,7 +345,7 @@ If ``TEMPLATE_CONTEXT_PROCESSORS`` contains this processor, every
``request.user.get_and_delete_messages()`` for every request. That method ``request.user.get_and_delete_messages()`` for every request. That method
collects the user's messages and deletes them from the database. collects the user's messages and deletes them from the database.
Note that messages are set with ``user.add_message()``. See the Note that messages are set with ``user.message_set.create``. See the
`message docs`_ for more. `message docs`_ for more.
* ``perms`` -- An instance of * ``perms`` -- An instance of

View File

@ -166,7 +166,7 @@ To assist in testing various features of your application, Django provides
tools that can be used to establish tests and test conditions. tools that can be used to establish tests and test conditions.
* `Test Client`_ * `Test Client`_
* Fixtures_ * `TestCase`_
Test Client Test Client
----------- -----------
@ -357,9 +357,31 @@ The following is a simple unit test using the Test Client::
# Check that the rendered context contains 5 customers # Check that the rendered context contains 5 customers
self.failUnlessEqual(len(response.context['customers']), 5) self.failUnlessEqual(len(response.context['customers']), 5)
Fixtures TestCase
-------- --------
Normal python unit tests extend a base class of ``unittest.testCase``.
Django provides an extension of this base class - ``django.test.TestCase``
- that provides some additional capabilities that can be useful for
testing web sites.
Moving from a normal unittest TestCase to a Django TestCase is easy - just
change the base class of your test from ``unittest.TestCase`` to
``django.test.TestCase``. All of the standard Python unit test facilities
will continue to be available, but they will be augmented with some useful
extra facilities.
Default Test Client
~~~~~~~~~~~~~~~~~~~
** New in Django development version **
Every test case in a ``django.test.TestCase`` instance has access to an
instance of a Django `Test Client`_. This Client can be accessed as
``self.client``. This client is recreated for each test.
Fixture loading
~~~~~~~~~~~~~~~
A test case for a database-backed website isn't much use if there isn't any A test case for a database-backed website isn't much use if there isn't any
data in the database. To make it easy to put test data into the database, data in the database. To make it easy to put test data into the database,
Django provides a fixtures framework. Django provides a fixtures framework.
@ -377,15 +399,13 @@ multiple applications.
data (such as a default set of categories). Fixtures with other names data (such as a default set of categories). Fixtures with other names
can be installed manually using ``django-admin.py loaddata``. can be installed manually using ``django-admin.py loaddata``.
However, for the purposes of unit testing, each test must be able to However, for the purposes of unit testing, each test must be able to
guarantee the contents of the database at the start of each and every guarantee the contents of the database at the start of each and every
test. To do this, Django provides a TestCase baseclass that can integrate test.
with fixtures.
Moving from a normal unittest TestCase to a Django TestCase is easy - just To define a fixture for a test, all you need to do is add a class
change the base class of your test, and define a list of fixtures attribute to your test describing the fixtures you want the test to use.
to be used. For example, the test case from `Writing unittests`_ would For example, the test case from `Writing unittests`_ would
look like:: look like::
from django.test import TestCase from django.test import TestCase
@ -410,6 +430,24 @@ This flush/load procedure is repeated for each test in the test case, so you
can be certain that the outcome of a test will not be affected by can be certain that the outcome of a test will not be affected by
another test, or the order of test execution. another test, or the order of test execution.
Assertions
~~~~~~~~~~
** New in Django development version **
Normal Python unit tests have a wide range of assertions, such as
``assertTrue`` and ``assertEquals`` that can be used to validate behavior.
``django.TestCase`` adds to these, providing some assertions
that can be useful in testing the behavior of web sites.
``assertRedirects(response, expected_path)``
Assert that the response received redirects the browser to the provided
path, and that the expected_path can be retrieved.
``assertContains(response, text, count=1)``
Assert that a response indicates that a page was retreived successfully,
(i.e., the HTTP status code was 200), and that ``text`` occurs ``count``
times in the content of the response.
Running tests Running tests
============= =============

View File

@ -71,7 +71,7 @@ __test__ = {'API_TESTS':"""
>>> Author.objects.filter(firstname__exact='John') >>> Author.objects.filter(firstname__exact='John')
Traceback (most recent call last): Traceback (most recent call last):
... ...
TypeError: Cannot resolve keyword 'firstname' into field TypeError: Cannot resolve keyword 'firstname' into field, choices are: article, id, first_name, last_name
>>> a = Author.objects.get(last_name__exact='Smith') >>> a = Author.objects.get(last_name__exact='Smith')
>>> a.first_name >>> a.first_name

View File

@ -223,11 +223,11 @@ DoesNotExist: Article matching query does not exist.
>>> Article.objects.filter(pub_date_year='2005').count() >>> Article.objects.filter(pub_date_year='2005').count()
Traceback (most recent call last): Traceback (most recent call last):
... ...
TypeError: Cannot resolve keyword 'pub_date_year' into field TypeError: Cannot resolve keyword 'pub_date_year' into field, choices are: id, headline, pub_date
>>> Article.objects.filter(headline__starts='Article') >>> Article.objects.filter(headline__starts='Article')
Traceback (most recent call last): Traceback (most recent call last):
... ...
TypeError: Cannot resolve keyword 'headline__starts' into field TypeError: Cannot resolve keyword 'headline__starts' into field, choices are: id, headline, pub_date
"""} """}

View File

@ -174,13 +174,13 @@ False
>>> Article.objects.filter(reporter_id__exact=1) >>> Article.objects.filter(reporter_id__exact=1)
Traceback (most recent call last): Traceback (most recent call last):
... ...
TypeError: Cannot resolve keyword 'reporter_id' into field TypeError: Cannot resolve keyword 'reporter_id' into field, choices are: id, headline, pub_date, reporter
# You need to specify a comparison clause # You need to specify a comparison clause
>>> Article.objects.filter(reporter_id=1) >>> Article.objects.filter(reporter_id=1)
Traceback (most recent call last): Traceback (most recent call last):
... ...
TypeError: Cannot resolve keyword 'reporter_id' into field TypeError: Cannot resolve keyword 'reporter_id' into field, choices are: id, headline, pub_date, reporter
# You can also instantiate an Article by passing # You can also instantiate an Article by passing
# the Reporter's ID instead of a Reporter object. # the Reporter's ID instead of a Reporter object.

View File

@ -55,5 +55,5 @@ __test__ = {'API_TESTS':"""
>>> Poll.objects.get(choice__name__exact="This is the answer") >>> Poll.objects.get(choice__name__exact="This is the answer")
Traceback (most recent call last): Traceback (most recent call last):
... ...
TypeError: Cannot resolve keyword 'choice' into field TypeError: Cannot resolve keyword 'choice' into field, choices are: poll_choice, related_choice, id, question, creator
"""} """}

View File

@ -24,19 +24,14 @@ from django.test import Client, TestCase
class ClientTest(TestCase): class ClientTest(TestCase):
fixtures = ['testdata.json'] fixtures = ['testdata.json']
def setUp(self):
"Set up test environment"
self.client = Client()
def test_get_view(self): def test_get_view(self):
"GET a view" "GET a view"
response = self.client.get('/test_client/get_view/') response = self.client.get('/test_client/get_view/')
# Check some response details # Check some response details
self.assertEqual(response.status_code, 200) self.assertContains(response, 'This is a test')
self.assertEqual(response.context['var'], 42) self.assertEqual(response.context['var'], 42)
self.assertEqual(response.template.name, 'GET Template') self.assertEqual(response.template.name, 'GET Template')
self.failUnless('This is a test.' in response.content)
def test_get_post_view(self): def test_get_post_view(self):
"GET a view that normally expects POSTs" "GET a view that normally expects POSTs"
@ -80,7 +75,7 @@ class ClientTest(TestCase):
response = self.client.get('/test_client/redirect_view/') response = self.client.get('/test_client/redirect_view/')
# Check that the response was a 302 (redirect) # Check that the response was a 302 (redirect)
self.assertEqual(response.status_code, 302) self.assertRedirects(response, '/test_client/get_view/')
def test_valid_form(self): def test_valid_form(self):
"POST valid data to a form" "POST valid data to a form"
@ -102,7 +97,7 @@ class ClientTest(TestCase):
'value': 37 'value': 37
} }
response = self.client.post('/test_client/form_view/', post_data) response = self.client.post('/test_client/form_view/', post_data)
self.assertEqual(response.status_code, 200) self.assertContains(response, 'This field is required', 3)
self.assertEqual(response.template.name, "Invalid POST Template") self.assertEqual(response.template.name, "Invalid POST Template")
def test_form_error(self): def test_form_error(self):
@ -130,7 +125,7 @@ class ClientTest(TestCase):
# Get the page without logging in. Should result in 302. # Get the page without logging in. Should result in 302.
response = self.client.get('/test_client/login_protected_view/') response = self.client.get('/test_client/login_protected_view/')
self.assertEqual(response.status_code, 302) self.assertRedirects(response, '/accounts/login/')
# Request a page that requires a login # Request a page that requires a login
response = self.client.login('/test_client/login_protected_view/', 'testclient', 'password') response = self.client.login('/test_client/login_protected_view/', 'testclient', 'password')

View File

@ -1011,6 +1011,60 @@ Traceback (most recent call last):
... ...
ValidationError: [u'Enter a valid German identity card number in XXXXXXXXXXX-XXXXXXX-XXXXXXX-X format.'] ValidationError: [u'Enter a valid German identity card number in XXXXXXXXXXX-XXXXXXX-XXXXXXX-X format.']
# CHZipCodeField ############################################################
>>> from django.contrib.localflavor.ch.forms import CHZipCodeField
>>> f = CHZipCodeField()
>>> f.clean('800x')
Traceback (most recent call last):
...
ValidationError: [u'Enter a zip code in the format XXXX.']
>>> f.clean('80 00')
Traceback (most recent call last):
...
ValidationError: [u'Enter a zip code in the format XXXX.']
>>> f.clean('8000')
u'8000'
# CHPhoneNumberField ########################################################
>>> from django.contrib.localflavor.ch.forms import CHPhoneNumberField
>>> f = CHPhoneNumberField()
>>> f.clean('01234567890')
Traceback (most recent call last):
...
ValidationError: [u'Phone numbers must be in 0XX XXX XX XX format.']
>>> f.clean('1234567890')
Traceback (most recent call last):
...
ValidationError: [u'Phone numbers must be in 0XX XXX XX XX format.']
>>> f.clean('0123456789')
u'012 345 67 89'
# CHIdentityCardNumberField #################################################
>>> from django.contrib.localflavor.ch.forms import CHIdentityCardNumberField
>>> f = CHIdentityCardNumberField()
>>> f.clean('C1234567<0')
u'C1234567<0'
>>> f.clean('C1234567<1')
Traceback (most recent call last):
...
ValidationError: [u'Enter a valid Swiss identity or passport card number in X1234567<0 or 1234567890 format.']
>>> f.clean('2123456700')
u'2123456700'
>>> f.clean('2123456701')
Traceback (most recent call last):
...
ValidationError: [u'Enter a valid Swiss identity or passport card number in X1234567<0 or 1234567890 format.']
# CHStateSelect #############################################################
>>> from django.contrib.localflavor.ch.forms import CHStateSelect
>>> w = CHStateSelect()
>>> w.render('state', 'AG')
u'<select name="state">\n<option value="AG" selected="selected">Aargau</option>\n<option value="AI">Appenzell Innerrhoden</option>\n<option value="AR">Appenzell Ausserrhoden</option>\n<option value="BS">Basel-Stadt</option>\n<option value="BL">Basel-Land</option>\n<option value="BE">Berne</option>\n<option value="FR">Fribourg</option>\n<option value="GE">Geneva</option>\n<option value="GL">Glarus</option>\n<option value="GR">Graubuenden</option>\n<option value="JU">Jura</option>\n<option value="LU">Lucerne</option>\n<option value="NE">Neuchatel</option>\n<option value="NW">Nidwalden</option>\n<option value="OW">Obwalden</option>\n<option value="SH">Schaffhausen</option>\n<option value="SZ">Schwyz</option>\n<option value="SO">Solothurn</option>\n<option value="SG">St. Gallen</option>\n<option value="TG">Thurgau</option>\n<option value="TI">Ticino</option>\n<option value="UR">Uri</option>\n<option value="VS">Valais</option>\n<option value="VD">Vaud</option>\n<option value="ZG">Zug</option>\n<option value="ZH">Zurich</option>\n</select>'
## AUPostCodeField ########################################################## ## AUPostCodeField ##########################################################
A field that accepts a four digit Australian post code. A field that accepts a four digit Australian post code.

View File

@ -32,7 +32,7 @@ __test__ = {'API_TESTS':"""
>>> Choice.objects.filter(foo__exact=None) >>> Choice.objects.filter(foo__exact=None)
Traceback (most recent call last): Traceback (most recent call last):
... ...
TypeError: Cannot resolve keyword 'foo' into field TypeError: Cannot resolve keyword 'foo' into field, choices are: id, poll, choice
# Can't use None on anything other than __exact # Can't use None on anything other than __exact
>>> Choice.objects.filter(id__gt=None) >>> Choice.objects.filter(id__gt=None)