mirror of
				https://github.com/django/django.git
				synced 2025-10-31 01:25:32 +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:
		
							
								
								
									
										2
									
								
								AUTHORS
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								AUTHORS
									
									
									
									
									
								
							| @@ -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 | ||||||
|   | |||||||
| @@ -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 = () | ||||||
|   | |||||||
										
											Binary file not shown.
										
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -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) | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										0
									
								
								django/contrib/localflavor/ch/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								django/contrib/localflavor/ch/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										31
									
								
								django/contrib/localflavor/ch/ch_states.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								django/contrib/localflavor/ch/ch_states.py
									
									
									
									
									
										Normal 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')) | ||||||
|  | ) | ||||||
							
								
								
									
										109
									
								
								django/contrib/localflavor/ch/forms.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								django/contrib/localflavor/ch/forms.py
									
									
									
									
									
										Normal 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) | ||||||
|  |  | ||||||
| @@ -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) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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) | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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. | ||||||
|   | |||||||
| @@ -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)) | ||||||
|  |              | ||||||
| @@ -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 | ||||||
| ============================ | ============================ | ||||||
|   | |||||||
| @@ -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) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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.") | ||||||
|   | |||||||
| @@ -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. | ||||||
|   | |||||||
| @@ -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. | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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 | ||||||
| ============= | ============= | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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 | ||||||
|  |  | ||||||
| """} | """} | ||||||
|   | |||||||
| @@ -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. | ||||||
|   | |||||||
| @@ -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 | ||||||
| """} | """} | ||||||
|   | |||||||
| @@ -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') | ||||||
|   | |||||||
| @@ -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. | ||||||
|   | |||||||
| @@ -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) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user