Merged w/trunk up to r11732. trunk tip
authorJustin Bronn <jbronn@geodjango.org>
Fri Nov 13 11:27:59 2009 -0600 (2 years ago)
branchtrunk
changeset 1144bb6089640675
parent 1035 fc91ed5fbd99
parent 1143 afb55180da78
Merged w/trunk up to r11732.
AUTHORS
django/contrib/comments/templates/comments/moderation_queue.html
django/contrib/csrf/models.py
django/contrib/csrf/tests.py
django/contrib/gis/db/models/sql/aggregates.py
django/contrib/gis/gdal/geometries.py
django/contrib/gis/gdal/layer.py
django/contrib/gis/gdal/tests/test_ds.py
django/contrib/gis/gdal/tests/test_geom.py
django/contrib/gis/geos/geometry.py
django/contrib/gis/geos/io.py
django/contrib/gis/geos/prototypes/geom.py
django/contrib/gis/geos/tests/test_geos.py
django/contrib/gis/tests/layermap/tests.py
django/core/mail.py
django/db/models/query.py
django/db/models/sql/query.py
docs/ref/models/querysets.txt
     1.1 --- a/AUTHORS	Sat Sep 12 18:53:56 2009 -0500
     1.2 +++ b/AUTHORS	Fri Nov 13 11:27:59 2009 -0600
     1.3 @@ -16,6 +16,7 @@
     1.4      * Brian Rosner
     1.5      * Justin Bronn
     1.6      * Karen Tracey
     1.7 +    * Jannis Leidel
     1.8  
     1.9  More information on the main contributors to Django can be found in
    1.10  docs/internals/committers.txt.
    1.11 @@ -26,6 +27,7 @@
    1.12  
    1.13      ajs <adi@sieker.info>
    1.14      alang@bright-green.com
    1.15 +    Andi Albrecht <albrecht.andi@gmail.com>
    1.16      Marty Alchin <gulopine@gamemusic.org>
    1.17      Ahmad Alhashemi <trans@ahmadh.com>
    1.18      Daniel Alves Barbosa de Oliveira Vaz <danielvaz@gmail.com>
    1.19 @@ -267,7 +269,6 @@
    1.20      lcordier@point45.com
    1.21      Jeong-Min Lee <falsetru@gmail.com>
    1.22      Tai Lee <real.human@mrmachine.net>
    1.23 -    Jannis Leidel <jl@websushi.org>
    1.24      Christopher Lenz <http://www.cmlenz.net/>
    1.25      lerouxb@gmail.com
    1.26      Piotr Lewandowski <piotr.lewandowski@gmail.com>
    1.27 @@ -422,7 +423,7 @@
    1.28      Travis Terry <tdterry7@gmail.com>
    1.29      thebjorn <bp@datakortet.no>
    1.30      Zach Thompson <zthompson47@gmail.com>
    1.31 -    Michael Thornhill
    1.32 +    Michael Thornhill <michael.thornhill@gmail.com>
    1.33      Deepak Thukral <deep.thukral@gmail.com>
    1.34      tibimicu@gmx.net
    1.35      tobias@neuyork.de
    1.36 @@ -470,6 +471,8 @@
    1.37      Gasper Zejn <zejn@kiberpipa.org>
    1.38      Jarek Zgoda <jarek.zgoda@gmail.com>
    1.39      Cheng Zhang
    1.40 +    Glenn Maynard <glenn@zewt.org>
    1.41 +    bthomas
    1.42  
    1.43  A big THANK YOU goes to:
    1.44  
     2.1 --- a/INSTALL	Sat Sep 12 18:53:56 2009 -0500
     2.2 +++ b/INSTALL	Fri Nov 13 11:27:59 2009 -0600
     2.3 @@ -1,22 +1,16 @@
     2.4  Thanks for downloading Django.
     2.5  
     2.6 -To install it, make sure you have Python 2.3 or greater installed. Then run
     2.7 +To install it, make sure you have Python 2.4 or greater installed. Then run
     2.8  this command from the command prompt:
     2.9  
    2.10      python setup.py install
    2.11  
    2.12 -Note this requires a working Internet connection if you don't already have the
    2.13 -Python utility "setuptools" installed.
    2.14 -
    2.15  AS AN ALTERNATIVE, you can just copy the entire "django" directory to Python's
    2.16  site-packages directory, which is located wherever your Python installation
    2.17  lives. Some places you might check are:
    2.18  
    2.19 +    /usr/lib/python2.5/site-packages (Unix, Python 2.5)
    2.20      /usr/lib/python2.4/site-packages (Unix, Python 2.4)
    2.21 -    /usr/lib/python2.3/site-packages (Unix, Python 2.3)
    2.22      C:\\PYTHON\site-packages         (Windows)
    2.23  
    2.24 -This second solution does not require a working Internet connection; it
    2.25 -bypasses "setuptools" entirely.
    2.26 -
    2.27  For more detailed instructions, see docs/intro/install.txt.
     3.1 --- a/django/conf/__init__.py	Sat Sep 12 18:53:56 2009 -0500
     3.2 +++ b/django/conf/__init__.py	Fri Nov 13 11:27:59 2009 -0600
     3.3 @@ -108,9 +108,6 @@
     3.4              os.environ['TZ'] = self.TIME_ZONE
     3.5              time.tzset()
     3.6  
     3.7 -    def get_all_members(self):
     3.8 -        return dir(self)
     3.9 -
    3.10  class UserSettingsHolder(object):
    3.11      """
    3.12      Holder for user configured settings.
    3.13 @@ -129,8 +126,11 @@
    3.14      def __getattr__(self, name):
    3.15          return getattr(self.default_settings, name)
    3.16  
    3.17 -    def get_all_members(self):
    3.18 +    def __dir__(self):
    3.19          return dir(self) + dir(self.default_settings)
    3.20  
    3.21 +    # For Python < 2.6:
    3.22 +    __members__ = property(lambda self: self.__dir__())
    3.23 +
    3.24  settings = LazySettings()
    3.25  
     4.1 --- a/django/conf/global_settings.py	Sat Sep 12 18:53:56 2009 -0500
     4.2 +++ b/django/conf/global_settings.py	Fri Nov 13 11:27:59 2009 -0600
     4.3 @@ -131,6 +131,12 @@
     4.4  DATABASE_PORT = ''             # Set to empty string for default. Not used with sqlite3.
     4.5  DATABASE_OPTIONS = {}          # Set to empty dictionary for default.
     4.6  
     4.7 +# The email backend to use. For possible shortcuts see django.core.mail.
     4.8 +# The default is to use the SMTP backend.
     4.9 +# Third-party backends can be specified by providing a Python path
    4.10 +# to a module that defines an EmailBackend class.
    4.11 +EMAIL_BACKEND = 'django.core.mail.backends.smtp'
    4.12 +
    4.13  # Host for sending e-mail.
    4.14  EMAIL_HOST = 'localhost'
    4.15  
    4.16 @@ -300,6 +306,7 @@
    4.17  MIDDLEWARE_CLASSES = (
    4.18      'django.middleware.common.CommonMiddleware',
    4.19      'django.contrib.sessions.middleware.SessionMiddleware',
    4.20 +    'django.middleware.csrf.CsrfViewMiddleware',
    4.21      'django.contrib.auth.middleware.AuthenticationMiddleware',
    4.22  #     'django.middleware.http.ConditionalGetMiddleware',
    4.23  #     'django.middleware.gzip.GZipMiddleware',
    4.24 @@ -374,6 +381,18 @@
    4.25  # The number of days a password reset link is valid for
    4.26  PASSWORD_RESET_TIMEOUT_DAYS = 3
    4.27  
    4.28 +########
    4.29 +# CSRF #
    4.30 +########
    4.31 +
    4.32 +# Dotted path to callable to be used as view when a request is
    4.33 +# rejected by the CSRF middleware.
    4.34 +CSRF_FAILURE_VIEW = 'django.views.csrf.csrf_failure'
    4.35 +
    4.36 +# Name and domain for CSRF cookie.
    4.37 +CSRF_COOKIE_NAME = 'csrftoken'
    4.38 +CSRF_COOKIE_DOMAIN = None
    4.39 +
    4.40  ###########
    4.41  # TESTING #
    4.42  ###########
     5.1 Binary file django/conf/locale/he/LC_MESSAGES/django.mo has changed
     6.1 --- a/django/conf/locale/he/LC_MESSAGES/django.po	Sat Sep 12 18:53:56 2009 -0500
     6.2 +++ b/django/conf/locale/he/LC_MESSAGES/django.po	Fri Nov 13 11:27:59 2009 -0600
     6.3 @@ -391,7 +391,7 @@
     6.4  #: contrib/admin/options.py:1003
     6.5  #, python-format
     6.6  msgid "%(name)s object with primary key %(key)r does not exist."
     6.7 -msgstr "הפריט %(name)s עם המקש %(key)r אינו קיים."
     6.8 +msgstr "הפריט %(name)s עם המפתח הראשי %(key)r אינו קיים."
     6.9  
    6.10  #: contrib/admin/options.py:860
    6.11  #, python-format
     7.1 Binary file django/conf/locale/pl/LC_MESSAGES/django.mo has changed
     8.1 --- a/django/conf/locale/pl/LC_MESSAGES/django.po	Sat Sep 12 18:53:56 2009 -0500
     8.2 +++ b/django/conf/locale/pl/LC_MESSAGES/django.po	Fri Nov 13 11:27:59 2009 -0600
     8.3 @@ -5,7 +5,7 @@
     8.4  msgstr ""
     8.5  "Project-Id-Version: Django\n"
     8.6  "Report-Msgid-Bugs-To: \n"
     8.7 -"POT-Creation-Date: 2009-07-17 21:59+0200\n"
     8.8 +"POT-Creation-Date: 2009-10-25 20:56+0100\n"
     8.9  "PO-Revision-Date: 2008-02-25 15:53+0100\n"
    8.10  "Last-Translator: Jarek Zgoda <jarek.zgoda@gmail.com>\n"
    8.11  "MIME-Version: 1.0\n"
    8.12 @@ -266,15 +266,15 @@
    8.13  msgid "This year"
    8.14  msgstr "Ten rok"
    8.15  
    8.16 -#: contrib/admin/filterspecs.py:147 forms/widgets.py:434
    8.17 +#: contrib/admin/filterspecs.py:147 forms/widgets.py:435
    8.18  msgid "Yes"
    8.19  msgstr "Tak"
    8.20  
    8.21 -#: contrib/admin/filterspecs.py:147 forms/widgets.py:434
    8.22 +#: contrib/admin/filterspecs.py:147 forms/widgets.py:435
    8.23  msgid "No"
    8.24  msgstr "Nie"
    8.25  
    8.26 -#: contrib/admin/filterspecs.py:154 forms/widgets.py:434
    8.27 +#: contrib/admin/filterspecs.py:154 forms/widgets.py:435
    8.28  msgid "Unknown"
    8.29  msgstr "Nieznany"
    8.30  
    8.31 @@ -320,8 +320,8 @@
    8.32  msgstr "Zmieniono %s"
    8.33  
    8.34  #: contrib/admin/options.py:519 contrib/admin/options.py:529
    8.35 -#: contrib/comments/templates/comments/preview.html:16 forms/models.py:388
    8.36 -#: forms/models.py:600
    8.37 +#: contrib/comments/templates/comments/preview.html:16 forms/models.py:384
    8.38 +#: forms/models.py:596
    8.39  msgid "and"
    8.40  msgstr "i"
    8.41  
    8.42 @@ -417,11 +417,11 @@
    8.43  "Proszę wpisać poprawną nazwę użytkownika i hasło. Uwaga: wielkość liter ma "
    8.44  "znaczenie."
    8.45  
    8.46 -#: contrib/admin/sites.py:285 contrib/admin/views/decorators.py:40
    8.47 +#: contrib/admin/sites.py:288 contrib/admin/views/decorators.py:40
    8.48  msgid "Please log in again, because your session has expired."
    8.49  msgstr "Twoja sesja wygasła, zaloguj się ponownie."
    8.50  
    8.51 -#: contrib/admin/sites.py:292 contrib/admin/views/decorators.py:47
    8.52 +#: contrib/admin/sites.py:295 contrib/admin/views/decorators.py:47
    8.53  msgid ""
    8.54  "Looks like your browser isn't configured to accept cookies. Please enable "
    8.55  "cookies, reload this page, and try again."
    8.56 @@ -429,27 +429,27 @@
    8.57  "Twoja przeglądarka nie chce akceptować ciasteczek. Zmień jej ustawienia i "
    8.58  "spróbuj ponownie."
    8.59  
    8.60 -#: contrib/admin/sites.py:308 contrib/admin/sites.py:314
    8.61 +#: contrib/admin/sites.py:311 contrib/admin/sites.py:317
    8.62  #: contrib/admin/views/decorators.py:66
    8.63  msgid "Usernames cannot contain the '@' character."
    8.64  msgstr "Nazwy użytkowników nie mogą zawierać znaku '@'."
    8.65  
    8.66 -#: contrib/admin/sites.py:311 contrib/admin/views/decorators.py:62
    8.67 +#: contrib/admin/sites.py:314 contrib/admin/views/decorators.py:62
    8.68  #, python-format
    8.69  msgid "Your e-mail address is not your username. Try '%s' instead."
    8.70  msgstr "Podany adres e-mail nie jest Twoją nazwą użytkownika. Spróbuj '%s'."
    8.71  
    8.72 -#: contrib/admin/sites.py:367
    8.73 +#: contrib/admin/sites.py:370
    8.74  msgid "Site administration"
    8.75  msgstr "Administracja stroną"
    8.76  
    8.77 -#: contrib/admin/sites.py:381 contrib/admin/templates/admin/login.html:26
    8.78 +#: contrib/admin/sites.py:384 contrib/admin/templates/admin/login.html:26
    8.79  #: contrib/admin/templates/registration/password_reset_complete.html:14
    8.80  #: contrib/admin/views/decorators.py:20
    8.81  msgid "Log in"
    8.82  msgstr "Zaloguj się"
    8.83  
    8.84 -#: contrib/admin/sites.py:426
    8.85 +#: contrib/admin/sites.py:429
    8.86  #, python-format
    8.87  msgid "%s administration"
    8.88  msgstr "%s - administracja"
    8.89 @@ -464,27 +464,27 @@
    8.90  msgid "One or more %(fieldname)s in %(name)s:"
    8.91  msgstr "Jedno lub więcej %(fieldname)s w %(name)s:"
    8.92  
    8.93 -#: contrib/admin/widgets.py:71
    8.94 +#: contrib/admin/widgets.py:72
    8.95  msgid "Date:"
    8.96  msgstr "Data:"
    8.97  
    8.98 -#: contrib/admin/widgets.py:71
    8.99 +#: contrib/admin/widgets.py:72
   8.100  msgid "Time:"
   8.101  msgstr "Czas:"
   8.102  
   8.103 -#: contrib/admin/widgets.py:95
   8.104 +#: contrib/admin/widgets.py:96
   8.105  msgid "Currently:"
   8.106  msgstr "Teraz:"
   8.107  
   8.108 -#: contrib/admin/widgets.py:95
   8.109 +#: contrib/admin/widgets.py:96
   8.110  msgid "Change:"
   8.111  msgstr "Zmień:"
   8.112  
   8.113 -#: contrib/admin/widgets.py:124
   8.114 +#: contrib/admin/widgets.py:125
   8.115  msgid "Lookup"
   8.116  msgstr "Szukaj"
   8.117  
   8.118 -#: contrib/admin/widgets.py:235
   8.119 +#: contrib/admin/widgets.py:237
   8.120  msgid "Add Another"
   8.121  msgstr "Dodaj kolejny"
   8.122  
   8.123 @@ -598,7 +598,7 @@
   8.124  
   8.125  #: contrib/admin/templates/admin/change_form.html:28
   8.126  #: contrib/admin/templates/admin/edit_inline/stacked.html:13
   8.127 -#: contrib/admin/templates/admin/edit_inline/tabular.html:27
   8.128 +#: contrib/admin/templates/admin/edit_inline/tabular.html:28
   8.129  msgid "View on site"
   8.130  msgstr "Pokaż na stronie"
   8.131  
   8.132 @@ -668,10 +668,10 @@
   8.133  #, python-format
   8.134  msgid ""
   8.135  "Are you sure you want to delete the selected %(object_name)s objects? All of "
   8.136 -"the following objects and it's related items will be deleted:"
   8.137 -msgstr ""
   8.138 -"Czy chcesz skasować %(object_name)s? Następujące obiekty i zależne od nich "
   8.139 -"zostaną skasowane:"
   8.140 +"the following objects and their related items will be deleted:"
   8.141 +msgstr ""
   8.142 +"Czy chcesz skasować wybrane %(object_name)s? Następujące obiekty i zależne od "
   8.143 +"nich zostaną skasowane:"
   8.144  
   8.145  #: contrib/admin/templates/admin/filter.html:2
   8.146  #, python-format
   8.147 @@ -734,7 +734,6 @@
   8.148  msgstr "Użytkownik"
   8.149  
   8.150  #: contrib/admin/templates/admin/object_history.html:24
   8.151 -#: contrib/comments/templates/comments/moderation_queue.html:33
   8.152  msgid "Action"
   8.153  msgstr "Akcja"
   8.154  
   8.155 @@ -1125,7 +1124,6 @@
   8.156  msgstr "Czas"
   8.157  
   8.158  #: contrib/admindocs/views.py:359 contrib/comments/forms.py:95
   8.159 -#: contrib/comments/templates/comments/moderation_queue.html:37
   8.160  #: contrib/flatpages/admin.py:8 contrib/flatpages/models.py:7
   8.161  msgid "URL"
   8.162  msgstr "URL"
   8.163 @@ -1428,22 +1426,54 @@
   8.164  msgid "message"
   8.165  msgstr "wiadomość"
   8.166  
   8.167 -#: contrib/auth/views.py:56
   8.168 +#: contrib/auth/views.py:58
   8.169  msgid "Logged out"
   8.170  msgstr "Wylogowany"
   8.171  
   8.172 -#: contrib/auth/management/commands/createsuperuser.py:23 forms/fields.py:429
   8.173 +#: contrib/auth/management/commands/createsuperuser.py:23 forms/fields.py:428
   8.174  msgid "Enter a valid e-mail address."
   8.175  msgstr "Wprowadź poprawny adres e-mail."
   8.176  
   8.177 -#: contrib/comments/admin.py:11
   8.178 +#: contrib/comments/admin.py:12
   8.179  msgid "Content"
   8.180  msgstr "Zawartość"
   8.181  
   8.182 -#: contrib/comments/admin.py:14
   8.183 +#: contrib/comments/admin.py:15
   8.184  msgid "Metadata"
   8.185  msgstr "Metadane"
   8.186  
   8.187 +#: contrib/comments/admin.py:39
   8.188 +msgid "flagged"
   8.189 +msgstr "oflagowany"
   8.190 +
   8.191 +#: contrib/comments/admin.py:40
   8.192 +msgid "Flag selected comments"
   8.193 +msgstr "Oflaguj wybrane komentarze"
   8.194 +
   8.195 +#: contrib/comments/admin.py:43
   8.196 +msgid "approved"
   8.197 +msgstr "zaakceptowany"
   8.198 +
   8.199 +#: contrib/comments/admin.py:44
   8.200 +msgid "Approve selected comments"
   8.201 +msgstr "Zaakceptuj wybrane komentarze"
   8.202 +
   8.203 +#: contrib/comments/admin.py:47
   8.204 +msgid "removed"
   8.205 +msgstr "usunięty"
   8.206 +
   8.207 +#: contrib/comments/admin.py:48
   8.208 +msgid "Remove selected comments"
   8.209 +msgstr "Usuń wybrane komentarze"
   8.210 +
   8.211 +#: contrib/comments/admin.py:60
   8.212 +#, python-format
   8.213 +msgid "1 comment was successfully %(action)s."
   8.214 +msgid_plural "%(count)s comments were successfully %(action)s."
   8.215 +msgstr[0] "1 komentarz został %(action)s"
   8.216 +msgstr[1] "%(count)s komentarze zostały %(action)s"
   8.217 +msgstr[2] "%(count)s komentarzy zostało %(action)s"
   8.218 +
   8.219  #: contrib/comments/feeds.py:13
   8.220  #, python-format
   8.221  msgid "%(site_name)s comments"
   8.222 @@ -1455,7 +1485,6 @@
   8.223  msgstr "Ostatnie komentarze na %(site_name)s"
   8.224  
   8.225  #: contrib/comments/forms.py:93
   8.226 -#: contrib/comments/templates/comments/moderation_queue.html:34
   8.227  msgid "Name"
   8.228  msgstr "Nazwa"
   8.229  
   8.230 @@ -1464,7 +1493,6 @@
   8.231  msgstr "Adres e-mail"
   8.232  
   8.233  #: contrib/comments/forms.py:96
   8.234 -#: contrib/comments/templates/comments/moderation_queue.html:35
   8.235  msgid "Comment"
   8.236  msgstr "Komentarz"
   8.237  
   8.238 @@ -1592,7 +1620,6 @@
   8.239  msgstr "Czy ten komentarz na pewno ma być publiczny?"
   8.240  
   8.241  #: contrib/comments/templates/comments/approve.html:12
   8.242 -#: contrib/comments/templates/comments/moderation_queue.html:49
   8.243  msgid "Approve"
   8.244  msgstr "Zaakceptuj"
   8.245  
   8.246 @@ -1618,7 +1645,6 @@
   8.247  msgstr "Czy na pewno usunąć ten komentarz?"
   8.248  
   8.249  #: contrib/comments/templates/comments/delete.html:12
   8.250 -#: contrib/comments/templates/comments/moderation_queue.html:53
   8.251  msgid "Remove"
   8.252  msgstr "Usuń"
   8.253  
   8.254 @@ -1652,39 +1678,6 @@
   8.255  msgid "Preview"
   8.256  msgstr "Podgląd"
   8.257  
   8.258 -#: contrib/comments/templates/comments/moderation_queue.html:4
   8.259 -#: contrib/comments/templates/comments/moderation_queue.html:19
   8.260 -msgid "Comment moderation queue"
   8.261 -msgstr "Kolejka moderacji komentarzy"
   8.262 -
   8.263 -#: contrib/comments/templates/comments/moderation_queue.html:26
   8.264 -msgid "No comments to moderate"
   8.265 -msgstr "Żaden komentarz nie oczekuje na akceptację"
   8.266 -
   8.267 -#: contrib/comments/templates/comments/moderation_queue.html:36
   8.268 -msgid "Email"
   8.269 -msgstr "E-mail"
   8.270 -
   8.271 -#: contrib/comments/templates/comments/moderation_queue.html:38
   8.272 -msgid "Authenticated?"
   8.273 -msgstr "Zalogowany?"
   8.274 -
   8.275 -#: contrib/comments/templates/comments/moderation_queue.html:39
   8.276 -msgid "IP Address"
   8.277 -msgstr "Adres IP"
   8.278 -
   8.279 -#: contrib/comments/templates/comments/moderation_queue.html:40
   8.280 -msgid "Date posted"
   8.281 -msgstr "Data dodania"
   8.282 -
   8.283 -#: contrib/comments/templates/comments/moderation_queue.html:63
   8.284 -msgid "yes"
   8.285 -msgstr "tak"
   8.286 -
   8.287 -#: contrib/comments/templates/comments/moderation_queue.html:63
   8.288 -msgid "no"
   8.289 -msgstr "nie"
   8.290 -
   8.291  #: contrib/comments/templates/comments/posted.html:4
   8.292  msgid "Thanks for commenting"
   8.293  msgstr "Dziękujemy za dodanie komentarza"
   8.294 @@ -2599,6 +2592,10 @@
   8.295  msgid "Enter a valid Finnish social security number."
   8.296  msgstr "Wpis poprawny numer fińskiego ubezpieczenia socjalnego."
   8.297  
   8.298 +#: contrib/localflavor/fr/forms.py:30
   8.299 +msgid "Phone numbers must be in 0X XX XX XX XX format."
   8.300 +msgstr "Numery telefoniczne muszą być w formacie 0X XX XX XX XX."
   8.301 +
   8.302  #: contrib/localflavor/in_/forms.py:14
   8.303  msgid "Enter a zip code in the format XXXXXXX."
   8.304  msgstr "Wpisz kod pocztowy w formacie XXXXXXX."
   8.305 @@ -3944,86 +3941,86 @@
   8.306  "Proszę podać poprawne identyfikatory %(self)s. Wartości %(value)r są "
   8.307  "niepoprawne."
   8.308  
   8.309 -#: forms/fields.py:54
   8.310 +#: forms/fields.py:53
   8.311  msgid "This field is required."
   8.312  msgstr "To pole jest wymagane."
   8.313  
   8.314 -#: forms/fields.py:55
   8.315 +#: forms/fields.py:54
   8.316  msgid "Enter a valid value."
   8.317  msgstr "Wpisz poprawną wartość."
   8.318  
   8.319 -#: forms/fields.py:138
   8.320 +#: forms/fields.py:137
   8.321  #, python-format
   8.322  msgid "Ensure this value has at most %(max)d characters (it has %(length)d)."
   8.323  msgstr ""
   8.324  "Upewnij się, że ta wartość ma co najwyżej %(max)d znaków (ma długość %"
   8.325  "(length)d)."
   8.326  
   8.327 -#: forms/fields.py:139
   8.328 +#: forms/fields.py:138
   8.329  #, python-format
   8.330  msgid "Ensure this value has at least %(min)d characters (it has %(length)d)."
   8.331  msgstr ""
   8.332  "Upewnij się, że ta wartość ma co najmniej %(min)d znaków (ma długość %"
   8.333  "(length)d)."
   8.334  
   8.335 -#: forms/fields.py:166
   8.336 +#: forms/fields.py:165
   8.337  msgid "Enter a whole number."
   8.338  msgstr "Wpisz liczbę całkowitą."
   8.339  
   8.340 -#: forms/fields.py:167 forms/fields.py:196 forms/fields.py:225
   8.341 +#: forms/fields.py:166 forms/fields.py:195 forms/fields.py:224
   8.342  #, python-format
   8.343  msgid "Ensure this value is less than or equal to %s."
   8.344  msgstr "Upewnij się, że ta wartość jest mniejsza lub równa %s."
   8.345  
   8.346 -#: forms/fields.py:168 forms/fields.py:197 forms/fields.py:226
   8.347 +#: forms/fields.py:167 forms/fields.py:196 forms/fields.py:225
   8.348  #, python-format
   8.349  msgid "Ensure this value is greater than or equal to %s."
   8.350  msgstr "Upewnij się, że ta wartość jest większa lub równa %s."
   8.351  
   8.352 -#: forms/fields.py:195 forms/fields.py:224
   8.353 +#: forms/fields.py:194 forms/fields.py:223
   8.354  msgid "Enter a number."
   8.355  msgstr "Wpisz liczbę."
   8.356  
   8.357 -#: forms/fields.py:227
   8.358 +#: forms/fields.py:226
   8.359  #, python-format
   8.360  msgid "Ensure that there are no more than %s digits in total."
   8.361  msgstr "Upewnij się, że jest nie więcej niż %s cyfr."
   8.362  
   8.363 -#: forms/fields.py:228
   8.364 +#: forms/fields.py:227
   8.365  #, python-format
   8.366  msgid "Ensure that there are no more than %s decimal places."
   8.367  msgstr "Upewnij się, że jest nie więcej niż %s miejsc po przecinku."
   8.368  
   8.369 -#: forms/fields.py:229
   8.370 +#: forms/fields.py:228
   8.371  #, python-format
   8.372  msgid "Ensure that there are no more than %s digits before the decimal point."
   8.373  msgstr "Upewnij się, że jest nie więcej niż %s miejsc przed przecinkiem."
   8.374  
   8.375 -#: forms/fields.py:288 forms/fields.py:863
   8.376 +#: forms/fields.py:287 forms/fields.py:862
   8.377  msgid "Enter a valid date."
   8.378  msgstr "Wpisz poprawną datę."
   8.379  
   8.380 -#: forms/fields.py:322 forms/fields.py:864
   8.381 +#: forms/fields.py:321 forms/fields.py:863
   8.382  msgid "Enter a valid time."
   8.383  msgstr "Wpisz poprawną godzinę."
   8.384  
   8.385 -#: forms/fields.py:361
   8.386 +#: forms/fields.py:360
   8.387  msgid "Enter a valid date/time."
   8.388  msgstr "Wpisz poprawną datę/godzinę."
   8.389  
   8.390 -#: forms/fields.py:447
   8.391 +#: forms/fields.py:446
   8.392  msgid "No file was submitted. Check the encoding type on the form."
   8.393  msgstr "Nie wysłano żadnego pliku. Sprawdź typ kodowania formularza."
   8.394  
   8.395 -#: forms/fields.py:448
   8.396 +#: forms/fields.py:447
   8.397  msgid "No file was submitted."
   8.398  msgstr "Żaden plik nie został przesłany."
   8.399  
   8.400 -#: forms/fields.py:449
   8.401 +#: forms/fields.py:448
   8.402  msgid "The submitted file is empty."
   8.403  msgstr "Wysłany plik jest pusty."
   8.404  
   8.405 -#: forms/fields.py:450
   8.406 +#: forms/fields.py:449
   8.407  #, python-format
   8.408  msgid ""
   8.409  "Ensure this filename has at most %(max)d characters (it has %(length)d)."
   8.410 @@ -4031,7 +4028,7 @@
   8.411  "Upewnij się, że nazwa tego pliku ma co najwyżej %(max)d znaków (ma długość %"
   8.412  "(length)d)."
   8.413  
   8.414 -#: forms/fields.py:483
   8.415 +#: forms/fields.py:482
   8.416  msgid ""
   8.417  "Upload a valid image. The file you uploaded was either not an image or a "
   8.418  "corrupted image."
   8.419 @@ -4039,29 +4036,29 @@
   8.420  "Wgraj poprawny plik graficzny. Ten, który został wgrany, nie jest obrazem, "
   8.421  "albo jest uszkodzony."
   8.422  
   8.423 -#: forms/fields.py:544
   8.424 +#: forms/fields.py:543
   8.425  msgid "Enter a valid URL."
   8.426  msgstr "Wpisz poprawny URL."
   8.427  
   8.428 -#: forms/fields.py:545
   8.429 +#: forms/fields.py:544
   8.430  msgid "This URL appears to be a broken link."
   8.431  msgstr "Ten odnośnik jest nieprawidłowy."
   8.432  
   8.433 -#: forms/fields.py:625 forms/fields.py:703
   8.434 +#: forms/fields.py:624 forms/fields.py:702
   8.435  #, python-format
   8.436  msgid "Select a valid choice. %(value)s is not one of the available choices."
   8.437  msgstr ""
   8.438  "Wybierz poprawną wartość. %(value)s nie jest jednym z dostępnych wyborów."
   8.439  
   8.440 -#: forms/fields.py:704 forms/fields.py:765 forms/models.py:1003
   8.441 +#: forms/fields.py:703 forms/fields.py:764 forms/models.py:999
   8.442  msgid "Enter a list of values."
   8.443  msgstr "Podaj listę wartości."
   8.444  
   8.445 -#: forms/fields.py:892
   8.446 +#: forms/fields.py:891
   8.447  msgid "Enter a valid IPv4 address."
   8.448  msgstr "Wprowadź poprawny adres IPv4."
   8.449  
   8.450 -#: forms/fields.py:902
   8.451 +#: forms/fields.py:901
   8.452  msgid ""
   8.453  "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens."
   8.454  msgstr "To pole może zawierać jedynie litery, cyfry, podkreślenia i myślniki."
   8.455 @@ -4070,29 +4067,29 @@
   8.456  msgid "Order"
   8.457  msgstr "Porządek"
   8.458  
   8.459 -#: forms/models.py:367
   8.460 +#: forms/models.py:363
   8.461  #, python-format
   8.462  msgid "%(field_name)s must be unique for %(date_field)s %(lookup)s."
   8.463  msgstr ""
   8.464  "Wartości w %(field_name)s muszą być unikalne dla wyszukiwań %(lookup)s w %"
   8.465  "(date_field)s"
   8.466  
   8.467 -#: forms/models.py:381 forms/models.py:389
   8.468 +#: forms/models.py:377 forms/models.py:385
   8.469  #, python-format
   8.470  msgid "%(model_name)s with this %(field_label)s already exists."
   8.471  msgstr "%(field_label)s już istnieje w %(model_name)s."
   8.472  
   8.473 -#: forms/models.py:594
   8.474 +#: forms/models.py:590
   8.475  #, python-format
   8.476  msgid "Please correct the duplicate data for %(field)s."
   8.477  msgstr "Popraw zduplikowane dane w %(field)s."
   8.478  
   8.479 -#: forms/models.py:598
   8.480 +#: forms/models.py:594
   8.481  #, python-format
   8.482  msgid "Please correct the duplicate data for %(field)s, which must be unique."
   8.483  msgstr "Popraw zduplikowane dane w %(field)s, które wymaga unikalności."
   8.484  
   8.485 -#: forms/models.py:604
   8.486 +#: forms/models.py:600
   8.487  #, python-format
   8.488  msgid ""
   8.489  "Please correct the duplicate data for %(field_name)s which must be unique "
   8.490 @@ -4101,24 +4098,24 @@
   8.491  "Popraw zduplikowane dane w %(field_name)s, które wymaga unikalności dla %"
   8.492  "(lookup)s w polu %(date_field)s."
   8.493  
   8.494 -#: forms/models.py:612
   8.495 +#: forms/models.py:608
   8.496  msgid "Please correct the duplicate values below."
   8.497  msgstr "Popraw poniższe zduplikowane wartości."
   8.498  
   8.499 -#: forms/models.py:867
   8.500 +#: forms/models.py:863
   8.501  msgid "The inline foreign key did not match the parent instance primary key."
   8.502  msgstr "Osadzony klucz obcy nie pasuje do klucza głównego obiektu rodzica."
   8.503  
   8.504 -#: forms/models.py:930
   8.505 +#: forms/models.py:926
   8.506  msgid "Select a valid choice. That choice is not one of the available choices."
   8.507  msgstr "Wybierz poprawną wartość. Podana nie jest jednym z dostępnych wyborów."
   8.508  
   8.509 -#: forms/models.py:1004
   8.510 +#: forms/models.py:1000
   8.511  #, python-format
   8.512  msgid "Select a valid choice. %s is not one of the available choices."
   8.513  msgstr "Wybierz poprawną wartość. %s nie jest jednym z dostępnych wyborów."
   8.514  
   8.515 -#: forms/models.py:1006
   8.516 +#: forms/models.py:1002
   8.517  #, python-format
   8.518  msgid "\"%s\" is not a valid value for a primary key."
   8.519  msgstr "\"%s\" nie jest poprawną wartością klucza głównego."
   8.520 @@ -4444,3 +4441,27 @@
   8.521  #, python-format
   8.522  msgid "The %(verbose_name)s was deleted."
   8.523  msgstr "%(verbose_name)s zostało usunięte."
   8.524 +
   8.525 +#~ msgid "Comment moderation queue"
   8.526 +#~ msgstr "Kolejka moderacji komentarzy"
   8.527 +
   8.528 +#~ msgid "No comments to moderate"
   8.529 +#~ msgstr "Żaden komentarz nie oczekuje na akceptację"
   8.530 +
   8.531 +#~ msgid "Email"
   8.532 +#~ msgstr "E-mail"
   8.533 +
   8.534 +#~ msgid "Authenticated?"
   8.535 +#~ msgstr "Zalogowany?"
   8.536 +
   8.537 +#~ msgid "IP Address"
   8.538 +#~ msgstr "Adres IP"
   8.539 +
   8.540 +#~ msgid "Date posted"
   8.541 +#~ msgstr "Data dodania"
   8.542 +
   8.543 +#~ msgid "yes"
   8.544 +#~ msgstr "tak"
   8.545 +
   8.546 +#~ msgid "no"
   8.547 +#~ msgstr "nie"
     9.1 --- a/django/conf/project_template/settings.py	Sat Sep 12 18:53:56 2009 -0500
     9.2 +++ b/django/conf/project_template/settings.py	Fri Nov 13 11:27:59 2009 -0600
     9.3 @@ -60,6 +60,7 @@
     9.4  MIDDLEWARE_CLASSES = (
     9.5      'django.middleware.common.CommonMiddleware',
     9.6      'django.contrib.sessions.middleware.SessionMiddleware',
     9.7 +    'django.middleware.csrf.CsrfViewMiddleware',
     9.8      'django.contrib.auth.middleware.AuthenticationMiddleware',
     9.9  )
    9.10  
    10.1 --- a/django/contrib/admin/media/css/changelists.css	Sat Sep 12 18:53:56 2009 -0500
    10.2 +++ b/django/contrib/admin/media/css/changelists.css	Fri Nov 13 11:27:59 2009 -0600
    10.3 @@ -53,7 +53,7 @@
    10.4      vertical-align: middle;
    10.5  }
    10.6  
    10.7 -#changelist table thead th:first-child {
    10.8 +#changelist table thead th.action-checkbox-column {
    10.9      width: 1.5em;
   10.10      text-align: center;
   10.11  }
    11.1 --- a/django/contrib/admin/options.py	Sat Sep 12 18:53:56 2009 -0500
    11.2 +++ b/django/contrib/admin/options.py	Fri Nov 13 11:27:59 2009 -0600
    11.3 @@ -6,6 +6,7 @@
    11.4  from django.contrib.admin import widgets
    11.5  from django.contrib.admin import helpers
    11.6  from django.contrib.admin.util import unquote, flatten_fieldsets, get_deleted_objects, model_ngettext, model_format_dict
    11.7 +from django.views.decorators.csrf import csrf_protect
    11.8  from django.core.exceptions import PermissionDenied
    11.9  from django.db import models, transaction
   11.10  from django.db.models.fields import BLANK_CHOICE_DASH
   11.11 @@ -152,8 +153,9 @@
   11.12          """
   11.13          Get a form Field for a ManyToManyField.
   11.14          """
   11.15 -        # If it uses an intermediary model, don't show field in admin.
   11.16 -        if db_field.rel.through is not None:
   11.17 +        # If it uses an intermediary model that isn't auto created, don't show
   11.18 +        # a field in admin.
   11.19 +        if not db_field.rel.through._meta.auto_created:
   11.20              return None
   11.21  
   11.22          if db_field.name in self.raw_id_fields:
   11.23 @@ -701,6 +703,8 @@
   11.24              else:
   11.25                  return HttpResponseRedirect(".")
   11.26  
   11.27 +    @csrf_protect
   11.28 +    @transaction.commit_on_success
   11.29      def add_view(self, request, form_url='', extra_context=None):
   11.30          "The 'add' admin view for this model."
   11.31          model = self.model
   11.32 @@ -782,8 +786,9 @@
   11.33          }
   11.34          context.update(extra_context or {})
   11.35          return self.render_change_form(request, context, form_url=form_url, add=True)
   11.36 -    add_view = transaction.commit_on_success(add_view)
   11.37  
   11.38 +    @csrf_protect
   11.39 +    @transaction.commit_on_success
   11.40      def change_view(self, request, object_id, extra_context=None):
   11.41          "The 'change' admin view for this model."
   11.42          model = self.model
   11.43 @@ -871,8 +876,8 @@
   11.44          }
   11.45          context.update(extra_context or {})
   11.46          return self.render_change_form(request, context, change=True, obj=obj)
   11.47 -    change_view = transaction.commit_on_success(change_view)
   11.48  
   11.49 +    @csrf_protect
   11.50      def changelist_view(self, request, extra_context=None):
   11.51          "The 'change list' admin view for this model."
   11.52          from django.contrib.admin.views.main import ChangeList, ERROR_FLAG
   11.53 @@ -985,6 +990,7 @@
   11.54              'admin/change_list.html'
   11.55          ], context, context_instance=context_instance)
   11.56  
   11.57 +    @csrf_protect
   11.58      def delete_view(self, request, object_id, extra_context=None):
   11.59          "The 'delete' admin view for this model."
   11.60          opts = self.model._meta
    12.1 --- a/django/contrib/admin/sites.py	Sat Sep 12 18:53:56 2009 -0500
    12.2 +++ b/django/contrib/admin/sites.py	Fri Nov 13 11:27:59 2009 -0600
    12.3 @@ -3,6 +3,7 @@
    12.4  from django.contrib.admin import ModelAdmin
    12.5  from django.contrib.admin import actions
    12.6  from django.contrib.auth import authenticate, login
    12.7 +from django.views.decorators.csrf import csrf_protect
    12.8  from django.db.models.base import ModelBase
    12.9  from django.core.exceptions import ImproperlyConfigured
   12.10  from django.core.urlresolvers import reverse
   12.11 @@ -186,11 +187,17 @@
   12.12              return view(request, *args, **kwargs)
   12.13          if not cacheable:
   12.14              inner = never_cache(inner)
   12.15 +        # We add csrf_protect here so this function can be used as a utility
   12.16 +        # function for any view, without having to repeat 'csrf_protect'.
   12.17 +        inner = csrf_protect(inner)
   12.18          return update_wrapper(inner, view)
   12.19  
   12.20      def get_urls(self):
   12.21          from django.conf.urls.defaults import patterns, url, include
   12.22  
   12.23 +        if settings.DEBUG:
   12.24 +            self.check_dependencies()
   12.25 +
   12.26          def wrap(view, cacheable=False):
   12.27              def wrapper(*args, **kwargs):
   12.28                  return self.admin_view(view, cacheable)(*args, **kwargs)
    13.1 --- a/django/contrib/admin/templates/admin/auth/user/change_password.html	Sat Sep 12 18:53:56 2009 -0500
    13.2 +++ b/django/contrib/admin/templates/admin/auth/user/change_password.html	Fri Nov 13 11:27:59 2009 -0600
    13.3 @@ -15,7 +15,7 @@
    13.4  </div>
    13.5  {% endif %}{% endblock %}
    13.6  {% block content %}<div id="content-main">
    13.7 -<form action="{{ form_url }}" method="post" id="{{ opts.module_name }}_form">{% block form_top %}{% endblock %}
    13.8 +<form action="{{ form_url }}" method="post" id="{{ opts.module_name }}_form">{% csrf_token %}{% block form_top %}{% endblock %}
    13.9  <div>
   13.10  {% if is_popup %}<input type="hidden" name="_popup" value="1" />{% endif %}
   13.11  {% if form.errors %}
    14.1 --- a/django/contrib/admin/templates/admin/change_form.html	Sat Sep 12 18:53:56 2009 -0500
    14.2 +++ b/django/contrib/admin/templates/admin/change_form.html	Fri Nov 13 11:27:59 2009 -0600
    14.3 @@ -29,7 +29,7 @@
    14.4    </ul>
    14.5  {% endif %}{% endif %}
    14.6  {% endblock %}
    14.7 -<form {% if has_file_field %}enctype="multipart/form-data" {% endif %}action="{{ form_url }}" method="post" id="{{ opts.module_name }}_form">{% block form_top %}{% endblock %}
    14.8 +<form {% if has_file_field %}enctype="multipart/form-data" {% endif %}action="{{ form_url }}" method="post" id="{{ opts.module_name }}_form">{% csrf_token %}{% block form_top %}{% endblock %}
    14.9  <div>
   14.10  {% if is_popup %}<input type="hidden" name="_popup" value="1" />{% endif %}
   14.11  {% if save_on_top %}{% submit_row %}{% endif %}
    15.1 --- a/django/contrib/admin/templates/admin/change_list.html	Sat Sep 12 18:53:56 2009 -0500
    15.2 +++ b/django/contrib/admin/templates/admin/change_list.html	Fri Nov 13 11:27:59 2009 -0600
    15.3 @@ -68,7 +68,7 @@
    15.4          {% endif %}
    15.5        {% endblock %}
    15.6        
    15.7 -      <form action="" method="post"{% if cl.formset.is_multipart %} enctype="multipart/form-data"{% endif %}>
    15.8 +      <form action="" method="post"{% if cl.formset.is_multipart %} enctype="multipart/form-data"{% endif %}>{% csrf_token %}
    15.9        {% if cl.formset %}
   15.10          {{ cl.formset.management_form }}
   15.11        {% endif %}
    16.1 --- a/django/contrib/admin/templates/admin/delete_confirmation.html	Sat Sep 12 18:53:56 2009 -0500
    16.2 +++ b/django/contrib/admin/templates/admin/delete_confirmation.html	Fri Nov 13 11:27:59 2009 -0600
    16.3 @@ -22,7 +22,7 @@
    16.4  {% else %}
    16.5      <p>{% blocktrans with object as escaped_object %}Are you sure you want to delete the {{ object_name }} "{{ escaped_object }}"? All of the following related items will be deleted:{% endblocktrans %}</p>
    16.6      <ul>{{ deleted_objects|unordered_list }}</ul>
    16.7 -    <form action="" method="post">
    16.8 +    <form action="" method="post">{% csrf_token %}
    16.9      <div>
   16.10      <input type="hidden" name="post" value="yes" />
   16.11      <input type="submit" value="{% trans "Yes, I'm sure" %}" />
    17.1 --- a/django/contrib/admin/templates/admin/delete_selected_confirmation.html	Sat Sep 12 18:53:56 2009 -0500
    17.2 +++ b/django/contrib/admin/templates/admin/delete_selected_confirmation.html	Fri Nov 13 11:27:59 2009 -0600
    17.3 @@ -23,7 +23,7 @@
    17.4      {% for deleteable_object in deletable_objects %}
    17.5          <ul>{{ deleteable_object|unordered_list }}</ul>
    17.6      {% endfor %}
    17.7 -    <form action="" method="post">
    17.8 +    <form action="" method="post">{% csrf_token %}
    17.9      <div>
   17.10      {% for obj in queryset %}
   17.11      <input type="hidden" name="{{ action_checkbox_name }}" value="{{ obj.pk }}" />
    18.1 --- a/django/contrib/admin/templates/admin/login.html	Sat Sep 12 18:53:56 2009 -0500
    18.2 +++ b/django/contrib/admin/templates/admin/login.html	Fri Nov 13 11:27:59 2009 -0600
    18.3 @@ -14,7 +14,7 @@
    18.4  <p class="errornote">{{ error_message }}</p>
    18.5  {% endif %}
    18.6  <div id="content-main">
    18.7 -<form action="{{ app_path }}" method="post" id="login-form">
    18.8 +<form action="{{ app_path }}" method="post" id="login-form">{% csrf_token %}
    18.9    <div class="form-row">
   18.10      <label for="id_username">{% trans 'Username:' %}</label> <input type="text" name="username" id="id_username" />
   18.11    </div>
    19.1 --- a/django/contrib/admin/templates/admin/template_validator.html	Sat Sep 12 18:53:56 2009 -0500
    19.2 +++ b/django/contrib/admin/templates/admin/template_validator.html	Fri Nov 13 11:27:59 2009 -0600
    19.3 @@ -4,7 +4,7 @@
    19.4  
    19.5  <div id="content-main">
    19.6  
    19.7 -<form action="" method="post">
    19.8 +<form action="" method="post">{% csrf_token %}
    19.9  
   19.10  {% if form.errors %}
   19.11  <p class="errornote">Your template had {{ form.errors|length }} error{{ form.errors|pluralize }}:</p>
    20.1 --- a/django/contrib/admin/templates/registration/password_change_form.html	Sat Sep 12 18:53:56 2009 -0500
    20.2 +++ b/django/contrib/admin/templates/registration/password_change_form.html	Fri Nov 13 11:27:59 2009 -0600
    20.3 @@ -11,7 +11,7 @@
    20.4  
    20.5  <p>{% trans "Please enter your old password, for security's sake, and then enter your new password twice so we can verify you typed it in correctly." %}</p>
    20.6  
    20.7 -<form action="" method="post">
    20.8 +<form action="" method="post">{% csrf_token %}
    20.9  
   20.10  {{ form.old_password.errors }}
   20.11  <p class="aligned wide"><label for="id_old_password">{% trans 'Old password:' %}</label>{{ form.old_password }}</p>
    21.1 --- a/django/contrib/admin/templates/registration/password_reset_confirm.html	Sat Sep 12 18:53:56 2009 -0500
    21.2 +++ b/django/contrib/admin/templates/registration/password_reset_confirm.html	Fri Nov 13 11:27:59 2009 -0600
    21.3 @@ -13,7 +13,7 @@
    21.4  
    21.5  <p>{% trans "Please enter your new password twice so we can verify you typed it in correctly." %}</p>
    21.6  
    21.7 -<form action="" method="post">
    21.8 +<form action="" method="post">{% csrf_token %}
    21.9  {{ form.new_password1.errors }}
   21.10  <p class="aligned wide"><label for="id_new_password1">{% trans 'New password:' %}</label>{{ form.new_password1 }}</p>
   21.11  {{ form.new_password2.errors }}
    22.1 --- a/django/contrib/admin/templates/registration/password_reset_form.html	Sat Sep 12 18:53:56 2009 -0500
    22.2 +++ b/django/contrib/admin/templates/registration/password_reset_form.html	Fri Nov 13 11:27:59 2009 -0600
    22.3 @@ -11,7 +11,7 @@
    22.4  
    22.5  <p>{% trans "Forgotten your password? Enter your e-mail address below, and we'll e-mail instructions for setting a new one." %}</p>
    22.6  
    22.7 -<form action="" method="post">
    22.8 +<form action="" method="post">{% csrf_token %}
    22.9  {{ form.email.errors }}
   22.10  <p><label for="id_email">{% trans 'E-mail address:' %}</label> {{ form.email }} <input type="submit" value="{% trans 'Reset my password' %}" /></p>
   22.11  </form>
    23.1 --- a/django/contrib/admin/templatetags/admin_list.py	Sat Sep 12 18:53:56 2009 -0500
    23.2 +++ b/django/contrib/admin/templatetags/admin_list.py	Fri Nov 13 11:27:59 2009 -0600
    23.3 @@ -106,6 +106,11 @@
    23.4                      else:
    23.5                          header = field_name
    23.6                      header = header.replace('_', ' ')
    23.7 +            # if the field is the action checkbox: no sorting and special class
    23.8 +            if field_name == 'action_checkbox':
    23.9 +                yield {"text": header,
   23.10 +                       "class_attrib": mark_safe(' class="action-checkbox-column"')}
   23.11 +                continue
   23.12  
   23.13              # It is a non-field, but perhaps one that is sortable
   23.14              admin_order_field = getattr(attr, "admin_order_field", None)
    24.1 --- a/django/contrib/admin/validation.py	Sat Sep 12 18:53:56 2009 -0500
    24.2 +++ b/django/contrib/admin/validation.py	Fri Nov 13 11:27:59 2009 -0600
    24.3 @@ -149,12 +149,16 @@
    24.4              validate_inline(inline, cls, model)
    24.5  
    24.6  def validate_inline(cls, parent, parent_model):
    24.7 +    
    24.8      # model is already verified to exist and be a Model
    24.9      if cls.fk_name: # default value is None
   24.10          f = get_field(cls, cls.model, cls.model._meta, 'fk_name', cls.fk_name)
   24.11          if not isinstance(f, models.ForeignKey):
   24.12              raise ImproperlyConfigured("'%s.fk_name is not an instance of "
   24.13                      "models.ForeignKey." % cls.__name__)
   24.14 +
   24.15 +    fk = _get_foreign_key(parent_model, cls.model, fk_name=cls.fk_name, can_fail=True)
   24.16 +
   24.17      # extra = 3
   24.18      # max_num = 0
   24.19      for attr in ('extra', 'max_num'):
   24.20 @@ -169,7 +173,6 @@
   24.21  
   24.22      # exclude
   24.23      if hasattr(cls, 'exclude') and cls.exclude:
   24.24 -        fk = _get_foreign_key(parent_model, cls.model, can_fail=True)
   24.25          if fk and fk.name in cls.exclude:
   24.26              raise ImproperlyConfigured("%s cannot exclude the field "
   24.27                      "'%s' - this is the foreign key to the parent model "
    25.1 --- a/django/contrib/auth/decorators.py	Sat Sep 12 18:53:56 2009 -0500
    25.2 +++ b/django/contrib/auth/decorators.py	Fri Nov 13 11:27:59 2009 -0600
    25.3 @@ -1,11 +1,12 @@
    25.4  try:
    25.5 -    from functools import update_wrapper
    25.6 +    from functools import update_wrapper, wraps
    25.7  except ImportError:
    25.8 -    from django.utils.functional import update_wrapper  # Python 2.3, 2.4 fallback.
    25.9 +    from django.utils.functional import update_wrapper, wraps  # Python 2.3, 2.4 fallback.
   25.10  
   25.11  from django.contrib.auth import REDIRECT_FIELD_NAME
   25.12  from django.http import HttpResponseRedirect
   25.13  from django.utils.http import urlquote
   25.14 +from django.utils.decorators import auto_adapt_to_methods
   25.15  
   25.16  def user_passes_test(test_func, login_url=None, redirect_field_name=REDIRECT_FIELD_NAME):
   25.17      """
   25.18 @@ -13,9 +14,19 @@
   25.19      redirecting to the log-in page if necessary. The test should be a callable
   25.20      that takes the user object and returns True if the user passes.
   25.21      """
   25.22 -    def decorate(view_func):
   25.23 -        return _CheckLogin(view_func, test_func, login_url, redirect_field_name)
   25.24 -    return decorate
   25.25 +    if not login_url:
   25.26 +        from django.conf import settings
   25.27 +        login_url = settings.LOGIN_URL
   25.28 +
   25.29 +    def decorator(view_func):
   25.30 +        def _wrapped_view(request, *args, **kwargs):
   25.31 +            if test_func(request.user):
   25.32 +                return view_func(request, *args, **kwargs)
   25.33 +            path = urlquote(request.get_full_path())
   25.34 +            tup = login_url, redirect_field_name, path
   25.35 +            return HttpResponseRedirect('%s?%s=%s' % tup)
   25.36 +        return wraps(view_func)(_wrapped_view)
   25.37 +    return auto_adapt_to_methods(decorator)
   25.38  
   25.39  def login_required(function=None, redirect_field_name=REDIRECT_FIELD_NAME):
   25.40      """
   25.41 @@ -36,46 +47,3 @@
   25.42      enabled, redirecting to the log-in page if necessary.
   25.43      """
   25.44      return user_passes_test(lambda u: u.has_perm(perm), login_url=login_url)
   25.45 -
   25.46 -class _CheckLogin(object):
   25.47 -    """
   25.48 -    Class that checks that the user passes the given test, redirecting to
   25.49 -    the log-in page if necessary. If the test is passed, the view function
   25.50 -    is invoked. The test should be a callable that takes the user object
   25.51 -    and returns True if the user passes.
   25.52 -
   25.53 -    We use a class here so that we can define __get__. This way, when a
   25.54 -    _CheckLogin object is used as a method decorator, the view function
   25.55 -    is properly bound to its instance.
   25.56 -    """
   25.57 -    def __init__(self, view_func, test_func, login_url=None, redirect_field_name=REDIRECT_FIELD_NAME):
   25.58 -        if not login_url:
   25.59 -            from django.conf import settings
   25.60 -            login_url = settings.LOGIN_URL
   25.61 -        self.view_func = view_func
   25.62 -        self.test_func = test_func
   25.63 -        self.login_url = login_url
   25.64 -        self.redirect_field_name = redirect_field_name
   25.65 -        
   25.66 -        # We can't blindly apply update_wrapper because it udpates __dict__ and 
   25.67 -        # if the view function is already a _CheckLogin object then 
   25.68 -        # self.test_func and friends will get stomped. However, we also can't 
   25.69 -        # *not* update the wrapper's dict because then view function attributes
   25.70 -        # don't get updated into the wrapper. So we need to split the
   25.71 -        # difference: don't let update_wrapper update __dict__, but then update
   25.72 -        # the (parts of) __dict__ that we care about ourselves.
   25.73 -        update_wrapper(self, view_func, updated=())
   25.74 -        for k in view_func.__dict__:
   25.75 -            if k not in self.__dict__:
   25.76 -                self.__dict__[k] = view_func.__dict__[k]
   25.77 -
   25.78 -    def __get__(self, obj, cls=None):
   25.79 -        view_func = self.view_func.__get__(obj, cls)
   25.80 -        return _CheckLogin(view_func, self.test_func, self.login_url, self.redirect_field_name)
   25.81 -    
   25.82 -    def __call__(self, request, *args, **kwargs):
   25.83 -        if self.test_func(request.user):
   25.84 -            return self.view_func(request, *args, **kwargs)
   25.85 -        path = urlquote(request.get_full_path())
   25.86 -        tup = self.login_url, self.redirect_field_name, path
   25.87 -        return HttpResponseRedirect('%s?%s=%s' % tup)
    26.1 --- a/django/contrib/auth/tests/remote_user.py	Sat Sep 12 18:53:56 2009 -0500
    26.2 +++ b/django/contrib/auth/tests/remote_user.py	Fri Nov 13 11:27:59 2009 -0600
    26.3 @@ -2,7 +2,7 @@
    26.4  
    26.5  from django.conf import settings
    26.6  from django.contrib.auth.backends import RemoteUserBackend
    26.7 -from django.contrib.auth.models import AnonymousUser, User
    26.8 +from django.contrib.auth.models import User
    26.9  from django.test import TestCase
   26.10  
   26.11  
   26.12 @@ -30,15 +30,15 @@
   26.13          num_users = User.objects.count()
   26.14  
   26.15          response = self.client.get('/remote_user/')
   26.16 -        self.assert_(isinstance(response.context['user'], AnonymousUser))
   26.17 +        self.assert_(response.context['user'].is_anonymous())
   26.18          self.assertEqual(User.objects.count(), num_users)
   26.19  
   26.20          response = self.client.get('/remote_user/', REMOTE_USER=None)
   26.21 -        self.assert_(isinstance(response.context['user'], AnonymousUser))
   26.22 +        self.assert_(response.context['user'].is_anonymous())
   26.23          self.assertEqual(User.objects.count(), num_users)
   26.24  
   26.25          response = self.client.get('/remote_user/', REMOTE_USER='')
   26.26 -        self.assert_(isinstance(response.context['user'], AnonymousUser))
   26.27 +        self.assert_(response.context['user'].is_anonymous())
   26.28          self.assertEqual(User.objects.count(), num_users)
   26.29  
   26.30      def test_unknown_user(self):
   26.31 @@ -115,7 +115,7 @@
   26.32      def test_unknown_user(self):
   26.33          num_users = User.objects.count()
   26.34          response = self.client.get('/remote_user/', REMOTE_USER='newuser')
   26.35 -        self.assert_(isinstance(response.context['user'], AnonymousUser))
   26.36 +        self.assert_(response.context['user'].is_anonymous())
   26.37          self.assertEqual(User.objects.count(), num_users)
   26.38  
   26.39  
    27.1 --- a/django/contrib/auth/views.py	Sat Sep 12 18:53:56 2009 -0500
    27.2 +++ b/django/contrib/auth/views.py	Fri Nov 13 11:27:59 2009 -0600
    27.3 @@ -4,6 +4,7 @@
    27.4  from django.contrib.auth.forms import AuthenticationForm
    27.5  from django.contrib.auth.forms import PasswordResetForm, SetPasswordForm, PasswordChangeForm
    27.6  from django.contrib.auth.tokens import default_token_generator
    27.7 +from django.views.decorators.csrf import csrf_protect
    27.8  from django.core.urlresolvers import reverse
    27.9  from django.shortcuts import render_to_response, get_object_or_404
   27.10  from django.contrib.sites.models import Site, RequestSite
   27.11 @@ -14,11 +15,15 @@
   27.12  from django.contrib.auth.models import User
   27.13  from django.views.decorators.cache import never_cache
   27.14  
   27.15 -def login(request, template_name='registration/login.html', redirect_field_name=REDIRECT_FIELD_NAME):
   27.16 +@csrf_protect
   27.17 +@never_cache
   27.18 +def login(request, template_name='registration/login.html',
   27.19 +          redirect_field_name=REDIRECT_FIELD_NAME,
   27.20 +          authentication_form=AuthenticationForm):
   27.21      "Displays the login form and handles the login action."
   27.22      redirect_to = request.REQUEST.get(redirect_field_name, '')
   27.23      if request.method == "POST":
   27.24 -        form = AuthenticationForm(data=request.POST)
   27.25 +        form = authentication_form(data=request.POST)
   27.26          if form.is_valid():
   27.27              # Light security check -- make sure redirect_to isn't garbage.
   27.28              if not redirect_to or '//' in redirect_to or ' ' in redirect_to:
   27.29 @@ -29,7 +34,7 @@
   27.30                  request.session.delete_test_cookie()
   27.31              return HttpResponseRedirect(redirect_to)
   27.32      else:
   27.33 -        form = AuthenticationForm(request)
   27.34 +        form = authentication_form(request)
   27.35      request.session.set_test_cookie()
   27.36      if Site._meta.installed:
   27.37          current_site = Site.objects.get_current()
   27.38 @@ -41,7 +46,6 @@
   27.39          'site': current_site,
   27.40          'site_name': current_site.name,
   27.41      }, context_instance=RequestContext(request))
   27.42 -login = never_cache(login)
   27.43  
   27.44  def logout(request, next_page=None, template_name='registration/logged_out.html', redirect_field_name=REDIRECT_FIELD_NAME):
   27.45      "Logs out the user and displays 'You are logged out' message."
   27.46 @@ -78,6 +82,7 @@
   27.47  #   prompts for a new password
   27.48  # - password_reset_complete shows a success message for the above
   27.49  
   27.50 +@csrf_protect
   27.51  def password_reset(request, is_admin_site=False, template_name='registration/password_reset_form.html',
   27.52          email_template_name='registration/password_reset_email.html',
   27.53          password_reset_form=PasswordResetForm, token_generator=default_token_generator,
   27.54 @@ -107,6 +112,7 @@
   27.55  def password_reset_done(request, template_name='registration/password_reset_done.html'):
   27.56      return render_to_response(template_name, context_instance=RequestContext(request))
   27.57  
   27.58 +# Doesn't need csrf_protect since no-one can guess the URL
   27.59  def password_reset_confirm(request, uidb36=None, token=None, template_name='registration/password_reset_confirm.html',
   27.60                             token_generator=default_token_generator, set_password_form=SetPasswordForm,
   27.61                             post_reset_redirect=None):
   27.62 @@ -137,28 +143,29 @@
   27.63      else:
   27.64          context_instance['validlink'] = False
   27.65          form = None
   27.66 -    context_instance['form'] = form    
   27.67 +    context_instance['form'] = form
   27.68      return render_to_response(template_name, context_instance=context_instance)
   27.69  
   27.70  def password_reset_complete(request, template_name='registration/password_reset_complete.html'):
   27.71      return render_to_response(template_name, context_instance=RequestContext(request,
   27.72                                                                               {'login_url': settings.LOGIN_URL}))
   27.73  
   27.74 +@csrf_protect
   27.75 +@login_required
   27.76  def password_change(request, template_name='registration/password_change_form.html',
   27.77 -                    post_change_redirect=None):
   27.78 +                    post_change_redirect=None, password_change_form=PasswordChangeForm):
   27.79      if post_change_redirect is None:
   27.80          post_change_redirect = reverse('django.contrib.auth.views.password_change_done')
   27.81      if request.method == "POST":
   27.82 -        form = PasswordChangeForm(request.user, request.POST)
   27.83 +        form = password_change_form(user=request.user, data=request.POST)
   27.84          if form.is_valid():
   27.85              form.save()
   27.86              return HttpResponseRedirect(post_change_redirect)
   27.87      else:
   27.88 -        form = PasswordChangeForm(request.user)
   27.89 +        form = password_change_form(user=request.user)
   27.90      return render_to_response(template_name, {
   27.91          'form': form,
   27.92      }, context_instance=RequestContext(request))
   27.93 -password_change = login_required(password_change)
   27.94  
   27.95  def password_change_done(request, template_name='registration/password_change_done.html'):
   27.96      return render_to_response(template_name, context_instance=RequestContext(request))
    28.1 --- a/django/contrib/comments/admin.py	Sat Sep 12 18:53:56 2009 -0500
    28.2 +++ b/django/contrib/comments/admin.py	Fri Nov 13 11:27:59 2009 -0600
    28.3 @@ -1,7 +1,8 @@
    28.4  from django.contrib import admin
    28.5  from django.contrib.comments.models import Comment
    28.6 -from django.utils.translation import ugettext_lazy as _
    28.7 +from django.utils.translation import ugettext_lazy as _, ungettext
    28.8  from django.contrib.comments import get_model
    28.9 +from django.contrib.comments.views.moderation import perform_flag, perform_approve, perform_delete
   28.10  
   28.11  class CommentsAdmin(admin.ModelAdmin):
   28.12      fieldsets = (
   28.13 @@ -20,7 +21,46 @@
   28.14      list_filter = ('submit_date', 'site', 'is_public', 'is_removed')
   28.15      date_hierarchy = 'submit_date'
   28.16      ordering = ('-submit_date',)
   28.17 +    raw_id_fields = ('user',)
   28.18      search_fields = ('comment', 'user__username', 'user_name', 'user_email', 'user_url', 'ip_address')
   28.19 +    actions = ["flag_comments", "approve_comments", "remove_comments"]
   28.20 +
   28.21 +    def get_actions(self, request):
   28.22 +        actions = super(CommentsAdmin, self).get_actions(request)
   28.23 +        # Only superusers should be able to delete the comments from the DB.
   28.24 +        if not request.user.is_superuser:
   28.25 +            actions.pop('delete_selected')
   28.26 +        if not request.user.has_perm('comments.can_moderate'):
   28.27 +            actions.pop('approve_comments')
   28.28 +            actions.pop('remove_comments')
   28.29 +        return actions
   28.30 +
   28.31 +    def flag_comments(self, request, queryset):
   28.32 +        self._bulk_flag(request, queryset, perform_flag, _("flagged"))
   28.33 +    flag_comments.short_description = _("Flag selected comments")
   28.34 +
   28.35 +    def approve_comments(self, request, queryset):
   28.36 +        self._bulk_flag(request, queryset, perform_approve, _('approved'))
   28.37 +    approve_comments.short_description = _("Approve selected comments")
   28.38 +
   28.39 +    def remove_comments(self, request, queryset):
   28.40 +        self._bulk_flag(request, queryset, perform_delete, _('removed'))
   28.41 +    remove_comments.short_description = _("Remove selected comments")
   28.42 +
   28.43 +    def _bulk_flag(self, request, queryset, action, description):
   28.44 +        """
   28.45 +        Flag, approve, or remove some comments from an admin action. Actually
   28.46 +        calls the `action` argument to perform the heavy lifting.
   28.47 +        """
   28.48 +        n_comments = 0
   28.49 +        for comment in queryset:
   28.50 +            action(request, comment)
   28.51 +            n_comments += 1
   28.52 +        
   28.53 +        msg = ungettext(u'1 comment was successfully %(action)s.',
   28.54 +                        u'%(count)s comments were successfully %(action)s.',
   28.55 +                        n_comments)
   28.56 +        self.message_user(request, msg % {'count': n_comments, 'action': description})
   28.57  
   28.58  # Only register the default admin if the model is the built-in comment model
   28.59  # (this won't be true if there's a custom comment app).
    29.1 --- a/django/contrib/comments/templates/comments/approve.html	Sat Sep 12 18:53:56 2009 -0500
    29.2 +++ b/django/contrib/comments/templates/comments/approve.html	Fri Nov 13 11:27:59 2009 -0600
    29.3 @@ -6,7 +6,7 @@
    29.4  {% block content %}
    29.5    <h1>{% trans "Really make this comment public?" %}</h1>
    29.6    <blockquote>{{ comment|linebreaks }}</blockquote>
    29.7 -  <form action="." method="post">
    29.8 +  <form action="." method="post">{% csrf_token %}
    29.9      {% if next %}<input type="hidden" name="next" value="{{ next }}" id="next" />{% endif %}
   29.10      <p class="submit">
   29.11        <input type="submit" name="submit" value="{% trans "Approve" %}" /> or <a href="{{ comment.get_absolute_url }}">cancel</a>
    30.1 --- a/django/contrib/comments/templates/comments/delete.html	Sat Sep 12 18:53:56 2009 -0500
    30.2 +++ b/django/contrib/comments/templates/comments/delete.html	Fri Nov 13 11:27:59 2009 -0600
    30.3 @@ -6,7 +6,7 @@
    30.4  {% block content %}
    30.5  <h1>{% trans "Really remove this comment?" %}</h1>
    30.6    <blockquote>{{ comment|linebreaks }}</blockquote>
    30.7 -  <form action="." method="post">
    30.8 +  <form action="." method="post">{% csrf_token %}
    30.9      {% if next %}<input type="hidden" name="next" value="{{ next }}" id="next" />{% endif %}
   30.10      <p class="submit">
   30.11      <input type="submit" name="submit" value="{% trans "Remove" %}" /> or <a href="{{ comment.get_absolute_url }}">cancel</a>
    31.1 --- a/django/contrib/comments/templates/comments/flag.html	Sat Sep 12 18:53:56 2009 -0500
    31.2 +++ b/django/contrib/comments/templates/comments/flag.html	Fri Nov 13 11:27:59 2009 -0600
    31.3 @@ -6,7 +6,7 @@
    31.4  {% block content %}
    31.5  <h1>{% trans "Really flag this comment?" %}</h1>
    31.6    <blockquote>{{ comment|linebreaks }}</blockquote>
    31.7 -  <form action="." method="post">
    31.8 +  <form action="." method="post">{% csrf_token %}
    31.9      {% if next %}<input type="hidden" name="next" value="{{ next }}" id="next" />{% endif %}
   31.10      <p class="submit">
   31.11      <input type="submit" name="submit" value="{% trans "Flag" %}" /> or <a href="{{ comment.get_absolute_url }}">cancel</a>
    32.1 --- a/django/contrib/comments/templates/comments/form.html	Sat Sep 12 18:53:56 2009 -0500
    32.2 +++ b/django/contrib/comments/templates/comments/form.html	Fri Nov 13 11:27:59 2009 -0600
    32.3 @@ -1,5 +1,5 @@
    32.4  {% load comments i18n %}
    32.5 -<form action="{% comment_form_target %}" method="post">
    32.6 +<form action="{% comment_form_target %}" method="post">{% csrf_token %}
    32.7    {% if next %}<input type="hidden" name="next" value="{{ next }}" />{% endif %}
    32.8    {% for field in form %}
    32.9      {% if field.is_hidden %}
    33.1 --- a/django/contrib/comments/templates/comments/moderation_queue.html	Sat Sep 12 18:53:56 2009 -0500
    33.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    33.3 @@ -1,75 +0,0 @@
    33.4 -{% extends "admin/change_list.html" %}
    33.5 -{% load adminmedia i18n %}
    33.6 -
    33.7 -{% block title %}{% trans "Comment moderation queue" %}{% endblock %}
    33.8 -
    33.9 -{% block extrahead %}
   33.10 -  {{ block.super }}
   33.11 -  <style type="text/css" media="screen">
   33.12 -    p#nocomments { font-size: 200%; text-align: center; border: 1px #ccc dashed; padding: 4em; }
   33.13 -    td.actions { width: 11em; }
   33.14 -    td.actions form { display: inline; }
   33.15 -    td.actions form input.submit { width: 5em; padding: 2px 4px; margin-right: 4px;}
   33.16 -    td.actions form input.approve { background: green; color: white; }
   33.17 -    td.actions form input.remove { background: red; color: white; }
   33.18 -  </style>
   33.19 -{% endblock %}
   33.20 -
   33.21 -{% block branding %}
   33.22 -<h1 id="site-name">{% trans "Comment moderation queue" %}</h1>
   33.23 -{% endblock %}
   33.24 -
   33.25 -{% block breadcrumbs %}{% endblock %}
   33.26 -
   33.27 -{% block content %}
   33.28 -{% if empty %}
   33.29 -<p id="nocomments">{% trans "No comments to moderate" %}.</p>
   33.30 -{% else %}
   33.31 -<div id="content-main">
   33.32 -  <div class="module" id="changelist">
   33.33 -    <table cellspacing="0">
   33.34 -      <thead>
   33.35 -        <tr>
   33.36 -          <th>{% trans "Action" %}</th>
   33.37 -          <th>{% trans "Name" %}</th>
   33.38 -          <th>{% trans "Comment" %}</th>
   33.39 -          <th>{% trans "Email" %}</th>
   33.40 -          <th>{% trans "URL" %}</th>
   33.41 -          <th>{% trans "Authenticated?" %}</th>
   33.42 -          <th>{% trans "IP Address" %}</th>
   33.43 -          <th class="sorted desc">{% trans "Date posted" %}</th>
   33.44 -        </tr>
   33.45 -    </thead>
   33.46 -    <tbody>
   33.47 -      {% for comment in comments %}
   33.48 -        <tr class="{% cycle 'row1' 'row2' %}">
   33.49 -          <td class="actions">
   33.50 -            <form action="{% url comments-approve comment.pk %}" method="post">
   33.51 -              <input type="hidden" name="next" value="{% url comments-moderation-queue %}" />
   33.52 -              <input class="approve submit" type="submit" name="submit" value="{% trans "Approve" %}" />
   33.53 -            </form>
   33.54 -            <form action="{% url comments-delete comment.pk %}" method="post">
   33.55 -              <input type="hidden" name="next" value="{% url comments-moderation-queue %}" />
   33.56 -              <input class="remove submit" type="submit" name="submit" value="{% trans "Remove" %}" />
   33.57 -            </form>
   33.58 -          </td>
   33.59 -          <td>{{ comment.name }}</td>
   33.60 -          <td>{{ comment.comment|truncatewords:"50" }}</td>
   33.61 -          <td>{{ comment.email }}</td>
   33.62 -          <td>{{ comment.url }}</td>
   33.63 -          <td>
   33.64 -            <img
   33.65 -              src="{% admin_media_prefix %}img/admin/icon-{% if comment.user %}yes{% else %}no{% endif %}.gif"
   33.66 -              alt="{% if comment.user %}{% trans "yes" %}{% else %}{% trans "no" %}{% endif %}"
   33.67 -            />
   33.68 -          </td>
   33.69 -          <td>{{ comment.ip_address }}</td>
   33.70 -          <td>{{ comment.submit_date|date:"F j, P" }}</td>
   33.71 -        </tr>
   33.72 -      {% endfor %}
   33.73 -    </tbody>
   33.74 -    </table>
   33.75 -  </div>
   33.76 -</div>
   33.77 -{% endif %}
   33.78 -{% endblock %}
    34.1 --- a/django/contrib/comments/templates/comments/preview.html	Sat Sep 12 18:53:56 2009 -0500
    34.2 +++ b/django/contrib/comments/templates/comments/preview.html	Fri Nov 13 11:27:59 2009 -0600
    34.3 @@ -5,7 +5,7 @@
    34.4  
    34.5  {% block content %}
    34.6    {% load comments %}
    34.7 -  <form action="{% comment_form_target %}" method="post">
    34.8 +  <form action="{% comment_form_target %}" method="post">{% csrf_token %}
    34.9      {% if next %}<input type="hidden" name="next" value="{{ next }}" />{% endif %}
   34.10      {% if form.errors %}
   34.11      <h1>{% blocktrans count form.errors|length as counter %}Please correct the error below{% plural %}Please correct the errors below{% endblocktrans %}</h1>
    35.1 --- a/django/contrib/comments/urls.py	Sat Sep 12 18:53:56 2009 -0500
    35.2 +++ b/django/contrib/comments/urls.py	Fri Nov 13 11:27:59 2009 -0600
    35.3 @@ -7,7 +7,6 @@
    35.4      url(r'^flagged/$',       'moderation.flag_done',        name='comments-flag-done'),
    35.5      url(r'^delete/(\d+)/$',  'moderation.delete',           name='comments-delete'),
    35.6      url(r'^deleted/$',       'moderation.delete_done',      name='comments-delete-done'),
    35.7 -    url(r'^moderate/$',      'moderation.moderation_queue', name='comments-moderation-queue'),
    35.8      url(r'^approve/(\d+)/$', 'moderation.approve',          name='comments-approve'),
    35.9      url(r'^approved/$',      'moderation.approve_done',     name='comments-approve-done'),
   35.10  )
    36.1 --- a/django/contrib/comments/views/comments.py	Sat Sep 12 18:53:56 2009 -0500
    36.2 +++ b/django/contrib/comments/views/comments.py	Fri Nov 13 11:27:59 2009 -0600
    36.3 @@ -10,6 +10,7 @@
    36.4  from django.views.decorators.http import require_POST
    36.5  from django.contrib import comments
    36.6  from django.contrib.comments import signals
    36.7 +from django.views.decorators.csrf import csrf_protect
    36.8  
    36.9  class CommentPostBadRequest(http.HttpResponseBadRequest):
   36.10      """
   36.11 @@ -22,6 +23,8 @@
   36.12          if settings.DEBUG:
   36.13              self.content = render_to_string("comments/400-debug.html", {"why": why})
   36.14  
   36.15 +@csrf_protect
   36.16 +@require_POST
   36.17  def post_comment(request, next=None):
   36.18      """
   36.19      Post a comment.
   36.20 @@ -116,8 +119,6 @@
   36.21  
   36.22      return next_redirect(data, next, comment_done, c=comment._get_pk_val())
   36.23  
   36.24 -post_comment = require_POST(post_comment)
   36.25 -
   36.26  comment_done = confirmation_view(
   36.27      template = "comments/posted.html",
   36.28      doc = """Display a "comment was posted" success page."""
    37.1 --- a/django/contrib/comments/views/moderation.py	Sat Sep 12 18:53:56 2009 -0500
    37.2 +++ b/django/contrib/comments/views/moderation.py	Fri Nov 13 11:27:59 2009 -0600
    37.3 @@ -3,12 +3,12 @@
    37.4  from django.shortcuts import get_object_or_404, render_to_response
    37.5  from django.contrib.auth.decorators import login_required, permission_required
    37.6  from utils import next_redirect, confirmation_view
    37.7 -from django.core.paginator import Paginator, InvalidPage
    37.8 -from django.http import Http404
    37.9  from django.contrib import comments
   37.10  from django.contrib.comments import signals
   37.11 +from django.views.decorators.csrf import csrf_protect
   37.12  
   37.13 -#@login_required
   37.14 +@csrf_protect
   37.15 +@login_required
   37.16  def flag(request, comment_id, next=None):
   37.17      """
   37.18      Flags a comment. Confirmation on GET, action on POST.
   37.19 @@ -22,18 +22,7 @@
   37.20  
   37.21      # Flag on POST
   37.22      if request.method == 'POST':
   37.23 -        flag, created = comments.models.CommentFlag.objects.get_or_create(
   37.24 -            comment = comment,
   37.25 -            user    = request.user,
   37.26 -            flag    = comments.models.CommentFlag.SUGGEST_REMOVAL
   37.27 -        )
   37.28 -        signals.comment_was_flagged.send(
   37.29 -            sender  = comment.__class__,
   37.30 -            comment = comment,
   37.31 -            flag    = flag,
   37.32 -            created = created,
   37.33 -            request = request,
   37.34 -        )
   37.35 +        perform_flag(request, comment)
   37.36          return next_redirect(request.POST.copy(), next, flag_done, c=comment.pk)
   37.37  
   37.38      # Render a form on GET
   37.39 @@ -42,9 +31,9 @@
   37.40              {'comment': comment, "next": next},
   37.41              template.RequestContext(request)
   37.42          )
   37.43 -flag = login_required(flag)
   37.44  
   37.45 -#@permission_required("comments.delete_comment")
   37.46 +@csrf_protect
   37.47 +@permission_required("comments.can_moderate")
   37.48  def delete(request, comment_id, next=None):
   37.49      """
   37.50      Deletes a comment. Confirmation on GET, action on POST. Requires the "can
   37.51 @@ -60,20 +49,7 @@
   37.52      # Delete on POST
   37.53      if request.method == 'POST':
   37.54          # Flag the comment as deleted instead of actually deleting it.
   37.55 -        flag, created = comments.models.CommentFlag.objects.get_or_create(
   37.56 -            comment = comment,
   37.57 -            user    = request.user,
   37.58 -            flag    = comments.models.CommentFlag.MODERATOR_DELETION
   37.59 -        )
   37.60 -        comment.is_removed = True
   37.61 -        comment.save()
   37.62 -        signals.comment_was_flagged.send(
   37.63 -            sender  = comment.__class__,
   37.64 -            comment = comment,
   37.65 -            flag    = flag,
   37.66 -            created = created,
   37.67 -            request = request,
   37.68 -        )
   37.69 +        perform_delete(request, comment)
   37.70          return next_redirect(request.POST.copy(), next, delete_done, c=comment.pk)
   37.71  
   37.72      # Render a form on GET
   37.73 @@ -82,9 +58,9 @@
   37.74              {'comment': comment, "next": next},
   37.75              template.RequestContext(request)
   37.76          )
   37.77 -delete = permission_required("comments.can_moderate")(delete)
   37.78  
   37.79 -#@permission_required("comments.can_moderate")
   37.80 +@csrf_protect
   37.81 +@permission_required("comments.can_moderate")
   37.82  def approve(request, comment_id, next=None):
   37.83      """
   37.84      Approve a comment (that is, mark it as public and non-removed). Confirmation
   37.85 @@ -100,23 +76,7 @@
   37.86      # Delete on POST
   37.87      if request.method == 'POST':
   37.88          # Flag the comment as approved.
   37.89 -        flag, created = comments.models.CommentFlag.objects.get_or_create(
   37.90 -            comment = comment,
   37.91 -            user    = request.user,
   37.92 -            flag    = comments.models.CommentFlag.MODERATOR_APPROVAL,
   37.93 -        )
   37.94 -
   37.95 -        comment.is_removed = False
   37.96 -        comment.is_public = True
   37.97 -        comment.save()
   37.98 -
   37.99 -        signals.comment_was_flagged.send(
  37.100 -            sender  = comment.__class__,
  37.101 -            comment = comment,
  37.102 -            flag    = flag,
  37.103 -            created = created,
  37.104 -            request = request,
  37.105 -        )
  37.106 +        perform_approve(request, comment)
  37.107          return next_redirect(request.POST.copy(), next, approve_done, c=comment.pk)
  37.108  
  37.109      # Render a form on GET
  37.110 @@ -126,69 +86,64 @@
  37.111              template.RequestContext(request)
  37.112          )
  37.113  
  37.114 -approve = permission_required("comments.can_moderate")(approve)
  37.115 +# The following functions actually perform the various flag/aprove/delete
  37.116 +# actions. They've been broken out into seperate functions to that they
  37.117 +# may be called from admin actions.
  37.118  
  37.119 +def perform_flag(request, comment):
  37.120 +    """
  37.121 +    Actually perform the flagging of a comment from a request.
  37.122 +    """
  37.123 +    flag, created = comments.models.CommentFlag.objects.get_or_create(
  37.124 +        comment = comment,
  37.125 +        user    = request.user,
  37.126 +        flag    = comments.models.CommentFlag.SUGGEST_REMOVAL
  37.127 +    )
  37.128 +    signals.comment_was_flagged.send(
  37.129 +        sender  = comment.__class__,
  37.130 +        comment = comment,
  37.131 +        flag    = flag,
  37.132 +        created = created,
  37.133 +        request = request,
  37.134 +    )
  37.135  
  37.136 -#@permission_required("comments.can_moderate")
  37.137 -def moderation_queue(request):
  37.138 -    """
  37.139 -    Displays a list of unapproved comments to be approved.
  37.140 +def perform_delete(request, comment):
  37.141 +    flag, created = comments.models.CommentFlag.objects.get_or_create(
  37.142 +        comment = comment,
  37.143 +        user    = request.user,
  37.144 +        flag    = comments.models.CommentFlag.MODERATOR_DELETION
  37.145 +    )
  37.146 +    comment.is_removed = True
  37.147 +    comment.save()
  37.148 +    signals.comment_was_flagged.send(
  37.149 +        sender  = comment.__class__,
  37.150 +        comment = comment,
  37.151 +        flag    = flag,
  37.152 +        created = created,
  37.153 +        request = request,
  37.154 +    )
  37.155  
  37.156 -    Templates: `comments/moderation_queue.html`
  37.157 -    Context:
  37.158 -        comments
  37.159 -            Comments to be approved (paginated).
  37.160 -        empty
  37.161 -            Is the comment list empty?
  37.162 -        is_paginated
  37.163 -            Is there more than one page?
  37.164 -        results_per_page
  37.165 -            Number of comments per page
  37.166 -        has_next
  37.167 -            Is there a next page?
  37.168 -        has_previous
  37.169 -            Is there a previous page?
  37.170 -        page
  37.171 -            The current page number
  37.172 -        next
  37.173 -            The next page number
  37.174 -        pages
  37.175 -            Number of pages
  37.176 -        hits
  37.177 -            Total number of comments
  37.178 -        page_range
  37.179 -            Range of page numbers
  37.180  
  37.181 -    """
  37.182 -    qs = comments.get_model().objects.filter(is_public=False, is_removed=False)
  37.183 -    paginator = Paginator(qs, 100)
  37.184 +def perform_approve(request, comment):
  37.185 +    flag, created = comments.models.CommentFlag.objects.get_or_create(
  37.186 +        comment = comment,
  37.187 +        user    = request.user,
  37.188 +        flag    = comments.models.CommentFlag.MODERATOR_APPROVAL,
  37.189 +    )
  37.190  
  37.191 -    try:
  37.192 -        page = int(request.GET.get("page", 1))
  37.193 -    except ValueError:
  37.194 -        raise Http404
  37.195 +    comment.is_removed = False
  37.196 +    comment.is_public = True
  37.197 +    comment.save()
  37.198  
  37.199 -    try:
  37.200 -        comments_per_page = paginator.page(page)
  37.201 -    except InvalidPage:
  37.202 -        raise Http404
  37.203 +    signals.comment_was_flagged.send(
  37.204 +        sender  = comment.__class__,
  37.205 +        comment = comment,
  37.206 +        flag    = flag,
  37.207 +        created = created,
  37.208 +        request = request,
  37.209 +    )
  37.210  
  37.211 -    return render_to_response("comments/moderation_queue.html", {
  37.212 -        'comments' : comments_per_page.object_list,
  37.213 -        'empty' : page == 1 and paginator.count == 0,
  37.214 -        'is_paginated': paginator.num_pages > 1,
  37.215 -        'results_per_page': 100,
  37.216 -        'has_next': comments_per_page.has_next(),
  37.217 -        'has_previous': comments_per_page.has_previous(),
  37.218 -        'page': page,
  37.219 -        'next': page + 1,
  37.220 -        'previous': page - 1,
  37.221 -        'pages': paginator.num_pages,
  37.222 -        'hits' : paginator.count,
  37.223 -        'page_range' : paginator.page_range
  37.224 -    }, context_instance=template.RequestContext(request))
  37.225 -
  37.226 -moderation_queue = permission_required("comments.can_moderate")(moderation_queue)
  37.227 +# Confirmation views.
  37.228  
  37.229  flag_done = confirmation_view(
  37.230      template = "comments/flagged.html",
    38.1 --- a/django/contrib/contenttypes/generic.py	Sat Sep 12 18:53:56 2009 -0500
    38.2 +++ b/django/contrib/contenttypes/generic.py	Fri Nov 13 11:27:59 2009 -0600
    38.3 @@ -105,8 +105,6 @@
    38.4                              limit_choices_to=kwargs.pop('limit_choices_to', None),
    38.5                              symmetrical=kwargs.pop('symmetrical', True))
    38.6  
    38.7 -        # By its very nature, a GenericRelation doesn't create a table.
    38.8 -        self.creates_table = False
    38.9  
   38.10          # Override content-type/object-id field names on the related class
   38.11          self.object_id_field_name = kwargs.pop("object_id_field", "object_id")
    39.1 --- a/django/contrib/csrf/middleware.py	Sat Sep 12 18:53:56 2009 -0500
    39.2 +++ b/django/contrib/csrf/middleware.py	Fri Nov 13 11:27:59 2009 -0600
    39.3 @@ -1,160 +1,7 @@
    39.4 -"""
    39.5 -Cross Site Request Forgery Middleware.
    39.6 +from django.middleware.csrf import CsrfMiddleware, CsrfViewMiddleware, CsrfResponseMiddleware
    39.7 +from django.views.decorators.csrf import csrf_exempt, csrf_view_exempt, csrf_response_exempt
    39.8  
    39.9 -This module provides a middleware that implements protection
   39.10 -against request forgeries from other sites.
   39.11 -"""
   39.12 -
   39.13 -import re
   39.14 -import itertools
   39.15 -try:
   39.16 -    from functools import wraps
   39.17 -except ImportError:
   39.18 -    from django.utils.functional import wraps  # Python 2.3, 2.4 fallback.
   39.19 -
   39.20 -from django.conf import settings
   39.21 -from django.http import HttpResponseForbidden
   39.22 -from django.utils.hashcompat import md5_constructor
   39.23 -from django.utils.safestring import mark_safe
   39.24 -
   39.25 -_ERROR_MSG = mark_safe('<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"><body><h1>403 Forbidden</h1><p>Cross Site Request Forgery detected. Request aborted.</p></body></html>')
   39.26 -
   39.27 -_POST_FORM_RE = \
   39.28 -    re.compile(r'(<form\W[^>]*\bmethod\s*=\s*(\'|"|)POST(\'|"|)\b[^>]*>)', re.IGNORECASE)
   39.29 -
   39.30 -_HTML_TYPES = ('text/html', 'application/xhtml+xml')
   39.31 -
   39.32 -def _make_token(session_id):
   39.33 -    return md5_constructor(settings.SECRET_KEY + session_id).hexdigest()
   39.34 -
   39.35 -class CsrfViewMiddleware(object):
   39.36 -    """
   39.37 -    Middleware that requires a present and correct csrfmiddlewaretoken
   39.38 -    for POST requests that have an active session.
   39.39 -    """
   39.40 -    def process_view(self, request, callback, callback_args, callback_kwargs):
   39.41 -        if request.method == 'POST':
   39.42 -            if getattr(callback, 'csrf_exempt', False):
   39.43 -                return None
   39.44 -
   39.45 -            if request.is_ajax():
   39.46 -                return None
   39.47 -
   39.48 -            try:
   39.49 -                session_id = request.COOKIES[settings.SESSION_COOKIE_NAME]
   39.50 -            except KeyError:
   39.51 -                # No session, no check required
   39.52 -                return None
   39.53 -
   39.54 -            csrf_token = _make_token(session_id)
   39.55 -            # check incoming token
   39.56 -            try:
   39.57 -                request_csrf_token = request.POST['csrfmiddlewaretoken']
   39.58 -            except KeyError:
   39.59 -                return HttpResponseForbidden(_ERROR_MSG)
   39.60 -
   39.61 -            if request_csrf_token != csrf_token:
   39.62 -                return HttpResponseForbidden(_ERROR_MSG)
   39.63 -
   39.64 -        return None
   39.65 -
   39.66 -class CsrfResponseMiddleware(object):
   39.67 -    """
   39.68 -    Middleware that post-processes a response to add a
   39.69 -    csrfmiddlewaretoken if the response/request have an active
   39.70 -    session.
   39.71 -    """
   39.72 -    def process_response(self, request, response):
   39.73 -        if getattr(response, 'csrf_exempt', False):
   39.74 -            return response
   39.75 -
   39.76 -        csrf_token = None
   39.77 -        try:
   39.78 -            # This covers a corner case in which the outgoing response
   39.79 -            # both contains a form and sets a session cookie.  This
   39.80 -            # really should not be needed, since it is best if views
   39.81 -            # that create a new session (login pages) also do a
   39.82 -            # redirect, as is done by all such view functions in
   39.83 -            # Django.
   39.84 -            cookie = response.cookies[settings.SESSION_COOKIE_NAME]
   39.85 -            csrf_token = _make_token(cookie.value)
   39.86 -        except KeyError:
   39.87 -            # Normal case - look for existing session cookie
   39.88 -            try:
   39.89 -                session_id = request.COOKIES[settings.SESSION_COOKIE_NAME]
   39.90 -                csrf_token = _make_token(session_id)
   39.91 -            except KeyError:
   39.92 -                # no incoming or outgoing cookie
   39.93 -                pass
   39.94 -
   39.95 -        if csrf_token is not None and \
   39.96 -                response['Content-Type'].split(';')[0] in _HTML_TYPES:
   39.97 -
   39.98 -            # ensure we don't add the 'id' attribute twice (HTML validity)
   39.99 -            idattributes = itertools.chain(("id='csrfmiddlewaretoken'",),
  39.100 -                                            itertools.repeat(''))
  39.101 -            def add_csrf_field(match):
  39.102 -                """Returns the matched <form> tag plus the added <input> element"""
  39.103 -                return mark_safe(match.group() + "<div style='display:none;'>" + \
  39.104 -                "<input type='hidden' " + idattributes.next() + \
  39.105 -                " name='csrfmiddlewaretoken' value='" + csrf_token + \
  39.106 -                "' /></div>")
  39.107 -
  39.108 -            # Modify any POST forms
  39.109 -            response.content = _POST_FORM_RE.sub(add_csrf_field, response.content)
  39.110 -        return response
  39.111 -
  39.112 -class CsrfMiddleware(CsrfViewMiddleware, CsrfResponseMiddleware):
  39.113 -    """Django middleware that adds protection against Cross Site
  39.114 -    Request Forgeries by adding hidden form fields to POST forms and
  39.115 -    checking requests for the correct value.
  39.116 -
  39.117 -    In the list of middlewares, SessionMiddleware is required, and
  39.118 -    must come after this middleware.  CsrfMiddleWare must come after
  39.119 -    compression middleware.
  39.120 -
  39.121 -    If a session ID cookie is present, it is hashed with the
  39.122 -    SECRET_KEY setting to create an authentication token.  This token
  39.123 -    is added to all outgoing POST forms and is expected on all
  39.124 -    incoming POST requests that have a session ID cookie.
  39.125 -
  39.126 -    If you are setting cookies directly, instead of using Django's
  39.127 -    session framework, this middleware will not work.
  39.128 -
  39.129 -    CsrfMiddleWare is composed of two middleware, CsrfViewMiddleware
  39.130 -    and CsrfResponseMiddleware which can be used independently.
  39.131 -    """
  39.132 -    pass
  39.133 -
  39.134 -def csrf_response_exempt(view_func):
  39.135 -    """
  39.136 -    Modifies a view function so that its response is exempt
  39.137 -    from the post-processing of the CSRF middleware.
  39.138 -    """
  39.139 -    def wrapped_view(*args, **kwargs):
  39.140 -        resp = view_func(*args, **kwargs)
  39.141 -        resp.csrf_exempt = True
  39.142 -        return resp
  39.143 -    return wraps(view_func)(wrapped_view)
  39.144 -
  39.145 -def csrf_view_exempt(view_func):
  39.146 -    """
  39.147 -    Marks a view function as being exempt from CSRF view protection.
  39.148 -    """
  39.149 -    # We could just do view_func.csrf_exempt = True, but decorators
  39.150 -    # are nicer if they don't have side-effects, so we return a new
  39.151 -    # function.
  39.152 -    def wrapped_view(*args, **kwargs):
  39.153 -        return view_func(*args, **kwargs)
  39.154 -    wrapped_view.csrf_exempt = True
  39.155 -    return wraps(view_func)(wrapped_view)
  39.156 -
  39.157 -def csrf_exempt(view_func):
  39.158 -    """
  39.159 -    Marks a view function as being exempt from the CSRF checks
  39.160 -    and post processing.
  39.161 -
  39.162 -    This is the same as using both the csrf_view_exempt and
  39.163 -    csrf_response_exempt decorators.
  39.164 -    """
  39.165 -    return csrf_response_exempt(csrf_view_exempt(view_func))
  39.166 +import warnings
  39.167 +warnings.warn("This import for CSRF functionality is deprecated.  Please use django.middleware.csrf for the middleware and django.views.decorators.csrf for decorators.",
  39.168 +              PendingDeprecationWarning
  39.169 +              )
    40.1 --- a/django/contrib/csrf/models.py	Sat Sep 12 18:53:56 2009 -0500
    40.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    40.3 @@ -1,1 +0,0 @@
    40.4 -# models.py file for tests to run.
    41.1 --- a/django/contrib/csrf/tests.py	Sat Sep 12 18:53:56 2009 -0500
    41.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    41.3 @@ -1,144 +0,0 @@
    41.4 -# -*- coding: utf-8 -*-
    41.5 -
    41.6 -from django.test import TestCase
    41.7 -from django.http import HttpRequest, HttpResponse, HttpResponseForbidden
    41.8 -from django.contrib.csrf.middleware import CsrfMiddleware, _make_token, csrf_exempt
    41.9 -from django.conf import settings
   41.10 -
   41.11 -
   41.12 -def post_form_response():
   41.13 -    resp = HttpResponse(content="""
   41.14 -<html><body><form method="POST"><input type="text" /></form></body></html>
   41.15 -""", mimetype="text/html")
   41.16 -    return resp
   41.17 -
   41.18 -def test_view(request):
   41.19 -    return post_form_response()
   41.20 -
   41.21 -class CsrfMiddlewareTest(TestCase):
   41.22 -
   41.23 -    _session_id = "1"
   41.24 -
   41.25 -    def _get_GET_no_session_request(self):
   41.26 -        return HttpRequest()
   41.27 -
   41.28 -    def _get_GET_session_request(self):
   41.29 -        req = self._get_GET_no_session_request()
   41.30 -        req.COOKIES[settings.SESSION_COOKIE_NAME] = self._session_id
   41.31 -        return req
   41.32 -
   41.33 -    def _get_POST_session_request(self):
   41.34 -        req = self._get_GET_session_request()
   41.35 -        req.method = "POST"
   41.36 -        return req
   41.37 -
   41.38 -    def _get_POST_no_session_request(self):
   41.39 -        req = self._get_GET_no_session_request()
   41.40 -        req.method = "POST"
   41.41 -        return req
   41.42 -
   41.43 -    def _get_POST_session_request_with_token(self):
   41.44 -        req = self._get_POST_session_request()
   41.45 -        req.POST['csrfmiddlewaretoken'] = _make_token(self._session_id)
   41.46 -        return req
   41.47 -
   41.48 -    def _get_post_form_response(self):
   41.49 -        return post_form_response()
   41.50 -
   41.51 -    def _get_new_session_response(self):
   41.52 -        resp = self._get_post_form_response()
   41.53 -        resp.cookies[settings.SESSION_COOKIE_NAME] = self._session_id
   41.54 -        return resp
   41.55 -
   41.56 -    def _check_token_present(self, response):
   41.57 -        self.assertContains(response, "name='csrfmiddlewaretoken' value='%s'" % _make_token(self._session_id))
   41.58 -
   41.59 -    def get_view(self):
   41.60 -        return test_view
   41.61 -
   41.62 -    # Check the post processing
   41.63 -    def test_process_response_no_session(self):
   41.64 -        """
   41.65 -        Check the post-processor does nothing if no session active
   41.66 -        """
   41.67 -        req = self._get_GET_no_session_request()
   41.68 -        resp = self._get_post_form_response()
   41.69 -        resp_content = resp.content # needed because process_response modifies resp
   41.70 -        resp2 = CsrfMiddleware().process_response(req, resp)
   41.71 -        self.assertEquals(resp_content, resp2.content)
   41.72 -
   41.73 -    def test_process_response_existing_session(self):
   41.74 -        """
   41.75 -        Check that the token is inserted if there is an existing session
   41.76 -        """
   41.77 -        req = self._get_GET_session_request()
   41.78 -        resp = self._get_post_form_response()
   41.79 -        resp_content = resp.content # needed because process_response modifies resp
   41.80 -        resp2 = CsrfMiddleware().process_response(req, resp)
   41.81 -        self.assertNotEqual(resp_content, resp2.content)
   41.82 -        self._check_token_present(resp2)
   41.83 -
   41.84 -    def test_process_response_new_session(self):
   41.85 -        """
   41.86 -        Check that the token is inserted if there is a new session being started
   41.87 -        """
   41.88 -        req = self._get_GET_no_session_request() # no session in request
   41.89 -        resp = self._get_new_session_response() # but new session started
   41.90 -        resp_content = resp.content # needed because process_response modifies resp
   41.91 -        resp2 = CsrfMiddleware().process_response(req, resp)
   41.92 -        self.assertNotEqual(resp_content, resp2.content)
   41.93 -        self._check_token_present(resp2)
   41.94 -
   41.95 -    def test_process_response_exempt_view(self):
   41.96 -        """
   41.97 -        Check that no post processing is done for an exempt view
   41.98 -        """
   41.99 -        req = self._get_POST_session_request()
  41.100 -        resp = csrf_exempt(self.get_view())(req)
  41.101 -        resp_content = resp.content
  41.102 -        resp2 = CsrfMiddleware().process_response(req, resp)
  41.103 -        self.assertEquals(resp_content, resp2.content)
  41.104 -
  41.105 -    # Check the request processing
  41.106 -    def test_process_request_no_session(self):
  41.107 -        """
  41.108 -        Check that if no session is present, the middleware does nothing.
  41.109 -        to the incoming request.
  41.110 -        """
  41.111 -        req = self._get_POST_no_session_request()
  41.112 -        req2 = CsrfMiddleware().process_view(req, self.get_view(), (), {})
  41.113 -        self.assertEquals(None, req2)
  41.114 -
  41.115 -    def test_process_request_session_no_token(self):
  41.116 -        """
  41.117 -        Check that if a session is present but no token, we get a 'forbidden'
  41.118 -        """
  41.119 -        req = self._get_POST_session_request()
  41.120 -        req2 = CsrfMiddleware().process_view(req, self.get_view(), (), {})
  41.121 -        self.assertEquals(HttpResponseForbidden, req2.__class__)
  41.122 -
  41.123 -    def test_process_request_session_and_token(self):
  41.124 -        """
  41.125 -        Check that if a session is present and a token, the middleware lets it through
  41.126 -        """
  41.127 -        req = self._get_POST_session_request_with_token()
  41.128 -        req2 = CsrfMiddleware().process_view(req, self.get_view(), (), {})
  41.129 -        self.assertEquals(None, req2)
  41.130 -
  41.131 -    def test_process_request_session_no_token_exempt_view(self):
  41.132 -        """
  41.133 -        Check that if a session is present and no token, but the csrf_exempt
  41.134 -        decorator has been applied to the view, the middleware lets it through
  41.135 -        """
  41.136 -        req = self._get_POST_session_request()
  41.137 -        req2 = CsrfMiddleware().process_view(req, csrf_exempt(self.get_view()), (), {})
  41.138 -        self.assertEquals(None, req2)
  41.139 -
  41.140 -    def test_ajax_exemption(self):
  41.141 -        """
  41.142 -        Check that AJAX requests are automatically exempted.
  41.143 -        """
  41.144 -        req = self._get_POST_session_request()
  41.145 -        req.META['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
  41.146 -        req2 = CsrfMiddleware().process_view(req, self.get_view(), (), {})
  41.147 -        self.assertEquals(None, req2)
    42.1 --- a/django/contrib/formtools/templates/formtools/form.html	Sat Sep 12 18:53:56 2009 -0500
    42.2 +++ b/django/contrib/formtools/templates/formtools/form.html	Fri Nov 13 11:27:59 2009 -0600
    42.3 @@ -4,7 +4,7 @@
    42.4  
    42.5  {% if form.errors %}<h1>Please correct the following errors</h1>{% else %}<h1>Submit</h1>{% endif %}
    42.6  
    42.7 -<form action="" method="post">
    42.8 +<form action="" method="post">{% csrf_token %}
    42.9  <table>
   42.10  {{ form }}
   42.11  </table>
    43.1 --- a/django/contrib/formtools/templates/formtools/preview.html	Sat Sep 12 18:53:56 2009 -0500
    43.2 +++ b/django/contrib/formtools/templates/formtools/preview.html	Fri Nov 13 11:27:59 2009 -0600
    43.3 @@ -15,7 +15,7 @@
    43.4  
    43.5  <p>Security hash: {{ hash_value }}</p>
    43.6  
    43.7 -<form action="" method="post">
    43.8 +<form action="" method="post">{% csrf_token %}
    43.9  {% for field in form %}{{ field.as_hidden }}
   43.10  {% endfor %}
   43.11  <input type="hidden" name="{{ stage_field }}" value="2" />
   43.12 @@ -25,7 +25,7 @@
   43.13  
   43.14  <h1>Or edit it again</h1>
   43.15  
   43.16 -<form action="" method="post">
   43.17 +<form action="" method="post">{% csrf_token %}
   43.18  <table>
   43.19  {{ form }}
   43.20  </table>
    44.1 --- a/django/contrib/formtools/tests.py	Sat Sep 12 18:53:56 2009 -0500
    44.2 +++ b/django/contrib/formtools/tests.py	Fri Nov 13 11:27:59 2009 -0600
    44.3 @@ -147,15 +147,18 @@
    44.4  
    44.5  class WizardClass(wizard.FormWizard):
    44.6      def render_template(self, *args, **kw):
    44.7 -        return ""
    44.8 +        return http.HttpResponse("")
    44.9  
   44.10      def done(self, request, cleaned_data):
   44.11          return http.HttpResponse(success_string)
   44.12  
   44.13 -class DummyRequest(object):
   44.14 +class DummyRequest(http.HttpRequest):
   44.15      def __init__(self, POST=None):
   44.16 +        super(DummyRequest, self).__init__()
   44.17          self.method = POST and "POST" or "GET"
   44.18 -        self.POST = POST
   44.19 +        if POST is not None:
   44.20 +            self.POST.update(POST)
   44.21 +        self._dont_enforce_csrf_checks = True
   44.22  
   44.23  class WizardTests(TestCase):
   44.24      def test_step_starts_at_zero(self):
    45.1 --- a/django/contrib/formtools/wizard.py	Sat Sep 12 18:53:56 2009 -0500
    45.2 +++ b/django/contrib/formtools/wizard.py	Fri Nov 13 11:27:59 2009 -0600
    45.3 @@ -14,6 +14,7 @@
    45.4  from django.utils.hashcompat import md5_constructor
    45.5  from django.utils.translation import ugettext_lazy as _
    45.6  from django.contrib.formtools.utils import security_hash
    45.7 +from django.views.decorators.csrf import csrf_protect
    45.8  
    45.9  class FormWizard(object):
   45.10      # Dictionary of extra template context variables.
   45.11 @@ -44,6 +45,7 @@
   45.12          # hook methods might alter self.form_list.
   45.13          return len(self.form_list)
   45.14  
   45.15 +    @csrf_protect
   45.16      def __call__(self, request, *args, **kwargs):
   45.17          """
   45.18          Main method that does all the hard work, conforming to the Django view
    46.1 --- a/django/contrib/gis/db/backend/postgis/adaptor.py	Sat Sep 12 18:53:56 2009 -0500
    46.2 +++ b/django/contrib/gis/db/backend/postgis/adaptor.py	Fri Nov 13 11:27:59 2009 -0600
    46.3 @@ -30,7 +30,7 @@
    46.4      def getquoted(self):
    46.5          "Returns a properly quoted string for use in PostgreSQL/PostGIS."
    46.6          # Want to use WKB, so wrap with psycopg2 Binary() to quote properly.
    46.7 -        return "%s(%s, %s)" % (GEOM_FROM_WKB, Binary(self.wkb), self.srid or -1)
    46.8 +        return "%s(E%s, %s)" % (GEOM_FROM_WKB, Binary(self.wkb), self.srid or -1)
    46.9  
   46.10      def prepare_database_save(self, unused):
   46.11          return self
    47.1 --- a/django/contrib/gis/db/models/sql/aggregates.py	Sat Sep 12 18:53:56 2009 -0500
    47.2 +++ b/django/contrib/gis/db/models/sql/aggregates.py	Fri Nov 13 11:27:59 2009 -0600
    47.3 @@ -16,7 +16,7 @@
    47.4  
    47.5  if SpatialBackend.postgis:
    47.6      def convert_extent(box):
    47.7 -        # Box text will be something like "BOX(-90.0 30.0, -85.0 40.0)"; 
    47.8 +        # Box text will be something like "BOX(-90.0 30.0, -85.0 40.0)";
    47.9          # parsing out and returning as a 4-tuple.
   47.10          ll, ur = box[4:-1].split(',')
   47.11          xmin, ymin = map(float, ll.split())
   47.12 @@ -32,19 +32,28 @@
   47.13  
   47.14      def convert_extent(clob):
   47.15          if clob:
   47.16 -            # Oracle returns a polygon for the extent, we construct
   47.17 -            # the 4-tuple from the coordinates in the polygon.
   47.18 -            poly = SpatialBackend.Geometry(clob.read())
   47.19 -            shell = poly.shell
   47.20 -            ll, ur = shell[0], shell[2]
   47.21 +            # Generally, Oracle returns a polygon for the extent -- however,
   47.22 +            # it can return a single point if there's only one Point in the
   47.23 +            # table.
   47.24 +            ext_geom = SpatialBackend.Geometry(clob.read())
   47.25 +            gtype = str(ext_geom.geom_type)
   47.26 +            if gtype == 'Polygon':
   47.27 +                # Construct the 4-tuple from the coordinates in the polygon.
   47.28 +                shell = ext_geom.shell
   47.29 +                ll, ur = shell[0][:2], shell[2][:2]
   47.30 +            elif gtype == 'Point':
   47.31 +                ll = ext_geom.coords[:2]
   47.32 +                ur = ll
   47.33 +            else:
   47.34 +                raise Exception('Unexpected geometry type returned for extent: %s' % gtype)
   47.35              xmin, ymin = ll
   47.36              xmax, ymax = ur
   47.37              return (xmin, ymin, xmax, ymax)
   47.38          else:
   47.39              return None
   47.40 -    
   47.41 +
   47.42      def convert_geom(clob, geo_field):
   47.43 -        if clob: 
   47.44 +        if clob:
   47.45              return SpatialBackend.Geometry(clob.read(), geo_field.srid)
   47.46          else:
   47.47              return None
   47.48 @@ -73,7 +82,7 @@
   47.49              self.extra.setdefault('tolerance', 0.05)
   47.50  
   47.51          # Can't use geographic aggregates on non-geometry fields.
   47.52 -        if not isinstance(self.source, GeometryField): 
   47.53 +        if not isinstance(self.source, GeometryField):
   47.54              raise ValueError('Geospatial aggregates only allowed on geometry fields.')
   47.55  
   47.56          # Making sure the SQL function is available for this spatial backend.
   47.57 @@ -87,7 +96,7 @@
   47.58  class Extent(GeoAggregate):
   47.59      is_extent = True
   47.60      sql_function = SpatialBackend.extent
   47.61 -        
   47.62 +
   47.63  if SpatialBackend.oracle:
   47.64      # Have to change Extent's attributes here for Oracle.
   47.65      Extent.conversion_class = GeomField
    48.1 --- a/django/contrib/gis/gdal/geometries.py	Sat Sep 12 18:53:56 2009 -0500
    48.2 +++ b/django/contrib/gis/gdal/geometries.py	Fri Nov 13 11:27:59 2009 -0600
    48.3 @@ -29,7 +29,7 @@
    48.4    +proj=longlat +ellps=clrk66 +datum=NAD27 +no_defs
    48.5    >>> print mpnt
    48.6    MULTIPOINT (-89.999930378602485 29.999797886557641,-89.999930378602485 29.999797886557641)
    48.7 -  
    48.8 +
    48.9    The OGRGeomType class is to make it easy to specify an OGR geometry type:
   48.10    >>> from django.contrib.gis.gdal import OGRGeomType
   48.11    >>> gt1 = OGRGeomType(3) # Using an integer for the type
   48.12 @@ -78,7 +78,7 @@
   48.13              geom_input = buffer(a2b_hex(geom_input.upper()))
   48.14              str_instance = False
   48.15  
   48.16 -        # Constructing the geometry, 
   48.17 +        # Constructing the geometry,
   48.18          if str_instance:
   48.19              # Checking if unicode
   48.20              if isinstance(geom_input, unicode):
   48.21 @@ -130,12 +130,12 @@
   48.22          self.__class__ = GEO_CLASSES[self.geom_type.num]
   48.23  
   48.24      @classmethod
   48.25 -    def from_bbox(cls, bbox):   
   48.26 +    def from_bbox(cls, bbox):
   48.27          "Constructs a Polygon from a bounding box (4-tuple)."
   48.28          x0, y0, x1, y1 = bbox
   48.29          return OGRGeometry( 'POLYGON((%s %s, %s %s, %s %s, %s %s, %s %s))' %  (
   48.30                  x0, y0, x0, y1, x1, y1, x1, y0, x0, y0) )
   48.31 - 
   48.32 +
   48.33      def __del__(self):
   48.34          "Deletes this Geometry."
   48.35          if self._ptr: capi.destroy_geom(self._ptr)
   48.36 @@ -179,10 +179,17 @@
   48.37          "Returns 0 for points, 1 for lines, and 2 for surfaces."
   48.38          return capi.get_dims(self.ptr)
   48.39  
   48.40 -    @property
   48.41 -    def coord_dim(self):
   48.42 +    def _get_coord_dim(self):
   48.43          "Returns the coordinate dimension of the Geometry."
   48.44 -        return capi.get_coord_dims(self.ptr)
   48.45 +        return capi.get_coord_dim(self.ptr)
   48.46 +
   48.47 +    def _set_coord_dim(self, dim):
   48.48 +        "Sets the coordinate dimension of this Geometry."
   48.49 +        if not dim in (2, 3):
   48.50 +            raise ValueError('Geometry dimension must be either 2 or 3')
   48.51 +        capi.set_coord_dim(self.ptr, dim)
   48.52 +
   48.53 +    coord_dim = property(_get_coord_dim, _set_coord_dim)
   48.54  
   48.55      @property
   48.56      def geom_count(self):
   48.57 @@ -237,7 +244,7 @@
   48.58          return self.envelope.tuple
   48.59  
   48.60      #### SpatialReference-related Properties ####
   48.61 -    
   48.62 +
   48.63      # The SRS property
   48.64      def _get_srs(self):
   48.65          "Returns the Spatial Reference for this Geometry."
   48.66 @@ -249,11 +256,15 @@
   48.67  
   48.68      def _set_srs(self, srs):
   48.69          "Sets the SpatialReference for this geometry."
   48.70 +        # Do not have to clone the `SpatialReference` object pointer because
   48.71 +        # when it is assigned to this `OGRGeometry` it's internal OGR
   48.72 +        # reference count is incremented, and will likewise be released
   48.73 +        # (decremented) when this geometry's destructor is called.
   48.74          if isinstance(srs, SpatialReference):
   48.75 -            srs_ptr = srs_api.clone_srs(srs.ptr)
   48.76 +            srs_ptr = srs.ptr
   48.77          elif isinstance(srs, (int, long, basestring)):
   48.78              sr = SpatialReference(srs)
   48.79 -            srs_ptr = srs_api.clone_srs(sr.ptr)
   48.80 +            srs_ptr = sr.ptr
   48.81          else:
   48.82              raise TypeError('Cannot assign spatial reference with object of type: %s' % type(srs))
   48.83          capi.assign_srs(self.ptr, srs_ptr)
   48.84 @@ -298,7 +309,7 @@
   48.85          Returns the GeoJSON representation of this Geometry (requires
   48.86          GDAL 1.5+).
   48.87          """
   48.88 -        if GEOJSON: 
   48.89 +        if GEOJSON:
   48.90              return capi.to_json(self.ptr)
   48.91          else:
   48.92              raise NotImplementedError('GeoJSON output only supported on GDAL 1.5+.')
   48.93 @@ -335,7 +346,7 @@
   48.94      def wkt(self):
   48.95          "Returns the WKT representation of the Geometry."
   48.96          return capi.to_wkt(self.ptr, byref(c_char_p()))
   48.97 -    
   48.98 +
   48.99      #### Geometry Methods ####
  48.100      def clone(self):
  48.101          "Clones this OGR Geometry."
  48.102 @@ -363,6 +374,16 @@
  48.103              klone = self.clone()
  48.104              klone.transform(coord_trans)
  48.105              return klone
  48.106 +
  48.107 +        # Have to get the coordinate dimension of the original geometry
  48.108 +        # so it can be used to reset the transformed geometry's dimension
  48.109 +        # afterwards.  This is done because of GDAL bug (in versions prior
  48.110 +        # to 1.7) that turns geometries 3D after transformation, see:
  48.111 +        #  http://trac.osgeo.org/gdal/changeset/17792
  48.112 +        orig_dim = self.coord_dim
  48.113 +
  48.114 +        # Depending on the input type, use the appropriate OGR routine
  48.115 +        # to perform the transformation.
  48.116          if isinstance(coord_trans, CoordTransform):
  48.117              capi.geom_transform(self.ptr, coord_trans.ptr)
  48.118          elif isinstance(coord_trans, SpatialReference):
  48.119 @@ -373,6 +394,10 @@
  48.120          else:
  48.121              raise TypeError('Transform only accepts CoordTransform, SpatialReference, string, and integer objects.')
  48.122  
  48.123 +        # Setting with original dimension, see comment above.
  48.124 +        if self.coord_dim != orig_dim:
  48.125 +            self.coord_dim = orig_dim
  48.126 +
  48.127      def transform_to(self, srs):
  48.128          "For backwards-compatibility."
  48.129          self.transform(srs)
  48.130 @@ -391,7 +416,7 @@
  48.131      def intersects(self, other):
  48.132          "Returns True if this geometry intersects with the other."
  48.133          return self._topology(capi.ogr_intersects, other)
  48.134 -    
  48.135 +
  48.136      def equals(self, other):
  48.137          "Returns True if this geometry is equivalent to the other."
  48.138          return self._topology(capi.ogr_equals, other)
  48.139 @@ -436,7 +461,7 @@
  48.140      @property
  48.141      def convex_hull(self):
  48.142          """
  48.143 -        Returns the smallest convex Polygon that contains all the points in 
  48.144 +        Returns the smallest convex Polygon that contains all the points in
  48.145          this Geometry.
  48.146          """
  48.147          return self._geomgen(capi.geom_convex_hull)
  48.148 @@ -456,7 +481,7 @@
  48.149          return self._geomgen(capi.geom_intersection, other)
  48.150  
  48.151      def sym_difference(self, other):
  48.152 -        """                                                                                                                                                
  48.153 +        """
  48.154          Returns a new geometry which is the symmetric difference of this
  48.155          geometry and the other.
  48.156          """
  48.157 @@ -545,7 +570,7 @@
  48.158      def y(self):
  48.159          "Returns the Y coordinates in a list."
  48.160          return self._listarr(capi.gety)
  48.161 -    
  48.162 +
  48.163      @property
  48.164      def z(self):
  48.165          "Returns the Z coordinates in a list."
  48.166 @@ -610,7 +635,7 @@
  48.167              raise OGRIndexError('index out of range: %s' % index)
  48.168          else:
  48.169              return OGRGeometry(capi.clone_geom(capi.get_geom_ref(self.ptr, index)), self.srs)
  48.170 -        
  48.171 +
  48.172      def __iter__(self):
  48.173          "Iterates over each Geometry."
  48.174          for i in xrange(self.geom_count):
  48.175 @@ -658,5 +683,5 @@
  48.176                 5 : MultiLineString,
  48.177                 6 : MultiPolygon,
  48.178                 7 : GeometryCollection,
  48.179 -               101: LinearRing, 
  48.180 +               101: LinearRing,
  48.181                 }
    49.1 --- a/django/contrib/gis/gdal/layer.py	Sat Sep 12 18:53:56 2009 -0500
    49.2 +++ b/django/contrib/gis/gdal/layer.py	Fri Nov 13 11:27:59 2009 -0600
    49.3 @@ -1,5 +1,5 @@
    49.4  # Needed ctypes routines
    49.5 -from ctypes import byref
    49.6 +from ctypes import c_double, byref
    49.7  
    49.8  # Other GDAL imports.
    49.9  from django.contrib.gis.gdal.base import GDALBase
   49.10 @@ -7,11 +7,12 @@
   49.11  from django.contrib.gis.gdal.error import OGRException, OGRIndexError, SRSException
   49.12  from django.contrib.gis.gdal.feature import Feature
   49.13  from django.contrib.gis.gdal.field import OGRFieldTypes
   49.14 -from django.contrib.gis.gdal.geometries import OGRGeomType
   49.15 +from django.contrib.gis.gdal.geomtype import OGRGeomType
   49.16 +from django.contrib.gis.gdal.geometries import OGRGeometry
   49.17  from django.contrib.gis.gdal.srs import SpatialReference
   49.18  
   49.19  # GDAL ctypes function prototypes.
   49.20 -from django.contrib.gis.gdal.prototypes import ds as capi, srs as srs_api
   49.21 +from django.contrib.gis.gdal.prototypes import ds as capi, geom as geom_api, srs as srs_api
   49.22  
   49.23  # For more information, see the OGR C API source code:
   49.24  #  http://www.gdal.org/ogr/ogr__api_8h.html
   49.25 @@ -156,6 +157,29 @@
   49.26          return [capi.get_field_precision(capi.get_field_defn(self._ldefn, i))
   49.27                  for i in xrange(self.num_fields)]
   49.28  
   49.29 +    def _get_spatial_filter(self):
   49.30 +        try:
   49.31 +            return OGRGeometry(geom_api.clone_geom(capi.get_spatial_filter(self.ptr)))
   49.32 +        except OGRException:
   49.33 +            return None
   49.34 +
   49.35 +    def _set_spatial_filter(self, filter):
   49.36 +        if isinstance(filter, OGRGeometry):
   49.37 +            capi.set_spatial_filter(self.ptr, filter.ptr)
   49.38 +        elif isinstance(filter, (tuple, list)):
   49.39 +            if not len(filter) == 4:
   49.40 +                raise ValueError('Spatial filter list/tuple must have 4 elements.')
   49.41 +            # Map c_double onto params -- if a bad type is passed in it
   49.42 +            # will be caught here.
   49.43 +            xmin, ymin, xmax, ymax = map(c_double, filter)
   49.44 +            capi.set_spatial_filter_rect(self.ptr, xmin, ymin, xmax, ymax)
   49.45 +        elif filter is None:
   49.46 +            capi.set_spatial_filter(self.ptr, None)
   49.47 +        else:
   49.48 +            raise TypeError('Spatial filter must be either an OGRGeometry instance, a 4-tuple, or None.')
   49.49 +
   49.50 +    spatial_filter = property(_get_spatial_filter, _set_spatial_filter)
   49.51 +
   49.52      #### Layer Methods ####
   49.53      def get_fields(self, field_name):
   49.54          """
    50.1 --- a/django/contrib/gis/gdal/prototypes/ds.py	Sat Sep 12 18:53:56 2009 -0500
    50.2 +++ b/django/contrib/gis/gdal/prototypes/ds.py	Fri Nov 13 11:27:59 2009 -0600
    50.3 @@ -3,7 +3,7 @@
    50.4   related data structures. OGR_Dr_*, OGR_DS_*, OGR_L_*, OGR_F_*, 
    50.5   OGR_Fld_* routines are relevant here.
    50.6  """
    50.7 -from ctypes import c_char_p, c_int, c_long, c_void_p, POINTER
    50.8 +from ctypes import c_char_p, c_double, c_int, c_long, c_void_p, POINTER
    50.9  from django.contrib.gis.gdal.envelope import OGREnvelope
   50.10  from django.contrib.gis.gdal.libgdal import lgdal
   50.11  from django.contrib.gis.gdal.prototypes.generation import \
   50.12 @@ -38,6 +38,9 @@
   50.13  get_next_feature = voidptr_output(lgdal.OGR_L_GetNextFeature, [c_void_p])
   50.14  reset_reading = void_output(lgdal.OGR_L_ResetReading, [c_void_p], errcheck=False)
   50.15  test_capability = int_output(lgdal.OGR_L_TestCapability, [c_void_p, c_char_p])
   50.16 +get_spatial_filter = geom_output(lgdal.OGR_L_GetSpatialFilter, [c_void_p])
   50.17 +set_spatial_filter = void_output(lgdal.OGR_L_SetSpatialFilter, [c_void_p, c_void_p], errcheck=False)
   50.18 +set_spatial_filter_rect = void_output(lgdal.OGR_L_SetSpatialFilterRect, [c_void_p, c_double, c_double, c_double, c_double], errcheck=False)
   50.19  
   50.20  ### Feature Definition Routines ###
   50.21  get_fd_geom_type = int_output(lgdal.OGR_FD_GetGeomType, [c_void_p])
    51.1 --- a/django/contrib/gis/gdal/prototypes/geom.py	Sat Sep 12 18:53:56 2009 -0500
    51.2 +++ b/django/contrib/gis/gdal/prototypes/geom.py	Fri Nov 13 11:27:59 2009 -0600
    51.3 @@ -83,7 +83,8 @@
    51.4  get_area = double_output(lgdal.OGR_G_GetArea, [c_void_p])
    51.5  get_centroid = void_output(lgdal.OGR_G_Centroid, [c_void_p, c_void_p])
    51.6  get_dims = int_output(lgdal.OGR_G_GetDimension, [c_void_p])
    51.7 -get_coord_dims = int_output(lgdal.OGR_G_GetCoordinateDimension, [c_void_p])
    51.8 +get_coord_dim = int_output(lgdal.OGR_G_GetCoordinateDimension, [c_void_p])
    51.9 +set_coord_dim = void_output(lgdal.OGR_G_SetCoordinateDimension, [c_void_p, c_int], errcheck=False)
   51.10  
   51.11  get_geom_count = int_output(lgdal.OGR_G_GetGeometryCount, [c_void_p])
   51.12  get_geom_name = const_string_output(lgdal.OGR_G_GetGeometryName, [c_void_p])
    52.1 --- a/django/contrib/gis/gdal/tests/test_ds.py	Sat Sep 12 18:53:56 2009 -0500
    52.2 +++ b/django/contrib/gis/gdal/tests/test_ds.py	Fri Nov 13 11:27:59 2009 -0600
    52.3 @@ -1,13 +1,11 @@
    52.4  import os, os.path, unittest
    52.5 -from django.contrib.gis.gdal import DataSource, Envelope, OGRException, OGRIndexError
    52.6 +from django.contrib.gis.gdal import DataSource, Envelope, OGRGeometry, OGRException, OGRIndexError
    52.7  from django.contrib.gis.gdal.field import OFTReal, OFTInteger, OFTString
    52.8  from django.contrib import gis
    52.9  
   52.10  # Path for SHP files
   52.11  data_path = os.path.join(os.path.dirname(gis.__file__), 'tests' + os.sep + 'data')
   52.12  def get_ds_file(name, ext):
   52.13 -    
   52.14 -
   52.15      return os.sep.join([data_path, name, name + '.%s' % ext])
   52.16  
   52.17  # Test SHP data source object
   52.18 @@ -191,7 +189,41 @@
   52.19                      if hasattr(source, 'srs_wkt'):
   52.20                          self.assertEqual(source.srs_wkt, g.srs.wkt)
   52.21  
   52.22 +    def test06_spatial_filter(self):
   52.23 +        "Testing the Layer.spatial_filter property."
   52.24 +        ds = DataSource(get_ds_file('cities', 'shp'))
   52.25 +        lyr = ds[0]
   52.26  
   52.27 +        # When not set, it should be None.
   52.28 +        self.assertEqual(None, lyr.spatial_filter)
   52.29 +
   52.30 +        # Must be set a/an OGRGeometry or 4-tuple.
   52.31 +        self.assertRaises(TypeError, lyr._set_spatial_filter, 'foo')
   52.32 +
   52.33 +        # Setting the spatial filter with a tuple/list with the extent of
   52.34 +        # a buffer centering around Pueblo.
   52.35 +        self.assertRaises(ValueError, lyr._set_spatial_filter, range(5))
   52.36 +        filter_extent = (-105.609252, 37.255001, -103.609252, 39.255001)
   52.37 +        lyr.spatial_filter = (-105.609252, 37.255001, -103.609252, 39.255001)
   52.38 +        self.assertEqual(OGRGeometry.from_bbox(filter_extent), lyr.spatial_filter)
   52.39 +        feats = [feat for feat in lyr]
   52.40 +        self.assertEqual(1, len(feats))
   52.41 +        self.assertEqual('Pueblo', feats[0].get('Name'))
   52.42 +
   52.43 +        # Setting the spatial filter with an OGRGeometry for buffer centering
   52.44 +        # around Houston.
   52.45 +        filter_geom = OGRGeometry('POLYGON((-96.363151 28.763374,-94.363151 28.763374,-94.363151 30.763374,-96.363151 30.763374,-96.363151 28.763374))')
   52.46 +        lyr.spatial_filter = filter_geom
   52.47 +        self.assertEqual(filter_geom, lyr.spatial_filter)
   52.48 +        feats = [feat for feat in lyr]
   52.49 +        self.assertEqual(1, len(feats))
   52.50 +        self.assertEqual('Houston', feats[0].get('Name'))
   52.51 +
   52.52 +        # Clearing the spatial filter by setting it to None.  Now
   52.53 +        # should indicate that there are 3 features in the Layer.
   52.54 +        lyr.spatial_filter = None
   52.55 +        self.assertEqual(3, len(lyr))
   52.56 +        
   52.57  def suite():
   52.58      s = unittest.TestSuite()
   52.59      s.addTest(unittest.makeSuite(DataSourceTest))
    53.1 --- a/django/contrib/gis/gdal/tests/test_geom.py	Sat Sep 12 18:53:56 2009 -0500
    53.2 +++ b/django/contrib/gis/gdal/tests/test_geom.py	Fri Nov 13 11:27:59 2009 -0600
    53.3 @@ -319,6 +319,18 @@
    53.4              self.assertAlmostEqual(trans.x, p.x, prec)
    53.5              self.assertAlmostEqual(trans.y, p.y, prec)
    53.6  
    53.7 +    def test09c_transform_dim(self):
    53.8 +        "Testing coordinate dimension is the same on transformed geometries."
    53.9 +        ls_orig = OGRGeometry('LINESTRING(-104.609 38.255)', 4326)
   53.10 +        ls_trans = OGRGeometry('LINESTRING(992385.4472045 481455.4944650)', 2774)
   53.11 +        
   53.12 +        prec = 3
   53.13 +        ls_orig.transform(ls_trans.srs)
   53.14 +        # Making sure the coordinate dimension is still 2D.
   53.15 +        self.assertEqual(2, ls_orig.coord_dim)
   53.16 +        self.assertAlmostEqual(ls_trans.x[0], ls_orig.x[0], prec)
   53.17 +        self.assertAlmostEqual(ls_trans.y[0], ls_orig.y[0], prec)
   53.18 +
   53.19      def test10_difference(self):
   53.20          "Testing difference()."
   53.21          for i in xrange(len(topology_geoms)):
    54.1 --- a/django/contrib/gis/geos/geometry.py	Sat Sep 12 18:53:56 2009 -0500
    54.2 +++ b/django/contrib/gis/geos/geometry.py	Fri Nov 13 11:27:59 2009 -0600
    54.3 @@ -357,33 +357,52 @@
    54.4      #### Output Routines ####
    54.5      @property
    54.6      def ewkt(self):
    54.7 -        "Returns the EWKT (WKT + SRID) of the Geometry."
    54.8 +        """
    54.9 +        Returns the EWKT (WKT + SRID) of the Geometry.  Note that Z values
   54.10 +        are *not* included in this representation because GEOS does not yet
   54.11 +        support serializing them.
   54.12 +        """
   54.13          if self.get_srid(): return 'SRID=%s;%s' % (self.srid, self.wkt)
   54.14          else: return self.wkt
   54.15  
   54.16      @property
   54.17      def wkt(self):
   54.18 -        "Returns the WKT (Well-Known Text) of the Geometry."
   54.19 +        "Returns the WKT (Well-Known Text) representation of this Geometry."
   54.20          return wkt_w().write(self)
   54.21  
   54.22      @property
   54.23      def hex(self):
   54.24          """
   54.25          Returns the HEX of the Geometry -- please note that the SRID is not
   54.26 -        included in this representation, because the GEOS C library uses
   54.27 -        -1 by default, even if the SRID is set.
   54.28 +        included in this representation, because it is not a part of the
   54.29 +        OGC specification (use the `hexewkb` property instead).
   54.30          """
   54.31          # A possible faster, all-python, implementation:
   54.32          #  str(self.wkb).encode('hex')
   54.33          return wkb_w().write_hex(self)
   54.34  
   54.35      @property
   54.36 +    def hexewkb(self):
   54.37 +        """
   54.38 +        Returns the HEXEWKB of this Geometry.  This is an extension of the WKB
   54.39 +        specification that includes SRID and Z values taht are a part of this
   54.40 +        geometry.
   54.41 +        """
   54.42 +        if self.hasz:
   54.43 +            if not GEOS_PREPARE:
   54.44 +                # See: http://trac.osgeo.org/geos/ticket/216
   54.45 +                raise GEOSException('Upgrade GEOS to 3.1 to get valid 3D HEXEWKB.')               
   54.46 +            return ewkb_w3d().write_hex(self)
   54.47 +        else:
   54.48 +            return ewkb_w().write_hex(self)
   54.49 +
   54.50 +    @property
   54.51      def json(self):
   54.52          """
   54.53          Returns GeoJSON representation of this Geometry if GDAL 1.5+
   54.54          is installed.
   54.55          """
   54.56 -        if gdal.GEOJSON: 
   54.57 +        if gdal.GEOJSON:
   54.58              return self.ogr.json
   54.59          else:
   54.60              raise GEOSException('GeoJSON output only supported on GDAL 1.5+.')
   54.61 @@ -391,10 +410,29 @@
   54.62  
   54.63      @property
   54.64      def wkb(self):
   54.65 -        "Returns the WKB of the Geometry as a buffer."
   54.66 +        """
   54.67 +        Returns the WKB (Well-Known Binary) representation of this Geometry
   54.68 +        as a Python buffer.  SRID and Z values are not included, use the
   54.69 +        `ewkb` property instead.
   54.70 +        """
   54.71          return wkb_w().write(self)
   54.72  
   54.73      @property
   54.74 +    def ewkb(self):
   54.75 +        """
   54.76 +        Return the EWKB representation of this Geometry as a Python buffer.
   54.77 +        This is an extension of the WKB specification that includes any SRID
   54.78 +        and Z values that are a part of this geometry.
   54.79 +        """
   54.80 +        if self.hasz:
   54.81 +            if not GEOS_PREPARE:
   54.82 +                # See: http://trac.osgeo.org/geos/ticket/216
   54.83 +                raise GEOSException('Upgrade GEOS to 3.1 to get valid 3D EWKB.')
   54.84 +            return ewkb_w3d().write(self)
   54.85 +        else:
   54.86 +            return ewkb_w().write(self)
   54.87 +
   54.88 +    @property
   54.89      def kml(self):
   54.90          "Returns the KML representation of this Geometry."
   54.91          gtype = self.geom_type
   54.92 @@ -617,7 +655,7 @@
   54.93                  }
   54.94  
   54.95  # Similarly, import the GEOS I/O instances here to avoid conflicts.
   54.96 -from django.contrib.gis.geos.io import wkt_r, wkt_w, wkb_r, wkb_w
   54.97 +from django.contrib.gis.geos.io import wkt_r, wkt_w, wkb_r, wkb_w, ewkb_w, ewkb_w3d
   54.98  
   54.99  # If supported, import the PreparedGeometry class.
  54.100  if GEOS_PREPARE:
    55.1 --- a/django/contrib/gis/geos/io.py	Sat Sep 12 18:53:56 2009 -0500
    55.2 +++ b/django/contrib/gis/geos/io.py	Fri Nov 13 11:27:59 2009 -0600
    55.3 @@ -15,7 +15,7 @@
    55.4      "Base class for GEOS I/O objects."
    55.5      def __init__(self, managed=False):
    55.6          # Getting the pointer with the constructor.
    55.7 -        self.ptr = self.constructor()
    55.8 +        self.ptr = self._constructor()
    55.9          # The managed flag indicates whether or not the memory
   55.10          # for this IO object will be cleaned up by __del__().
   55.11          self.managed = managed
   55.12 @@ -25,15 +25,16 @@
   55.13          if not self.managed: self._delete()
   55.14  
   55.15      def _delete(self):
   55.16 -        if self._ptr: self.destructor(self._ptr)
   55.17 +        # Cleaning up with the appropriate destructor.
   55.18 +        if self._ptr: self._destructor(self._ptr)
   55.19  
   55.20  ### WKT Reading and Writing objects ###
   55.21  
   55.22  # Non-public class for internal use because its `read` method returns
   55.23  # _pointers_ instead of a GEOSGeometry object.
   55.24  class _WKTReader(IOBase):
   55.25 -    constructor = capi.wkt_reader_create
   55.26 -    destructor = capi.wkt_reader_destroy
   55.27 +    _constructor = capi.wkt_reader_create
   55.28 +    _destructor = capi.wkt_reader_destroy
   55.29      ptr_type = capi.WKT_READ_PTR
   55.30  
   55.31      def read(self, wkt):
   55.32 @@ -46,8 +47,8 @@
   55.33          return GEOSGeometry(super(WKTReader, self).read(wkt))
   55.34  
   55.35  class WKTWriter(IOBase):
   55.36 -    constructor = capi.wkt_writer_create
   55.37 -    destructor = capi.wkt_writer_destroy
   55.38 +    _constructor = capi.wkt_writer_create
   55.39 +    _destructor = capi.wkt_writer_destroy
   55.40      ptr_type = capi.WKT_WRITE_PTR
   55.41  
   55.42      def write(self, geom):
   55.43 @@ -58,8 +59,8 @@
   55.44  
   55.45  # Non-public class for the same reason as _WKTReader above.
   55.46  class _WKBReader(IOBase):
   55.47 -    constructor = capi.wkb_reader_create
   55.48 -    destructor = capi.wkb_reader_destroy
   55.49 +    _constructor = capi.wkb_reader_create
   55.50 +    _destructor = capi.wkb_reader_destroy
   55.51      ptr_type = capi.WKB_READ_PTR
   55.52  
   55.53      def read(self, wkb):
   55.54 @@ -78,8 +79,8 @@
   55.55          return GEOSGeometry(super(WKBReader, self).read(wkb))
   55.56  
   55.57  class WKBWriter(IOBase):
   55.58 -    constructor = capi.wkb_writer_create
   55.59 -    destructor = capi.wkb_writer_destroy
   55.60 +    _constructor = capi.wkb_writer_create
   55.61 +    _destructor = capi.wkb_writer_destroy
   55.62      ptr_type = capi.WKB_WRITE_PTR
   55.63  
   55.64      def write(self, geom):
   55.65 @@ -129,6 +130,8 @@
   55.66      wkt_w = None
   55.67      wkb_r = None
   55.68      wkb_w = None
   55.69 +    ewkb_w = None
   55.70 +    ewkb_w3d = None
   55.71  
   55.72  thread_context = ThreadLocalIO()
   55.73  
   55.74 @@ -151,3 +154,16 @@
   55.75     if not thread_context.wkb_w:
   55.76         thread_context.wkb_w = WKBWriter(managed=True)
   55.77     return thread_context.wkb_w
   55.78 +
   55.79 +def ewkb_w():
   55.80 +    if not thread_context.ewkb_w:
   55.81 +        thread_context.ewkb_w = WKBWriter(managed=True)
   55.82 +        thread_context.ewkb_w.srid = True
   55.83 +    return thread_context.ewkb_w
   55.84 +
   55.85 +def ewkb_w3d():
   55.86 +    if not thread_context.ewkb_w3d:
   55.87 +        thread_context.ewkb_w3d = WKBWriter(managed=True)
   55.88 +        thread_context.ewkb_w3d.srid = True
   55.89 +        thread_context.ewkb_w3d.outdim = 3
   55.90 +    return thread_context.ewkb_w3d
    56.1 --- a/django/contrib/gis/geos/prototypes/geom.py	Sat Sep 12 18:53:56 2009 -0500
    56.2 +++ b/django/contrib/gis/geos/prototypes/geom.py	Fri Nov 13 11:27:59 2009 -0600
    56.3 @@ -68,7 +68,7 @@
    56.4  from_wkb = bin_constructor(GEOSFunc('GEOSGeomFromWKB_buf'))
    56.5  from_wkt = geom_output(GEOSFunc('GEOSGeomFromWKT'), [c_char_p])
    56.6  
    56.7 -# Output routines
    56.8 +# Deprecated output routines
    56.9  to_hex = bin_output(GEOSFunc('GEOSGeomToHEX_buf'))
   56.10  to_wkb = bin_output(GEOSFunc('GEOSGeomToWKB_buf'))
   56.11  to_wkt = string_from_geom(GEOSFunc('GEOSGeomToWKT'))
    57.1 --- a/django/contrib/gis/geos/tests/test_geos.py	Sat Sep 12 18:53:56 2009 -0500
    57.2 +++ b/django/contrib/gis/geos/tests/test_geos.py	Fri Nov 13 11:27:59 2009 -0600
    57.3 @@ -71,6 +71,49 @@
    57.4              geom = fromstr(g.wkt)
    57.5              self.assertEqual(g.hex, geom.hex)
    57.6  
    57.7 +    def test01b_hexewkb(self):
    57.8 +        "Testing (HEX)EWKB output."
    57.9 +        from binascii import a2b_hex
   57.10 +
   57.11 +        pnt_2d = Point(0, 1, srid=4326)
   57.12 +        pnt_3d = Point(0, 1, 2, srid=4326)
   57.13 +        
   57.14 +        # OGC-compliant HEX will not have SRID nor Z value.
   57.15 +        self.assertEqual(ogc_hex, pnt_2d.hex)
   57.16 +        self.assertEqual(ogc_hex, pnt_3d.hex)
   57.17 +
   57.18 +        # HEXEWKB should be appropriate for its dimension -- have to use an
   57.19 +        # a WKBWriter w/dimension set accordingly, else GEOS will insert
   57.20 +        # garbage into 3D coordinate if there is none.  Also, GEOS has a
   57.21 +        # a bug in versions prior to 3.1 that puts the X coordinate in
   57.22 +        # place of Z; an exception should be raised on those versions.
   57.23 +        self.assertEqual(hexewkb_2d, pnt_2d.hexewkb)
   57.24 +        if GEOS_PREPARE:
   57.25 +            self.assertEqual(hexewkb_3d, pnt_3d.hexewkb)
   57.26 +            self.assertEqual(True, GEOSGeometry(hexewkb_3d).hasz)
   57.27 +        else:
   57.28 +            try:
   57.29 +                hexewkb = pnt_3d.hexewkb
   57.30 +            except GEOSException:
   57.31 +                pass
   57.32 +            else:
   57.33 +                self.fail('Should have raised GEOSException.')
   57.34 +
   57.35 +        # Same for EWKB.
   57.36 +        self.assertEqual(buffer(a2b_hex(hexewkb_2d)), pnt_2d.ewkb)
   57.37 +        if GEOS_PREPARE:
   57.38 +            self.assertEqual(buffer(a2b_hex(hexewkb_3d)), pnt_3d.ewkb)
   57.39 +        else:
   57.40 +            try:
   57.41 +                ewkb = pnt_3d.ewkb
   57.42 +            except GEOSException:
   57.43 +                pass
   57.44 +            else:
   57.45 +                self.fail('Should have raised GEOSException')
   57.46 +        
   57.47 +        # Redundant sanity check.
   57.48 +        self.assertEqual(4326, GEOSGeometry(hexewkb_2d).srid)
   57.49 +
   57.50      def test01c_kml(self):
   57.51          "Testing KML output."
   57.52          for tg in wkt_out:
    58.1 --- a/django/contrib/gis/tests/geoapp/test_regress.py	Sat Sep 12 18:53:56 2009 -0500
    58.2 +++ b/django/contrib/gis/tests/geoapp/test_regress.py	Fri Nov 13 11:27:59 2009 -0600
    58.3 @@ -1,6 +1,6 @@
    58.4  import os, unittest
    58.5  from django.contrib.gis.db.backend import SpatialBackend
    58.6 -from django.contrib.gis.tests.utils import no_mysql, no_oracle, no_postgis
    58.7 +from django.contrib.gis.tests.utils import no_mysql, no_oracle, no_postgis, no_spatialite
    58.8  from django.contrib.gis.shortcuts import render_to_kmz
    58.9  from models import City
   58.10  
   58.11 @@ -27,3 +27,12 @@
   58.12                    }]
   58.13          kmz = render_to_kmz('gis/kml/placemarks.kml', {'places' : places})
   58.14  
   58.15 +    @no_spatialite
   58.16 +    @no_mysql
   58.17 +    def test03_extent(self):
   58.18 +        "Testing `extent` on a table with a single point, see #11827."
   58.19 +        pnt = City.objects.get(name='Pueblo').point
   58.20 +        ref_ext = (pnt.x, pnt.y, pnt.x, pnt.y)
   58.21 +        extent = City.objects.filter(name='Pueblo').extent()
   58.22 +        for ref_val, val in zip(ref_ext, extent):
   58.23 +            self.assertAlmostEqual(ref_val, val, 4)
    59.1 --- a/django/contrib/gis/tests/geometries.py	Sat Sep 12 18:53:56 2009 -0500
    59.2 +++ b/django/contrib/gis/tests/geometries.py	Fri Nov 13 11:27:59 2009 -0600
    59.3 @@ -171,3 +171,10 @@
    59.4                         not_equal=True,
    59.5                         ),
    59.6                )
    59.7 +
    59.8 +# For testing HEX(EWKB).
    59.9 +ogc_hex = '01010000000000000000000000000000000000F03F'
   59.10 +# `SELECT ST_AsHEXEWKB(ST_GeomFromText('POINT(0 1)', 4326));`
   59.11 +hexewkb_2d = '0101000020E61000000000000000000000000000000000F03F'
   59.12 +# `SELECT ST_AsHEXEWKB(ST_GeomFromEWKT('SRID=4326;POINT(0 1 2)'));`
   59.13 +hexewkb_3d = '01010000A0E61000000000000000000000000000000000F03F0000000000000040'
    60.1 --- a/django/contrib/gis/tests/layermap/models.py	Sat Sep 12 18:53:56 2009 -0500
    60.2 +++ b/django/contrib/gis/tests/layermap/models.py	Fri Nov 13 11:27:59 2009 -0600
    60.3 @@ -29,6 +29,20 @@
    60.4      path = models.LineStringField()
    60.5      objects = models.GeoManager()
    60.6  
    60.7 +# Same as `City` above, but for testing model inheritance.
    60.8 +class CityBase(models.Model):
    60.9 +    name = models.CharField(max_length=25)
   60.10 +    population = models.IntegerField()
   60.11 +    density = models.DecimalField(max_digits=7, decimal_places=1)
   60.12 +    point = models.PointField()
   60.13 +    objects = models.GeoManager()
   60.14 +
   60.15 +class ICity1(CityBase):
   60.16 +    dt = models.DateField()
   60.17 +    
   60.18 +class ICity2(ICity1):
   60.19 +    dt_time = models.DateTimeField(auto_now=True)
   60.20 +
   60.21  # Mapping dictionaries for the models above.
   60.22  co_mapping = {'name' : 'Name',
   60.23                'state' : {'name' : 'State'}, # ForeignKey's use another mapping dictionary for the _related_ Model (State in this case).
    61.1 --- a/django/contrib/gis/tests/layermap/tests.py	Sat Sep 12 18:53:56 2009 -0500
    61.2 +++ b/django/contrib/gis/tests/layermap/tests.py	Fri Nov 13 11:27:59 2009 -0600
    61.3 @@ -1,7 +1,7 @@
    61.4  import os, unittest
    61.5  from copy import copy
    61.6  from decimal import Decimal
    61.7 -from models import City, County, CountyFeat, Interstate, State, city_mapping, co_mapping, cofeat_mapping, inter_mapping
    61.8 +from models import City, County, CountyFeat, Interstate, ICity1, ICity2, State, city_mapping, co_mapping, cofeat_mapping, inter_mapping
    61.9  from django.contrib.gis.db.backend import SpatialBackend
   61.10  from django.contrib.gis.utils.layermapping import LayerMapping, LayerMapError, InvalidDecimal, MissingForeignKey
   61.11  from django.contrib.gis.gdal import DataSource
   61.12 @@ -242,6 +242,26 @@
   61.13              lm.save(step=st, strict=True)
   61.14              self.county_helper(county_feat=False)
   61.15  
   61.16 +    def test06_model_inheritance(self):
   61.17 +        "Tests LayerMapping on inherited models.  See #12093."
   61.18 +        icity_mapping = {'name' : 'Name',
   61.19 +                         'population' : 'Population',
   61.20 +                         'density' : 'Density',
   61.21 +                         'point' : 'POINT',
   61.22 +                         'dt' : 'Created',
   61.23 +                         }
   61.24 +
   61.25 +        # Parent model has geometry field.
   61.26 +        lm1 = LayerMapping(ICity1, city_shp, icity_mapping)
   61.27 +        lm1.save()
   61.28 +
   61.29 +        # Grandparent has geometry field.
   61.30 +        lm2 = LayerMapping(ICity2, city_shp, icity_mapping)
   61.31 +        lm2.save()
   61.32 +
   61.33 +        self.assertEqual(6, ICity1.objects.count())
   61.34 +        self.assertEqual(3, ICity2.objects.count())
   61.35 +        
   61.36  def suite():
   61.37      s = unittest.TestSuite()
   61.38      s.addTest(unittest.makeSuite(LayerMapTest))
    62.1 --- a/django/contrib/gis/utils/layermapping.py	Sat Sep 12 18:53:56 2009 -0500
    62.2 +++ b/django/contrib/gis/utils/layermapping.py	Fri Nov 13 11:27:59 2009 -0600
    62.3 @@ -514,16 +514,26 @@
    62.4      def geometry_column(self):
    62.5          "Returns the GeometryColumn model associated with the geographic column."
    62.6          from django.contrib.gis.models import GeometryColumns
    62.7 -        # Getting the GeometryColumn object.
    62.8 +        # Use the `get_field_by_name` on the model's options so that we
    62.9 +        # get the correct model if there's model inheritance -- otherwise
   62.10 +        # the returned model is None.
   62.11 +        opts = self.model._meta
   62.12 +        fld, model, direct, m2m = opts.get_field_by_name(self.geom_field)
   62.13 +        if model is None: model = self.model
   62.14 +
   62.15 +        # Trying to get the `GeometryColumns` object that corresponds to the
   62.16 +        # the geometry field.
   62.17          try:
   62.18 -            db_table = self.model._meta.db_table
   62.19 -            geo_col = self.geom_field
   62.20 +            db_table = model._meta.db_table
   62.21 +            geo_col = fld.column
   62.22 +
   62.23              if SpatialBackend.oracle:
   62.24                  # Making upper case for Oracle.
   62.25                  db_table = db_table.upper()
   62.26                  geo_col = geo_col.upper()
   62.27 -            gc_kwargs = {GeometryColumns.table_name_col() : db_table,
   62.28 -                         GeometryColumns.geom_col_name() : geo_col,
   62.29 +
   62.30 +            gc_kwargs = { GeometryColumns.table_name_col() : db_table,
   62.31 +                          GeometryColumns.geom_col_name() : geo_col,
   62.32                           }
   62.33              return GeometryColumns.objects.get(**gc_kwargs)
   62.34          except Exception, msg:
    63.1 --- a/django/contrib/localflavor/fr/forms.py	Sat Sep 12 18:53:56 2009 -0500
    63.2 +++ b/django/contrib/localflavor/fr/forms.py	Fri Nov 13 11:27:59 2009 -0600
    63.3 @@ -27,7 +27,7 @@
    63.4      '0X XX XX XX XX'.
    63.5      """
    63.6      default_error_messages = {
    63.7 -        'invalid': u'Phone numbers must be in 0X XX XX XX XX format.',
    63.8 +        'invalid': _('Phone numbers must be in 0X XX XX XX XX format.'),
    63.9      }
   63.10  
   63.11      def clean(self, value):
    64.1 --- a/django/core/context_processors.py	Sat Sep 12 18:53:56 2009 -0500
    64.2 +++ b/django/core/context_processors.py	Fri Nov 13 11:27:59 2009 -0600
    64.3 @@ -8,6 +8,8 @@
    64.4  """
    64.5  
    64.6  from django.conf import settings
    64.7 +from django.middleware.csrf import get_token
    64.8 +from django.utils.functional import lazy, memoize, SimpleLazyObject
    64.9  
   64.10  def auth(request):
   64.11      """
   64.12 @@ -17,17 +19,46 @@
   64.13      If there is no 'user' attribute in the request, uses AnonymousUser (from
   64.14      django.contrib.auth).
   64.15      """
   64.16 -    if hasattr(request, 'user'):
   64.17 -        user = request.user
   64.18 -    else:
   64.19 -        from django.contrib.auth.models import AnonymousUser
   64.20 -        user = AnonymousUser()
   64.21 +    # If we access request.user, request.session is accessed, which results in
   64.22 +    # 'Vary: Cookie' being sent in every request that uses this context
   64.23 +    # processor, which can easily be every request on a site if
   64.24 +    # TEMPLATE_CONTEXT_PROCESSORS has this context processor added.  This kills
   64.25 +    # the ability to cache.  So, we carefully ensure these attributes are lazy.
   64.26 +    # We don't use django.utils.functional.lazy() for User, because that
   64.27 +    # requires knowing the class of the object we want to proxy, which could
   64.28 +    # break with custom auth backends.  LazyObject is a less complete but more
   64.29 +    # flexible solution that is a good enough wrapper for 'User'.
   64.30 +    def get_user():
   64.31 +        if hasattr(request, 'user'):
   64.32 +            return request.user
   64.33 +        else:
   64.34 +            from django.contrib.auth.models import AnonymousUser
   64.35 +            return AnonymousUser()
   64.36 +
   64.37      return {
   64.38 -        'user': user,
   64.39 -        'messages': user.get_and_delete_messages(),
   64.40 -        'perms': PermWrapper(user),
   64.41 +        'user': SimpleLazyObject(get_user),
   64.42 +        'messages': lazy(memoize(lambda: get_user().get_and_delete_messages(), {}, 0), list)(),
   64.43 +        'perms':  lazy(lambda: PermWrapper(get_user()), PermWrapper)(),
   64.44      }
   64.45  
   64.46 +def csrf(request):
   64.47 +    """
   64.48 +    Context processor that provides a CSRF token, or the string 'NOTPROVIDED' if
   64.49 +    it has not been provided by either a view decorator or the middleware
   64.50 +    """
   64.51 +    def _get_val():
   64.52 +        token = get_token(request)
   64.53 +        if token is None:
   64.54 +            # In order to be able to provide debugging info in the
   64.55 +            # case of misconfiguration, we use a sentinel value
   64.56 +            # instead of returning an empty dict.
   64.57 +            return 'NOTPROVIDED'
   64.58 +        else:
   64.59 +            return token
   64.60 +    _get_val = lazy(_get_val, str)
   64.61 +
   64.62 +    return {'csrf_token': _get_val() }
   64.63 +
   64.64  def debug(request):
   64.65      "Returns context variables helpful for debugging."
   64.66      context_extras = {}
   64.67 @@ -79,7 +110,7 @@
   64.68  
   64.69      def __getitem__(self, module_name):
   64.70          return PermLookupDict(self.user, module_name)
   64.71 -        
   64.72 +
   64.73      def __iter__(self):
   64.74          # I am large, I contain multitudes.
   64.75          raise TypeError("PermWrapper is not iterable.")
    65.1 --- a/django/core/files/storage.py	Sat Sep 12 18:53:56 2009 -0500
    65.2 +++ b/django/core/files/storage.py	Fri Nov 13 11:27:59 2009 -0600
    65.3 @@ -118,10 +118,6 @@
    65.4          """
    65.5          raise NotImplementedError()
    65.6  
    65.7 -    # Needed by django.utils.functional.LazyObject (via DefaultStorage).
    65.8 -    def get_all_members(self):
    65.9 -        return self.__members__
   65.10 -
   65.11  class FileSystemStorage(Storage):
   65.12      """
   65.13      Standard filesystem storage
    66.1 --- a/django/core/mail.py	Sat Sep 12 18:53:56 2009 -0500
    66.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    66.3 @@ -1,426 +0,0 @@
    66.4 -"""
    66.5 -Tools for sending email.
    66.6 -"""
    66.7 -
    66.8 -import mimetypes
    66.9 -import os
   66.10 -import smtplib
   66.11 -import socket
   66.12 -import time
   66.13 -import random
   66.14 -from email import Charset, Encoders
   66.15 -from email.MIMEText import MIMEText
   66.16 -from email.MIMEMultipart import MIMEMultipart
   66.17 -from email.MIMEBase import MIMEBase
   66.18 -from email.Header import Header
   66.19 -from email.Utils import formatdate, parseaddr, formataddr
   66.20 -
   66.21 -from django.conf import settings
   66.22 -from django.utils.encoding import smart_str, force_unicode
   66.23 -
   66.24 -# Don't BASE64-encode UTF-8 messages so that we avoid unwanted attention from
   66.25 -# some spam filters.
   66.26 -Charset.add_charset('utf-8', Charset.SHORTEST, Charset.QP, 'utf-8')
   66.27 -
   66.28 -# Default MIME type to use on attachments (if it is not explicitly given
   66.29 -# and cannot be guessed).
   66.30 -DEFAULT_ATTACHMENT_MIME_TYPE = 'application/octet-stream'
   66.31 -
   66.32 -# Cache the hostname, but do it lazily: socket.getfqdn() can take a couple of
   66.33 -# seconds, which slows down the restart of the server.
   66.34 -class CachedDnsName(object):
   66.35 -    def __str__(self):
   66.36 -        return self.get_fqdn()
   66.37 -
   66.38 -    def get_fqdn(self):
   66.39 -        if not hasattr(self, '_fqdn'):
   66.40 -            self._fqdn = socket.getfqdn()
   66.41 -        return self._fqdn
   66.42 -
   66.43 -DNS_NAME = CachedDnsName()
   66.44 -
   66.45 -# Copied from Python standard library, with the following modifications:
   66.46 -# * Used cached hostname for performance.
   66.47 -# * Added try/except to support lack of getpid() in Jython (#5496).
   66.48 -def make_msgid(idstring=None):
   66.49 -    """Returns a string suitable for RFC 2822 compliant Message-ID, e.g:
   66.50 -
   66.51 -    <20020201195627.33539.96671@nightshade.la.mastaler.com>
   66.52 -
   66.53 -    Optional idstring if given is a string used to strengthen the
   66.54 -    uniqueness of the message id.
   66.55 -    """
   66.56 -    timeval = time.time()
   66.57 -    utcdate = time.strftime('%Y%m%d%H%M%S', time.gmtime(timeval))
   66.58 -    try:
   66.59 -        pid = os.getpid()
   66.60 -    except AttributeError:
   66.61 -        # No getpid() in Jython, for example.
   66.62 -        pid = 1
   66.63 -    randint = random.randrange(100000)
   66.64 -    if idstring is None:
   66.65 -        idstring = ''
   66.66 -    else:
   66.67 -        idstring = '.' + idstring
   66.68 -    idhost = DNS_NAME
   66.69 -    msgid = '<%s.%s.%s%s@%s>' % (utcdate, pid, randint, idstring, idhost)
   66.70 -    return msgid
   66.71 -
   66.72 -class BadHeaderError(ValueError):
   66.73 -    pass
   66.74 -
   66.75 -def forbid_multi_line_headers(name, val):
   66.76 -    """Forbids multi-line headers, to prevent header injection."""
   66.77 -    val = force_unicode(val)
   66.78 -    if '\n' in val or '\r' in val:
   66.79 -        raise BadHeaderError("Header values can't contain newlines (got %r for header %r)" % (val, name))
   66.80 -    try:
   66.81 -        val = val.encode('ascii')
   66.82 -    except UnicodeEncodeError:
   66.83 -        if name.lower() in ('to', 'from', 'cc'):
   66.84 -            result = []
   66.85 -            for item in val.split(', '):
   66.86 -                nm, addr = parseaddr(item)
   66.87 -                nm = str(Header(nm, settings.DEFAULT_CHARSET))
   66.88 -                result.append(formataddr((nm, str(addr))))
   66.89 -            val = ', '.join(result)
   66.90 -        else:
   66.91 -            val = Header(val, settings.DEFAULT_CHARSET)
   66.92 -    else:
   66.93 -        if name.lower() == 'subject':
   66.94 -            val = Header(val)
   66.95 -    return name, val
   66.96 -
   66.97 -class SafeMIMEText(MIMEText):
   66.98 -    def __setitem__(self, name, val):
   66.99 -        name, val = forbid_multi_line_headers(name, val)
  66.100 -        MIMEText.__setitem__(self, name, val)
  66.101 -
  66.102 -class SafeMIMEMultipart(MIMEMultipart):
  66.103 -    def __setitem__(self, name, val):
  66.104 -        name, val = forbid_multi_line_headers(name, val)
  66.105 -        MIMEMultipart.__setitem__(self, name, val)
  66.106 -
  66.107 -class SMTPConnection(object):
  66.108 -    """
  66.109 -    A wrapper that manages the SMTP network connection.
  66.110 -    """
  66.111 -
  66.112 -    def __init__(self, host=None, port=None, username=None, password=None,
  66.113 -                 use_tls=None, fail_silently=False):
  66.114 -        self.host = host or settings.EMAIL_HOST
  66.115 -        self.port = port or settings.EMAIL_PORT
  66.116 -        self.username = username or settings.EMAIL_HOST_USER
  66.117 -        self.password = password or settings.EMAIL_HOST_PASSWORD
  66.118 -        self.use_tls = (use_tls is not None) and use_tls or settings.EMAIL_USE_TLS
  66.119 -        self.fail_silently = fail_silently
  66.120 -        self.connection = None
  66.121 -
  66.122 -    def open(self):
  66.123 -        """
  66.124 -        Ensures we have a connection to the email server. Returns whether or
  66.125 -        not a new connection was required (True or False).
  66.126 -        """
  66.127 -        if self.connection:
  66.128 -            # Nothing to do if the connection is already open.
  66.129 -            return False
  66.130 -        try:
  66.131 -            # If local_hostname is not specified, socket.getfqdn() gets used.
  66.132 -            # For performance, we use the cached FQDN for local_hostname.
  66.133 -            self.connection = smtplib.SMTP(self.host, self.port,
  66.134 -                                           local_hostname=DNS_NAME.get_fqdn())
  66.135 -            if self.use_tls:
  66.136 -                self.connection.ehlo()
  66.137 -                self.connection.starttls()
  66.138 -                self.connection.ehlo()
  66.139 -            if self.username and self.password:
  66.140 -                self.connection.login(self.username, self.password)
  66.141 -            return True
  66.142 -        except:
  66.143 -            if not self.fail_silently:
  66.144 -                raise
  66.145 -
  66.146 -    def close(self):
  66.147 -        """Closes the connection to the email server."""
  66.148 -        try:
  66.149 -            try:
  66.150 -                self.connection.quit()
  66.151 -            except socket.sslerror:
  66.152 -                # This happens when calling quit() on a TLS connection
  66.153 -                # sometimes.
  66.154 -                self.connection.close()
  66.155 -            except:
  66.156 -                if self.fail_silently:
  66.157 -                    return
  66.158 -                raise
  66.159 -        finally:
  66.160 -            self.connection = None
  66.161 -
  66.162 -    def send_messages(self, email_messages):
  66.163 -        """
  66.164 -        Sends one or more EmailMessage objects and returns the number of email
  66.165 -        messages sent.
  66.166 -        """
  66.167 -        if not email_messages:
  66.168 -            return
  66.169 -        new_conn_created = self.open()
  66.170 -        if not self.connection:
  66.171 -            # We failed silently on open(). Trying to send would be pointless.
  66.172 -            return
  66.173 -        num_sent = 0
  66.174 -        for message in email_messages:
  66.175 -            sent = self._send(message)
  66.176 -            if sent:
  66.177 -                num_sent += 1
  66.178 -        if new_conn_created:
  66.179 -            self.close()
  66.180 -        return num_sent
  66.181 -
  66.182 -    def _send(self, email_message):
  66.183 -        """A helper method that does the actual sending."""
  66.184 -        if not email_message.recipients():
  66.185 -            return False
  66.186 -        try:
  66.187 -            self.connection.sendmail(email_message.from_email,
  66.188 -                    email_message.recipients(),
  66.189 -                    email_message.message().as_string())
  66.190 -        except:
  66.191 -            if not self.fail_silently:
  66.192 -                raise
  66.193 -            return False
  66.194 -        return True
  66.195 -
  66.196 -class EmailMessage(object):
  66.197 -    """
  66.198 -    A container for email information.
  66.199 -    """
  66.200 -    content_subtype = 'plain'
  66.201 -    mixed_subtype = 'mixed'
  66.202 -    encoding = None     # None => use settings default
  66.203 -
  66.204 -    def __init__(self, subject='', body='', from_email=None, to=None, bcc=None,
  66.205 -            connection=None, attachments=None, headers=None):
  66.206 -        """
  66.207 -        Initialize a single email message (which can be sent to multiple
  66.208 -        recipients).
  66.209 -
  66.210 -        All strings used to create the message can be unicode strings (or UTF-8
  66.211 -        bytestrings). The SafeMIMEText class will handle any necessary encoding
  66.212 -        conversions.
  66.213 -        """
  66.214 -        if to:
  66.215 -            assert not isinstance(to, basestring), '"to" argument must be a list or tuple'
  66.216 -            self.to = list(to)
  66.217 -        else:
  66.218 -            self.to = []
  66.219 -        if bcc:
  66.220 -            assert not isinstance(bcc, basestring), '"bcc" argument must be a list or tuple'
  66.221 -            self.bcc = list(bcc)
  66.222 -        else:
  66.223 -            self.bcc = []
  66.224 -        self.from_email = from_email or settings.DEFAULT_FROM_EMAIL
  66.225 -        self.subject = subject
  66.226 -        self.body = body
  66.227 -        self.attachments = attachments or []
  66.228 -        self.extra_headers = headers or {}
  66.229 -        self.connection = connection
  66.230 -
  66.231 -    def get_connection(self, fail_silently=False):
  66.232 -        if not self.connection:
  66.233 -            self.connection = SMTPConnection(fail_silently=fail_silently)
  66.234 -        return self.connection
  66.235 -
  66.236 -    def message(self):
  66.237 -        encoding = self.encoding or settings.DEFAULT_CHARSET
  66.238 -        msg = SafeMIMEText(smart_str(self.body, settings.DEFAULT_CHARSET),
  66.239 -                           self.content_subtype, encoding)
  66.240 -        msg = self._create_message(msg)
  66.241 -        msg['Subject'] = self.subject
  66.242 -        msg['From'] = self.extra_headers.pop('From', self.from_email)
  66.243 -        msg['To'] = ', '.join(self.to)
  66.244 -
  66.245 -        # Email header names are case-insensitive (RFC 2045), so we have to
  66.246 -        # accommodate that when doing comparisons.
  66.247 -        header_names = [key.lower() for key in self.extra_headers]
  66.248 -        if 'date' not in header_names:
  66.249 -            msg['Date'] = formatdate()
  66.250 -        if 'message-id' not in header_names:
  66.251 -            msg['Message-ID'] = make_msgid()
  66.252 -        for name, value in self.extra_headers.items():
  66.253 -            msg[name] = value
  66.254 -        return msg
  66.255 -
  66.256 -    def recipients(self):
  66.257 -        """
  66.258 -        Returns a list of all recipients of the email (includes direct
  66.259 -        addressees as well as Bcc entries).
  66.260 -        """
  66.261 -        return self.to + self.bcc
  66.262 -
  66.263 -    def send(self, fail_silently=False):
  66.264 -        """Sends the email message."""
  66.265 -        if not self.recipients():
  66.266 -            # Don't bother creating the network connection if there's nobody to
  66.267 -            # send to.
  66.268 -            return 0
  66.269 -        return self.get_connection(fail_silently).send_messages([self])
  66.270 -
  66.271 -    def attach(self, filename=None, content=None, mimetype=None):
  66.272 -        """
  66.273 -        Attaches a file with the given filename and content. The filename can
  66.274 -        be omitted and the mimetype is guessed, if not provided.
  66.275 -
  66.276 -        If the first parameter is a MIMEBase subclass it is inserted directly
  66.277 -        into the resulting message attachments.
  66.278 -        """
  66.279 -        if isinstance(filename, MIMEBase):
  66.280 -            assert content == mimetype == None
  66.281 -            self.attachments.append(filename)
  66.282 -        else:
  66.283 -            assert content is not None
  66.284 -            self.attachments.append((filename, content, mimetype))
  66.285 -
  66.286 -    def attach_file(self, path, mimetype=None):
  66.287 -        """Attaches a file from the filesystem."""
  66.288 -        filename = os.path.basename(path)
  66.289 -        content = open(path, 'rb').read()
  66.290 -        self.attach(filename, content, mimetype)
  66.291 -
  66.292 -    def _create_message(self, msg):
  66.293 -        return self._create_attachments(msg)
  66.294 -
  66.295 -    def _create_attachments(self, msg):
  66.296 -        if self.attachments:
  66.297 -            body_msg = msg
  66.298 -            msg = SafeMIMEMultipart(_subtype=self.mixed_subtype)
  66.299 -            if self.body:
  66.300 -                msg.attach(body_msg)
  66.301 -            for attachment in self.attachments:
  66.302 -                if isinstance(attachment, MIMEBase):
  66.303 -                    msg.attach(attachment)
  66.304 -                else:
  66.305 -                    msg.attach(self._create_attachment(*attachment))
  66.306 -        return msg
  66.307 -
  66.308 -    def _create_mime_attachment(self, content, mimetype):
  66.309 -        """
  66.310 -        Converts the content, mimetype pair into a MIME attachment object.
  66.311 -        """
  66.312 -        basetype, subtype = mimetype.split('/', 1)
  66.313 -        if basetype == 'text':
  66.314 -            attachment = SafeMIMEText(smart_str(content,
  66.315 -                settings.DEFAULT_CHARSET), subtype, settings.DEFAULT_CHARSET)
  66.316 -        else:
  66.317 -            # Encode non-text attachments with base64.
  66.318 -            attachment = MIMEBase(basetype, subtype)
  66.319 -            attachment.set_payload(content)
  66.320 -            Encoders.encode_base64(attachment)
  66.321 -        return attachment
  66.322 -
  66.323 -    def _create_attachment(self, filename, content, mimetype=None):
  66.324 -        """
  66.325 -        Converts the filename, content, mimetype triple into a MIME attachment
  66.326 -        object.
  66.327 -        """
  66.328 -        if mimetype is None:
  66.329 -            mimetype, _ = mimetypes.guess_type(filename)
  66.330 -            if mimetype is None:
  66.331 -                mimetype = DEFAULT_ATTACHMENT_MIME_TYPE
  66.332 -        attachment = self._create_mime_attachment(content, mimetype)
  66.333 -        if filename:
  66.334 -            attachment.add_header('Content-Disposition', 'attachment',
  66.335 -                                  filename=filename)
  66.336 -        return attachment
  66.337 -
  66.338 -class EmailMultiAlternatives(EmailMessage):
  66.339 -    """
  66.340 -    A version of EmailMessage that makes it easy to send multipart/alternative
  66.341 -    messages. For example, including text and HTML versions of the text is
  66.342 -    made easier.
  66.343 -    """
  66.344 -    alternative_subtype = 'alternative'
  66.345 -
  66.346 -    def __init__(self, subject='', body='', from_email=None, to=None, bcc=None,
  66.347 -            connection=None, attachments=None, headers=None, alternatives=None):
  66.348 -        """
  66.349 -        Initialize a single email message (which can be sent to multiple
  66.350 -        recipients).
  66.351 -
  66.352 -        All strings used to create the message can be unicode strings (or UTF-8
  66.353 -        bytestrings). The SafeMIMEText class will handle any necessary encoding
  66.354 -        conversions.
  66.355 -        """
  66.356 -        super(EmailMultiAlternatives, self).__init__(subject, body, from_email, to, bcc, connection, attachments, headers)
  66.357 -        self.alternatives=alternatives or []
  66.358 -
  66.359 -    def attach_alternative(self, content, mimetype):
  66.360 -        """Attach an alternative content representation."""
  66.361 -        assert content is not None
  66.362 -        assert mimetype is not None
  66.363 -        self.alternatives.append((content, mimetype))
  66.364 -
  66.365 -    def _create_message(self, msg):
  66.366 -        return self._create_attachments(self._create_alternatives(msg))
  66.367 -
  66.368 -    def _create_alternatives(self, msg):
  66.369 -        if self.alternatives:
  66.370 -            body_msg = msg
  66.371 -            msg = SafeMIMEMultipart(_subtype=self.alternative_subtype)
  66.372 -            if self.body:
  66.373 -                msg.attach(body_msg)
  66.374 -            for alternative in self.alternatives:
  66.375 -                msg.attach(self._create_mime_attachment(*alternative))
  66.376 -        return msg
  66.377 -
  66.378 -def send_mail(subject, message, from_email, recipient_list,
  66.379 -              fail_silently=False, auth_user=None, auth_password=None):
  66.380 -    """
  66.381 -    Easy wrapper for sending a single message to a recipient list. All members
  66.382 -    of the recipient list will see the other recipients in the 'To' field.
  66.383 -
  66.384 -    If auth_user is None, the EMAIL_HOST_USER setting is used.
  66.385 -    If auth_password is None, the EMAIL_HOST_PASSWORD setting is used.
  66.386 -
  66.387 -    Note: The API for this method is frozen. New code wanting to extend the
  66.388 -    functionality should use the EmailMessage class directly.
  66.389 -    """
  66.390 -    connection = SMTPConnection(username=auth_user, password=auth_password,
  66.391 -                                fail_silently=fail_silently)
  66.392 -    return EmailMessage(subject, message, from_email, recipient_list,
  66.393 -                        connection=connection).send()
  66.394 -
  66.395 -def send_mass_mail(datatuple, fail_silently=False, auth_user=None,
  66.396 -                   auth_password=None):
  66.397 -    """
  66.398 -    Given a datatuple of (subject, message, from_email, recipient_list), sends
  66.399 -    each message to each recipient list. Returns the number of e-mails sent.
  66.400 -
  66.401 -    If from_email is None, the DEFAULT_FROM_EMAIL setting is used.
  66.402 -    If auth_user and auth_password are set, they're used to log in.
  66.403 -    If auth_user is None, the EMAIL_HOST_USER setting is used.
  66.404 -    If auth_password is None, the EMAIL_HOST_PASSWORD setting is used.
  66.405 -
  66.406 -    Note: The API for this method is frozen. New code wanting to extend the
  66.407 -    functionality should use the EmailMessage class directly.
  66.408 -    """
  66.409 -    connection = SMTPConnection(username=auth_user, password=auth_password,
  66.410 -                                fail_silently=fail_silently)
  66.411 -    messages = [EmailMessage(subject, message, sender, recipient)
  66.412 -                for subject, message, sender, recipient in datatuple]
  66.413 -    return connection.send_messages(messages)
  66.414 -
  66.415 -def mail_admins(subject, message, fail_silently=False):
  66.416 -    """Sends a message to the admins, as defined by the ADMINS setting."""
  66.417 -    if not settings.ADMINS:
  66.418 -        return
  66.419 -    EmailMessage(settings.EMAIL_SUBJECT_PREFIX + subject, message,
  66.420 -                 settings.SERVER_EMAIL, [a[1] for a in settings.ADMINS]
  66.421 -                 ).send(fail_silently=fail_silently)
  66.422 -
  66.423 -def mail_managers(subject, message, fail_silently=False):
  66.424 -    """Sends a message to the managers, as defined by the MANAGERS setting."""
  66.425 -    if not settings.MANAGERS:
  66.426 -        return
  66.427 -    EmailMessage(settings.EMAIL_SUBJECT_PREFIX + subject, message,
  66.428 -                 settings.SERVER_EMAIL, [a[1] for a in settings.MANAGERS]
  66.429 -                 ).send(fail_silently=fail_silently)
    67.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    67.2 +++ b/django/core/mail/__init__.py	Fri Nov 13 11:27:59 2009 -0600
    67.3 @@ -0,0 +1,110 @@
    67.4 +"""
    67.5 +Tools for sending email.
    67.6 +"""
    67.7 +
    67.8 +from django.conf import settings
    67.9 +from django.core.exceptions import ImproperlyConfigured
   67.10 +from django.utils.importlib import import_module
   67.11 +
   67.12 +# Imported for backwards compatibility, and for the sake
   67.13 +# of a cleaner namespace. These symbols used to be in
   67.14 +# django/core/mail.py before the introduction of email
   67.15 +# backends and the subsequent reorganization (See #10355)
   67.16 +from django.core.mail.utils import CachedDnsName, DNS_NAME
   67.17 +from django.core.mail.message import \
   67.18 +    EmailMessage, EmailMultiAlternatives, \
   67.19 +    SafeMIMEText, SafeMIMEMultipart, \
   67.20 +    DEFAULT_ATTACHMENT_MIME_TYPE, make_msgid, \
   67.21 +    BadHeaderError, forbid_multi_line_headers
   67.22 +from django.core.mail.backends.smtp import EmailBackend as _SMTPConnection
   67.23 +
   67.24 +def get_connection(backend=None, fail_silently=False, **kwds):
   67.25 +    """Load an e-mail backend and return an instance of it.
   67.26 +
   67.27 +    If backend is None (default) settings.EMAIL_BACKEND is used.
   67.28 +
   67.29 +    Both fail_silently and other keyword arguments are used in the
   67.30 +    constructor of the backend.
   67.31 +    """
   67.32 +    path = backend or settings.EMAIL_BACKEND
   67.33 +    try:
   67.34 +        mod = import_module(path)
   67.35 +    except ImportError, e:
   67.36 +        raise ImproperlyConfigured(('Error importing email backend %s: "%s"'
   67.37 +                                    % (path, e)))
   67.38 +    try:
   67.39 +        cls = getattr(mod, 'EmailBackend')
   67.40 +    except AttributeError:
   67.41 +        raise ImproperlyConfigured(('Module "%s" does not define a '
   67.42 +                                    '"EmailBackend" class' % path))
   67.43 +    return cls(fail_silently=fail_silently, **kwds)
   67.44 +
   67.45 +
   67.46 +def send_mail(subject, message, from_email, recipient_list,
   67.47 +              fail_silently=False, auth_user=None, auth_password=None,
   67.48 +              connection=None):
   67.49 +    """
   67.50 +    Easy wrapper for sending a single message to a recipient list. All members
   67.51 +    of the recipient list will see the other recipients in the 'To' field.
   67.52 +
   67.53 +    If auth_user is None, the EMAIL_HOST_USER setting is used.
   67.54 +    If auth_password is None, the EMAIL_HOST_PASSWORD setting is used.
   67.55 +
   67.56 +    Note: The API for this method is frozen. New code wanting to extend the
   67.57 +    functionality should use the EmailMessage class directly.
   67.58 +    """
   67.59 +    connection = connection or get_connection(username=auth_user,
   67.60 +                                    password=auth_password,
   67.61 +                                    fail_silently=fail_silently)
   67.62 +    return EmailMessage(subject, message, from_email, recipient_list,
   67.63 +                        connection=connection).send()
   67.64 +
   67.65 +
   67.66 +def send_mass_mail(datatuple, fail_silently=False, auth_user=None,
   67.67 +                   auth_password=None, connection=None):
   67.68 +    """
   67.69 +    Given a datatuple of (subject, message, from_email, recipient_list), sends
   67.70 +    each message to each recipient list. Returns the number of e-mails sent.
   67.71 +
   67.72 +    If from_email is None, the DEFAULT_FROM_EMAIL setting is used.
   67.73 +    If auth_user and auth_password are set, they're used to log in.
   67.74 +    If auth_user is None, the EMAIL_HOST_USER setting is used.
   67.75 +    If auth_password is None, the EMAIL_HOST_PASSWORD setting is used.
   67.76 +
   67.77 +    Note: The API for this method is frozen. New code wanting to extend the
   67.78 +    functionality should use the EmailMessage class directly.
   67.79 +    """
   67.80 +    connection = connection or get_connection(username=auth_user,
   67.81 +                                    password=auth_password,
   67.82 +                                    fail_silently=fail_silently)
   67.83 +    messages = [EmailMessage(subject, message, sender, recipient)
   67.84 +                for subject, message, sender, recipient in datatuple]
   67.85 +    return connection.send_messages(messages)
   67.86 +
   67.87 +
   67.88 +def mail_admins(subject, message, fail_silently=False, connection=None):
   67.89 +    """Sends a message to the admins, as defined by the ADMINS setting."""
   67.90 +    if not settings.ADMINS:
   67.91 +        return
   67.92 +    EmailMessage(settings.EMAIL_SUBJECT_PREFIX + subject, message,
   67.93 +                 settings.SERVER_EMAIL, [a[1] for a in settings.ADMINS],
   67.94 +                 connection=connection).send(fail_silently=fail_silently)
   67.95 +
   67.96 +
   67.97 +def mail_managers(subject, message, fail_silently=False, connection=None):
   67.98 +    """Sends a message to the managers, as defined by the MANAGERS setting."""
   67.99 +    if not settings.MANAGERS:
  67.100 +        return
  67.101 +    EmailMessage(settings.EMAIL_SUBJECT_PREFIX + subject, message,
  67.102 +                 settings.SERVER_EMAIL, [a[1] for a in settings.MANAGERS],
  67.103 +                 connection=connection).send(fail_silently=fail_silently)
  67.104 +
  67.105 +
  67.106 +class SMTPConnection(_SMTPConnection):
  67.107 +    def __init__(self, *args, **kwds):
  67.108 +        import warnings
  67.109 +        warnings.warn(
  67.110 +            'mail.SMTPConnection is deprecated; use mail.get_connection() instead.',
  67.111 +            DeprecationWarning
  67.112 +        )
  67.113 +        super(SMTPConnection, self).__init__(*args, **kwds)
    68.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    68.2 +++ b/django/core/mail/backends/__init__.py	Fri Nov 13 11:27:59 2009 -0600
    68.3 @@ -0,0 +1,1 @@
    68.4 +# Mail backends shipped with Django.
    69.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    69.2 +++ b/django/core/mail/backends/base.py	Fri Nov 13 11:27:59 2009 -0600
    69.3 @@ -0,0 +1,39 @@
    69.4 +"""Base email backend class."""
    69.5 +
    69.6 +class BaseEmailBackend(object):
    69.7 +    """
    69.8 +    Base class for email backend implementations.
    69.9 +
   69.10 +    Subclasses must at least overwrite send_messages().
   69.11 +    """
   69.12 +    def __init__(self, fail_silently=False, **kwargs):
   69.13 +        self.fail_silently = fail_silently
   69.14 +
   69.15 +    def open(self):
   69.16 +        """Open a network connection.
   69.17 +
   69.18 +        This method can be overwritten by backend implementations to
   69.19 +        open a network connection.
   69.20 +
   69.21 +        It's up to the backend implementation to track the status of
   69.22 +        a network connection if it's needed by the backend.
   69.23 +
   69.24 +        This method can be called by applications to force a single
   69.25 +        network connection to be used when sending mails. See the
   69.26 +        send_messages() method of the SMTP backend for a reference
   69.27 +        implementation.
   69.28 +
   69.29 +        The default implementation does nothing.
   69.30 +        """
   69.31 +        pass
   69.32 +
   69.33 +    def close(self):
   69.34 +        """Close a network connection."""
   69.35 +        pass
   69.36 +
   69.37 +    def send_messages(self, email_messages):
   69.38 +        """
   69.39 +        Sends one or more EmailMessage objects and returns the number of email
   69.40 +        messages sent.
   69.41 +        """
   69.42 +        raise NotImplementedError
    70.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    70.2 +++ b/django/core/mail/backends/console.py	Fri Nov 13 11:27:59 2009 -0600
    70.3 @@ -0,0 +1,37 @@
    70.4 +"""
    70.5 +Email backend that writes messages to console instead of sending them.
    70.6 +"""
    70.7 +import sys
    70.8 +import threading
    70.9 +
   70.10 +from django.core.mail.backends.base import BaseEmailBackend
   70.11 +
   70.12 +class EmailBackend(BaseEmailBackend):
   70.13 +    def __init__(self, *args, **kwargs):
   70.14 +        self.stream = kwargs.pop('stream', sys.stdout)
   70.15 +        self._lock = threading.RLock()
   70.16 +        super(EmailBackend, self).__init__(*args, **kwargs)
   70.17 +
   70.18 +    def send_messages(self, email_messages):
   70.19 +        """Write all messages to the stream in a thread-safe way."""
   70.20 +        if not email_messages:
   70.21 +            return
   70.22 +        self._lock.acquire()
   70.23 +        try:
   70.24 +            # The try-except is nested to allow for
   70.25 +            # Python 2.4 support (Refs #12147)
   70.26 +            try:
   70.27 +                stream_created = self.open()
   70.28 +                for message in email_messages:
   70.29 +                    self.stream.write('%s\n' % message.message().as_string())
   70.30 +                    self.stream.write('-'*79)
   70.31 +                    self.stream.write('\n')
   70.32 +                    self.stream.flush()  # flush after each message
   70.33 +                if stream_created:
   70.34 +                    self.close()
   70.35 +            except:
   70.36 +                if not self.fail_silently:
   70.37 +                    raise
   70.38 +        finally:
   70.39 +            self._lock.release()
   70.40 +        return len(email_messages)
    71.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    71.2 +++ b/django/core/mail/backends/dummy.py	Fri Nov 13 11:27:59 2009 -0600
    71.3 @@ -0,0 +1,9 @@
    71.4 +"""
    71.5 +Dummy email backend that does nothing.
    71.6 +"""
    71.7 +
    71.8 +from django.core.mail.backends.base import BaseEmailBackend
    71.9 +
   71.10 +class EmailBackend(BaseEmailBackend):
   71.11 +    def send_messages(self, email_messages):
   71.12 +        return len(email_messages)
    72.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    72.2 +++ b/django/core/mail/backends/filebased.py	Fri Nov 13 11:27:59 2009 -0600
    72.3 @@ -0,0 +1,59 @@
    72.4 +"""Email backend that writes messages to a file."""
    72.5 +
    72.6 +import datetime
    72.7 +import os
    72.8 +
    72.9 +from django.conf import settings
   72.10 +from django.core.exceptions import ImproperlyConfigured
   72.11 +from django.core.mail.backends.console import EmailBackend as ConsoleEmailBackend
   72.12 +
   72.13 +class EmailBackend(ConsoleEmailBackend):
   72.14 +    def __init__(self, *args, **kwargs):
   72.15 +        self._fname = None
   72.16 +        if 'file_path' in kwargs:
   72.17 +            self.file_path = kwargs.pop('file_path')
   72.18 +        else:
   72.19 +            self.file_path = getattr(settings, 'EMAIL_FILE_PATH',None)
   72.20 +        # Make sure self.file_path is a string.
   72.21 +        if not isinstance(self.file_path, basestring):
   72.22 +            raise ImproperlyConfigured('Path for saving emails is invalid: %r' % self.file_path)
   72.23 +        self.file_path = os.path.abspath(self.file_path)
   72.24 +        # Make sure that self.file_path is an directory if it exists.
   72.25 +        if os.path.exists(self.file_path) and not os.path.isdir(self.file_path):
   72.26 +            raise ImproperlyConfigured('Path for saving email messages exists, but is not a directory: %s' % self.file_path)
   72.27 +        # Try to create it, if it not exists.
   72.28 +        elif not os.path.exists(self.file_path):
   72.29 +            try:
   72.30 +                os.makedirs(self.file_path)
   72.31 +            except OSError, err:
   72.32 +                raise ImproperlyConfigured('Could not create directory for saving email messages: %s (%s)' % (self.file_path, err))
   72.33 +        # Make sure that self.file_path is writable.
   72.34 +        if not os.access(self.file_path, os.W_OK):
   72.35 +            raise ImproperlyConfigured('Could not write to directory: %s' % self.file_path)
   72.36 +        # Finally, call super().
   72.37 +        # Since we're using the console-based backend as a base,
   72.38 +        # force the stream to be None, so we don't default to stdout
   72.39 +        kwargs['stream'] = None
   72.40 +        super(EmailBackend, self).__init__(*args, **kwargs)
   72.41 +
   72.42 +    def _get_filename(self):
   72.43 +        """Return a unique file name."""
   72.44 +        if self._fname is None:
   72.45 +            timestamp = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
   72.46 +            fname = "%s-%s.log" % (timestamp, abs(id(self)))
   72.47 +            self._fname = os.path.join(self.file_path, fname)
   72.48 +        return self._fname
   72.49 +
   72.50 +    def open(self):
   72.51 +        if self.stream is None:
   72.52 +            self.stream = open(self._get_filename(), 'a')
   72.53 +            return True
   72.54 +        return False
   72.55 +
   72.56 +    def close(self):
   72.57 +        try:
   72.58 +            if self.stream is not None:
   72.59 +                self.stream.close()
   72.60 +        finally:
   72.61 +            self.stream = None
   72.62 +
    73.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    73.2 +++ b/django/core/mail/backends/locmem.py	Fri Nov 13 11:27:59 2009 -0600
    73.3 @@ -0,0 +1,24 @@
    73.4 +"""
    73.5 +Backend for test environment.
    73.6 +"""
    73.7 +
    73.8 +from django.core import mail
    73.9 +from django.core.mail.backends.base import BaseEmailBackend
   73.10 +
   73.11 +class EmailBackend(BaseEmailBackend):
   73.12 +    """A email backend for use during test sessions.
   73.13 +
   73.14 +    The test connection stores email messages in a dummy outbox,
   73.15 +    rather than sending them out on the wire.
   73.16 +
   73.17 +    The dummy outbox is accessible through the outbox instance attribute.
   73.18 +    """
   73.19 +    def __init__(self, *args, **kwargs):
   73.20 +        super(EmailBackend, self).__init__(*args, **kwargs)
   73.21 +        if not hasattr(mail, 'outbox'):
   73.22 +            mail.outbox = []
   73.23 +
   73.24 +    def send_messages(self, messages):
   73.25 +        """Redirect messages to the dummy outbox"""
   73.26 +        mail.outbox.extend(messages)
   73.27 +        return len(messages)
    74.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    74.2 +++ b/django/core/mail/backends/smtp.py	Fri Nov 13 11:27:59 2009 -0600
    74.3 @@ -0,0 +1,106 @@
    74.4 +"""SMTP email backend class."""
    74.5 +
    74.6 +import smtplib
    74.7 +import socket
    74.8 +import threading
    74.9 +
   74.10 +from django.conf import settings
   74.11 +from django.core.mail.backends.base import BaseEmailBackend
   74.12 +from django.core.mail.utils import DNS_NAME
   74.13 +
   74.14 +class EmailBackend(BaseEmailBackend):
   74.15 +    """
   74.16 +    A wrapper that manages the SMTP network connection.
   74.17 +    """
   74.18 +    def __init__(self, host=None, port=None, username=None, password=None,
   74.19 +                 use_tls=None, fail_silently=False, **kwargs):
   74.20 +        super(EmailBackend, self).__init__(fail_silently=fail_silently)
   74.21 +        self.host = host or settings.EMAIL_HOST
   74.22 +        self.port = port or settings.EMAIL_PORT
   74.23 +        self.username = username or settings.EMAIL_HOST_USER
   74.24 +        self.password = password or settings.EMAIL_HOST_PASSWORD
   74.25 +        if use_tls is None:
   74.26 +            self.use_tls = settings.EMAIL_USE_TLS
   74.27 +        else:
   74.28 +            self.use_tls = use_tls
   74.29 +        self.connection = None
   74.30 +        self._lock = threading.RLock()
   74.31 +
   74.32 +    def open(self):
   74.33 +        """
   74.34 +        Ensures we have a connection to the email server. Returns whether or
   74.35 +        not a new connection was required (True or False).
   74.36 +        """
   74.37 +        if self.connection:
   74.38 +            # Nothing to do if the connection is already open.
   74.39 +            return False
   74.40 +        try:
   74.41 +            # If local_hostname is not specified, socket.getfqdn() gets used.
   74.42 +            # For performance, we use the cached FQDN for local_hostname.
   74.43 +            self.connection = smtplib.SMTP(self.host, self.port,
   74.44 +                                           local_hostname=DNS_NAME.get_fqdn())
   74.45 +            if self.use_tls:
   74.46 +                self.connection.ehlo()
   74.47 +                self.connection.starttls()
   74.48 +                self.connection.ehlo()
   74.49 +            if self.username and self.password:
   74.50 +                self.connection.login(self.username, self.password)
   74.51 +            return True
   74.52 +        except:
   74.53 +            if not self.fail_silently:
   74.54 +                raise
   74.55 +
   74.56 +    def close(self):
   74.57 +        """Closes the connection to the email server."""
   74.58 +        try:
   74.59 +            try:
   74.60 +                self.connection.quit()
   74.61 +            except socket.sslerror:
   74.62 +                # This happens when calling quit() on a TLS connection
   74.63 +                # sometimes.
   74.64 +                self.connection.close()
   74.65 +            except:
   74.66 +                if self.fail_silently:
   74.67 +                    return
   74.68 +                raise
   74.69 +        finally:
   74.70 +            self.connection = None
   74.71 +
   74.72 +    def send_messages(self, email_messages):
   74.73 +        """
   74.74 +        Sends one or more EmailMessage objects and returns the number of email
   74.75 +        messages sent.
   74.76 +        """
   74.77 +        if not email_messages:
   74.78 +            return
   74.79 +        self._lock.acquire()
   74.80 +        try:
   74.81 +            new_conn_created = self.open()
   74.82 +            if not self.connection:
   74.83 +                # We failed silently on open().
   74.84 +                # Trying to send would be pointless.
   74.85 +                return
   74.86 +            num_sent = 0
   74.87 +            for message in email_messages:
   74.88 +                sent = self._send(message)
   74.89 +                if sent:
   74.90 +                    num_sent += 1
   74.91 +            if new_conn_created:
   74.92 +                self.close()
   74.93 +        finally:
   74.94 +            self._lock.release()
   74.95 +        return num_sent
   74.96 +
   74.97 +    def _send(self, email_message):
   74.98 +        """A helper method that does the actual sending."""
   74.99 +        if not email_message.recipients():
  74.100 +            return False
  74.101 +        try:
  74.102 +            self.connection.sendmail(email_message.from_email,
  74.103 +                    email_message.recipients(),
  74.104 +                    email_message.message().as_string())
  74.105 +        except:
  74.106 +            if not self.fail_silently:
  74.107 +                raise
  74.108 +            return False
  74.109 +        return True
    75.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    75.2 +++ b/django/core/mail/message.py	Fri Nov 13 11:27:59 2009 -0600
    75.3 @@ -0,0 +1,273 @@
    75.4 +import mimetypes
    75.5 +import os
    75.6 +import random
    75.7 +import time
    75.8 +from email import Charset, Encoders
    75.9 +from email.MIMEText import MIMEText
   75.10 +from email.MIMEMultipart import MIMEMultipart
   75.11 +from email.MIMEBase import MIMEBase
   75.12 +from email.Header import Header
   75.13 +from email.Utils import formatdate, getaddresses, formataddr
   75.14 +
   75.15 +from django.conf import settings
   75.16 +from django.core.mail.utils import DNS_NAME
   75.17 +from django.utils.encoding import smart_str, force_unicode
   75.18 +
   75.19 +# Don't BASE64-encode UTF-8 messages so that we avoid unwanted attention from
   75.20 +# some spam filters.
   75.21 +Charset.add_charset('utf-8', Charset.SHORTEST, Charset.QP, 'utf-8')
   75.22 +
   75.23 +# Default MIME type to use on attachments (if it is not explicitly given
   75.24 +# and cannot be guessed).
   75.25 +DEFAULT_ATTACHMENT_MIME_TYPE = 'application/octet-stream'
   75.26 +
   75.27 +
   75.28 +class BadHeaderError(ValueError):
   75.29 +    pass
   75.30 +
   75.31 +
   75.32 +# Copied from Python standard library, with the following modifications:
   75.33 +# * Used cached hostname for performance.
   75.34 +# * Added try/except to support lack of getpid() in Jython (#5496).
   75.35 +def make_msgid(idstring=None):
   75.36 +    """Returns a string suitable for RFC 2822 compliant Message-ID, e.g:
   75.37 +
   75.38 +    <20020201195627.33539.96671@nightshade.la.mastaler.com>
   75.39 +
   75.40 +    Optional idstring if given is a string used to strengthen the
   75.41 +    uniqueness of the message id.
   75.42 +    """
   75.43 +    timeval = time.time()
   75.44 +    utcdate = time.strftime('%Y%m%d%H%M%S', time.gmtime(timeval))
   75.45 +    try:
   75.46 +        pid = os.getpid()
   75.47 +    except AttributeError:
   75.48 +        # No getpid() in Jython, for example.
   75.49 +        pid = 1
   75.50 +    randint = random.randrange(100000)
   75.51 +    if idstring is None:
   75.52 +        idstring = ''
   75.53 +    else:
   75.54 +        idstring = '.' + idstring
   75.55 +    idhost = DNS_NAME
   75.56 +    msgid = '<%s.%s.%s%s@%s>' % (utcdate, pid, randint, idstring, idhost)
   75.57 +    return msgid
   75.58 +
   75.59 +
   75.60 +def forbid_multi_line_headers(name, val):
   75.61 +    """Forbids multi-line headers, to prevent header injection."""
   75.62 +    val = force_unicode(val)
   75.63 +    if '\n' in val or '\r' in val:
   75.64 +        raise BadHeaderError("Header values can't contain newlines (got %r for header %r)" % (val, name))
   75.65 +    try:
   75.66 +        val = val.encode('ascii')
   75.67 +    except UnicodeEncodeError:
   75.68 +        if name.lower() in ('to', 'from', 'cc'):
   75.69 +            result = []
   75.70 +            for nm, addr in getaddresses((val,)):
   75.71 +                nm = str(Header(nm, settings.DEFAULT_CHARSET))
   75.72 +                result.append(formataddr((nm, str(addr))))
   75.73 +            val = ', '.join(result)
   75.74 +        else:
   75.75 +            val = Header(val, settings.DEFAULT_CHARSET)
   75.76 +    else:
   75.77 +        if name.lower() == 'subject':
   75.78 +            val = Header(val)
   75.79 +    return name, val
   75.80 +
   75.81 +
   75.82 +class SafeMIMEText(MIMEText):
   75.83 +    def __setitem__(self, name, val):
   75.84 +        name, val = forbid_multi_line_headers(name, val)
   75.85 +        MIMEText.__setitem__(self, name, val)
   75.86 +
   75.87 +
   75.88 +class SafeMIMEMultipart(MIMEMultipart):
   75.89 +    def __setitem__(self, name, val):
   75.90 +        name, val = forbid_multi_line_headers(name, val)
   75.91 +        MIMEMultipart.__setitem__(self, name, val)
   75.92 +
   75.93 +
   75.94 +class EmailMessage(object):
   75.95 +    """
   75.96 +    A container for email information.
   75.97 +    """
   75.98 +    content_subtype = 'plain'
   75.99 +    mixed_subtype = 'mixed'
  75.100 +    encoding = None     # None => use settings default
  75.101 +
  75.102 +    def __init__(self, subject='', body='', from_email=None, to=None, bcc=None,
  75.103 +                 connection=None, attachments=None, headers=None):
  75.104 +        """
  75.105 +        Initialize a single email message (which can be sent to multiple
  75.106 +        recipients).
  75.107 +
  75.108 +        All strings used to create the message can be unicode strings
  75.109 +        (or UTF-8 bytestrings). The SafeMIMEText class will handle any
  75.110 +        necessary encoding conversions.
  75.111 +        """
  75.112 +        if to:
  75.113 +            assert not isinstance(to, basestring), '"to" argument must be a list or tuple'
  75.114 +            self.to = list(to)
  75.115 +        else:
  75.116 +            self.to = []
  75.117 +        if bcc:
  75.118 +            assert not isinstance(bcc, basestring), '"bcc" argument must be a list or tuple'
  75.119 +            self.bcc = list(bcc)
  75.120 +        else:
  75.121 +            self.bcc = []
  75.122 +        self.from_email = from_email or settings.DEFAULT_FROM_EMAIL
  75.123 +        self.subject = subject
  75.124 +        self.body = body
  75.125 +        self.attachments = attachments or []
  75.126 +        self.extra_headers = headers or {}
  75.127 +        self.connection = connection
  75.128 +
  75.129 +    def get_connection(self, fail_silently=False):
  75.130 +        from django.core.mail import get_connection
  75.131 +        if not self.connection:
  75.132 +            self.connection = get_connection(fail_silently=fail_silently)
  75.133 +        return self.connection
  75.134 +
  75.135 +    def message(self):
  75.136 +        encoding = self.encoding or settings.DEFAULT_CHARSET
  75.137 +        msg = SafeMIMEText(smart_str(self.body, settings.DEFAULT_CHARSET),
  75.138 +                           self.content_subtype, encoding)
  75.139 +        msg = self._create_message(msg)
  75.140 +        msg['Subject'] = self.subject
  75.141 +        msg['From'] = self.extra_headers.pop('From', self.from_email)
  75.142 +        msg['To'] = ', '.join(self.to)
  75.143 +
  75.144 +        # Email header names are case-insensitive (RFC 2045), so we have to
  75.145 +        # accommodate that when doing comparisons.
  75.146 +        header_names = [key.lower() for key in self.extra_headers]
  75.147 +        if 'date' not in header_names:
  75.148 +            msg['Date'] = formatdate()
  75.149 +        if 'message-id' not in header_names:
  75.150 +            msg['Message-ID'] = make_msgid()
  75.151 +        for name, value in self.extra_headers.items():
  75.152 +            msg[name] = value
  75.153 +        return msg
  75.154 +
  75.155 +    def recipients(self):
  75.156 +        """
  75.157 +        Returns a list of all recipients of the email (includes direct
  75.158 +        addressees as well as Bcc entries).
  75.159 +        """
  75.160 +        return self.to + self.bcc
  75.161 +
  75.162 +    def send(self, fail_silently=False):
  75.163 +        """Sends the email message."""
  75.164 +        if not self.recipients():
  75.165 +            # Don't bother creating the network connection if there's nobody to
  75.166 +            # send to.
  75.167 +            return 0
  75.168 +        return self.get_connection(fail_silently).send_messages([self])
  75.169 +
  75.170 +    def attach(self, filename=None, content=None, mimetype=None):
  75.171 +        """
  75.172 +        Attaches a file with the given filename and content. The filename can
  75.173 +        be omitted and the mimetype is guessed, if not provided.
  75.174 +
  75.175 +        If the first parameter is a MIMEBase subclass it is inserted directly
  75.176 +        into the resulting message attachments.
  75.177 +        """
  75.178 +        if isinstance(filename, MIMEBase):
  75.179 +            assert content == mimetype == None
  75.180 +            self.attachments.append(filename)
  75.181 +        else:
  75.182 +            assert content is not None
  75.183 +            self.attachments.append((filename, content, mimetype))
  75.184 +
  75.185 +    def attach_file(self, path, mimetype=None):
  75.186 +        """Attaches a file from the filesystem."""
  75.187 +        filename = os.path.basename(path)
  75.188 +        content = open(path, 'rb').read()
  75.189 +        self.attach(filename, content, mimetype)
  75.190 +
  75.191 +    def _create_message(self, msg):
  75.192 +        return self._create_attachments(msg)
  75.193 +
  75.194 +    def _create_attachments(self, msg):
  75.195 +        if self.attachments:
  75.196 +            body_msg = msg
  75.197 +            msg = SafeMIMEMultipart(_subtype=self.mixed_subtype)
  75.198 +            if self.body:
  75.199 +                msg.attach(body_msg)
  75.200 +            for attachment in self.attachments:
  75.201 +                if isinstance(attachment, MIMEBase):
  75.202 +                    msg.attach(attachment)
  75.203 +                else:
  75.204 +                    msg.attach(self._create_attachment(*attachment))
  75.205 +        return msg
  75.206 +
  75.207 +    def _create_mime_attachment(self, content, mimetype):
  75.208 +        """
  75.209 +        Converts the content, mimetype pair into a MIME attachment object.
  75.210 +        """
  75.211 +        basetype, subtype = mimetype.split('/', 1)
  75.212 +        if basetype == 'text':
  75.213 +            attachment = SafeMIMEText(smart_str(content,
  75.214 +                settings.DEFAULT_CHARSET), subtype, settings.DEFAULT_CHARSET)
  75.215 +        else:
  75.216 +            # Encode non-text attachments with base64.
  75.217 +            attachment = MIMEBase(basetype, subtype)
  75.218 +            attachment.set_payload(content)
  75.219 +            Encoders.encode_base64(attachment)
  75.220 +        return attachment
  75.221 +
  75.222 +    def _create_attachment(self, filename, content, mimetype=None):
  75.223 +        """
  75.224 +        Converts the filename, content, mimetype triple into a MIME attachment
  75.225 +        object.
  75.226 +        """
  75.227 +        if mimetype is None:
  75.228 +            mimetype, _ = mimetypes.guess_type(filename)
  75.229 +            if mimetype is None:
  75.230 +                mimetype = DEFAULT_ATTACHMENT_MIME_TYPE
  75.231 +        attachment = self._create_mime_attachment(content, mimetype)
  75.232 +        if filename:
  75.233 +            attachment.add_header('Content-Disposition', 'attachment',
  75.234 +                                  filename=filename)
  75.235 +        return attachment
  75.236 +
  75.237 +
  75.238 +class EmailMultiAlternatives(EmailMessage):
  75.239 +    """
  75.240 +    A version of EmailMessage that makes it easy to send multipart/alternative
  75.241 +    messages. For example, including text and HTML versions of the text is
  75.242 +    made easier.
  75.243 +    """
  75.244 +    alternative_subtype = 'alternative'
  75.245 +
  75.246 +    def __init__(self, subject='', body='', from_email=None, to=None, bcc=None,
  75.247 +            connection=None, attachments=None, headers=None, alternatives=None):
  75.248 +        """
  75.249 +        Initialize a single email message (which can be sent to multiple
  75.250 +        recipients).
  75.251 +
  75.252 +        All strings used to create the message can be unicode strings (or UTF-8
  75.253 +        bytestrings). The SafeMIMEText class will handle any necessary encoding
  75.254 +        conversions.
  75.255 +        """
  75.256 +        super(EmailMultiAlternatives, self).__init__(subject, body, from_email, to, bcc, connection, attachments, headers)
  75.257 +        self.alternatives=alternatives or []
  75.258 +
  75.259 +    def attach_alternative(self, content, mimetype):
  75.260 +        """Attach an alternative content representation."""
  75.261 +        assert content is not None
  75.262 +        assert mimetype is not None
  75.263 +        self.alternatives.append((content, mimetype))
  75.264 +
  75.265 +    def _create_message(self, msg):
  75.266 +        return self._create_attachments(self._create_alternatives(msg))
  75.267 +
  75.268 +    def _create_alternatives(self, msg):
  75.269 +        if self.alternatives:
  75.270 +            body_msg = msg
  75.271 +            msg = SafeMIMEMultipart(_subtype=self.alternative_subtype)
  75.272 +            if self.body:
  75.273 +                msg.attach(body_msg)
  75.274 +            for alternative in self.alternatives:
  75.275 +                msg.attach(self._create_mime_attachment(*alternative))
  75.276 +        return msg
    76.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    76.2 +++ b/django/core/mail/utils.py	Fri Nov 13 11:27:59 2009 -0600
    76.3 @@ -0,0 +1,19 @@
    76.4 +"""
    76.5 +Email message and email sending related helper functions.
    76.6 +"""
    76.7 +
    76.8 +import socket
    76.9 +
   76.10 +
   76.11 +# Cache the hostname, but do it lazily: socket.getfqdn() can take a couple of
   76.12 +# seconds, which slows down the restart of the server.
   76.13 +class CachedDnsName(object):
   76.14 +    def __str__(self):
   76.15 +        return self.get_fqdn()
   76.16 +
   76.17 +    def get_fqdn(self):
   76.18 +        if not hasattr(self, '_fqdn'):
   76.19 +            self._fqdn = socket.getfqdn()
   76.20 +        return self._fqdn
   76.21 +
   76.22 +DNS_NAME = CachedDnsName()
    77.1 --- a/django/core/management/commands/syncdb.py	Sat Sep 12 18:53:56 2009 -0500
    77.2 +++ b/django/core/management/commands/syncdb.py	Fri Nov 13 11:27:59 2009 -0600
    77.3 @@ -57,12 +57,15 @@
    77.4          # Create the tables for each model
    77.5          for app in models.get_apps():
    77.6              app_name = app.__name__.split('.')[-2]
    77.7 -            model_list = models.get_models(app)
    77.8 +            model_list = models.get_models(app, include_auto_created=True)
    77.9              for model in model_list:
   77.10                  # Create the model's database table, if it doesn't already exist.
   77.11                  if verbosity >= 2:
   77.12                      print "Processing %s.%s model" % (app_name, model._meta.object_name)
   77.13 -                if connection.introspection.table_name_converter(model._meta.db_table) in tables:
   77.14 +                opts = model._meta
   77.15 +                if (connection.introspection.table_name_converter(opts.db_table) in tables or
   77.16 +                    (opts.auto_created and
   77.17 +                    connection.introspection.table_name_converter(opts.auto_created._meta.db_table in tables))):
   77.18                      continue
   77.19                  sql, references = connection.creation.sql_create_model(model, self.style, seen_models)
   77.20                  seen_models.add(model)
   77.21 @@ -78,19 +81,6 @@
   77.22                      cursor.execute(statement)
   77.23                  tables.append(connection.introspection.table_name_converter(model._meta.db_table))
   77.24  
   77.25 -        # Create the m2m tables. This must be done after all tables have been created
   77.26 -        # to ensure that all referred tables will exist.
   77.27 -        for app in models.get_apps():
   77.28 -            app_name = app.__name__.split('.')[-2]
   77.29 -            model_list = models.get_models(app)
   77.30 -            for model in model_list:
   77.31 -                if model in created_models:
   77.32 -                    sql = connection.creation.sql_for_many_to_many(model, self.style)
   77.33 -                    if sql:
   77.34 -                        if verbosity >= 2:
   77.35 -                            print "Creating many-to-many tables for %s.%s model" % (app_name, model._meta.object_name)
   77.36 -                        for statement in sql:
   77.37 -                            cursor.execute(statement)
   77.38  
   77.39          transaction.commit_unless_managed()
   77.40  
    78.1 --- a/django/core/management/sql.py	Sat Sep 12 18:53:56 2009 -0500
    78.2 +++ b/django/core/management/sql.py	Fri Nov 13 11:27:59 2009 -0600
    78.3 @@ -23,7 +23,7 @@
    78.4      # We trim models from the current app so that the sqlreset command does not
    78.5      # generate invalid SQL (leaving models out of known_models is harmless, so
    78.6      # we can be conservative).
    78.7 -    app_models = models.get_models(app)
    78.8 +    app_models = models.get_models(app, include_auto_created=True)
    78.9      final_output = []
   78.10      tables = connection.introspection.table_names()
   78.11      known_models = set([model for model in connection.introspection.installed_models(tables) if model not in app_models])
   78.12 @@ -40,10 +40,6 @@
   78.13          # Keep track of the fact that we've created the table for this model.
   78.14          known_models.add(model)
   78.15  
   78.16 -    # Create the many-to-many join tables.
   78.17 -    for model in app_models:
   78.18 -        final_output.extend(connection.creation.sql_for_many_to_many(model, style))
   78.19 -
   78.20      # Handle references to tables that are from other apps
   78.21      # but don't exist physically.
   78.22      not_installed_models = set(pending_references.keys())
   78.23 @@ -82,7 +78,7 @@
   78.24      to_delete = set()
   78.25  
   78.26      references_to_delete = {}
   78.27 -    app_models = models.get_models(app)
   78.28 +    app_models = models.get_models(app, include_auto_created=True)
   78.29      for model in app_models:
   78.30          if cursor and connection.introspection.table_name_converter(model._meta.db_table) in table_names:
   78.31              # The table exists, so it needs to be dropped
   78.32 @@ -97,13 +93,6 @@
   78.33          if connection.introspection.table_name_converter(model._meta.db_table) in table_names:
   78.34              output.extend(connection.creation.sql_destroy_model(model, references_to_delete, style))
   78.35  
   78.36 -    # Output DROP TABLE statements for many-to-many tables.
   78.37 -    for model in app_models:
   78.38 -        opts = model._meta
   78.39 -        for f in opts.local_many_to_many:
   78.40 -            if cursor and connection.introspection.table_name_converter(f.m2m_db_table()) in table_names:
   78.41 -                output.extend(connection.creation.sql_destroy_many_to_many(model, f, style))
   78.42 -
   78.43      # Close database connection explicitly, in case this output is being piped
   78.44      # directly into a database client, to avoid locking issues.
   78.45      if cursor:
    79.1 --- a/django/core/management/validation.py	Sat Sep 12 18:53:56 2009 -0500
    79.2 +++ b/django/core/management/validation.py	Fri Nov 13 11:27:59 2009 -0600
    79.3 @@ -79,27 +79,28 @@
    79.4                  rel_opts = f.rel.to._meta
    79.5                  rel_name = RelatedObject(f.rel.to, cls, f).get_accessor_name()
    79.6                  rel_query_name = f.related_query_name()
    79.7 -                for r in rel_opts.fields:
    79.8 -                    if r.name == rel_name:
    79.9 -                        e.add(opts, "Accessor for field '%s' clashes with field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
   79.10 -                    if r.name == rel_query_name:
   79.11 -                        e.add(opts, "Reverse query name for field '%s' clashes with field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
   79.12 -                for r in rel_opts.local_many_to_many:
   79.13 -                    if r.name == rel_name:
   79.14 -                        e.add(opts, "Accessor for field '%s' clashes with m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
   79.15 -                    if r.name == rel_query_name:
   79.16 -                        e.add(opts, "Reverse query name for field '%s' clashes with m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
   79.17 -                for r in rel_opts.get_all_related_many_to_many_objects():
   79.18 -                    if r.get_accessor_name() == rel_name:
   79.19 -                        e.add(opts, "Accessor for field '%s' clashes with related m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name))
   79.20 -                    if r.get_accessor_name() == rel_query_name:
   79.21 -                        e.add(opts, "Reverse query name for field '%s' clashes with related m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name))
   79.22 -                for r in rel_opts.get_all_related_objects():
   79.23 -                    if r.field is not f:
   79.24 +                if not f.rel.is_hidden():
   79.25 +                    for r in rel_opts.fields:
   79.26 +                        if r.name == rel_name:
   79.27 +                            e.add(opts, "Accessor for field '%s' clashes with field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
   79.28 +                        if r.name == rel_query_name:
   79.29 +                            e.add(opts, "Reverse query name for field '%s' clashes with field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
   79.30 +                    for r in rel_opts.local_many_to_many:
   79.31 +                        if r.name == rel_name:
   79.32 +                            e.add(opts, "Accessor for field '%s' clashes with m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
   79.33 +                        if r.name == rel_query_name:
   79.34 +                            e.add(opts, "Reverse query name for field '%s' clashes with m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
   79.35 +                    for r in rel_opts.get_all_related_many_to_many_objects():
   79.36                          if r.get_accessor_name() == rel_name:
   79.37 -                            e.add(opts, "Accessor for field '%s' clashes with related field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name))
   79.38 +                            e.add(opts, "Accessor for field '%s' clashes with related m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name))
   79.39                          if r.get_accessor_name() == rel_query_name:
   79.40 -                            e.add(opts, "Reverse query name for field '%s' clashes with related field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name))
   79.41 +                            e.add(opts, "Reverse query name for field '%s' clashes with related m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name))
   79.42 +                    for r in rel_opts.get_all_related_objects():
   79.43 +                        if r.field is not f:
   79.44 +                            if r.get_accessor_name() == rel_name:
   79.45 +                                e.add(opts, "Accessor for field '%s' clashes with related field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name))
   79.46 +                            if r.get_accessor_name() == rel_query_name:
   79.47 +                                e.add(opts, "Reverse query name for field '%s' clashes with related field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name))
   79.48  
   79.49          seen_intermediary_signatures = []
   79.50          for i, f in enumerate(opts.local_many_to_many):
   79.51 @@ -117,48 +118,80 @@
   79.52              if f.unique:
   79.53                  e.add(opts, "ManyToManyFields cannot be unique.  Remove the unique argument on '%s'." % f.name)
   79.54  
   79.55 -            if getattr(f.rel, 'through', None) is not None:
   79.56 -                if hasattr(f.rel, 'through_model'):
   79.57 -                    from_model, to_model = cls, f.rel.to
   79.58 -                    if from_model == to_model and f.rel.symmetrical:
   79.59 -                        e.add(opts, "Many-to-many fields with intermediate tables cannot be symmetrical.")
   79.60 -                    seen_from, seen_to, seen_self = False, False, 0
   79.61 -                    for inter_field in f.rel.through_model._meta.fields:
   79.62 -                        rel_to = getattr(inter_field.rel, 'to', None)
   79.63 -                        if from_model == to_model: # relation to self
   79.64 -                            if rel_to == from_model:
   79.65 -                                seen_self += 1
   79.66 -                            if seen_self > 2:
   79.67 -                                e.add(opts, "Intermediary model %s has more than two foreign keys to %s, which is ambiguous and is not permitted." % (f.rel.through_model._meta.object_name, from_model._meta.object_name))
   79.68 -                        else:
   79.69 -                            if rel_to == from_model:
   79.70 -                                if seen_from:
   79.71 -                                    e.add(opts, "Intermediary model %s has more than one foreign key to %s, which is ambiguous and is not permitted." % (f.rel.through_model._meta.object_name, from_model._meta.object_name))
   79.72 -                                else:
   79.73 -                                    seen_from = True
   79.74 -                            elif rel_to == to_model:
   79.75 -                                if seen_to:
   79.76 -                                    e.add(opts, "Intermediary model %s has more than one foreign key to %s, which is ambiguous and is not permitted." % (f.rel.through_model._meta.object_name, rel_to._meta.object_name))
   79.77 -                                else:
   79.78 -                                    seen_to = True
   79.79 -                    if f.rel.through_model not in models.get_models():
   79.80 -                        e.add(opts, "'%s' specifies an m2m relation through model %s, which has not been installed." % (f.name, f.rel.through))
   79.81 -                    signature = (f.rel.to, cls, f.rel.through_model)
   79.82 -                    if signature in seen_intermediary_signatures:
   79.83 -                        e.add(opts, "The model %s has two manually-defined m2m relations through the model %s, which is not permitted. Please consider using an extra field on your intermediary model instead." % (cls._meta.object_name, f.rel.through_model._meta.object_name))
   79.84 +            if f.rel.through is not None and not isinstance(f.rel.through, basestring):
   79.85 +                from_model, to_model = cls, f.rel.to
   79.86 +                if from_model == to_model and f.rel.symmetrical and not f.rel.through._meta.auto_created:
   79.87 +                    e.add(opts, "Many-to-many fields with intermediate tables cannot be symmetrical.")
   79.88 +                seen_from, seen_to, seen_self = False, False, 0
   79.89 +                for inter_field in f.rel.through._meta.fields:
   79.90 +                    rel_to = getattr(inter_field.rel, 'to', None)
   79.91 +                    if from_model == to_model: # relation to self
   79.92 +                        if rel_to == from_model:
   79.93 +                            seen_self += 1
   79.94 +                        if seen_self > 2:
   79.95 +                            e.add(opts, "Intermediary model %s has more than "
   79.96 +                                "two foreign keys to %s, which is ambiguous "
   79.97 +                                "and is not permitted." % (
   79.98 +                                    f.rel.through._meta.object_name,
   79.99 +                                    from_model._meta.object_name
  79.100 +                                )
  79.101 +                            )
  79.102                      else:
  79.103 -                        seen_intermediary_signatures.append(signature)
  79.104 -                    seen_related_fk, seen_this_fk = False, False
  79.105 -                    for field in f.rel.through_model._meta.fields:
  79.106 -                        if field.rel:
  79.107 -                            if not seen_related_fk and field.rel.to == f.rel.to:
  79.108 -                                seen_related_fk = True
  79.109 -                            elif field.rel.to == cls:
  79.110 -                                seen_this_fk = True
  79.111 -                    if not seen_related_fk or not seen_this_fk:
  79.112 -                        e.add(opts, "'%s' has a manually-defined m2m relation through model %s, which does not have foreign keys to %s and %s" % (f.name, f.rel.through, f.rel.to._meta.object_name, cls._meta.object_name))
  79.113 +                        if rel_to == from_model:
  79.114 +                            if seen_from:
  79.115 +                                e.add(opts, "Intermediary model %s has more "
  79.116 +                                    "than one foreign key to %s, which is "
  79.117 +                                    "ambiguous and is not permitted." % (
  79.118 +                                        f.rel.through._meta.object_name,
  79.119 +                                         from_model._meta.object_name
  79.120 +                                     )
  79.121 +                                 )
  79.122 +                            else:
  79.123 +                                seen_from = True
  79.124 +                        elif rel_to == to_model:
  79.125 +                            if seen_to:
  79.126 +                                e.add(opts, "Intermediary model %s has more "
  79.127 +                                    "than one foreign key to %s, which is "
  79.128 +                                    "ambiguous and is not permitted." % (
  79.129 +                                        f.rel.through._meta.object_name,
  79.130 +                                        rel_to._meta.object_name
  79.131 +                                    )
  79.132 +                                )
  79.133 +                            else:
  79.134 +                                seen_to = True
  79.135 +                if f.rel.through not in models.get_models(include_auto_created=True):
  79.136 +                    e.add(opts, "'%s' specifies an m2m relation through model "
  79.137 +                        "%s, which has not been installed." % (f.name, f.rel.through)
  79.138 +                    )
  79.139 +                signature = (f.rel.to, cls, f.rel.through)
  79.140 +                if signature in seen_intermediary_signatures:
  79.141 +                    e.add(opts, "The model %s has two manually-defined m2m "
  79.142 +                        "relations through the model %s, which is not "
  79.143 +                        "permitted. Please consider using an extra field on "
  79.144 +                        "your intermediary model instead." % (
  79.145 +                            cls._meta.object_name,
  79.146 +                            f.rel.through._meta.object_name
  79.147 +                        )
  79.148 +                    )
  79.149                  else:
  79.150 -                    e.add(opts, "'%s' specifies an m2m relation through model %s, which has not been installed" % (f.name, f.rel.through))
  79.151 +                    seen_intermediary_signatures.append(signature)
  79.152 +                seen_related_fk, seen_this_fk = False, False
  79.153 +                for field in f.rel.through._meta.fields:
  79.154 +                    if field.rel:
  79.155 +                        if not seen_related_fk and field.rel.to == f.rel.to:
  79.156 +                            seen_related_fk = True
  79.157 +                        elif field.rel.to == cls:
  79.158 +                            seen_this_fk = True
  79.159 +                if not seen_related_fk or not seen_this_fk:
  79.160 +                    e.add(opts, "'%s' has a manually-defined m2m relation "
  79.161 +                        "through model %s, which does not have foreign keys "
  79.162 +                        "to %s and %s" % (f.name, f.rel.through._meta.object_name,
  79.163 +                            f.rel.to._meta.object_name, cls._meta.object_name)
  79.164 +                    )
  79.165 +            elif isinstance(f.rel.through, basestring):
  79.166 +                e.add(opts, "'%s' specifies an m2m relation through model %s, "
  79.167 +                    "which has not been installed" % (f.name, f.rel.through)
  79.168 +                )
  79.169  
  79.170              rel_opts = f.rel.to._meta
  79.171              rel_name = RelatedObject(f.rel.to, cls, f).get_accessor_name()
    80.1 --- a/django/core/serializers/python.py	Sat Sep 12 18:53:56 2009 -0500
    80.2 +++ b/django/core/serializers/python.py	Fri Nov 13 11:27:59 2009 -0600
    80.3 @@ -56,7 +56,7 @@
    80.4          self._current[field.name] = smart_unicode(related, strings_only=True)
    80.5  
    80.6      def handle_m2m_field(self, obj, field):
    80.7 -        if field.creates_table:
    80.8 +        if field.rel.through._meta.auto_created:
    80.9              self._current[field.name] = [smart_unicode(related._get_pk_val(), strings_only=True)
   80.10                                 for related in getattr(obj, field.name).iterator()]
   80.11  
    81.1 --- a/django/core/serializers/xml_serializer.py	Sat Sep 12 18:53:56 2009 -0500
    81.2 +++ b/django/core/serializers/xml_serializer.py	Fri Nov 13 11:27:59 2009 -0600
    81.3 @@ -98,7 +98,7 @@
    81.4          serialized as references to the object's PK (i.e. the related *data*
    81.5          is not dumped, just the relation).
    81.6          """
    81.7 -        if field.creates_table:
    81.8 +        if field.rel.through._meta.auto_created:
    81.9              self._start_relational_field(field)
   81.10              for relobj in getattr(obj, field.name).iterator():
   81.11                  self.xml.addQuickElement("object", attrs={"pk" : smart_unicode(relobj._get_pk_val())})
   81.12 @@ -233,4 +233,3 @@
   81.13          else:
   81.14             pass
   81.15      return u"".join(inner_text)
   81.16 -
    82.1 --- a/django/db/models/base.py	Sat Sep 12 18:53:56 2009 -0500
    82.2 +++ b/django/db/models/base.py	Fri Nov 13 11:27:59 2009 -0600
    82.3 @@ -3,11 +3,6 @@
    82.4  import sys
    82.5  import os
    82.6  from itertools import izip
    82.7 -try:
    82.8 -    set
    82.9 -except NameError:
   82.10 -    from sets import Set as set     # Python 2.3 fallback.
   82.11 -
   82.12  import django.db.models.manager     # Imported to register signal handler.
   82.13  from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned, FieldError
   82.14  from django.db.models.fields import AutoField, FieldDoesNotExist
   82.15 @@ -22,7 +17,6 @@
   82.16  from django.utils.encoding import smart_str, force_unicode, smart_unicode
   82.17  from django.conf import settings
   82.18  
   82.19 -
   82.20  class ModelBase(type):
   82.21      """
   82.22      Metaclass for all models.
   82.23 @@ -236,7 +230,6 @@
   82.24  
   82.25          signals.class_prepared.send(sender=cls)
   82.26  
   82.27 -
   82.28  class Model(object):
   82.29      __metaclass__ = ModelBase
   82.30      _deferred = False
   82.31 @@ -300,7 +293,14 @@
   82.32                          if rel_obj is None and field.null:
   82.33                              val = None
   82.34                  else:
   82.35 -                    val = kwargs.pop(field.attname, field.get_default())
   82.36 +                    try:
   82.37 +                        val = kwargs.pop(field.attname)
   82.38 +                    except KeyError:
   82.39 +                        # This is done with an exception rather than the
   82.40 +                        # default argument on pop because we don't want
   82.41 +                        # get_default() to be evaluated, and then not used.
   82.42 +                        # Refs #12057.
   82.43 +                        val = field.get_default()
   82.44              else:
   82.45                  val = field.get_default()
   82.46              if is_related_object:
   82.47 @@ -352,21 +352,30 @@
   82.48          only module-level classes can be pickled by the default path.
   82.49          """
   82.50          data = self.__dict__
   82.51 -        if not self._deferred:
   82.52 -            return (self.__class__, (), data)
   82.53 +        model = self.__class__
   82.54 +        # The obvious thing to do here is to invoke super().__reduce__()
   82.55 +        # for the non-deferred case. Don't do that.
   82.56 +        # On Python 2.4, there is something wierd with __reduce__,
   82.57 +        # and as a result, the super call will cause an infinite recursion.
   82.58 +        # See #10547 and #12121.
   82.59          defers = []
   82.60          pk_val = None
   82.61 -        for field in self._meta.fields:
   82.62 -            if isinstance(self.__class__.__dict__.get(field.attname),
   82.63 -                    DeferredAttribute):
   82.64 -                defers.append(field.attname)
   82.65 -                if pk_val is None:
   82.66 -                    # The pk_val and model values are the same for all
   82.67 -                    # DeferredAttribute classes, so we only need to do this
   82.68 -                    # once.
   82.69 -                    obj = self.__class__.__dict__[field.attname]
   82.70 -                    model = obj.model_ref()
   82.71 -        return (model_unpickle, (model, defers), data)
   82.72 +        if self._deferred:
   82.73 +            from django.db.models.query_utils import deferred_class_factory
   82.74 +            factory = deferred_class_factory
   82.75 +            for field in self._meta.fields:
   82.76 +                if isinstance(self.__class__.__dict__.get(field.attname),
   82.77 +                        DeferredAttribute):
   82.78 +                    defers.append(field.attname)
   82.79 +                    if pk_val is None:
   82.80 +                        # The pk_val and model values are the same for all
   82.81 +                        # DeferredAttribute classes, so we only need to do this
   82.82 +                        # once.
   82.83 +                        obj = self.__class__.__dict__[field.attname]
   82.84 +                        model = obj.model_ref()
   82.85 +        else:
   82.86 +            factory = simple_class_factory
   82.87 +        return (model_unpickle, (model, defers, factory), data)
   82.88  
   82.89      def _get_pk_val(self, meta=None):
   82.90          if not meta:
   82.91 @@ -428,7 +437,7 @@
   82.92          else:
   82.93              meta = cls._meta
   82.94  
   82.95 -        if origin:
   82.96 +        if origin and not meta.auto_created:
   82.97              signals.pre_save.send(sender=origin, instance=self, raw=raw)
   82.98  
   82.99          # If we are in a raw save, save the object exactly as presented.
  82.100 @@ -467,7 +476,7 @@
  82.101              if pk_set:
  82.102                  # Determine whether a record with the primary key already exists.
  82.103                  if (force_update or (not force_insert and
  82.104 -                        manager.filter(pk=pk_val).extra(select={'a': 1}).values('a').order_by())):
  82.105 +                        manager.filter(pk=pk_val).exists())):
  82.106                      # It does already exist, so do an UPDATE.
  82.107                      if force_update or non_pks:
  82.108                          values = [(f, None, (raw and getattr(self, f.attname) or f.pre_save(self, False))) for f in non_pks]
  82.109 @@ -501,7 +510,7 @@
  82.110                      setattr(self, meta.pk.attname, result)
  82.111              transaction.commit_unless_managed()
  82.112  
  82.113 -        if origin:
  82.114 +        if origin and not meta.auto_created:
  82.115              signals.post_save.send(sender=origin, instance=self,
  82.116                  created=(not record_exists), raw=raw)
  82.117  
  82.118 @@ -538,7 +547,12 @@
  82.119                          rel_descriptor = cls.__dict__[rel_opts_name]
  82.120                          break
  82.121                  else:
  82.122 -                    raise AssertionError("Should never get here.")
  82.123 +                    # in the case of a hidden fkey just skip it, it'll get
  82.124 +                    # processed as an m2m
  82.125 +                    if not related.field.rel.is_hidden():
  82.126 +                        raise AssertionError("Should never get here.")
  82.127 +                    else:
  82.128 +                        continue
  82.129                  delete_qs = rel_descriptor.delete_manager(self).all()
  82.130                  for sub_obj in delete_qs:
  82.131                      sub_obj._collect_sub_objects(seen_objs, self.__class__, related.field.null)
  82.132 @@ -646,12 +660,20 @@
  82.133  class Empty(object):
  82.134      pass
  82.135  
  82.136 -def model_unpickle(model, attrs):
  82.137 +def simple_class_factory(model, attrs):
  82.138 +    """Used to unpickle Models without deferred fields.
  82.139 +
  82.140 +    We need to do this the hard way, rather than just using
  82.141 +    the default __reduce__ implementation, because of a
  82.142 +    __deepcopy__ problem in Python 2.4
  82.143 +    """
  82.144 +    return model
  82.145 +
  82.146 +def model_unpickle(model, attrs, factory):
  82.147      """
  82.148      Used to unpickle Model subclasses with deferred fields.
  82.149      """
  82.150 -    from django.db.models.query_utils import deferred_class_factory
  82.151 -    cls = deferred_class_factory(model, attrs)
  82.152 +    cls = factory(model, attrs)
  82.153      return cls.__new__(cls)
  82.154  model_unpickle.__safe_for_unpickle__ = True
  82.155  
    83.1 --- a/django/db/models/fields/related.py	Sat Sep 12 18:53:56 2009 -0500
    83.2 +++ b/django/db/models/fields/related.py	Fri Nov 13 11:27:59 2009 -0600
    83.3 @@ -58,6 +58,10 @@
    83.4              # If we can't split, assume a model in current app
    83.5              app_label = cls._meta.app_label
    83.6              model_name = relation
    83.7 +        except AttributeError:
    83.8 +            # If it doesn't have a split it's actually a model class
    83.9 +            app_label = relation._meta.app_label
   83.10 +            model_name = relation._meta.object_name
   83.11  
   83.12      # Try to look up the related model, and if it's already loaded resolve the
   83.13      # string right away. If get_model returns None, it means that the related
   83.14 @@ -96,7 +100,7 @@
   83.15              self.rel.related_name = self.rel.related_name % {'class': cls.__name__.lower()}
   83.16  
   83.17          other = self.rel.to
   83.18 -        if isinstance(other, basestring):
   83.19 +        if isinstance(other, basestring) or other._meta.pk is None:
   83.20              def resolve_related_class(field, model, cls):
   83.21                  field.rel.to = model
   83.22                  field.do_related_class(model, cls)
   83.23 @@ -401,22 +405,22 @@
   83.24  
   83.25          return manager
   83.26  
   83.27 -def create_many_related_manager(superclass, through=False):
   83.28 +def create_many_related_manager(superclass, rel=False):
   83.29      """Creates a manager that subclasses 'superclass' (which is a Manager)
   83.30      and adds behavior for many-to-many related objects."""
   83.31 +    through = rel.through
   83.32      class ManyRelatedManager(superclass):
   83.33          def __init__(self, model=None, core_filters=None, instance=None, symmetrical=None,
   83.34 -                join_table=None, source_col_name=None, target_col_name=None):
   83.35 +                join_table=None, source_field_name=None, target_field_name=None):
   83.36              super(ManyRelatedManager, self).__init__()
   83.37              self.core_filters = core_filters
   83.38              self.model = model
   83.39              self.symmetrical = symmetrical
   83.40              self.instance = instance
   83.41 -            self.join_table = join_table
   83.42 -            self.source_col_name = source_col_name
   83.43 -            self.target_col_name = target_col_name
   83.44 +            self.source_field_name = source_field_name
   83.45 +            self.target_field_name = target_field_name
   83.46              self.through = through
   83.47 -            self._pk_val = self.instance._get_pk_val()
   83.48 +            self._pk_val = self.instance.pk
   83.49              if self._pk_val is None:
   83.50                  raise ValueError("%r instance needs to have a primary key value before a many-to-many relationship can be used." % instance.__class__.__name__)
   83.51  
   83.52 @@ -425,36 +429,37 @@
   83.53  
   83.54          # If the ManyToMany relation has an intermediary model,
   83.55          # the add and remove methods do not exist.
   83.56 -        if through is None:
   83.57 +        if rel.through._meta.auto_created:
   83.58              def add(self, *objs):
   83.59 -                self._add_items(self.source_col_name, self.target_col_name, *objs)
   83.60 +                self._add_items(self.source_field_name, self.target_field_name, *objs)
   83.61  
   83.62                  # If this is a symmetrical m2m relation to self, add the mirror entry in the m2m table
   83.63                  if self.symmetrical:
   83.64 -                    self._add_items(self.target_col_name, self.source_col_name, *objs)
   83.65 +                    self._add_items(self.target_field_name, self.source_field_name, *objs)
   83.66              add.alters_data = True
   83.67  
   83.68              def remove(self, *objs):
   83.69 -                self._remove_items(self.source_col_name, self.target_col_name, *objs)
   83.70 +                self._remove_items(self.source_field_name, self.target_field_name, *objs)
   83.71  
   83.72                  # If this is a symmetrical m2m relation to self, remove the mirror entry in the m2m table
   83.73                  if self.symmetrical:
   83.74 -                    self._remove_items(self.target_col_name, self.source_col_name, *objs)
   83.75 +                    self._remove_items(self.target_field_name, self.source_field_name, *objs)
   83.76              remove.alters_data = True
   83.77  
   83.78          def clear(self):
   83.79 -            self._clear_items(self.source_col_name)
   83.80 +            self._clear_items(self.source_field_name)
   83.81  
   83.82              # If this is a symmetrical m2m relation to self, clear the mirror entry in the m2m table
   83.83              if self.symmetrical:
   83.84 -                self._clear_items(self.target_col_name)
   83.85 +                self._clear_items(self.target_field_name)
   83.86          clear.alters_data = True
   83.87  
   83.88          def create(self, **kwargs):
   83.89              # This check needs to be done here, since we can't later remove this
   83.90              # from the method lookup table, as we do with add and remove.
   83.91 -            if through is not None:
   83.92 -                raise AttributeError, "Cannot use create() on a ManyToManyField which specifies an intermediary model. Use %s's Manager instead." % through
   83.93 +            if not rel.through._meta.auto_created:
   83.94 +                opts = through._meta
   83.95 +                raise AttributeError, "Cannot use create() on a ManyToManyField which specifies an intermediary model. Use %s.%s's Manager instead." % (opts.app_label, opts.object_name)
   83.96              new_obj = super(ManyRelatedManager, self).create(**kwargs)
   83.97              self.add(new_obj)
   83.98              return new_obj
   83.99 @@ -470,41 +475,38 @@
  83.100              return obj, created
  83.101          get_or_create.alters_data = True
  83.102  
  83.103 -        def _add_items(self, source_col_name, target_col_name, *objs):
  83.104 +        def _add_items(self, source_field_name, target_field_name, *objs):
  83.105              # join_table: name of the m2m link table
  83.106 -            # source_col_name: the PK colname in join_table for the source object
  83.107 -            # target_col_name: the PK colname in join_table for the target object
  83.108 +            # source_field_name: the PK fieldname in join_table for the source object
  83.109 +            # target_col_name: the PK fieldname in join_table for the target object
  83.110              # *objs - objects to add. Either object instances, or primary keys of object instances.
  83.111  
  83.112              # If there aren't any objects, there is nothing to do.
  83.113 +            from django.db.models import Model
  83.114              if objs:
  83.115 -                from django.db.models.base import Model
  83.116 -                # Check that all the objects are of the right type
  83.117                  new_ids = set()
  83.118                  for obj in objs:
  83.119                      if isinstance(obj, self.model):
  83.120 -                        new_ids.add(obj._get_pk_val())
  83.121 +                        new_ids.add(obj.pk)
  83.122                      elif isinstance(obj, Model):
  83.123                          raise TypeError, "'%s' instance expected" % self.model._meta.object_name
  83.124                      else:
  83.125                          new_ids.add(obj)
  83.126 -                # Add the newly created or already existing objects to the join table.
  83.127 -                # First find out which items are already added, to avoid adding them twice
  83.128 -                cursor = connection.cursor()
  83.129 -                cursor.execute("SELECT %s FROM %s WHERE %s = %%s AND %s IN (%s)" % \
  83.130 -                    (target_col_name, self.join_table, source_col_name,
  83.131 -                    target_col_name, ",".join(['%s'] * len(new_ids))),
  83.132 -                    [self._pk_val] + list(new_ids))
  83.133 -                existing_ids = set([row[0] for row in cursor.fetchall()])
  83.134 +                vals = self.through._default_manager.values_list(target_field_name, flat=True)
  83.135 +                vals = vals.filter(**{
  83.136 +                    source_field_name: self._pk_val,
  83.137 +                    '%s__in' % target_field_name: new_ids,
  83.138 +                })
  83.139 +                vals = set(vals)
  83.140  
  83.141                  # Add the ones that aren't there already
  83.142 -                for obj_id in (new_ids - existing_ids):
  83.143 -                    cursor.execute("INSERT INTO %s (%s, %s) VALUES (%%s, %%s)" % \
  83.144 -                        (self.join_table, source_col_name, target_col_name),
  83.145 -                        [self._pk_val, obj_id])
  83.146 -                transaction.commit_unless_managed()
  83.147 +                for obj_id in (new_ids - vals):
  83.148 +                    self.through._default_manager.create(**{
  83.149 +                        '%s_id' % source_field_name: self._pk_val,
  83.150 +                        '%s_id' % target_field_name: obj_id,
  83.151 +                    })
  83.152  
  83.153 -        def _remove_items(self, source_col_name, target_col_name, *objs):
  83.154 +        def _remove_items(self, source_field_name, target_field_name, *objs):
  83.155              # source_col_name: the PK colname in join_table for the source object
  83.156              # target_col_name: the PK colname in join_table for the target object
  83.157              # *objs - objects to remove
  83.158 @@ -515,24 +517,20 @@
  83.159                  old_ids = set()
  83.160                  for obj in objs:
  83.161                      if isinstance(obj, self.model):
  83.162 -                        old_ids.add(obj._get_pk_val())
  83.163 +                        old_ids.add(obj.pk)
  83.164                      else:
  83.165                          old_ids.add(obj)
  83.166                  # Remove the specified objects from the join table
  83.167 -                cursor = connection.cursor()
  83.168 -                cursor.execute("DELETE FROM %s WHERE %s = %%s AND %s IN (%s)" % \
  83.169 -                    (self.join_table, source_col_name,
  83.170 -                    target_col_name, ",".join(['%s'] * len(old_ids))),
  83.171 -                    [self._pk_val] + list(old_ids))
  83.172 -                transaction.commit_unless_managed()
  83.173 +                self.through._default_manager.filter(**{
  83.174 +                    source_field_name: self._pk_val,
  83.175 +                    '%s__in' % target_field_name: old_ids
  83.176 +                }).delete()
  83.177  
  83.178 -        def _clear_items(self, source_col_name):
  83.179 +        def _clear_items(self, source_field_name):
  83.180              # source_col_name: the PK colname in join_table for the source object
  83.181 -            cursor = connection.cursor()
  83.182 -            cursor.execute("DELETE FROM %s WHERE %s = %%s" % \
  83.183 -                (self.join_table, source_col_name),
  83.184 -                [self._pk_val])
  83.185 -            transaction.commit_unless_managed()
  83.186 +            self.through._default_manager.filter(**{
  83.187 +                source_field_name: self._pk_val
  83.188 +            }).delete()
  83.189  
  83.190      return ManyRelatedManager
  83.191  
  83.192 @@ -554,17 +552,15 @@
  83.193          # model's default manager.
  83.194          rel_model = self.related.model
  83.195          superclass = rel_model._default_manager.__class__
  83.196 -        RelatedManager = create_many_related_manager(superclass, self.related.field.rel.through)
  83.197 +        RelatedManager = create_many_related_manager(superclass, self.related.field.rel)
  83.198  
  83.199 -        qn = connection.ops.quote_name
  83.200          manager = RelatedManager(
  83.201              model=rel_model,
  83.202              core_filters={'%s__pk' % self.related.field.name: instance._get_pk_val()},
  83.203              instance=instance,
  83.204              symmetrical=False,
  83.205 -            join_table=qn(self.related.field.m2m_db_table()),
  83.206 -            source_col_name=qn(self.related.field.m2m_reverse_name()),
  83.207 -            target_col_name=qn(self.related.field.m2m_column_name())
  83.208 +            source_field_name=self.related.field.m2m_reverse_field_name(),
  83.209 +            target_field_name=self.related.field.m2m_field_name()
  83.210          )
  83.211  
  83.212          return manager
  83.213 @@ -573,9 +569,9 @@
  83.214          if instance is None:
  83.215              raise AttributeError, "Manager must be accessed via instance"
  83.216  
  83.217 -        through = getattr(self.related.field.rel, 'through', None)
  83.218 -        if through is not None:
  83.219 -            raise AttributeError, "Cannot set values on a ManyToManyField which specifies an intermediary model. Use %s's Manager instead." % through
  83.220 +        if not self.related.field.rel.through._meta.auto_created:
  83.221 +            opts = self.related.field.rel.through._meta
  83.222 +            raise AttributeError, "Cannot set values on a ManyToManyField which specifies an intermediary model. Use %s.%s's Manager instead." % (opts.app_label, opts.object_name)
  83.223  
  83.224          manager = self.__get__(instance)
  83.225          manager.clear()
  83.226 @@ -590,6 +586,9 @@
  83.227      # ReverseManyRelatedObjectsDescriptor instance.
  83.228      def __init__(self, m2m_field):
  83.229          self.field = m2m_field
  83.230 +        # through is provided so that you have easy access to the through
  83.231 +        # model (Book.authors.through) for inlines, etc.
  83.232 +        self.through = m2m_field.rel.through
  83.233  
  83.234      def __get__(self, instance, instance_type=None):
  83.235          if instance is None:
  83.236 @@ -599,17 +598,15 @@
  83.237          # model's default manager.
  83.238          rel_model=self.field.rel.to
  83.239          superclass = rel_model._default_manager.__class__
  83.240 -        RelatedManager = create_many_related_manager(superclass, self.field.rel.through)
  83.241 +        RelatedManager = create_many_related_manager(superclass, self.field.rel)
  83.242  
  83.243 -        qn = connection.ops.quote_name
  83.244          manager = RelatedManager(
  83.245              model=rel_model,
  83.246              core_filters={'%s__pk' % self.field.related_query_name(): instance._get_pk_val()},
  83.247              instance=instance,
  83.248              symmetrical=(self.field.rel.symmetrical and isinstance(instance, rel_model)),
  83.249 -            join_table=qn(self.field.m2m_db_table()),
  83.250 -            source_col_name=qn(self.field.m2m_column_name()),
  83.251 -            target_col_name=qn(self.field.m2m_reverse_name())
  83.252 +            source_field_name=self.field.m2m_field_name(),
  83.253 +            target_field_name=self.field.m2m_reverse_field_name()
  83.254          )
  83.255  
  83.256          return manager
  83.257 @@ -618,9 +615,9 @@
  83.258          if instance is None:
  83.259              raise AttributeError, "Manager must be accessed via instance"
  83.260  
  83.261 -        through = getattr(self.field.rel, 'through', None)
  83.262 -        if through is not None:
  83.263 -            raise AttributeError, "Cannot set values on a ManyToManyField which specifies an intermediary model.  Use %s's Manager instead." % through
  83.264 +        if not self.field.rel.through._meta.auto_created:
  83.265 +            opts = self.field.rel.through._meta
  83.266 +            raise AttributeError, "Cannot set values on a ManyToManyField which specifies an intermediary model.  Use %s.%s's Manager instead." % (opts.app_label, opts.object_name)
  83.267  
  83.268          manager = self.__get__(instance)
  83.269          manager.clear()
  83.270 @@ -642,6 +639,10 @@
  83.271          self.multiple = True
  83.272          self.parent_link = parent_link
  83.273  
  83.274 +    def is_hidden(self):
  83.275 +        "Should the related object be hidden?"
  83.276 +        return self.related_name and self.related_name[-1] == '+'
  83.277 +
  83.278      def get_related_field(self):
  83.279          """
  83.280          Returns the Field in the 'to' object to which this relationship is
  83.281 @@ -673,6 +674,10 @@
  83.282          self.multiple = True
  83.283          self.through = through
  83.284  
  83.285 +    def is_hidden(self):
  83.286 +        "Should the related object be hidden?"
  83.287 +        return self.related_name and self.related_name[-1] == '+'
  83.288 +
  83.289      def get_related_field(self):
  83.290          """
  83.291          Returns the field in the to' object to which this relationship is tied
  83.292 @@ -690,7 +695,10 @@
  83.293              assert isinstance(to, basestring), "%s(%r) is invalid. First parameter to ForeignKey must be either a model, a model name, or the string %r" % (self.__class__.__name__, to, RECURSIVE_RELATIONSHIP_CONSTANT)
  83.294          else:
  83.295              assert not to._meta.abstract, "%s cannot define a relation with abstract class %s" % (self.__class__.__name__, to._meta.object_name)
  83.296 -            to_field = to_field or to._meta.pk.name
  83.297 +            # For backwards compatibility purposes, we need to *try* and set
  83.298 +            # the to_field during FK construction. It won't be guaranteed to
  83.299 +            # be correct until contribute_to_class is called. Refs #12190.
  83.300 +            to_field = to_field or (to._meta.pk and to._meta.pk.name)
  83.301          kwargs['verbose_name'] = kwargs.get('verbose_name', None)
  83.302  
  83.303          kwargs['rel'] = rel_class(to, to_field,
  83.304 @@ -743,7 +751,12 @@
  83.305          cls._meta.duplicate_targets[self.column] = (target, "o2m")
  83.306  
  83.307      def contribute_to_related_class(self, cls, related):
  83.308 -        setattr(cls, related.get_accessor_name(), ForeignRelatedObjectsDescriptor(related))
  83.309 +        # Internal FK's - i.e., those with a related name ending with '+' -
  83.310 +        # don't get a related descriptor.
  83.311 +        if not self.rel.is_hidden():
  83.312 +            setattr(cls, related.get_accessor_name(), ForeignRelatedObjectsDescriptor(related))
  83.313 +        if self.rel.field_name is None:
  83.314 +            self.rel.field_name = cls._meta.pk.name
  83.315  
  83.316      def formfield(self, **kwargs):
  83.317          defaults = {
  83.318 @@ -790,6 +803,52 @@
  83.319              return None
  83.320          return super(OneToOneField, self).formfield(**kwargs)
  83.321  
  83.322 +def create_many_to_many_intermediary_model(field, klass):
  83.323 +    from django.db import models
  83.324 +    managed = True
  83.325 +    if isinstance(field.rel.to, basestring) and field.rel.to != RECURSIVE_RELATIONSHIP_CONSTANT:
  83.326 +        to = field.rel.to
  83.327 +        to_model = field.rel.to
  83.328 +        def set_managed(field, model, cls):
  83.329 +            field.rel.through._meta.managed = model._meta.managed or cls._meta.managed
  83.330 +        add_lazy_relation(klass, field, to_model, set_managed)
  83.331 +    elif isinstance(field.rel.to, basestring):
  83.332 +        to = klass._meta.object_name
  83.333 +        to_model = klass
  83.334 +        managed = klass._meta.managed
  83.335 +    else:
  83.336 +        to = field.rel.to._meta.object_name
  83.337 +        to_model = field.rel.to
  83.338 +        managed = klass._meta.managed or to_model._meta.managed
  83.339 +    name = '%s_%s' % (klass._meta.object_name, field.name)
  83.340 +    if field.rel.to == RECURSIVE_RELATIONSHIP_CONSTANT or field.rel.to == klass._meta.object_name:
  83.341 +        from_ = 'from_%s' % to.lower()
  83.342 +        to = 'to_%s' % to.lower()
  83.343 +    else:
  83.344 +        from_ = klass._meta.object_name.lower()
  83.345 +        to = to.lower()
  83.346 +    meta = type('Meta', (object,), {
  83.347 +        'db_table': field._get_m2m_db_table(klass._meta),
  83.348 +        'managed': managed,
  83.349 +        'auto_created': klass,
  83.350 +        'unique_together': (from_, to)
  83.351 +    })
  83.352 +    # If the models have been split into subpackages, klass.__module__
  83.353 +    # will be the subpackge, not the models module for the app. (See #12168)
  83.354 +    # Compose the actual models module name by stripping the trailing parts
  83.355 +    # of the namespace until we find .models
  83.356 +    parts = klass.__module__.split('.')
  83.357 +    while parts[-1] != 'models':
  83.358 +        parts.pop()
  83.359 +    module = '.'.join(parts)
  83.360 +    # Construct and return the new class.
  83.361 +    return type(name, (models.Model,), {
  83.362 +        'Meta': meta,
  83.363 +        '__module__': module,
  83.364 +        from_: models.ForeignKey(klass, related_name='%s+' % name),
  83.365 +        to: models.ForeignKey(to_model, related_name='%s+' % name)
  83.366 +    })
  83.367 +
  83.368  class ManyToManyField(RelatedField, Field):
  83.369      def __init__(self, to, **kwargs):
  83.370          try:
  83.371 @@ -806,10 +865,7 @@
  83.372  
  83.373          self.db_table = kwargs.pop('db_table', None)
  83.374          if kwargs['rel'].through is not None:
  83.375 -            self.creates_table = False
  83.376              assert self.db_table is None, "Cannot specify a db_table if an intermediary model is used."
  83.377 -        else:
  83.378 -            self.creates_table = True
  83.379  
  83.380          Field.__init__(self, **kwargs)
  83.381  
  83.382 @@ -822,62 +878,45 @@
  83.383      def _get_m2m_db_table(self, opts):
  83.384          "Function that can be curried to provide the m2m table name for this relation"
  83.385          if self.rel.through is not None:
  83.386 -            return self.rel.through_model._meta.db_table
  83.387 +            return self.rel.through._meta.db_table
  83.388          elif self.db_table:
  83.389              return self.db_table
  83.390          else:
  83.391              return util.truncate_name('%s_%s' % (opts.db_table, self.name),
  83.392                                        connection.ops.max_name_length())
  83.393  
  83.394 -    def _get_m2m_column_name(self, related):
  83.395 +    def _get_m2m_attr(self, related, attr):
  83.396          "Function that can be curried to provide the source column name for the m2m table"
  83.397 -        try:
  83.398 -            return self._m2m_column_name_cache
  83.399 -        except:
  83.400 -            if self.rel.through is not None:
  83.401 -                for f in self.rel.through_model._meta.fields:
  83.402 -                    if hasattr(f,'rel') and f.rel and f.rel.to == related.model:
  83.403 -                        self._m2m_column_name_cache = f.column
  83.404 +        cache_attr = '_m2m_%s_cache' % attr
  83.405 +        if hasattr(self, cache_attr):
  83.406 +            return getattr(self, cache_attr)
  83.407 +        for f in self.rel.through._meta.fields:
  83.408 +            if hasattr(f,'rel') and f.rel and f.rel.to == related.model:
  83.409 +                setattr(self, cache_attr, getattr(f, attr))
  83.410 +                return getattr(self, cache_attr)
  83.411 +
  83.412 +    def _get_m2m_reverse_attr(self, related, attr):
  83.413 +        "Function that can be curried to provide the related column name for the m2m table"
  83.414 +        cache_attr = '_m2m_reverse_%s_cache' % attr
  83.415 +        if hasattr(self, cache_attr):
  83.416 +            return getattr(self, cache_attr)
  83.417 +        found = False
  83.418 +        for f in self.rel.through._meta.fields:
  83.419 +            if hasattr(f,'rel') and f.rel and f.rel.to == related.parent_model:
  83.420 +                if related.model == related.parent_model:
  83.421 +                    # If this is an m2m-intermediate to self,
  83.422 +                    # the first foreign key you find will be
  83.423 +                    # the source column. Keep searching for
  83.424 +                    # the second foreign key.
  83.425 +                    if found:
  83.426 +                        setattr(self, cache_attr, getattr(f, attr))
  83.427                          break
  83.428 -            # If this is an m2m relation to self, avoid the inevitable name clash
  83.429 -            elif related.model == related.parent_model:
  83.430 -                self._m2m_column_name_cache = 'from_' + related.model._meta.object_name.lower() + '_id'
  83.431 -            else:
  83.432 -                self._m2m_column_name_cache = related.model._meta.object_name.lower() + '_id'
  83.433 -
  83.434 -            # Return the newly cached value
  83.435 -            return self._m2m_column_name_cache
  83.436 -
  83.437 -    def _get_m2m_reverse_name(self, related):
  83.438 -        "Function that can be curried to provide the related column name for the m2m table"
  83.439 -        try:
  83.440 -            return self._m2m_reverse_name_cache
  83.441 -        except:
  83.442 -            if self.rel.through is not None:
  83.443 -                found = False
  83.444 -                for f in self.rel.through_model._meta.fields:
  83.445 -                    if hasattr(f,'rel') and f.rel and f.rel.to == related.parent_model:
  83.446 -                        if related.model == related.parent_model:
  83.447 -                            # If this is an m2m-intermediate to self,
  83.448 -                            # the first foreign key you find will be
  83.449 -                            # the source column. Keep searching for
  83.450 -                            # the second foreign key.
  83.451 -                            if found:
  83.452 -                                self._m2m_reverse_name_cache = f.column
  83.453 -                                break
  83.454 -                            else:
  83.455 -                                found = True
  83.456 -                        else:
  83.457 -                            self._m2m_reverse_name_cache = f.column
  83.458 -                            break
  83.459 -            # If this is an m2m relation to self, avoid the inevitable name clash
  83.460 -            elif related.model == related.parent_model:
  83.461 -                self._m2m_reverse_name_cache = 'to_' + related.parent_model._meta.object_name.lower() + '_id'
  83.462 -            else:
  83.463 -                self._m2m_reverse_name_cache = related.parent_model._meta.object_name.lower() + '_id'
  83.464 -
  83.465 -            # Return the newly cached value
  83.466 -            return self._m2m_reverse_name_cache
  83.467 +                    else:
  83.468 +                        found = True
  83.469 +                else:
  83.470 +                    setattr(self, cache_attr, getattr(f, attr))
  83.471 +                    break
  83.472 +        return getattr(self, cache_attr)
  83.473  
  83.474      def isValidIDList(self, field_data, all_data):
  83.475          "Validates that the value is a valid list of foreign keys"
  83.476 @@ -919,10 +958,17 @@
  83.477          # specify *what* on my non-reversible relation?!"), so we set it up
  83.478          # automatically. The funky name reduces the chance of an accidental
  83.479          # clash.
  83.480 -        if self.rel.symmetrical and self.rel.to == "self" and self.rel.related_name is None:
  83.481 +        if self.rel.symmetrical and (self.rel.to == "self" or self.rel.to == cls._meta.object_name):
  83.482              self.rel.related_name = "%s_rel_+" % name
  83.483  
  83.484          super(ManyToManyField, self).contribute_to_class(cls, name)
  83.485 +
  83.486 +        # The intermediate m2m model is not auto created if:
  83.487 +        #  1) There is a manually specified intermediate, or
  83.488 +        #  2) The class owning the m2m field is abstract.
  83.489 +        if not self.rel.through and not cls._meta.abstract:
  83.490 +            self.rel.through = create_many_to_many_intermediary_model(self, cls)
  83.491 +
  83.492          # Add the descriptor for the m2m relation
  83.493          setattr(cls, self.name, ReverseManyRelatedObjectsDescriptor(self))
  83.494  
  83.495 @@ -933,11 +979,8 @@
  83.496          # work correctly.
  83.497          if isinstance(self.rel.through, basestring):
  83.498              def resolve_through_model(field, model, cls):
  83.499 -                field.rel.through_model = model
  83.500 +                field.rel.through = model
  83.501              add_lazy_relation(cls, self, self.rel.through, resolve_through_model)
  83.502 -        elif self.rel.through:
  83.503 -            self.rel.through_model = self.rel.through
  83.504 -            self.rel.through = self.rel.through._meta.object_name
  83.505  
  83.506          if isinstance(self.rel.to, basestring):
  83.507              target = self.rel.to
  83.508 @@ -946,15 +989,17 @@
  83.509          cls._meta.duplicate_targets[self.column] = (target, "m2m")
  83.510  
  83.511      def contribute_to_related_class(self, cls, related):
  83.512 -        # m2m relations to self do not have a ManyRelatedObjectsDescriptor,
  83.513 -        # as it would be redundant - unless the field is non-symmetrical.
  83.514 -        if related.model != related.parent_model or not self.rel.symmetrical:
  83.515 -            # Add the descriptor for the m2m relation
  83.516 +        # Internal M2Ms (i.e., those with a related name ending with '+')
  83.517 +        # don't get a related descriptor.
  83.518 +        if not self.rel.is_hidden():
  83.519              setattr(cls, related.get_accessor_name(), ManyRelatedObjectsDescriptor(related))
  83.520  
  83.521          # Set up the accessors for the column names on the m2m table
  83.522 -        self.m2m_column_name = curry(self._get_m2m_column_name, related)
  83.523 -        self.m2m_reverse_name = curry(self._get_m2m_reverse_name, related)
  83.524 +        self.m2m_column_name = curry(self._get_m2m_attr, related, 'column')
  83.525 +        self.m2m_reverse_name = curry(self._get_m2m_reverse_attr, related, 'column')
  83.526 +
  83.527 +        self.m2m_field_name = curry(self._get_m2m_attr, related, 'name')
  83.528 +        self.m2m_reverse_field_name = curry(self._get_m2m_reverse_attr, related, 'name')
  83.529  
  83.530      def set_attributes_from_rel(self):
  83.531          pass
    84.1 --- a/django/db/models/loading.py	Sat Sep 12 18:53:56 2009 -0500
    84.2 +++ b/django/db/models/loading.py	Fri Nov 13 11:27:59 2009 -0600
    84.3 @@ -131,19 +131,25 @@
    84.4          self._populate()
    84.5          return self.app_errors
    84.6  
    84.7 -    def get_models(self, app_mod=None):
    84.8 +    def get_models(self, app_mod=None, include_auto_created=False):
    84.9          """
   84.10          Given a module containing models, returns a list of the models.
   84.11          Otherwise returns a list of all installed models.
   84.12 +
   84.13 +        By default, auto-created models (i.e., m2m models without an
   84.14 +        explicit intermediate table) are not included. However, if you
   84.15 +        specify include_auto_created=True, they will be.
   84.16          """
   84.17          self._populate()
   84.18          if app_mod:
   84.19 -            return self.app_models.get(app_mod.__name__.split('.')[-2], SortedDict()).values()
   84.20 +            model_list = self.app_models.get(app_mod.__name__.split('.')[-2], SortedDict()).values()
   84.21          else:
   84.22              model_list = []
   84.23              for app_entry in self.app_models.itervalues():
   84.24                  model_list.extend(app_entry.values())
   84.25 -            return model_list
   84.26 +        if not include_auto_created:
   84.27 +            return filter(lambda o: not o._meta.auto_created, model_list)
   84.28 +        return model_list
   84.29  
   84.30      def get_model(self, app_label, model_name, seed_cache=True):
   84.31          """
    85.1 --- a/django/db/models/manager.py	Sat Sep 12 18:53:56 2009 -0500
    85.2 +++ b/django/db/models/manager.py	Fri Nov 13 11:27:59 2009 -0600
    85.3 @@ -1,5 +1,4 @@
    85.4  import copy
    85.5 -
    85.6  from django.db.models.query import QuerySet, EmptyQuerySet, insert_query
    85.7  from django.db.models import signals
    85.8  from django.db.models.fields import FieldDoesNotExist
    85.9 @@ -173,6 +172,9 @@
   85.10      def only(self, *args, **kwargs):
   85.11          return self.get_query_set().only(*args, **kwargs)
   85.12  
   85.13 +    def exists(self, *args, **kwargs):
   85.14 +        return self.get_query_set().exists(*args, **kwargs)
   85.15 +
   85.16      def _insert(self, values, **kwargs):
   85.17          return insert_query(self.model, values, **kwargs)
   85.18  
    86.1 --- a/django/db/models/options.py	Sat Sep 12 18:53:56 2009 -0500
    86.2 +++ b/django/db/models/options.py	Fri Nov 13 11:27:59 2009 -0600
    86.3 @@ -21,7 +21,7 @@
    86.4  DEFAULT_NAMES = ('verbose_name', 'db_table', 'ordering',
    86.5                   'unique_together', 'permissions', 'get_latest_by',
    86.6                   'order_with_respect_to', 'app_label', 'db_tablespace',
    86.7 -                 'abstract', 'managed', 'proxy')
    86.8 +                 'abstract', 'managed', 'proxy', 'auto_created')
    86.9  
   86.10  class Options(object):
   86.11      def __init__(self, meta, app_label=None):
   86.12 @@ -47,6 +47,7 @@
   86.13          self.proxy_for_model = None
   86.14          self.parents = SortedDict()
   86.15          self.duplicate_targets = {}
   86.16 +        self.auto_created = False
   86.17  
   86.18          # To handle various inheritance situations, we need to track where
   86.19          # managers came from (concrete or abstract base classes).
   86.20 @@ -487,4 +488,3 @@
   86.21          Returns the index of the primary key field in the self.fields list.
   86.22          """
   86.23          return self.fields.index(self.pk)
   86.24 -
    87.1 --- a/django/db/models/query.py	Sat Sep 12 18:53:56 2009 -0500
    87.2 +++ b/django/db/models/query.py	Fri Nov 13 11:27:59 2009 -0600
    87.3 @@ -2,20 +2,13 @@
    87.4  The main QuerySet implementation. This provides the public API for the ORM.
    87.5  """
    87.6  
    87.7 -try:
    87.8 -    set
    87.9 -except NameError:
   87.10 -    from sets import Set as set     # Python 2.3 fallback
   87.11 -
   87.12  from copy import deepcopy
   87.13 -
   87.14  from django.db import connection, transaction, IntegrityError
   87.15  from django.db.models.aggregates import Aggregate
   87.16  from django.db.models.fields import DateField
   87.17  from django.db.models.query_utils import Q, select_related_descend, CollectedObjects, CyclicDependency, deferred_class_factory
   87.18  from django.db.models import signals, sql
   87.19  
   87.20 -
   87.21  # Used to control how many objects are worked with at once in some cases (e.g.
   87.22  # when deleting objects).
   87.23  CHUNK_SIZE = 100
   87.24 @@ -444,6 +437,11 @@
   87.25          return query.execute_sql(None)
   87.26      _update.alters_data = True
   87.27  
   87.28 +    def exists(self):
   87.29 +        if self._result_cache is None:
   87.30 +            return self.query.has_results()
   87.31 +        return bool(self._result_cache)
   87.32 +
   87.33      ##################################################
   87.34      # PUBLIC METHODS THAT RETURN A QUERYSET SUBCLASS #
   87.35      ##################################################
   87.36 @@ -1030,7 +1028,8 @@
   87.37  
   87.38              # Pre-notify all instances to be deleted.
   87.39              for pk_val, instance in items:
   87.40 -                signals.pre_delete.send(sender=cls, instance=instance)
   87.41 +                if not cls._meta.auto_created:
   87.42 +                    signals.pre_delete.send(sender=cls, instance=instance)
   87.43  
   87.44              pk_list = [pk for pk,instance in items]
   87.45              del_query = sql.DeleteQuery(cls, connection)
   87.46 @@ -1064,7 +1063,8 @@
   87.47                      if field.rel and field.null and field.rel.to in seen_objs:
   87.48                          setattr(instance, field.attname, None)
   87.49  
   87.50 -                signals.post_delete.send(sender=cls, instance=instance)
   87.51 +                if not cls._meta.auto_created:
   87.52 +                    signals.post_delete.send(sender=cls, instance=instance)
   87.53                  setattr(instance, cls._meta.pk.attname, None)
   87.54  
   87.55          if forced_managed:
    88.1 --- a/django/db/models/sql/expressions.py	Sat Sep 12 18:53:56 2009 -0500
    88.2 +++ b/django/db/models/sql/expressions.py	Fri Nov 13 11:27:59 2009 -0600
    88.3 @@ -66,7 +66,7 @@
    88.4              else:
    88.5                  sql, params = '%s', (child,)
    88.6  
    88.7 -            if hasattr(child, 'children') > 1:
    88.8 +            if len(getattr(child, 'children', [])) > 1:
    88.9                  format = '(%s)'
   88.10              else:
   88.11                  format = '%s'
    89.1 --- a/django/db/models/sql/query.py	Sat Sep 12 18:53:56 2009 -0500
    89.2 +++ b/django/db/models/sql/query.py	Fri Nov 13 11:27:59 2009 -0600
    89.3 @@ -8,7 +8,6 @@
    89.4  """
    89.5  
    89.6  from copy import deepcopy
    89.7 -
    89.8  from django.utils.tree import Node
    89.9  from django.utils.datastructures import SortedDict
   89.10  from django.utils.encoding import force_unicode
   89.11 @@ -24,11 +23,6 @@
   89.12  from datastructures import EmptyResultSet, Empty, MultiJoin
   89.13  from constants import *
   89.14  
   89.15 -try:
   89.16 -    set
   89.17 -except NameError:
   89.18 -    from sets import Set as set     # Python 2.3 fallback
   89.19 -
   89.20  __all__ = ['Query', 'BaseQuery']
   89.21  
   89.22  class BaseQuery(object):
   89.23 @@ -384,6 +378,16 @@
   89.24  
   89.25          return number
   89.26  
   89.27 +    def has_results(self):
   89.28 +        q = self.clone()
   89.29 +        q.add_extra({'a': 1}, None, None, None, None, None)
   89.30 +        q.add_fields(())
   89.31 +        q.set_extra_mask(('a',))
   89.32 +        q.set_aggregate_mask(())
   89.33 +        q.clear_ordering()
   89.34 +        q.set_limits(high=1)
   89.35 +        return bool(q.execute_sql(SINGLE))
   89.36 +
   89.37      def as_sql(self, with_limits=True, with_col_aliases=False):
   89.38          """
   89.39          Creates the SQL for this query. Returns the SQL string and list of
    90.1 --- a/django/forms/fields.py	Sat Sep 12 18:53:56 2009 -0500
    90.2 +++ b/django/forms/fields.py	Fri Nov 13 11:27:59 2009 -0600
    90.3 @@ -421,7 +421,7 @@
    90.4  email_re = re.compile(
    90.5      r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*"  # dot-atom
    90.6      r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-011\013\014\016-\177])*"' # quoted-string
    90.7 -    r')@(?:[A-Z0-9]+(?:-*[A-Z0-9]+)*\.)+[A-Z]{2,6}$', re.IGNORECASE)  # domain
    90.8 +    r')@(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?$', re.IGNORECASE)  # domain
    90.9  
   90.10  class EmailField(RegexField):
   90.11      default_error_messages = {
   90.12 @@ -532,7 +532,7 @@
   90.13  
   90.14  url_re = re.compile(
   90.15      r'^https?://' # http:// or https://
   90.16 -    r'(?:(?:[A-Z0-9]+(?:-*[A-Z0-9]+)*\.)+[A-Z]{2,6}|' #domain...
   90.17 +    r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?|' #domain...
   90.18      r'localhost|' #localhost...
   90.19      r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' # ...or ip
   90.20      r'(?::\d+)?' # optional port
    91.1 --- a/django/forms/models.py	Sat Sep 12 18:53:56 2009 -0500
    91.2 +++ b/django/forms/models.py	Fri Nov 13 11:27:59 2009 -0600
    91.3 @@ -319,9 +319,7 @@
    91.4              if self.instance.pk is not None:
    91.5                  qs = qs.exclude(pk=self.instance.pk)
    91.6  
    91.7 -            # This cute trick with extra/values is the most efficient way to
    91.8 -            # tell if a particular query returns any results.
    91.9 -            if qs.extra(select={'a': 1}).values('a').order_by():
   91.10 +            if qs.exists():
   91.11                  if len(unique_check) == 1:
   91.12                      self._errors[unique_check[0]] = ErrorList([self.unique_error_message(unique_check)])
   91.13                  else:
   91.14 @@ -354,9 +352,7 @@
   91.15              if self.instance.pk is not None:
   91.16                  qs = qs.exclude(pk=self.instance.pk)
   91.17  
   91.18 -            # This cute trick with extra/values is the most efficient way to
   91.19 -            # tell if a particular query returns any results.
   91.20 -            if qs.extra(select={'a': 1}).values('a').order_by():
   91.21 +            if qs.exists():
   91.22                  self._errors[field] = ErrorList([
   91.23                      self.date_error_message(lookup_type, field, unique_for)
   91.24                  ])
   91.25 @@ -709,7 +705,7 @@
   91.26                   save_as_new=False, prefix=None):
   91.27          from django.db.models.fields.related import RelatedObject
   91.28          if instance is None:
   91.29 -            self.instance = self.model()
   91.30 +            self.instance = self.fk.rel.to()
   91.31          else:
   91.32              self.instance = instance
   91.33          self.save_as_new = save_as_new
    92.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    92.2 +++ b/django/middleware/csrf.py	Fri Nov 13 11:27:59 2009 -0600
    92.3 @@ -0,0 +1,265 @@
    92.4 +"""
    92.5 +Cross Site Request Forgery Middleware.
    92.6 +
    92.7 +This module provides a middleware that implements protection
    92.8 +against request forgeries from other sites.
    92.9 +"""
   92.10 +
   92.11 +import itertools
   92.12 +import re
   92.13 +import random
   92.14 +
   92.15 +from django.conf import settings
   92.16 +from django.core.urlresolvers import get_callable
   92.17 +from django.utils.cache import patch_vary_headers
   92.18 +from django.utils.hashcompat import md5_constructor
   92.19 +from django.utils.safestring import mark_safe
   92.20 +
   92.21 +_POST_FORM_RE = \
   92.22 +    re.compile(r'(<form\W[^>]*\bmethod\s*=\s*(\'|"|)POST(\'|"|)\b[^>]*>)', re.IGNORECASE)
   92.23 +
   92.24 +_HTML_TYPES = ('text/html', 'application/xhtml+xml')
   92.25 +
   92.26 +# Use the system (hardware-based) random number generator if it exists.
   92.27 +if hasattr(random, 'SystemRandom'):
   92.28 +    randrange = random.SystemRandom().randrange
   92.29 +else:
   92.30 +    randrange = random.randrange
   92.31 +_MAX_CSRF_KEY = 18446744073709551616L     # 2 << 63
   92.32 +
   92.33 +def _get_failure_view():
   92.34 +    """
   92.35 +    Returns the view to be used for CSRF rejections
   92.36 +    """
   92.37 +    return get_callable(settings.CSRF_FAILURE_VIEW)
   92.38 +
   92.39 +def _get_new_csrf_key():
   92.40 +    return md5_constructor("%s%s"
   92.41 +                % (randrange(0, _MAX_CSRF_KEY), settings.SECRET_KEY)).hexdigest()
   92.42 +
   92.43 +def _make_legacy_session_token(session_id):
   92.44 +    return md5_constructor(settings.SECRET_KEY + session_id).hexdigest()
   92.45 +
   92.46 +def get_token(request):
   92.47 +    """
   92.48 +    Returns the the CSRF token required for a POST form.
   92.49 +
   92.50 +    A side effect of calling this function is to make the the csrf_protect
   92.51 +    decorator and the CsrfViewMiddleware add a CSRF cookie and a 'Vary: Cookie'
   92.52 +    header to the outgoing response.  For this reason, you may need to use this
   92.53 +    function lazily, as is done by the csrf context processor.
   92.54 +    """
   92.55 +    request.META["CSRF_COOKIE_USED"] = True
   92.56 +    return request.META.get("CSRF_COOKIE", None)
   92.57 +
   92.58 +class CsrfViewMiddleware(object):
   92.59 +    """
   92.60 +    Middleware that requires a present and correct csrfmiddlewaretoken
   92.61 +    for POST requests that have a CSRF cookie, and sets an outgoing
   92.62 +    CSRF cookie.
   92.63 +
   92.64 +    This middleware should be used in conjunction with the csrf_token template
   92.65 +    tag.
   92.66 +    """
   92.67 +    def process_view(self, request, callback, callback_args, callback_kwargs):
   92.68 +        if getattr(callback, 'csrf_exempt', False):
   92.69 +            return None
   92.70 +
   92.71 +        if getattr(request, 'csrf_processing_done', False):
   92.72 +            return None
   92.73 +
   92.74 +        reject = lambda s: _get_failure_view()(request, reason=s)
   92.75 +        def accept():
   92.76 +            # Avoid checking the request twice by adding a custom attribute to
   92.77 +            # request.  This will be relevant when both decorator and middleware
   92.78 +            # are used.
   92.79 +            request.csrf_processing_done = True
   92.80 +            return None
   92.81 +
   92.82 +        # If the user doesn't have a CSRF cookie, generate one and store it in the
   92.83 +        # request, so it's available to the view.  We'll store it in a cookie when
   92.84 +        # we reach the response.
   92.85 +        try:
   92.86 +            request.META["CSRF_COOKIE"] = request.COOKIES[settings.CSRF_COOKIE_NAME]
   92.87 +            cookie_is_new = False
   92.88 +        except KeyError:
   92.89 +            # No cookie, so create one.  This will be sent with the next
   92.90 +            # response.
   92.91 +            request.META["CSRF_COOKIE"] = _get_new_csrf_key()
   92.92 +            # Set a flag to allow us to fall back and allow the session id in
   92.93 +            # place of a CSRF cookie for this request only.
   92.94 +            cookie_is_new = True
   92.95 +
   92.96 +        if request.method == 'POST':
   92.97 +            if getattr(request, '_dont_enforce_csrf_checks', False):
   92.98 +                # Mechanism to turn off CSRF checks for test suite.  It comes after
   92.99 +                # the creation of CSRF cookies, so that everything else continues to
  92.100 +                # work exactly the same (e.g. cookies are sent etc), but before the
  92.101 +                # any branches that call reject()
  92.102 +                return accept()
  92.103 +
  92.104 +            if request.is_ajax():
  92.105 +                # .is_ajax() is based on the presence of X-Requested-With.  In
  92.106 +                # the context of a browser, this can only be sent if using
  92.107 +                # XmlHttpRequest.  Browsers implement careful policies for
  92.108 +                # XmlHttpRequest:
  92.109 +                #
  92.110 +                #  * Normally, only same-domain requests are allowed.
  92.111 +                #
  92.112 +                #  * Some browsers (e.g. Firefox 3.5 and later) relax this
  92.113 +                #    carefully:
  92.114 +                #
  92.115 +                #    * if it is a 'simple' GET or POST request (which can
  92.116 +                #      include no custom headers), it is allowed to be cross
  92.117 +                #      domain.  These requests will not be recognized as AJAX.
  92.118 +                #
  92.119 +                #    * if a 'preflight' check with the server confirms that the
  92.120 +                #      server is expecting and allows the request, cross domain
  92.121 +                #      requests even with custom headers are allowed. These
  92.122 +                #      requests will be recognized as AJAX, but can only get
  92.123 +                #      through when the developer has specifically opted in to
  92.124 +                #      allowing the cross-domain POST request.
  92.125 +                #
  92.126 +                # So in all cases, it is safe to allow these requests through.
  92.127 +                return accept()
  92.128 +
  92.129 +            if request.is_secure():
  92.130 +                # Strict referer checking for HTTPS
  92.131 +                referer = request.META.get('HTTP_REFERER')
  92.132 +                if referer is None:
  92.133 +                    return reject("Referer checking failed - no Referer.")
  92.134 +
  92.135 +                # The following check ensures that the referer is HTTPS,
  92.136 +                # the domains match and the ports match.  This might be too strict.
  92.137 +                good_referer = 'https://%s/' % request.get_host()
  92.138 +                if not referer.startswith(good_referer):
  92.139 +                    return reject("Referer checking failed - %s does not match %s." %
  92.140 +                                  (referer, good_referer))
  92.141 +
  92.142 +            # If the user didn't already have a CSRF cookie, then fall back to
  92.143 +            # the Django 1.1 method (hash of session ID), so a request is not
  92.144 +            # rejected if the form was sent to the user before upgrading to the
  92.145 +            # Django 1.2 method (session independent nonce)
  92.146 +            if cookie_is_new:
  92.147 +                try:
  92.148 +                    session_id = request.COOKIES[settings.SESSION_COOKIE_NAME]
  92.149 +                    csrf_token = _make_legacy_session_token(session_id)
  92.150 +                except KeyError:
  92.151 +                    # No CSRF cookie and no session cookie. For POST requests,
  92.152 +                    # we insist on a CSRF cookie, and in this way we can avoid
  92.153 +                    # all CSRF attacks, including login CSRF.
  92.154 +                    return reject("No CSRF or session cookie.")
  92.155 +            else:
  92.156 +                csrf_token = request.META["CSRF_COOKIE"]
  92.157 +
  92.158 +            # check incoming token
  92.159 +            request_csrf_token = request.POST.get('csrfmiddlewaretoken', None)
  92.160 +            if request_csrf_token != csrf_token:
  92.161 +                if cookie_is_new:
  92.162 +                    # probably a problem setting the CSRF cookie
  92.163 +                    return reject("CSRF cookie not set.")
  92.164 +                else:
  92.165 +                    return reject("CSRF token missing or incorrect.")
  92.166 +
  92.167 +        return accept()
  92.168 +
  92.169 +    def process_response(self, request, response):
  92.170 +        if getattr(response, 'csrf_processing_done', False):
  92.171 +            return response
  92.172 +
  92.173 +        # If CSRF_COOKIE is unset, then CsrfViewMiddleware.process_view was
  92.174 +        # never called, probaby because a request middleware returned a response
  92.175 +        # (for example, contrib.auth redirecting to a login page).
  92.176 +        if request.META.get("CSRF_COOKIE") is None:
  92.177 +            return response
  92.178 +
  92.179 +        if not request.META.get("CSRF_COOKIE_USED", False):
  92.180 +            return response
  92.181 +
  92.182 +        # Set the CSRF cookie even if it's already set, so we renew the expiry timer.
  92.183 +        response.set_cookie(settings.CSRF_COOKIE_NAME,
  92.184 +                request.META["CSRF_COOKIE"], max_age = 60 * 60 * 24 * 7 * 52,
  92.185 +                domain=settings.CSRF_COOKIE_DOMAIN)
  92.186 +        # Content varies with the CSRF cookie, so set the Vary header.
  92.187 +        patch_vary_headers(response, ('Cookie',))
  92.188 +        response.csrf_processing_done = True
  92.189 +        return response
  92.190 +
  92.191 +class CsrfResponseMiddleware(object):
  92.192 +    """
  92.193 +    DEPRECATED
  92.194 +    Middleware that post-processes a response to add a csrfmiddlewaretoken.
  92.195 +
  92.196 +    This exists for backwards compatibility and as an interim measure until
  92.197 +    applications are converted to using use the csrf_token template tag
  92.198 +    instead. It will be removed in Django 1.4.
  92.199 +    """
  92.200 +    def __init__(self):
  92.201 +        import warnings
  92.202 +        warnings.warn(
  92.203 +            "CsrfResponseMiddleware and CsrfMiddleware are deprecated; use CsrfViewMiddleware and the template tag instead (see CSRF documentation).",
  92.204 +            PendingDeprecationWarning
  92.205 +        )
  92.206 +
  92.207 +    def process_response(self, request, response):
  92.208 +        if getattr(response, 'csrf_exempt', False):
  92.209 +            return response
  92.210 +
  92.211 +        if response['Content-Type'].split(';')[0] in _HTML_TYPES:
  92.212 +            csrf_token = get_token(request)
  92.213 +            # If csrf_token is None, we have no token for this request, which probably
  92.214 +            # means that this is a response from a request middleware.
  92.215 +            if csrf_token is None:
  92.216 +                return response
  92.217 +
  92.218 +            # ensure we don't add the 'id' attribute twice (HTML validity)
  92.219 +            idattributes = itertools.chain(("id='csrfmiddlewaretoken'",),
  92.220 +                                           itertools.repeat(''))
  92.221 +            def add_csrf_field(match):
  92.222 +                """Returns the matched <form> tag plus the added <input> element"""
  92.223 +                return mark_safe(match.group() + "<div style='display:none;'>" + \
  92.224 +                "<input type='hidden' " + idattributes.next() + \
  92.225 +                " name='csrfmiddlewaretoken' value='" + csrf_token + \
  92.226 +                "' /></div>")
  92.227 +
  92.228 +            # Modify any POST forms
  92.229 +            response.content, n = _POST_FORM_RE.subn(add_csrf_field, response.content)
  92.230 +            if n > 0:
  92.231 +                # Content varies with the CSRF cookie, so set the Vary header.
  92.232 +                patch_vary_headers(response, ('Cookie',))
  92.233 +
  92.234 +                # Since the content has been modified, any Etag will now be
  92.235 +                # incorrect.  We could recalculate, but only if we assume that
  92.236 +                # the Etag was set by CommonMiddleware. The safest thing is just
  92.237 +                # to delete. See bug #9163
  92.238 +                del response['ETag']
  92.239 +        return response
  92.240 +
  92.241 +class CsrfMiddleware(object):
  92.242 +    """
  92.243 +    Django middleware that adds protection against Cross Site
  92.244 +    Request Forgeries by adding hidden form fields to POST forms and
  92.245 +    checking requests for the correct value.
  92.246 +
  92.247 +    CsrfMiddleware uses two middleware, CsrfViewMiddleware and
  92.248 +    CsrfResponseMiddleware, which can be used independently.  It is recommended
  92.249 +    to use only CsrfViewMiddleware and use the csrf_token template tag in
  92.250 +    templates for inserting the token.
  92.251 +    """
  92.252 +    # We can't just inherit from CsrfViewMiddleware and CsrfResponseMiddleware
  92.253 +    # because both have process_response methods.
  92.254 +    def __init__(self):
  92.255 +        self.response_middleware = CsrfResponseMiddleware()
  92.256 +        self.view_middleware = CsrfViewMiddleware()
  92.257 +
  92.258 +    def process_response(self, request, resp):
  92.259 +        # We must do the response post-processing first, because that calls
  92.260 +        # get_token(), which triggers a flag saying that the CSRF cookie needs
  92.261 +        # to be sent (done in CsrfViewMiddleware.process_response)
  92.262 +        resp2 = self.response_middleware.process_response(request, resp)
  92.263 +        return self.view_middleware.process_response(request, resp2)
  92.264 +
  92.265 +    def process_view(self, request, callback, callback_args, callback_kwargs):
  92.266 +        return self.view_middleware.process_view(request, callback, callback_args,
  92.267 +                                                 callback_kwargs)
  92.268 +
    93.1 --- a/django/template/__init__.py	Sat Sep 12 18:53:56 2009 -0500
    93.2 +++ b/django/template/__init__.py	Fri Nov 13 11:27:59 2009 -0600
    93.3 @@ -942,8 +942,14 @@
    93.4                          else:
    93.5                              t = get_template(file_name)
    93.6                          self.nodelist = t.nodelist
    93.7 -                    return self.nodelist.render(context_class(dict,
    93.8 -                            autoescape=context.autoescape))
    93.9 +                    new_context = context_class(dict, autoescape=context.autoescape)
   93.10 +                    # Copy across the CSRF token, if present, because inclusion
   93.11 +                    # tags are often used for forms, and we need instructions
   93.12 +                    # for using CSRF protection to be as simple as possible.
   93.13 +                    csrf_token = context.get('csrf_token', None)
   93.14 +                    if csrf_token is not None:
   93.15 +                        new_context['csrf_token'] = csrf_token
   93.16 +                    return self.nodelist.render(new_context)
   93.17  
   93.18              compile_func = curry(generic_tag_compiler, params, defaults, getattr(func, "_decorated_function", func).__name__, InclusionNode)
   93.19              compile_func.__doc__ = func.__doc__
    94.1 --- a/django/template/context.py	Sat Sep 12 18:53:56 2009 -0500
    94.2 +++ b/django/template/context.py	Fri Nov 13 11:27:59 2009 -0600
    94.3 @@ -1,7 +1,12 @@
    94.4  from django.core.exceptions import ImproperlyConfigured
    94.5  from django.utils.importlib import import_module
    94.6  
    94.7 +# Cache of actual callables.
    94.8  _standard_context_processors = None
    94.9 +# We need the CSRF processor no matter what the user has in their settings,
   94.10 +# because otherwise it is a security vulnerability, and we can't afford to leave
   94.11 +# this to human error or failure to read migration instructions.
   94.12 +_builtin_context_processors =  ('django.core.context_processors.csrf',)
   94.13  
   94.14  class ContextPopException(Exception):
   94.15      "pop() has been called more times than push()"
   94.16 @@ -75,7 +80,10 @@
   94.17      global _standard_context_processors
   94.18      if _standard_context_processors is None:
   94.19          processors = []
   94.20 -        for path in settings.TEMPLATE_CONTEXT_PROCESSORS:
   94.21 +        collect = []
   94.22 +        collect.extend(_builtin_context_processors)
   94.23 +        collect.extend(settings.TEMPLATE_CONTEXT_PROCESSORS)
   94.24 +        for path in collect:
   94.25              i = path.rfind('.')
   94.26              module, attr = path[:i], path[i+1:]
   94.27              try:
    95.1 --- a/django/template/defaultfilters.py	Sat Sep 12 18:53:56 2009 -0500
    95.2 +++ b/django/template/defaultfilters.py	Fri Nov 13 11:27:59 2009 -0600
    95.3 @@ -162,7 +162,7 @@
    95.4  
    95.5      try:
    95.6          m = int(d) - d
    95.7 -    except (OverflowError, InvalidOperation):
    95.8 +    except (ValueError, OverflowError, InvalidOperation):
    95.9          return input_val
   95.10  
   95.11      if not m and p < 0:
    96.1 --- a/django/template/defaulttags.py	Sat Sep 12 18:53:56 2009 -0500
    96.2 +++ b/django/template/defaulttags.py	Fri Nov 13 11:27:59 2009 -0600
    96.3 @@ -37,6 +37,23 @@
    96.4      def render(self, context):
    96.5          return ''
    96.6  
    96.7 +class CsrfTokenNode(Node):
    96.8 +    def render(self, context):
    96.9 +        csrf_token = context.get('csrf_token', None)
   96.10 +        if csrf_token:
   96.11 +            if csrf_token == 'NOTPROVIDED':
   96.12 +                return mark_safe(u"")
   96.13 +            else:
   96.14 +                return mark_safe(u"<div style='display:none'><input type='hidden' name='csrfmiddlewaretoken' value='%s' /></div>" % (csrf_token))
   96.15 +        else:
   96.16 +            # It's very probable that the token is missing because of
   96.17 +            # misconfiguration, so we raise a warning
   96.18 +            from django.conf import settings
   96.19 +            if settings.DEBUG:
   96.20 +                import warnings
   96.21 +                warnings.warn("A {% csrf_token %} was used in a template, but the context did not provide the value.  This is usually caused by not using RequestContext.")
   96.22 +            return u''
   96.23 +
   96.24  class CycleNode(Node):
   96.25      def __init__(self, cyclevars, variable_name=None):
   96.26          self.cycle_iter = itertools_cycle(cyclevars)
   96.27 @@ -523,6 +540,10 @@
   96.28      return node
   96.29  cycle = register.tag(cycle)
   96.30  
   96.31 +def csrf_token(parser, token):
   96.32 +    return CsrfTokenNode()
   96.33 +register.tag(csrf_token)
   96.34 +
   96.35  def debug(parser, token):
   96.36      """
   96.37      Outputs a whole load of debugging information, including the current
    97.1 --- a/django/test/client.py	Sat Sep 12 18:53:56 2009 -0500
    97.2 +++ b/django/test/client.py	Fri Nov 13 11:27:59 2009 -0600
    97.3 @@ -66,6 +66,11 @@
    97.4          signals.request_started.send(sender=self.__class__)
    97.5          try:
    97.6              request = WSGIRequest(environ)
    97.7 +            # sneaky little hack so that we can easily get round
    97.8 +            # CsrfViewMiddleware.  This makes life easier, and is probably
    97.9 +            # required for backwards compatibility with external tests against
   97.10 +            # admin views.
   97.11 +            request._dont_enforce_csrf_checks = True
   97.12              response = self.get_response(request)
   97.13  
   97.14              # Apply response middleware.
   97.15 @@ -362,12 +367,18 @@
   97.16          else:
   97.17              post_data = data
   97.18  
   97.19 +        # Make `data` into a querystring only if it's not already a string. If
   97.20 +        # it is a string, we'll assume that the caller has already encoded it.
   97.21 +        query_string = None
   97.22 +        if not isinstance(data, basestring):
   97.23 +            query_string = urlencode(data, doseq=True)
   97.24 +
   97.25          parsed = urlparse(path)
   97.26          r = {
   97.27              'CONTENT_LENGTH': len(post_data),
   97.28              'CONTENT_TYPE':   content_type,
   97.29              'PATH_INFO':      urllib.unquote(parsed[2]),
   97.30 -            'QUERY_STRING':   urlencode(data, doseq=True) or parsed[4],
   97.31 +            'QUERY_STRING':   query_string or parsed[4],
   97.32              'REQUEST_METHOD': 'PUT',
   97.33              'wsgi.input':     FakePayload(post_data),
   97.34          }
    98.1 --- a/django/test/utils.py	Sat Sep 12 18:53:56 2009 -0500
    98.2 +++ b/django/test/utils.py	Fri Nov 13 11:27:59 2009 -0600
    98.3 @@ -2,6 +2,7 @@
    98.4  from django.conf import settings
    98.5  from django.db import connection
    98.6  from django.core import mail
    98.7 +from django.core.mail.backends import locmem
    98.8  from django.test import signals
    98.9  from django.template import Template
   98.10  from django.utils.translation import deactivate
   98.11 @@ -28,37 +29,22 @@
   98.12      signals.template_rendered.send(sender=self, template=self, context=context)
   98.13      return self.nodelist.render(context)
   98.14  
   98.15 -class TestSMTPConnection(object):
   98.16 -    """A substitute SMTP connection for use during test sessions.
   98.17 -    The test connection stores email messages in a dummy outbox,
   98.18 -    rather than sending them out on the wire.
   98.19 -
   98.20 -    """
   98.21 -    def __init__(*args, **kwargs):
   98.22 -        pass
   98.23 -    def open(self):
   98.24 -        "Mock the SMTPConnection open() interface"
   98.25 -        pass
   98.26 -    def close(self):
   98.27 -        "Mock the SMTPConnection close() interface"
   98.28 -        pass
   98.29 -    def send_messages(self, messages):
   98.30 -        "Redirect messages to the dummy outbox"
   98.31 -        mail.outbox.extend(messages)
   98.32 -        return len(messages)
   98.33  
   98.34  def setup_test_environment():
   98.35      """Perform any global pre-test setup. This involves:
   98.36  
   98.37          - Installing the instrumented test renderer
   98.38 -        - Diverting the email sending functions to a test buffer
   98.39 +        - Set the email backend to the locmem email backend.
   98.40          - Setting the active locale to match the LANGUAGE_CODE setting.
   98.41      """
   98.42      Template.original_render = Template.render
   98.43      Template.render = instrumented_test_render
   98.44  
   98.45      mail.original_SMTPConnection = mail.SMTPConnection
   98.46 -    mail.SMTPConnection = TestSMTPConnection
   98.47 +    mail.SMTPConnection = locmem.EmailBackend
   98.48 +
   98.49 +    mail.original_email_backend = settings.EMAIL_BACKEND
   98.50 +    settings.EMAIL_BACKEND = 'django.core.mail.backends.locmem'
   98.51  
   98.52      mail.outbox = []
   98.53  
   98.54 @@ -77,9 +63,11 @@
   98.55      mail.SMTPConnection = mail.original_SMTPConnection
   98.56      del mail.original_SMTPConnection
   98.57  
   98.58 +    settings.EMAIL_BACKEND = mail.original_email_backend
   98.59 +    del mail.original_email_backend
   98.60 +
   98.61      del mail.outbox
   98.62  
   98.63 -
   98.64  def get_runner(settings):
   98.65      test_path = settings.TEST_RUNNER.split('.')
   98.66      # Allow for Python 2.5 relative paths
    99.1 --- a/django/utils/decorators.py	Sat Sep 12 18:53:56 2009 -0500
    99.2 +++ b/django/utils/decorators.py	Fri Nov 13 11:27:59 2009 -0600
    99.3 @@ -2,60 +2,99 @@
    99.4  
    99.5  import types
    99.6  try:
    99.7 -    from functools import wraps
    99.8 +    from functools import wraps, update_wrapper
    99.9  except ImportError:
   99.10 -    from django.utils.functional import wraps  # Python 2.3, 2.4 fallback.
   99.11 +    from django.utils.functional import wraps, update_wrapper  # Python 2.3, 2.4 fallback.
   99.12 +
   99.13 +
   99.14 +# Licence for MethodDecoratorAdaptor and auto_adapt_to_methods
   99.15 +#
   99.16 +# This code is taken from stackoverflow.com [1], the code being supplied by
   99.17 +# users 'Ants Aasma' [2] and 'Silent Ghost' [3] with modifications.  It is
   99.18 +# legally included here under the terms of the Creative Commons
   99.19 +# Attribution-Share Alike 2.5 Generic Licence [4]
   99.20 +#
   99.21 +# [1] http://stackoverflow.com/questions/1288498/using-the-same-decorator-with-arguments-with-functions-and-methods
   99.22 +# [2] http://stackoverflow.com/users/107366/ants-aasma
   99.23 +# [3] http://stackoverflow.com/users/12855/silentghost
   99.24 +# [4] http://creativecommons.org/licenses/by-sa/2.5/
   99.25 +
   99.26 +class MethodDecoratorAdaptor(object):
   99.27 +    """
   99.28 +    Generic way of creating decorators that adapt to being
   99.29 +    used on methods
   99.30 +    """
   99.31 +    def __init__(self, decorator, func):
   99.32 +        update_wrapper(self, func)
   99.33 +        # NB: update the __dict__ first, *then* set
   99.34 +        # our own .func and .decorator, in case 'func' is actually
   99.35 +        # another MethodDecoratorAdaptor object, which has its
   99.36 +        # 'func' and 'decorator' attributes in its own __dict__
   99.37 +        self.decorator = decorator
   99.38 +        self.func = func
   99.39 +    def __call__(self, *args, **kwargs):
   99.40 +        return self.decorator(self.func)(*args, **kwargs)
   99.41 +    def __get__(self, instance, owner):
   99.42 +        return self.decorator(self.func.__get__(instance, owner))
   99.43 +
   99.44 +def auto_adapt_to_methods(decorator):
   99.45 +    """
   99.46 +    Takes a decorator function, and returns a decorator-like callable that can
   99.47 +    be used on methods as well as functions.
   99.48 +    """
   99.49 +    def adapt(func):
   99.50 +        return MethodDecoratorAdaptor(decorator, func)
   99.51 +    return wraps(decorator)(adapt)
   99.52 +
   99.53 +def decorator_from_middleware_with_args(middleware_class):
   99.54 +    """
   99.55 +    Like decorator_from_middleware, but returns a function
   99.56 +    that accepts the arguments to be passed to the middleware_class.
   99.57 +    Use like::
   99.58 +
   99.59 +         cache_page = decorator_from_middleware_with_args(CacheMiddleware)
   99.60 +         # ...
   99.61 +
   99.62 +         @cache_page(3600)
   99.63 +         def my_view(request):
   99.64 +             # ...
   99.65 +    """
   99.66 +    return make_middleware_decorator(middleware_class)
   99.67  
   99.68  def decorator_from_middleware(middleware_class):
   99.69      """
   99.70      Given a middleware class (not an instance), returns a view decorator. This
   99.71 -    lets you use middleware functionality on a per-view basis.
   99.72 +    lets you use middleware functionality on a per-view basis. The middleware
   99.73 +    is created with no params passed.
   99.74      """
   99.75 -    def _decorator_from_middleware(*args, **kwargs):
   99.76 -        # For historical reasons, these "decorators" are also called as
   99.77 -        # dec(func, *args) instead of dec(*args)(func). We handle both forms
   99.78 -        # for backwards compatibility.
   99.79 -        has_func = True
   99.80 -        try:
   99.81 -            view_func = kwargs.pop('view_func')
   99.82 -        except KeyError:
   99.83 -            if len(args):
   99.84 -                view_func, args = args[0], args[1:]
   99.85 -            else:
   99.86 -                has_func = False
   99.87 -        if not (has_func and isinstance(view_func, types.FunctionType)):
   99.88 -            # We are being called as a decorator.
   99.89 -            if has_func:
   99.90 -                args = (view_func,) + args
   99.91 -            middleware = middleware_class(*args, **kwargs)
   99.92 +    return make_middleware_decorator(middleware_class)()
   99.93  
   99.94 -            def decorator_func(fn):
   99.95 -                return _decorator_from_middleware(fn, *args, **kwargs)
   99.96 -            return decorator_func
   99.97 -
   99.98 -        middleware = middleware_class(*args, **kwargs)
   99.99 -
  99.100 -        def _wrapped_view(request, *args, **kwargs):
  99.101 -            if hasattr(middleware, 'process_request'):
  99.102 -                result = middleware.process_request(request)
  99.103 -                if result is not None:
  99.104 -                    return result
  99.105 -            if hasattr(middleware, 'process_view'):
  99.106 -                result = middleware.process_view(request, view_func, args, kwargs)
  99.107 -                if result is not None:
  99.108 -                    return result
  99.109 -            try:
  99.110 -                response = view_func(request, *args, **kwargs)
  99.111 -            except Exception, e:
  99.112 -                if hasattr(middleware, 'process_exception'):
  99.113 -                    result = middleware.process_exception(request, e)
  99.114 +def make_middleware_decorator(middleware_class):
  99.115 +    def _make_decorator(*m_args, **m_kwargs):
  99.116 +        middleware = middleware_class(*m_args, **m_kwargs)
  99.117 +        def _decorator(view_func):
  99.118 +            def _wrapped_view(request, *args, **kwargs):
  99.119 +                if hasattr(middleware, 'process_request'):
  99.120 +                    result = middleware.process_request(request)
  99.121                      if result is not None:
  99.122                          return result
  99.123 -                raise
  99.124 -            if hasattr(middleware, 'process_response'):
  99.125 -                result = middleware.process_response(request, response)
  99.126 -                if result is not None:
  99.127 -                    return result
  99.128 -            return response
  99.129 -        return wraps(view_func)(_wrapped_view)
  99.130 -    return _decorator_from_middleware
  99.131 +                if hasattr(middleware, 'process_view'):
  99.132 +                    result = middleware.process_view(request, view_func, args, kwargs)
  99.133 +                    if result is not None:
  99.134 +                        return result
  99.135 +                try:
  99.136 +                    response = view_func(request, *args, **kwargs)
  99.137 +                except Exception, e:
  99.138 +                    if hasattr(middleware, 'process_exception'):
  99.139 +                        result = middleware.process_exception(request, e)
  99.140 +                        if result is not None:
  99.141 +                            return result
  99.142 +                    raise
  99.143 +                if hasattr(middleware, 'process_response'):
  99.144 +                    result = middleware.process_response(request, response)
  99.145 +                    if result is not None:
  99.146 +                        return result
  99.147 +                return response
  99.148 +            return wraps(view_func)(_wrapped_view)
  99.149 +        return auto_adapt_to_methods(_decorator)
  99.150 +    return _make_decorator
   100.1 --- a/django/utils/functional.py	Sat Sep 12 18:53:56 2009 -0500
   100.2 +++ b/django/utils/functional.py	Fri Nov 13 11:27:59 2009 -0600
   100.3 @@ -257,9 +257,8 @@
   100.4      A wrapper for another class that can be used to delay instantiation of the
   100.5      wrapped class.
   100.6  
   100.7 -    This is useful, for example, if the wrapped class needs to use Django
   100.8 -    settings at creation time: we want to permit it to be imported without
   100.9 -    accessing settings.
  100.10 +    By subclassing, you have the opportunity to intercept and alter the
  100.11 +    instantiation. If you don't need to do that, use SimpleLazyObject.
  100.12      """
  100.13      def __init__(self):
  100.14          self._wrapped = None
  100.15 @@ -267,9 +266,6 @@
  100.16      def __getattr__(self, name):
  100.17          if self._wrapped is None:
  100.18              self._setup()
  100.19 -        if name == "__members__":
  100.20 -            # Used to implement dir(obj)
  100.21 -            return self._wrapped.get_all_members()
  100.22          return getattr(self._wrapped, name)
  100.23  
  100.24      def __setattr__(self, name, value):
  100.25 @@ -287,3 +283,68 @@
  100.26          """
  100.27          raise NotImplementedError
  100.28  
  100.29 +    # introspection support:
  100.30 +    __members__ = property(lambda self: self.__dir__())
  100.31 +
  100.32 +    def __dir__(self):
  100.33 +        if self._wrapped is None:
  100.34 +            self._setup()
  100.35 +        return  dir(self._wrapped)
  100.36 +
  100.37 +class SimpleLazyObject(LazyObject):
  100.38 +    """
  100.39 +    A lazy object initialised from any function.
  100.40 +
  100.41 +    Designed for compound objects of unknown type. For builtins or objects of
  100.42 +    known type, use django.utils.functional.lazy.
  100.43 +    """
  100.44 +    def __init__(self, func):
  100.45 +        """
  100.46 +        Pass in a callable that returns the object to be wrapped.
  100.47 +
  100.48 +        If copies are made of the resulting SimpleLazyObject, which can happen
  100.49 +        in various circumstances within Django, then you must ensure that the
  100.50 +        callable can be safely run more than once and will return the same
  100.51 +        value.
  100.52 +        """
  100.53 +        self.__dict__['_setupfunc'] = func
  100.54 +        # For some reason, we have to inline LazyObject.__init__ here to avoid
  100.55 +        # recursion
  100.56 +        self._wrapped = None
  100.57 +
  100.58 +    def __str__(self):
  100.59 +        if self._wrapped is None: self._setup()
  100.60 +        return str(self._wrapped)
  100.61 +
  100.62 +    def __unicode__(self):
  100.63 +        if self._wrapped is None: self._setup()
  100.64 +        return unicode(self._wrapped)
  100.65 +
  100.66 +    def __deepcopy__(self, memo):
  100.67 +        if self._wrapped is None:
  100.68 +            # We have to use SimpleLazyObject, not self.__class__, because the
  100.69 +            # latter is proxied.
  100.70 +            result = SimpleLazyObject(self._setupfunc)
  100.71 +            memo[id(self)] = result
  100.72 +            return result
  100.73 +        else:
  100.74 +            import copy
  100.75 +            return copy.deepcopy(self._wrapped, memo)
  100.76 +
  100.77 +    # Need to pretend to be the wrapped class, for the sake of objects that care
  100.78 +    # about this (especially in equality tests)
  100.79 +    def __get_class(self):
  100.80 +        if self._wrapped is None: self._setup()
  100.81 +        return self._wrapped.__class__
  100.82 +    __class__ = property(__get_class)
  100.83 +
  100.84 +    def __eq__(self, other):
  100.85 +        if self._wrapped is None: self._setup()
  100.86 +        return self._wrapped == other
  100.87 +
  100.88 +    def __hash__(self):
  100.89 +        if self._wrapped is None: self._setup()
  100.90 +        return hash(self._wrapped)
  100.91 +
  100.92 +    def _setup(self):
  100.93 +        self._wrapped = self._setupfunc()
   101.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   101.2 +++ b/django/views/csrf.py	Fri Nov 13 11:27:59 2009 -0600
   101.3 @@ -0,0 +1,69 @@
   101.4 +from django.http import HttpResponseForbidden
   101.5 +from django.template import Context, Template
   101.6 +from django.conf import settings
   101.7 +
   101.8 +# We include the template inline since we need to be able to reliably display
   101.9 +# this error message, especially for the sake of developers, and there isn't any
  101.10 +# other way of making it available independent of what is in the settings file.
  101.11 +
  101.12 +CSRF_FAILRE_TEMPLATE = """
  101.13 +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
  101.14 +<html lang="en">
  101.15 +<head>
  101.16 +  <meta http-equiv="content-type" content="text/html; charset=utf-8">
  101.17 +  <title>403 Forbidden</title>
  101.18 +</head>
  101.19 +<body>
  101.20 +  <h1>403 Forbidden</h1>
  101.21 +  <p>CSRF verification failed. Request aborted.</p>
  101.22 +  {% if DEBUG %}
  101.23 +  <h2>Help</h2>
  101.24 +    {% if reason %}
  101.25 +    <p>Reason given for failure:</p>
  101.26 +    <pre>
  101.27 +    {{ reason }}
  101.28 +    </pre>
  101.29 +    {% endif %}
  101.30 +
  101.31 +  <p>In general, this can occur when there is a genuine Cross Site Request Forgery, or when
  101.32 +  <a
  101.33 +  href='http://docs.djangoproject.com/en/dev/ref/contrib/csrf/#ref-contrib-csrf'>Django's
  101.34 +  CSRF mechanism</a> has not been used correctly.  For POST forms, you need to
  101.35 +  ensure:</p>
  101.36 +
  101.37 +  <ul>
  101.38 +    <li>The view function uses <a
  101.39 +    href='http://docs.djangoproject.com/en/dev/ref/templates/api/#subclassing-context-requestcontext'><code>RequestContext</code></a>
  101.40 +    for the template, instead of <code>Context</code>.</li>
  101.41 +
  101.42 +    <li>In the template, there is a <code>{% templatetag openblock %} csrf_token
  101.43 +    {% templatetag closeblock %}</code> template tag inside each POST form that
  101.44 +    targets an internal URL.</li>
  101.45 +
  101.46 +    <li>If you are not using <code>CsrfViewMiddleware</code>, then you must use
  101.47 +    <code>csrf_protect</code> on any views that use the <code>csrf_token</code>
  101.48 +    template tag, as well as those that accept the POST data.</li>
  101.49 +
  101.50 +  </ul>
  101.51 +
  101.52 +  <p>You're seeing the help section of this page because you have <code>DEBUG =
  101.53 +  True</code> in your Django settings file. Change that to <code>False</code>,
  101.54 +  and only the initial error message will be displayed.  </p>
  101.55 +
  101.56 +  <p>You can customize this page using the CSRF_FAILURE_VIEW setting.</p>
  101.57 +  {% else %}
  101.58 +  <p><small>More information is available with DEBUG=True.</small></p>
  101.59 +
  101.60 +  {% endif %}
  101.61 +</body>
  101.62 +</html>
  101.63 +"""
  101.64 +
  101.65 +def csrf_failure(request, reason=""):
  101.66 +    """
  101.67 +    Default view used when request fails CSRF protection
  101.68 +    """
  101.69 +    t = Template(CSRF_FAILRE_TEMPLATE)
  101.70 +    c = Context({'DEBUG': settings.DEBUG,
  101.71 +                 'reason': reason})
  101.72 +    return HttpResponseForbidden(t.render(c), mimetype='text/html')
   102.1 --- a/django/views/decorators/cache.py	Sat Sep 12 18:53:56 2009 -0500
   102.2 +++ b/django/views/decorators/cache.py	Fri Nov 13 11:27:59 2009 -0600
   102.3 @@ -16,11 +16,37 @@
   102.4  except ImportError:
   102.5      from django.utils.functional import wraps  # Python 2.3, 2.4 fallback.
   102.6  
   102.7 -from django.utils.decorators import decorator_from_middleware
   102.8 +from django.utils.decorators import decorator_from_middleware_with_args, auto_adapt_to_methods
   102.9  from django.utils.cache import patch_cache_control, add_never_cache_headers
  102.10  from django.middleware.cache import CacheMiddleware
  102.11  
  102.12 -cache_page = decorator_from_middleware(CacheMiddleware)
  102.13 +def cache_page(*args, **kwargs):
  102.14 +    # We need backwards compatibility with code which spells it this way:
  102.15 +    #   def my_view(): pass
  102.16 +    #   my_view = cache_page(my_view, 123)
  102.17 +    # and this way:
  102.18 +    #   my_view = cache_page(123)(my_view)
  102.19 +    # and this:
  102.20 +    #   my_view = cache_page(my_view, 123, key_prefix="foo")
  102.21 +    # and this:
  102.22 +    #   my_view = cache_page(123, key_prefix="foo")(my_view)
  102.23 +    # and possibly this way (?):
  102.24 +    #   my_view = cache_page(123, my_view)
  102.25 +
  102.26 +    # We also add some asserts to give better error messages in case people are
  102.27 +    # using other ways to call cache_page that no longer work.
  102.28 +    key_prefix = kwargs.pop('key_prefix', None)
  102.29 +    assert not kwargs, "The only keyword argument accepted is key_prefix"
  102.30 +    if len(args) > 1:
  102.31 +        assert len(args) == 2, "cache_page accepts at most 2 arguments"
  102.32 +        if callable(args[0]):
  102.33 +            return decorator_from_middleware_with_args(CacheMiddleware)(cache_timeout=args[1], key_prefix=key_prefix)(args[0])
  102.34 +        elif callable(args[1]):
  102.35 +            return decorator_from_middleware_with_args(CacheMiddleware)(cache_timeout=args[0], key_prefix=key_prefix)(args[1])
  102.36 +        else:
  102.37 +            assert False, "cache_page must be passed either a single argument (timeout) or a view function and a timeout"
  102.38 +    else:
  102.39 +        return decorator_from_middleware_with_args(CacheMiddleware)(cache_timeout=args[0], key_prefix=key_prefix)
  102.40  
  102.41  def cache_control(**kwargs):
  102.42  
  102.43 @@ -33,7 +59,7 @@
  102.44  
  102.45          return wraps(viewfunc)(_cache_controlled)
  102.46  
  102.47 -    return _cache_controller
  102.48 +    return auto_adapt_to_methods(_cache_controller)
  102.49  
  102.50  def never_cache(view_func):
  102.51      """
  102.52 @@ -45,3 +71,4 @@
  102.53          add_never_cache_headers(response)
  102.54          return response
  102.55      return wraps(view_func)(_wrapped_view_func)
  102.56 +never_cache = auto_adapt_to_methods(never_cache)
   103.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   103.2 +++ b/django/views/decorators/csrf.py	Fri Nov 13 11:27:59 2009 -0600
   103.3 @@ -0,0 +1,47 @@
   103.4 +from django.middleware.csrf import CsrfViewMiddleware
   103.5 +from django.utils.decorators import decorator_from_middleware
   103.6 +try:
   103.7 +    from functools import wraps
   103.8 +except ImportError:
   103.9 +    from django.utils.functional import wraps  # Python 2.3, 2.4 fallback.
  103.10 +
  103.11 +csrf_protect = decorator_from_middleware(CsrfViewMiddleware)
  103.12 +csrf_protect.__name__ = "csrf_protect"
  103.13 +csrf_protect.__doc__ = """
  103.14 +This decorator adds CSRF protection in exactly the same way as
  103.15 +CsrfViewMiddleware, but it can be used on a per view basis.  Using both, or
  103.16 +using the decorator multiple times, is harmless and efficient.
  103.17 +"""
  103.18 +
  103.19 +def csrf_response_exempt(view_func):
  103.20 +    """
  103.21 +    Modifies a view function so that its response is exempt
  103.22 +    from the post-processing of the CSRF middleware.
  103.23 +    """
  103.24 +    def wrapped_view(*args, **kwargs):
  103.25 +        resp = view_func(*args, **kwargs)
  103.26 +        resp.csrf_exempt = True
  103.27 +        return resp
  103.28 +    return wraps(view_func)(wrapped_view)
  103.29 +
  103.30 +def csrf_view_exempt(view_func):
  103.31 +    """
  103.32 +    Marks a view function as being exempt from CSRF view protection.
  103.33 +    """
  103.34 +    # We could just do view_func.csrf_exempt = True, but decorators
  103.35 +    # are nicer if they don't have side-effects, so we return a new
  103.36 +    # function.
  103.37 +    def wrapped_view(*args, **kwargs):
  103.38 +        return view_func(*args, **kwargs)
  103.39 +    wrapped_view.csrf_exempt = True
  103.40 +    return wraps(view_func)(wrapped_view)
  103.41 +
  103.42 +def csrf_exempt(view_func):
  103.43 +    """
  103.44 +    Marks a view function as being exempt from the CSRF checks
  103.45 +    and post processing.
  103.46 +
  103.47 +    This is the same as using both the csrf_view_exempt and
  103.48 +    csrf_response_exempt decorators.
  103.49 +    """
  103.50 +    return csrf_response_exempt(csrf_view_exempt(view_func))
   104.1 --- a/docs/faq/install.txt	Sat Sep 12 18:53:56 2009 -0500
   104.2 +++ b/docs/faq/install.txt	Fri Nov 13 11:27:59 2009 -0600
   104.3 @@ -18,7 +18,7 @@
   104.4  What are Django's prerequisites?
   104.5  --------------------------------
   104.6  
   104.7 -Django requires Python_, specifically any version of Python from 2.3
   104.8 +Django requires Python_, specifically any version of Python from 2.4
   104.9  through 2.6. No other Python libraries are required for basic Django
  104.10  usage.
  104.11  
  104.12 @@ -42,30 +42,35 @@
  104.13  .. _`SQLite 3`: http://www.sqlite.org/
  104.14  .. _Oracle: http://www.oracle.com/
  104.15  
  104.16 -Do I lose anything by using Python 2.3 versus newer Python versions, such as Python 2.5?
  104.17 -----------------------------------------------------------------------------------------
  104.18 +Do I lose anything by using Python 2.4 versus newer Python versions, such as Python 2.5 or 2.6?
  104.19 +-----------------------------------------------------------------------------------------------
  104.20  
  104.21 -Not in the core framework. Currently, Django itself officially
  104.22 -supports any version of Python from 2.3 through 2.6,
  104.23 -inclusive. However, some add-on components may require a more recent
  104.24 -Python version; the ``django.contrib.gis`` component, for example,
  104.25 -requires at least Python 2.4, and third-party applications for use
  104.26 -with Django are, of course, free to set their own version
  104.27 -requirements.
  104.28 +Not in the core framework. Currently, Django itself officially supports any
  104.29 +version of Python from 2.4 through 2.6, inclusive. However, newer versions of
  104.30 +Python are often faster, have more features, and are better supported.
  104.31 +Third-party applications for use with Django are, of course, free to set their
  104.32 +own version requirements.
  104.33  
  104.34 -Please note, however, that over the next year or two Django will begin
  104.35 -dropping support for older Python versions as part of a migration
  104.36 -which will end with Django running on Python 3.0 (see next question
  104.37 -for details). So if you're just starting out with Python, it's
  104.38 -recommended that you use the latest 2.x release (currently, Python
  104.39 -2.6). This will let you take advantage of the numerous improvements
  104.40 -and optimizations to the Python language since version 2.3, and will
  104.41 -help ease the process of dropping support for older Python versions on
  104.42 -the road to Python 3.0.
  104.43 +Over the next year or two Django will begin dropping support for older Python
  104.44 +versions as part of a migration which will end with Django running on Python 3
  104.45 +(see below for details). 
  104.46  
  104.47 -Can I use Django with Python 3.0?
  104.48 +All else being equal, we recommend that you use the latest 2.x release
  104.49 +(currently Python 2.6). This will let you take advantage of the numerous
  104.50 +improvements and optimizations to the Python language since version 2.4, and
  104.51 +will help ease the process of dropping support for older Python versions on
  104.52 +the road to Python 3.
  104.53 +
  104.54 +Can I use Django with Python 2.3?
  104.55  ---------------------------------
  104.56  
  104.57 +Django 1.1 (and earlier) supported Python 2.3. Django 1.2 and newer does not.
  104.58 +We highly recommend you upgrade Python if at all possible, but Django 1.1 will
  104.59 +continue to work on Python 2.3.
  104.60 +
  104.61 +Can I use Django with Python 3?
  104.62 +-------------------------------
  104.63 +
  104.64  Not at the moment. Python 3.0 introduced a number of
  104.65  backwards-incompatible changes to the Python language, and although
  104.66  these changes are generally a good thing for Python's future, it will
   105.1 --- a/docs/internals/committers.txt	Sat Sep 12 18:53:56 2009 -0500
   105.2 +++ b/docs/internals/committers.txt	Fri Nov 13 11:27:59 2009 -0600
   105.3 @@ -148,7 +148,6 @@
   105.4  
   105.5  .. _brian rosner: http://oebfare.com/
   105.6  .. _eldarion: http://eldarion.com/
   105.7 -.. _pinax: http://pinaxproject.com/
   105.8  .. _django dose: http://djangodose.com/
   105.9  
  105.10  `Gary Wilson`_
  105.11 @@ -189,6 +188,18 @@
  105.12  
  105.13      Karen lives in Apex, NC, USA.
  105.14  
  105.15 +`Jannis Leidel`_
  105.16 +    Jannis graduated in media design from `Bauhaus-University Weimar`_,
  105.17 +    is the author of a number of pluggable Django apps and likes to
  105.18 +    contribute to Open Source projects like Pinax_. He currently works as
  105.19 +    a freelance web developer and designer.
  105.20 +
  105.21 +    Jannis lives in Berlin, Germany.
  105.22 +
  105.23 +.. _Jannis Leidel: http://jezdez.com/
  105.24 +.. _Bauhaus-University Weimar: http://www.uni-weimar.de/
  105.25 +.. _pinax: http://pinaxproject.com/
  105.26 +
  105.27  Specialists
  105.28  -----------
  105.29  
   106.1 --- a/docs/internals/deprecation.txt	Sat Sep 12 18:53:56 2009 -0500
   106.2 +++ b/docs/internals/deprecation.txt	Fri Nov 13 11:27:59 2009 -0600
   106.3 @@ -13,6 +13,21 @@
   106.4            hooking up admin URLs.  This has been deprecated since the 1.1
   106.5            release.
   106.6  
   106.7 +    * 1.4
   106.8 +        * ``CsrfResponseMiddleware``.  This has been deprecated since the 1.2
   106.9 +          release, in favour of the template tag method for inserting the CSRF
  106.10 +          token.  ``CsrfMiddleware``, which combines ``CsrfResponseMiddleware``
  106.11 +          and ``CsrfViewMiddleware``, is also deprecated.
  106.12 +
  106.13 +        * The old imports for CSRF functionality (``django.contrib.csrf.*``),
  106.14 +          which moved to core in 1.2, will be removed.
  106.15 +
  106.16 +        * ``SMTPConnection``. The 1.2 release deprecated the ``SMTPConnection``
  106.17 +          class in favor of a generic E-mail backend API.
  106.18 +
  106.19 +        * The many to many SQL generation functions on the database backends
  106.20 +          will be removed.  These have been deprecated since the 1.2 release.
  106.21 +
  106.22      * 2.0
  106.23          * ``django.views.defaults.shortcut()``. This function has been moved
  106.24            to ``django.contrib.contenttypes.views.shortcut()`` as part of the
   107.1 --- a/docs/internals/index.txt	Sat Sep 12 18:53:56 2009 -0500
   107.2 +++ b/docs/internals/index.txt	Fri Nov 13 11:27:59 2009 -0600
   107.3 @@ -23,3 +23,4 @@
   107.4     committers
   107.5     release-process
   107.6     deprecation
   107.7 +   svn
   108.1 --- a/docs/internals/release-process.txt	Sat Sep 12 18:53:56 2009 -0500
   108.2 +++ b/docs/internals/release-process.txt	Fri Nov 13 11:27:59 2009 -0600
   108.3 @@ -56,7 +56,7 @@
   108.4  such. A minor release may deprecate certain features from previous releases. If a
   108.5  feature in version ``A.B`` is deprecated, it will continue to work in version
   108.6  ``A.B+1``. In version ``A.B+2``, use of the feature will raise a
   108.7 -``PendingDeprecationWarning`` but will continue to work. Version ``A.B+3`` will
   108.8 +``DeprecationWarning`` but will continue to work. Version ``A.B+3`` will
   108.9  remove the feature entirely.
  108.10  
  108.11  So, for example, if we decided to remove a function that existed in Django 1.0:
   109.1 --- a/docs/internals/svn.txt	Sat Sep 12 18:53:56 2009 -0500
   109.2 +++ b/docs/internals/svn.txt	Fri Nov 13 11:27:59 2009 -0600
   109.3 @@ -199,7 +199,7 @@
   109.4  release. For example, shortly after the release of Django 1.0, the
   109.5  branch ``django/branches/releases/1.0.X`` was created to receive bug
   109.6  fixes, and shortly after the release of Django 1.1 the branch
   109.7 -``django/branches/releases/1.1.X`` will be created.
   109.8 +``django/branches/releases/1.1.X`` was created.
   109.9  
  109.10  Prior to the Django 1.0 release, these branches were maintaind within
  109.11  the top-level ``django/branches`` directory, and so the following
   110.1 --- a/docs/intro/install.txt	Sat Sep 12 18:53:56 2009 -0500
   110.2 +++ b/docs/intro/install.txt	Fri Nov 13 11:27:59 2009 -0600
   110.3 @@ -12,7 +12,7 @@
   110.4  --------------
   110.5  
   110.6  Being a Python Web framework, Django requires Python. It works with any Python
   110.7 -version from 2.3 to 2.6 (due to backwards
   110.8 +version from 2.4 to 2.6 (due to backwards
   110.9  incompatibilities in Python 3.0, Django does not currently work with
  110.10  Python 3.0; see :ref:`the Django FAQ <faq-install>` for more
  110.11  information on supported Python versions and the 3.0 transition), but we recommend installing Python 2.5 or later. If you do so, you won't need to set up a database just yet: Python 2.5 or later includes a lightweight database called SQLite_.
   111.1 --- a/docs/intro/tutorial01.txt	Sat Sep 12 18:53:56 2009 -0500
   111.2 +++ b/docs/intro/tutorial01.txt	Fri Nov 13 11:27:59 2009 -0600
   111.3 @@ -281,6 +281,7 @@
   111.4      polls/
   111.5          __init__.py
   111.6          models.py
   111.7 +        tests.py
   111.8          views.py
   111.9  
  111.10  This directory structure will house the poll application.
   112.1 --- a/docs/intro/tutorial02.txt	Sat Sep 12 18:53:56 2009 -0500
   112.2 +++ b/docs/intro/tutorial02.txt	Fri Nov 13 11:27:59 2009 -0600
   112.3 @@ -34,11 +34,11 @@
   112.4      * Run ``python manage.py syncdb``. Since you have added a new application
   112.5        to :setting:`INSTALLED_APPS`, the database tables need to be updated.
   112.6  
   112.7 -    * Edit your ``mysite/urls.py`` file and uncomment the lines below the
   112.8 -      "Uncomment the next two lines..." comment. This file is a URLconf;
   112.9 -      we'll dig into URLconfs in the next tutorial. For now, all you need to
  112.10 -      know is that it maps URL roots to applications. In the end, you should
  112.11 -      have a ``urls.py`` file that looks like this:
  112.12 +    * Edit your ``mysite/urls.py`` file and uncomment the lines that reference
  112.13 +      the admin -- there are three lines in total to uncomment. This file is a
  112.14 +      URLconf; we'll dig into URLconfs in the next tutorial. For now, all you
  112.15 +      need to know is that it maps URL roots to applications. In the end, you
  112.16 +      should have a ``urls.py`` file that looks like this:
  112.17  
  112.18      .. versionchanged:: 1.1
  112.19          The method for adding admin urls has changed in Django 1.1.
   113.1 --- a/docs/intro/tutorial03.txt	Sat Sep 12 18:53:56 2009 -0500
   113.2 +++ b/docs/intro/tutorial03.txt	Fri Nov 13 11:27:59 2009 -0600
   113.3 @@ -171,15 +171,23 @@
   113.4  This is the simplest view possible. Go to "/polls/" in your browser, and you
   113.5  should see your text.
   113.6  
   113.7 -Now add the following view. It's slightly different, because it takes an
   113.8 -argument (which, remember, is passed in from whatever was captured by the
   113.9 -regular expression in the URLconf)::
  113.10 +Now lets add a few more views. These views are slightly different, because
  113.11 +they take an argument (which, remember, is passed in from whatever was
  113.12 +captured by the regular expression in the URLconf)::
  113.13  
  113.14      def detail(request, poll_id):
  113.15          return HttpResponse("You're looking at poll %s." % poll_id)
  113.16  
  113.17 -Take a look in your browser, at "/polls/34/". It'll display whatever ID you
  113.18 -provide in the URL.
  113.19 +    def results(request, poll_id):
  113.20 +        return HttpResponse("You're looking at the results of poll %s." % poll_id)
  113.21 +
  113.22 +    def vote(request, poll_id):
  113.23 +        return HttpResponse("You're voting on poll %s." % poll_id)
  113.24 +
  113.25 +Take a look in your browser, at "/polls/34/". It'll run the `detail()` method
  113.26 +and display whatever ID you provide in the URL. Try "/polls/34/results/" and
  113.27 +"/polls/34/vote/" too -- these will display the placeholder results and voting
  113.28 +pages.
  113.29  
  113.30  Write views that actually do something
  113.31  ======================================
  113.32 @@ -467,10 +475,10 @@
  113.33  ``mysite/urls.py`` to remove the poll-specific URLs and insert an
  113.34  :func:`~django.conf.urls.defaults.include`::
  113.35  
  113.36 -    ...
  113.37 +    # ...
  113.38      urlpatterns = patterns('',
  113.39          (r'^polls/', include('mysite.polls.urls')),
  113.40 -        ...
  113.41 +        # ...
  113.42  
  113.43  :func:`~django.conf.urls.defaults.include`, simply, references another URLconf.
  113.44  Note that the regular expression doesn't have a ``$`` (end-of-string match
   114.1 --- a/docs/intro/tutorial04.txt	Sat Sep 12 18:53:56 2009 -0500
   114.2 +++ b/docs/intro/tutorial04.txt	Fri Nov 13 11:27:59 2009 -0600
   114.3 @@ -21,6 +21,7 @@
   114.4      {% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}
   114.5  
   114.6      <form action="/polls/{{ poll.id }}/vote/" method="post">
   114.7 +    {% csrf_token %}
   114.8      {% for choice in poll.choice_set.all %}
   114.9          <input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}" />
  114.10          <label for="choice{{ forloop.counter }}">{{ choice.choice }}</label><br />
  114.11 @@ -46,17 +47,41 @@
  114.12      * ``forloop.counter`` indicates how many times the :ttag:`for` tag has gone
  114.13        through its loop
  114.14  
  114.15 +    * Since we are creating a POST form (which can have the effect of modifying
  114.16 +      data), we unfortunately need to worry about Cross Site Request Forgeries.
  114.17 +      Thankfully, you don't have to worry too hard, because Django comes with
  114.18 +      very easy-to-use system for protecting against it.  In short, all POST
  114.19 +      forms that are targetted at internal URLs need the ``{% csrf_token %}``
  114.20 +      template tag adding.
  114.21 +
  114.22 +The ``{% csrf_token %}`` tag requires information from the request object, which
  114.23 +is not normally accessible from within the template context.  To fix this, a
  114.24 +small adjustment needs to be made to the ``detail`` view, so that it looks like
  114.25 +the following::
  114.26 +
  114.27 +    from django.template import RequestContext
  114.28 +    # ...
  114.29 +    def detail(request, poll_id):
  114.30 +        p = get_object_or_404(Poll, pk=poll_id)
  114.31 +        return render_to_response('polls/detail.html', {'poll': p},
  114.32 +                                   context_instance=RequestContext(request))
  114.33 +
  114.34 +The details of how this works are explained in the documentation for
  114.35 +:ref:`RequestContext <subclassing-context-requestcontext>`.
  114.36 +
  114.37  Now, let's create a Django view that handles the submitted data and does
  114.38  something with it. Remember, in :ref:`Tutorial 3 <intro-tutorial03>`, we
  114.39  created a URLconf for the polls application that includes this line::
  114.40  
  114.41      (r'^(?P<poll_id>\d+)/vote/$', 'vote'),
  114.42  
  114.43 -So let's create a ``vote()`` function in ``mysite/polls/views.py``::
  114.44 +We also created a dummy implementation of the ``vote()`` function. Let's
  114.45 +create a real version. Add the following to ``mysite/polls/views.py``::
  114.46  
  114.47      from django.shortcuts import get_object_or_404, render_to_response
  114.48 -    from django.http import HttpResponseRedirect
  114.49 +    from django.http import HttpResponseRedirect, HttpResponse
  114.50      from django.core.urlresolvers import reverse
  114.51 +    from django.template import RequestContext
  114.52      from mysite.polls.models import Choice, Poll
  114.53      # ...
  114.54      def vote(request, poll_id):
  114.55 @@ -68,7 +93,7 @@
  114.56              return render_to_response('polls/detail.html', {
  114.57                  'poll': p,
  114.58                  'error_message': "You didn't select a choice.",
  114.59 -            })
  114.60 +            }, context_instance=RequestContext(request))
  114.61          else:
  114.62              selected_choice.votes += 1
  114.63              selected_choice.save()
   115.1 --- a/docs/ref/contrib/admin/index.txt	Sat Sep 12 18:53:56 2009 -0500
   115.2 +++ b/docs/ref/contrib/admin/index.txt	Fri Nov 13 11:27:59 2009 -0600
   115.3 @@ -25,21 +25,26 @@
   115.4  Overview
   115.5  ========
   115.6  
   115.7 -There are five steps in activating the Django admin site:
   115.8 +There are six steps in activating the Django admin site:
   115.9  
  115.10 -    1. Add ``django.contrib.admin`` to your ``INSTALLED_APPS`` setting.
  115.11 +    1. Add ``'django.contrib.admin'`` to your :setting:`INSTALLED_APPS`
  115.12 +       setting.
  115.13  
  115.14 -    2. Determine which of your application's models should be editable in the
  115.15 +    2. Admin has two dependencies - ``django.contrib.auth`` and
  115.16 +       ``django.contrib.contenttypes``. If these applications are not
  115.17 +       in your :setting:`INSTALLED_APPS` list, add them.
  115.18 +
  115.19 +    3. Determine which of your application's models should be editable in the
  115.20         admin interface.
  115.21  
  115.22 -    3. For each of those models, optionally create a ``ModelAdmin`` class that
  115.23 +    4. For each of those models, optionally create a ``ModelAdmin`` class that
  115.24         encapsulates the customized admin functionality and options for that
  115.25         particular model.
  115.26  
  115.27 -    4. Instantiate an ``AdminSite`` and tell it about each of your models and
  115.28 +    5. Instantiate an ``AdminSite`` and tell it about each of your models and
  115.29         ``ModelAdmin`` classes.
  115.30  
  115.31 -    5. Hook the ``AdminSite`` instance into your URLconf.
  115.32 +    6. Hook the ``AdminSite`` instance into your URLconf.
  115.33  
  115.34  Other topics
  115.35  ------------
  115.36 @@ -765,7 +770,7 @@
  115.37  However, the ``self.my_view`` function registered above suffers from two
  115.38  problems:
  115.39  
  115.40 -  * It will *not* perform and permission checks, so it will be accessible to
  115.41 +  * It will *not* perform any permission checks, so it will be accessible to
  115.42      the general public.
  115.43    * It will *not* provide any header details to prevent caching. This means if
  115.44      the page retrieves data from the database, and caching middleware is
  115.45 @@ -1043,16 +1048,70 @@
  115.46              FriendshipInline,
  115.47          ]
  115.48  
  115.49 +Working with Many-to-Many Models
  115.50 +--------------------------------
  115.51 +
  115.52 +.. versionadded:: 1.2
  115.53 +
  115.54 +By default, admin widgets for many-to-many relations will be displayed
  115.55 +on whichever model contains the actual reference to the ``ManyToManyField``.
  115.56 +Depending on your ``ModelAdmin`` definition, each many-to-many field in your
  115.57 +model will be represented by a standard HTML ``<select multiple>``, a
  115.58 +horizontal or vertical filter, or a ``raw_id_admin`` widget. However, it is
  115.59 +also possible to to replace these widgets with inlines.
  115.60 +
  115.61 +Suppose we have the following models::
  115.62 +
  115.63 +    class Person(models.Model):
  115.64 +        name = models.CharField(max_length=128)
  115.65 +
  115.66 +    class Group(models.Model):
  115.67 +        name = models.CharField(max_length=128)
  115.68 +        members = models.ManyToManyField(Person, related_name='groups')
  115.69 +
  115.70 +If you want to display many-to-many relations using an inline, you can do
  115.71 +so by defining an ``InlineModelAdmin`` object for the relationship::
  115.72 +
  115.73 +    class MembershipInline(admin.TabularInline):
  115.74 +        model = Group.members.through
  115.75 +
  115.76 +    class PersonAdmin(admin.ModelAdmin):
  115.77 +        inlines = [
  115.78 +            MembershipInline,
  115.79 +        ]
  115.80 +
  115.81 +    class GroupAdmin(admin.ModelAdmin):
  115.82 +        inlines = [
  115.83 +            MembershipInline,
  115.84 +        ]
  115.85 +        exclude = ('members',)
  115.86 +
  115.87 +There are two features worth noting in this example.
  115.88 +
  115.89 +Firstly - the ``MembershipInline`` class references ``Group.members.through``.
  115.90 +The ``through`` attribute is a reference to the model that manages the
  115.91 +many-to-many relation. This model is automatically created by Django when you
  115.92 +define a many-to-many field.
  115.93 +
  115.94 +Secondly, the ``GroupAdmin`` must manually exclude the ``members`` field.
  115.95 +Django displays an admin widget for a many-to-many field on the model that
  115.96 +defines the relation (in this case, ``Group``). If you want to use an inline
  115.97 +model to represent the many-to-many relationship, you must tell Django's admin
  115.98 +to *not* display this widget - otherwise you will end up with two widgets on
  115.99 +your admin page for managing the relation.
 115.100 +
 115.101 +In all other respects, the ``InlineModelAdmin`` is exactly the same as any
 115.102 +other. You can customize the appearance using any of the normal
 115.103 +``InlineModelAdmin`` properties.
 115.104 +
 115.105  Working with Many-to-Many Intermediary Models
 115.106  ----------------------------------------------
 115.107  
 115.108 -By default, admin widgets for many-to-many relations will be displayed inline
 115.109 -on whichever model contains the actual reference to the ``ManyToManyField``.
 115.110 -However, when you specify an intermediary model using the ``through``
 115.111 -argument to a ``ManyToManyField``, the admin will not display a widget by
 115.112 -default. This is because each instance of that intermediary model requires
 115.113 -more information than could be displayed in a single widget, and the layout
 115.114 -required for multiple widgets will vary depending on the intermediate model.
 115.115 +When you specify an intermediary model using the ``through`` argument to a
 115.116 +``ManyToManyField``, the admin will not display a widget by default. This is
 115.117 +because each instance of that intermediary model requires more information
 115.118 +than could be displayed in a single widget, and the layout required for
 115.119 +multiple widgets will vary depending on the intermediate model.
 115.120  
 115.121  However, we still want to be able to edit that information inline. Fortunately,
 115.122  this is easy to do with inline admin models. Suppose we have the following
   116.1 --- a/docs/ref/contrib/comments/index.txt	Sat Sep 12 18:53:56 2009 -0500
   116.2 +++ b/docs/ref/contrib/comments/index.txt	Fri Nov 13 11:27:59 2009 -0600
   116.3 @@ -216,6 +216,13 @@
   116.4        it with a warning field; if you use the comment form with a custom
   116.5        template you should be sure to do the same.
   116.6  
   116.7 +The comments app also depends on the more general :ref:`Cross Site Request
   116.8 +Forgery protection < ref-contrib-csrf>` that comes with Django.  As described in
   116.9 +the documentation, it is best to use ``CsrfViewMiddleware``.  However, if you
  116.10 +are not using that, you will need to use the ``csrf_protect`` decorator on any
  116.11 +views that include the comment form, in order for those views to be able to
  116.12 +output the CSRF token and cookie.
  116.13 +
  116.14  .. _honeypot: http://en.wikipedia.org/wiki/Honeypot_(computing)
  116.15  
  116.16  More information
   117.1 --- a/docs/ref/contrib/csrf.txt	Sat Sep 12 18:53:56 2009 -0500
   117.2 +++ b/docs/ref/contrib/csrf.txt	Fri Nov 13 11:27:59 2009 -0600
   117.3 @@ -4,121 +4,421 @@
   117.4  Cross Site Request Forgery protection
   117.5  =====================================
   117.6  
   117.7 -.. module:: django.contrib.csrf
   117.8 +.. module:: django.middleware.csrf
   117.9     :synopsis: Protects against Cross Site Request Forgeries
  117.10  
  117.11 -The CsrfMiddleware class provides easy-to-use protection against
  117.12 +The CSRF middleware and template tag provides easy-to-use protection against
  117.13  `Cross Site Request Forgeries`_.  This type of attack occurs when a malicious
  117.14 -Web site creates a link or form button that is intended to perform some action
  117.15 -on your Web site, using the credentials of a logged-in user who is tricked
  117.16 -into clicking on the link in their browser.
  117.17 +Web site contains a link, a form button or some javascript that is intended to
  117.18 +perform some action on your Web site, using the credentials of a logged-in user
  117.19 +who visits the malicious site in their browser.  A related type of attack,
  117.20 +'login CSRF', where an attacking site tricks a user's browser into logging into
  117.21 +a site with someone else's credentials, is also covered.
  117.22  
  117.23 -The first defense against CSRF attacks is to ensure that GET requests
  117.24 -are side-effect free.  POST requests can then be protected by adding this
  117.25 -middleware into your list of installed middleware.
  117.26 +The first defense against CSRF attacks is to ensure that GET requests are
  117.27 +side-effect free.  POST requests can then be protected by following the steps
  117.28 +below.
  117.29 +
  117.30 +.. versionadded:: 1.2
  117.31 +    The 'contrib' apps, including the admin, use the functionality described
  117.32 +    here. Because it is security related, a few things have been added to core
  117.33 +    functionality to allow this to happen without any required upgrade steps.
  117.34  
  117.35  .. _Cross Site Request Forgeries: http://www.squarefree.com/securitytips/web-developers.html#CSRF
  117.36  
  117.37  How to use it
  117.38  =============
  117.39  
  117.40 -Add the middleware ``'django.contrib.csrf.middleware.CsrfMiddleware'`` to
  117.41 -your list of middleware classes, :setting:`MIDDLEWARE_CLASSES`. It needs to process
  117.42 -the response after the SessionMiddleware, so must come before it in the
  117.43 -list. It also must process the response before things like compression
  117.44 -happen to the response, so it must come after GZipMiddleware in the
  117.45 -list.
  117.46 +.. versionchanged:: 1.2
  117.47 +    The template tag functionality (the recommended way to use this) was added
  117.48 +    in version 1.2. The previous method (still available) is described under
  117.49 +    `Legacy method`_.
  117.50  
  117.51 -The ``CsrfMiddleware`` class is actually composed of two middleware:
  117.52 -``CsrfViewMiddleware`` which performs the checks on incoming requests,
  117.53 -and ``CsrfResponseMiddleware`` which performs post-processing of the
  117.54 -result.  This allows the individual components to be used and/or
  117.55 -replaced instead of using ``CsrfMiddleware``.
  117.56 +To enable CSRF protection for your views, follow these steps:
  117.57  
  117.58 -.. versionchanged:: 1.1
  117.59 -    (previous versions of Django did not provide these two components
  117.60 -    of ``CsrfMiddleware`` as described above)
  117.61 +    1. Add the middleware
  117.62 +       ``'django.middleware.csrf.CsrfViewMiddleware'`` to your list of
  117.63 +       middleware classes, :setting:`MIDDLEWARE_CLASSES`.  (It should come
  117.64 +       before ``CsrfResponseMiddleware`` if that is being used, and before any
  117.65 +       view middleware that assume that CSRF attacks have been dealt with.)
  117.66 +
  117.67 +       Alternatively, you can use the decorator
  117.68 +       ``django.views.decorators.csrf.csrf_protect`` on particular views you
  117.69 +       want to protect (see below).
  117.70 +
  117.71 +    2. In any template that uses a POST form, use the ``csrf_token`` tag inside
  117.72 +       the ``<form>`` element if the form is for an internal URL, e.g.::
  117.73 +
  117.74 +           <form action="" method="POST">{% csrf_token %}
  117.75 +
  117.76 +       This should not be done for POST forms that target external URLs, since
  117.77 +       that would cause the CSRF token to be leaked, leading to a vulnerability.
  117.78 +
  117.79 +    3. In the corresponding view functions, ensure that the
  117.80 +       ``'django.core.context_processors.csrf'`` context processor is
  117.81 +       being used. Usually, this can be done in one of two ways:
  117.82 +
  117.83 +       1. Use RequestContext, which always uses
  117.84 +          ``'django.core.context_processors.csrf'`` (no matter what your
  117.85 +          TEMPLATE_CONTEXT_PROCESSORS setting).  If you are using
  117.86 +          generic views or contrib apps, you are covered already, since these
  117.87 +          apps use RequestContext throughout.
  117.88 +
  117.89 +       2. Manually import and use the processor to generate the CSRF token and
  117.90 +          add it to the template context. e.g.::
  117.91 +
  117.92 +              from django.core.context_processors import csrf
  117.93 +              from django.shortcuts import render_to_response
  117.94 +
  117.95 +              def my_view(request):
  117.96 +                  c = {}
  117.97 +                  c.update(csrf(request))
  117.98 +                  # ... view code here
  117.99 +                  return render_to_response("a_template.html", c)
 117.100 +
 117.101 +          You may want to write your own ``render_to_response`` wrapper that
 117.102 +          takes care of this step for you.
 117.103 +
 117.104 +The utility script ``extras/csrf_migration_helper.py`` can help to automate the
 117.105 +finding of code and templates that may need to be upgraded.  It contains full
 117.106 +help on how to use it.
 117.107 +
 117.108 +The decorator method
 117.109 +--------------------
 117.110 +
 117.111 +Rather than adding ``CsrfViewMiddleware`` as a blanket protection, you can use
 117.112 +the ``csrf_protect`` decorator, which has exactly the same functionality, on
 117.113 +particular views that need the protection.  It must be used **both** on views
 117.114 +that insert the CSRF token in the output, and on those that accept the POST form
 117.115 +data. (These are often the same view function, but not always).  It is used like
 117.116 +this::
 117.117 +
 117.118 +    from django.views.decorators.csrf import csrf_protect
 117.119 +    from django.template import RequestContext
 117.120 +
 117.121 +    @csrf_protect
 117.122 +    def my_view(request):
 117.123 +        c = {}
 117.124 +        # ...
 117.125 +        return render_to_response("a_template.html", c,
 117.126 +                                   context_instance=RequestContext(request))
 117.127 +
 117.128 +Use of the decorator is **not recommended** by itself, since if you forget to
 117.129 +use it, you will have a security hole.  The 'belt and braces' strategy of using
 117.130 +both is fine, and will incur minimal overhead.
 117.131 +
 117.132 +Legacy method
 117.133 +-------------
 117.134 +
 117.135 +In Django 1.1, the template tag did not exist.  Instead, a post-processing
 117.136 +middleware that re-wrote POST forms to include the CSRF token was used.  If you
 117.137 +are upgrading a site from version 1.1 or earlier, please read this section and
 117.138 +the `Upgrading notes`_ below.  The post-processing middleware is still available
 117.139 +as ``CsrfResponseMiddleware``, and it can be used by following these steps:
 117.140 +
 117.141 +    1. Follow step 1 above to install ``CsrfViewMiddleware``.
 117.142 +
 117.143 +    2. Add ``'django.middleware.csrf.CsrfResponseMiddleware'`` to your
 117.144 +       :setting:`MIDDLEWARE_CLASSES` setting.
 117.145 +
 117.146 +       ``CsrfResponseMiddleware`` needs to process the response before things
 117.147 +       like compression or setting ofETags happen to the response, so it must
 117.148 +       come after ``GZipMiddleware``, ``CommonMiddleware`` and 
 117.149 +       ``ConditionalGetMiddleware`` in the list. It also must come after
 117.150 +       ``CsrfViewMiddleware``.
 117.151 +
 117.152 +Use of the ``CsrfResponseMiddleware`` is not recommended because of the
 117.153 +performance hit it imposes, and because of a potential security problem (see
 117.154 +below).  It can be used as an interim measure until applications have been
 117.155 +updated to use the ``{% csrf_token %}`` tag.  It is deprecated and will be
 117.156 +removed in Django 1.4.
 117.157 +
 117.158 +Django 1.1 and earlier provided a single ``CsrfMiddleware`` class.  This is also
 117.159 +still available for backwards compatibility.  It combines the functions of the
 117.160 +two middleware.
 117.161 +
 117.162 +Note also that previous versions of these classes depended on the sessions
 117.163 +framework, but this dependency has now been removed, with backward compatibility
 117.164 +support so that upgrading will not produce any issues.
 117.165 +
 117.166 +Security of legacy method
 117.167 +~~~~~~~~~~~~~~~~~~~~~~~~~
 117.168 +
 117.169 +The post-processing ``CsrfResponseMiddleware`` adds the CSRF token to all POST
 117.170 +forms (unless the view has been decorated with ``csrf_response_exempt``).  If
 117.171 +the POST form has an external untrusted site as its target, rather than an
 117.172 +internal page, that site will be sent the CSRF token when the form is submitted.
 117.173 +Armed with this leaked information, that site will then be able to successfully
 117.174 +launch a CSRF attack on your site against that user.  The
 117.175 +``@csrf_response_exempt`` decorator can be used to fix this, but only if the
 117.176 +page doesn't also contain internal forms that require the token.
 117.177 +
 117.178 +Upgrading notes
 117.179 +---------------
 117.180 +
 117.181 +When upgrading to version 1.2 or later, you may have applications that rely on
 117.182 +the old post-processing functionality for CSRF protection, or you may not have
 117.183 +enabled any CSRF protection.  This section outlines the steps necessary for a
 117.184 +smooth upgrade, without having to fix all the applications to use the new
 117.185 +template tag method immediately.
 117.186 +
 117.187 +First of all, the location of the middleware and related functions have
 117.188 +changed.  There are backwards compatible stub files so that old imports will
 117.189 +continue to work for now, but they are deprecated and will be removed in Django
 117.190 +1.4.  The following changes have been made:
 117.191 +
 117.192 + * Middleware have been moved to ``django.middleware.csrf``
 117.193 + * Decorators have been moved to ``django.views.decorators.csrf``
 117.194 +
 117.195 +======================================================  ==============================================
 117.196 +   Old                                                       New
 117.197 +======================================================  ==============================================
 117.198 +django.contrib.csrf.middleware.CsrfMiddleware           django.middleware.csrf.CsrfMiddleware
 117.199 +django.contrib.csrf.middleware.CsrfViewMiddleware       django.middleware.csrf.CsrfViewMiddleware
 117.200 +django.contrib.csrf.middleware.CsrfResponseMiddleware   django.middleware.csrf.CsrfResponseMiddleware
 117.201 +django.contrib.csrf.middleware.csrf_exempt              django.views.decorators.csrf_exempt
 117.202 +django.contrib.csrf.middleware.csrf_view_exempt         django.views.decorators.csrf_view_exempt
 117.203 +django.contrib.csrf.middleware.csrf_response_exempt     django.views.decorators.csrf_response_exempt
 117.204 +======================================================  ==============================================
 117.205 +
 117.206 +You should update any imports, and also the paths in your
 117.207 +:setting:`MIDDLEWARE_CLASSES`.
 117.208 +
 117.209 +If you have ``CsrfMiddleware`` in your :setting:`MIDDLEWARE_CLASSES`, you will now
 117.210 +have a working installation with CSRF protection.  It is recommended at this
 117.211 +point that you replace ``CsrfMiddleware`` with its two components,
 117.212 +``CsrfViewMiddleware`` and ``CsrfResponseMiddleware`` (in that order).
 117.213 +
 117.214 +If you do not have any of the middleware in your :setting:`MIDDLEWARE_CLASSES`,
 117.215 +you will have a working installation but without any CSRF protection for your
 117.216 +views (just as you had before). It is strongly recommended to install
 117.217 +``CsrfViewMiddleware`` and ``CsrfResponseMiddleware``, as described above.
 117.218 +
 117.219 +Note that contrib apps, such as the admin, have been updated to use the
 117.220 +``csrf_protect`` decorator, so that they are secured even if you do not add the
 117.221 +``CsrfViewMiddleware`` to your settings.  However, if you have supplied
 117.222 +customised templates to any of the view functions of contrib apps (whether
 117.223 +explicitly via a keyword argument, or by overriding built-in templates), **you
 117.224 +MUST update them** to include the ``csrf_token`` template tag as described
 117.225 +above, or they will stop working.  (If you cannot update these templates for
 117.226 +some reason, you will be forced to use ``CsrfResponseMiddleware`` for these
 117.227 +views to continue working).
 117.228 +
 117.229 +Note also, if you are using the comments app, and you are not going to add
 117.230 +``CsrfViewMiddleware`` to your settings (not recommended), you will need to add
 117.231 +the ``csrf_protect`` decorator to any views that include the comment forms and
 117.232 +target the comment views (usually using the :ttag:`comment_form_target` template
 117.233 +tag).
 117.234 +
 117.235 +Assuming you have followed the above, all views in your Django site will now be
 117.236 +protected by the ``CsrfViewMiddleware``.  Contrib apps meet the requirements
 117.237 +imposed by the ``CsrfViewMiddleware`` using the template tag, and other
 117.238 +applications in your project will meet its requirements by virtue of the
 117.239 +``CsrfResponseMiddleware``.
 117.240 +
 117.241 +The next step is to update all your applications to use the template tag, as
 117.242 +described in `How to use it`_, steps 2-3.  This can be done as soon as is
 117.243 +practical. Any applications that are updated will now require Django 1.1.2 or
 117.244 +later, since they will use the CSRF template tag which was not available in
 117.245 +earlier versions. (The template tag in 1.1.2 is actually a no-op that exists
 117.246 +solely to ease the transition to 1.2 — it allows apps to be created that have
 117.247 +CSRF protection under 1.2 without requiring users of the apps to upgrade to the
 117.248 +Django 1.2.X series).
 117.249 +
 117.250 +The utility script ``extras/csrf_migration_helper.py`` can help to automate the
 117.251 +finding of code and templates that may need to be upgraded.  It contains full
 117.252 +help on how to use it.
 117.253 +
 117.254 +Finally, once all applications are upgraded, ``CsrfResponseMiddleware`` can be
 117.255 +removed from your settings.
 117.256 +
 117.257 +While ``CsrfResponseMiddleware`` is still in use, the ``csrf_response_exempt``
 117.258 +decorator, described in `Exceptions`_, may be useful.  The post-processing
 117.259 +middleware imposes a performance hit and a potential vulnerability, and any
 117.260 +views that have been upgraded to use the new template tag method no longer need
 117.261 +it.
 117.262  
 117.263  Exceptions
 117.264  ----------
 117.265  
 117.266  .. versionadded:: 1.1
 117.267  
 117.268 -To manually exclude a view function from being handled by the
 117.269 -CsrfMiddleware, you can use the ``csrf_exempt`` decorator, found in
 117.270 -the ``django.contrib.csrf.middleware`` module. For example::
 117.271 +To manually exclude a view function from being handled by either of the two CSRF
 117.272 +middleware, you can use the ``csrf_exempt`` decorator, found in the
 117.273 +``django.views.decorators.csrf`` module. For example::
 117.274  
 117.275 -    from django.contrib.csrf.middleware import csrf_exempt
 117.276 +    from django.views.decorators.csrf import csrf_exempt
 117.277  
 117.278 +    @csrf_exempt
 117.279      def my_view(request):
 117.280          return HttpResponse('Hello world')
 117.281 -    my_view = csrf_exempt(my_view)
 117.282  
 117.283 -Like the middleware itself, the ``csrf_exempt`` decorator is composed
 117.284 -of two parts: a ``csrf_view_exempt`` decorator and a
 117.285 -``csrf_response_exempt`` decorator, found in the same module.  These
 117.286 -disable the view protection mechanism (``CsrfViewMiddleware``) and the
 117.287 -response post-processing (``CsrfResponseMiddleware``) respectively.
 117.288 -They can be used individually if required.
 117.289 +Like the middleware, the ``csrf_exempt`` decorator is composed of two parts: a
 117.290 +``csrf_view_exempt`` decorator and a ``csrf_response_exempt`` decorator, found
 117.291 +in the same module.  These disable the view protection mechanism
 117.292 +(``CsrfViewMiddleware``) and the response post-processing
 117.293 +(``CsrfResponseMiddleware``) respectively.  They can be used individually if
 117.294 +required.
 117.295  
 117.296 -You don't have to worry about doing this for most AJAX views. Any
 117.297 -request sent with "X-Requested-With: XMLHttpRequest" is automatically
 117.298 -exempt. (See the next section.)
 117.299 +You don't have to worry about doing this for most AJAX views. Any request sent
 117.300 +with "X-Requested-With: XMLHttpRequest" is automatically exempt. (See the `How
 117.301 +it works`_ section.)
 117.302 +
 117.303 +Subdomains
 117.304 +----------
 117.305 +
 117.306 +By default, CSRF cookies are specific to the subdomain they are set for.  This
 117.307 +means that a form served from one subdomain (e.g. server1.example.com) will not
 117.308 +be able to have a target on another subdomain (e.g. server2.example.com).  This
 117.309 +restriction can be removed by setting :setting:`CSRF_COOKIE_DOMAIN` to be
 117.310 +something like ``".example.com"``.
 117.311 +
 117.312 +Please note that, with or without use of this setting, this CSRF protection
 117.313 +mechanism is not safe against cross-subdomain attacks -- see `Limitations`_.
 117.314 +
 117.315 +Rejected requests
 117.316 +=================
 117.317 +
 117.318 +By default, a '403 Forbidden' response is sent to the user if an incoming
 117.319 +request fails the checks performed by ``CsrfViewMiddleware``.  This should
 117.320 +usually only be seen when there is a genuine Cross Site Request Forgery, or
 117.321 +when, due to a programming error, the CSRF token has not been included with a
 117.322 +POST form.
 117.323 +
 117.324 +No logging is done, and the error message is not very friendly, so you may want
 117.325 +to provide your own page for handling this condition.  To do this, simply set
 117.326 +the :setting:`CSRF_FAILURE_VIEW` setting to a dotted path to your own view
 117.327 +function, which should have the following signature::
 117.328 +
 117.329 +    def csrf_failure(request, reason="")
 117.330 +
 117.331 +where ``reason`` is a short message (intended for developers or logging, not for
 117.332 +end users) indicating the reason the request was rejected.
 117.333  
 117.334  How it works
 117.335  ============
 117.336  
 117.337 -CsrfMiddleware does two things:
 117.338 +The CSRF protection is based on the following things:
 117.339  
 117.340 -1. It modifies outgoing requests by adding a hidden form field to all
 117.341 -   'POST' forms, with the name 'csrfmiddlewaretoken' and a value which is
 117.342 -   a hash of the session ID plus a secret. If there is no session ID set,
 117.343 -   this modification of the response isn't done, so there is very little
 117.344 -   performance penalty for those requests that don't have a session.
 117.345 -   (This is done by ``CsrfResponseMiddleware``).
 117.346 +1. A CSRF cookie that is set to a random value (a session independent nonce, as
 117.347 +   it is called), which other sites will not have access to.
 117.348  
 117.349 -2. On all incoming POST requests that have the session cookie set, it
 117.350 -   checks that the 'csrfmiddlewaretoken' is present and correct. If it
 117.351 -   isn't, the user will get a 403 error. (This is done by
 117.352 -   ``CsrfViewMiddleware``)
 117.353 +   This cookie is set by ``CsrfViewMiddleware``.  It is meant to be permanent,
 117.354 +   but since there is no way to set a cookie that never expires, it is sent with
 117.355 +   every response that has called ``django.middleware.csrf.get_token()``
 117.356 +   (the function used internally to retrieve the CSRF token).
 117.357  
 117.358 -This ensures that only forms that have originated from your Web site
 117.359 -can be used to POST data back.
 117.360 +2. A hidden form field with the name 'csrfmiddlewaretoken' present in all
 117.361 +   outgoing POST forms.  The value of this field is the value of the CSRF
 117.362 +   cookie.
 117.363 +
 117.364 +   This part is done by the template tag (and with the legacy method, it is done
 117.365 +   by ``CsrfResponseMiddleware``).
 117.366 +
 117.367 +3. For all incoming POST requests, a CSRF cookie must be present, and the
 117.368 +   'csrfmiddlewaretoken' field must be present and correct. If it isn't, the
 117.369 +   user will get a 403 error.
 117.370 +
 117.371 +   This check is done by ``CsrfViewMiddleware``.
 117.372 +
 117.373 +4. In addition, for HTTPS requests, strict referer checking is done by
 117.374 +   ``CsrfViewMiddleware``.  This is necessary to address a Man-In-The-Middle
 117.375 +   attack that is possible under HTTPS when using a session independent nonce,
 117.376 +   due to the fact that HTTP 'Set-Cookie' headers are (unfortunately) accepted
 117.377 +   by clients that are talking to a site under HTTPS.  (Referer checking is not
 117.378 +   done for HTTP requests because the presence of the Referer header is not
 117.379 +   reliable enough under HTTP.)
 117.380 +
 117.381 +This ensures that only forms that have originated from your Web site can be used
 117.382 +to POST data back.
 117.383  
 117.384  It deliberately only targets HTTP POST requests (and the corresponding POST
 117.385 -forms). GET requests ought never to have any potentially dangerous side
 117.386 -effects (see `9.1.1 Safe Methods, HTTP 1.1, RFC 2616`_), and so a
 117.387 -CSRF attack with a GET request ought to be harmless.
 117.388 +forms). GET requests ought never to have any potentially dangerous side effects
 117.389 +(see `9.1.1 Safe Methods, HTTP 1.1, RFC 2616`_), and so a CSRF attack with a GET
 117.390 +request ought to be harmless.
 117.391  
 117.392 -POST requests that are not accompanied by a session cookie are not protected,
 117.393 -but they do not need to be protected, since the 'attacking' Web site
 117.394 -could make these kind of requests anyway.
 117.395 +``CsrfResponseMiddleware`` checks the Content-Type before modifying the
 117.396 +response, and only pages that are served as 'text/html' or
 117.397 +'application/xml+xhtml' are modified.
 117.398  
 117.399 -The Content-Type is checked before modifying the response, and only
 117.400 -pages that are served as 'text/html' or 'application/xml+xhtml'
 117.401 -are modified.
 117.402 +AJAX
 117.403 +----
 117.404  
 117.405 -The middleware tries to be smart about requests that come in via AJAX. Many
 117.406 -JavaScript toolkits send an "X-Requested-With: XMLHttpRequest" HTTP header;
 117.407 -these requests are detected and automatically *not* handled by this middleware.
 117.408 -We can do this safely because, in the context of a browser, the header can only
 117.409 -be added by using ``XMLHttpRequest``, and browsers already implement a
 117.410 -same-domain policy for ``XMLHttpRequest``. (Note that this is not secure if you
 117.411 -don't trust content within the same domain or subdomains.)
 117.412 +The middleware tries to be smart about requests that come in via AJAX. Most
 117.413 +modern JavaScript toolkits send an "X-Requested-With: XMLHttpRequest" HTTP
 117.414 +header; these requests are detected and automatically *not* handled by this
 117.415 +middleware.  We can do this safely because, in the context of a browser, the
 117.416 +header can only be added by using ``XMLHttpRequest``, and browsers already
 117.417 +implement a same-domain policy for ``XMLHttpRequest``.
 117.418  
 117.419 +For the more recent browsers that relax this same-domain policy, custom headers
 117.420 +like "X-Requested-With" are only allowed after the browser has done a
 117.421 +'preflight' check to the server to see if the cross-domain request is allowed,
 117.422 +using a strictly 'opt in' mechanism, so the exception for AJAX is still safe—if
 117.423 +the developer has specifically opted in to allowing cross-site AJAX POST
 117.424 +requests on a specific URL, they obviously don't want the middleware to disallow
 117.425 +exactly that.
 117.426  
 117.427  .. _9.1.1 Safe Methods, HTTP 1.1, RFC 2616: http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
 117.428  
 117.429 +Caching
 117.430 +=======
 117.431 +
 117.432 +If the ``csrf_token`` template tag is used by a template (or the ``get_token``
 117.433 +function is called some other way), ``CsrfViewMiddleware`` will add a cookie and
 117.434 +a ``Vary: Cookie`` header to the response.  Similarly,
 117.435 +``CsrfResponseMiddleware`` will send the ``Vary: Cookie`` header if it inserted
 117.436 +a token.  This means that these middleware will play well with the cache
 117.437 +middleware if it is used as instructed (``UpdateCacheMiddleware`` goes before
 117.438 +all other middleware).
 117.439 +
 117.440 +However, if you use cache decorators on individual views, the CSRF middleware
 117.441 +will not yet have been able to set the Vary header.  In this case, on any views
 117.442 +that will require a CSRF token to be inserted you should use the
 117.443 +:func:`django.views.decorators.vary.vary_on_cookie` decorator first::
 117.444 +
 117.445 +  from django.views.decorators.cache import cache_page
 117.446 +  from django.views.decorators.vary import vary_on_cookie
 117.447 +
 117.448 +  @cache_page(60 * 15)
 117.449 +  @vary_on_cookie
 117.450 +  def my_view(request):
 117.451 +      # ...
 117.452 +
 117.453 +
 117.454 +Testing
 117.455 +=======
 117.456 +
 117.457 +The ``CsrfViewMiddleware`` will usually be a big hindrance to testing view
 117.458 +functions, due to the need for the CSRF token which must be sent with every POST
 117.459 +request.  For this reason, Django's HTTP client for tests has been modified to
 117.460 +set a flag on requests which relaxes the middleware and the ``csrf_protect``
 117.461 +decorator so that they no longer rejects requests.  In every other respect
 117.462 +(e.g. sending cookies etc.), they behave the same.
 117.463 +
 117.464  Limitations
 117.465  ===========
 117.466  
 117.467 -CsrfMiddleware requires Django's session framework to work. If you have
 117.468 -a custom authentication system that manually sets cookies and the like,
 117.469 -it won't help you.
 117.470 +Subdomains within a site will be able to set cookies on the client for the whole
 117.471 +domain.  By setting the cookie and using a corresponding token, subdomains will
 117.472 +be able to circumvent the CSRF protection.  The only way to avoid this is to
 117.473 +ensure that subdomains are controlled by trusted users (or, are at least unable
 117.474 +to set cookies).  Note that even without CSRF, there are other vulnerabilities,
 117.475 +such as session fixation, that make giving subdomains to untrusted parties a bad
 117.476 +idea, and these vulnerabilities cannot easily be fixed with current browsers.
 117.477  
 117.478 -If your app creates HTML pages and forms in some unusual way, (e.g.
 117.479 -it sends fragments of HTML in JavaScript document.write statements)
 117.480 -you might bypass the filter that adds the hidden field to the form,
 117.481 -in which case form submission will always fail.  It may still be possible
 117.482 -to use the middleware, provided you can find some way to get the
 117.483 -CSRF token and ensure that is included when your form is submitted.
 117.484 +If you are using ``CsrfResponseMiddleware`` and your app creates HTML pages and
 117.485 +forms in some unusual way, (e.g.  it sends fragments of HTML in JavaScript
 117.486 +document.write statements) you might bypass the filter that adds the hidden
 117.487 +field to the form, in which case form submission will always fail.  You should
 117.488 +use the template tag or :meth:`django.middleware.csrf.get_token` to get
 117.489 +the CSRF token and ensure it is included when your form is submitted.
 117.490 +
 117.491 +Contrib and reusable apps
 117.492 +=========================
 117.493 +
 117.494 +Because it is possible for the developer to turn off the ``CsrfViewMiddleware``,
 117.495 +all relevant views in contrib apps use the ``csrf_protect`` decorator to ensure
 117.496 +the security of these applications against CSRF.  It is recommended that the
 117.497 +developers of other reusable apps that want the same guarantees also use the
 117.498 +``csrf_protect`` decorator on their views.
   118.1 --- a/docs/ref/contrib/formtools/form-wizard.txt	Sat Sep 12 18:53:56 2009 -0500
   118.2 +++ b/docs/ref/contrib/formtools/form-wizard.txt	Fri Nov 13 11:27:59 2009 -0600
   118.3 @@ -177,7 +177,7 @@
   118.4  
   118.5      {% block content %}
   118.6      <p>Step {{ step }} of {{ step_count }}</p>
   118.7 -    <form action="." method="post">
   118.8 +    <form action="." method="post">{% csrf_token %}
   118.9      <table>
  118.10      {{ form }}
  118.11      </table>
   119.1 --- a/docs/ref/middleware.txt	Sat Sep 12 18:53:56 2009 -0500
   119.2 +++ b/docs/ref/middleware.txt	Fri Nov 13 11:27:59 2009 -0600
   119.3 @@ -165,11 +165,11 @@
   119.4  CSRF protection middleware
   119.5  --------------------------
   119.6  
   119.7 -.. module:: django.contrib.csrf.middleware
   119.8 +.. module:: django.middleware.csrf
   119.9     :synopsis: Middleware adding protection against Cross Site Request
  119.10                Forgeries.
  119.11  
  119.12 -.. class:: django.contrib.csrf.middleware.CsrfMiddleware
  119.13 +.. class:: django.middleware.csrf.CsrfMiddleware
  119.14  
  119.15  .. versionadded:: 1.0
  119.16  
   120.1 --- a/docs/ref/models/fields.txt	Sat Sep 12 18:53:56 2009 -0500
   120.2 +++ b/docs/ref/models/fields.txt	Fri Nov 13 11:27:59 2009 -0600
   120.3 @@ -844,7 +844,7 @@
   120.4      current date/time to be chosen.
   120.5  
   120.6      Instead of a dictionary this can also be a :class:`~django.db.models.Q`
   120.7 -    object (an object with a :meth:`get_sql` method) for more complex queries.
   120.8 +    object for more :ref:`complex queries <complex-lookups-with-q>`.
   120.9  
  120.10      ``limit_choices_to`` has no effect on the inline FormSets that are created
  120.11      to display related objects in the admin.
   121.1 --- a/docs/ref/models/options.txt	Sat Sep 12 18:53:56 2009 -0500
   121.2 +++ b/docs/ref/models/options.txt	Fri Nov 13 11:27:59 2009 -0600
   121.3 @@ -19,6 +19,17 @@
   121.4  
   121.5  If ``True``, this model will be an :ref:`abstract base class <abstract-base-classes>`.
   121.6  
   121.7 +``app_label``
   121.8 +-------------
   121.9 +
  121.10 +.. attribute:: Options.app_label
  121.11 +
  121.12 +If a model exists outside of the standard :file:`models.py` (for instance, if
  121.13 +the app's models are in submodules of ``myapp.models``), the model must define
  121.14 +which app it is part of::
  121.15 +
  121.16 +    app_label = 'myapp'
  121.17 +
  121.18  ``db_table``
  121.19  ------------
  121.20  
   122.1 --- a/docs/ref/models/querysets.txt	Sat Sep 12 18:53:56 2009 -0500
   122.2 +++ b/docs/ref/models/querysets.txt	Fri Nov 13 11:27:59 2009 -0600
   122.3 @@ -1114,6 +1114,17 @@
   122.4  
   122.5  .. _field-lookups:
   122.6  
   122.7 +``exists()``
   122.8 +~~~~~~~~~~~~
   122.9 +
  122.10 +.. versionadded:: 1.2
  122.11 +
  122.12 +Returns ``True`` if the :class:`QuerySet` contains any results, and ``False``
  122.13 +if not. This tries to perform the query in the simplest and fastest way
  122.14 +possible, but it *does* execute nearly the same query. This means that calling
  122.15 +:meth:`QuerySet.exists()` is faster that ``bool(some_query_set)``, but not by
  122.16 +a large degree.
  122.17 +
  122.18  Field lookups
  122.19  -------------
  122.20  
  122.21 @@ -1512,9 +1523,10 @@
  122.22  
  122.23  Case-sensitive regular expression match.
  122.24  
  122.25 -The regular expression syntax is that of the database backend in use. In the
  122.26 -case of SQLite, which doesn't natively support regular-expression lookups, the
  122.27 -syntax is that of Python's ``re`` module.
  122.28 +The regular expression syntax is that of the database backend in use.
  122.29 +In the case of SQLite, which has no built in regular expression support,
  122.30 +this feature is provided by a (Python) user-defined REGEXP function, and
  122.31 +the regular expression syntax is therefore that of Python's ``re`` module.
  122.32  
  122.33  Example::
  122.34  
   123.1 --- a/docs/ref/settings.txt	Sat Sep 12 18:53:56 2009 -0500
   123.2 +++ b/docs/ref/settings.txt	Fri Nov 13 11:27:59 2009 -0600
   123.3 @@ -144,6 +144,51 @@
   123.4  The default number of seconds to cache a page when the caching middleware or
   123.5  ``cache_page()`` decorator is used.
   123.6  
   123.7 +.. setting:: CSRF_COOKIE_NAME
   123.8 +
   123.9 +CSRF_COOKIE_NAME
  123.10 +----------------
  123.11 +
  123.12 +.. versionadded:: 1.2
  123.13 +
  123.14 +Default: ``'csrftoken'``
  123.15 +
  123.16 +The name of the cookie to use for the CSRF authentication token. This can be whatever you
  123.17 +want.  See :ref:`ref-contrib-csrf`.
  123.18 +
  123.19 +.. setting:: CSRF_COOKIE_DOMAIN
  123.20 +
  123.21 +CSRF_COOKIE_DOMAIN
  123.22 +------------------
  123.23 +
  123.24 +.. versionadded:: 1.2
  123.25 +
  123.26 +Default: ``None``
  123.27 +
  123.28 +The domain to be used when setting the CSRF cookie.  This can be useful for
  123.29 +allowing cross-subdomain requests to be exluded from the normal cross site
  123.30 +request forgery protection.  It should be set to a string such as
  123.31 +``".lawrence.com"`` to allow a POST request from a form on one subdomain to be
  123.32 +accepted by accepted by a view served from another subdomain.
  123.33 +
  123.34 +.. setting:: CSRF_FAILURE_VIEW
  123.35 +
  123.36 +CSRF_FAILURE_VIEW
  123.37 +-----------------
  123.38 +
  123.39 +.. versionadded:: 1.2
  123.40 +
  123.41 +Default: ``'django.views.csrf.csrf_failure'``
  123.42 +
  123.43 +A dotted path to the view function to be used when an incoming request
  123.44 +is rejected by the CSRF protection.  The function should have this signature::
  123.45 +
  123.46 +  def csrf_failure(request, reason="")
  123.47 +
  123.48 +where ``reason`` is a short message (intended for developers or logging, not for
  123.49 +end users) indicating the reason the request was rejected.  See
  123.50 +:ref:`ref-contrib-csrf`.
  123.51 +
  123.52  .. setting:: DATABASE_ENGINE
  123.53  
  123.54  DATABASE_ENGINE
  123.55 @@ -379,6 +424,29 @@
  123.56  This is only used if ``CommonMiddleware`` is installed (see
  123.57  :ref:`topics-http-middleware`).
  123.58  
  123.59 +.. setting:: EMAIL_BACKEND
  123.60 +
  123.61 +EMAIL_BACKEND
  123.62 +-------------
  123.63 +
  123.64 +.. versionadded:: 1.2
  123.65 +
  123.66 +Default: ``'django.core.mail.backends.smtp'``
  123.67 +
  123.68 +The backend to use for sending emails. For the list of available backends see
  123.69 +:ref:`topics-email`.
  123.70 +
  123.71 +.. setting:: EMAIL_FILE_PATH
  123.72 +
  123.73 +EMAIL_FILE_PATH
  123.74 +---------------
  123.75 +
  123.76 +.. versionadded:: 1.2
  123.77 +
  123.78 +Default: Not defined
  123.79 +
  123.80 +The directory used by the ``file`` email backend to store output files.
  123.81 +
  123.82  .. setting:: EMAIL_HOST
  123.83  
  123.84  EMAIL_HOST
  123.85 @@ -751,6 +819,7 @@
  123.86  
  123.87      ('django.middleware.common.CommonMiddleware',
  123.88       'django.contrib.sessions.middleware.SessionMiddleware',
  123.89 +     'django.middleware.csrf.CsrfViewMiddleware',
  123.90       'django.contrib.auth.middleware.AuthenticationMiddleware',)
  123.91  
  123.92  A tuple of middleware classes to use. See :ref:`topics-http-middleware`.
   124.1 --- a/docs/ref/signals.txt	Sat Sep 12 18:53:56 2009 -0500
   124.2 +++ b/docs/ref/signals.txt	Fri Nov 13 11:27:59 2009 -0600
   124.3 @@ -9,7 +9,7 @@
   124.4  .. seealso::
   124.5  
   124.6      See the documentation on the :ref:`signal dispatcher <topics-signals>` for
   124.7 - 	information regarding how to register for and receive signals.
   124.8 +    information regarding how to register for and receive signals.
   124.9  
  124.10      The :ref:`comment framework <ref-contrib-comments-index>` sends a :ref:`set
  124.11      of comment-related signals <ref-contrib-comments-signals>`.
   125.1 --- a/docs/ref/templates/api.txt	Sat Sep 12 18:53:56 2009 -0500
   125.2 +++ b/docs/ref/templates/api.txt	Fri Nov 13 11:27:59 2009 -0600
   125.3 @@ -313,6 +313,13 @@
   125.4      "django.core.context_processors.i18n",
   125.5      "django.core.context_processors.media")
   125.6  
   125.7 +.. versionadded:: 1.2
   125.8 +   In addition to these, ``RequestContext`` always uses
   125.9 +   ``'django.core.context_processors.csrf'``.  This is a security
  125.10 +   related context processor required by the admin and other contrib apps, and,
  125.11 +   in case of accidental misconfiguration, it is deliberately hardcoded in and
  125.12 +   cannot be turned off by the :setting:`TEMPLATE_CONTEXT_PROCESSORS` setting.
  125.13 +
  125.14  Each processor is applied in order. That means, if one processor adds a
  125.15  variable to the context and a second processor adds a variable with the same
  125.16  name, the second will override the first. The default processors are explained
  125.17 @@ -404,6 +411,14 @@
  125.18  ``RequestContext`` will contain a variable ``MEDIA_URL``, providing the
  125.19  value of the :setting:`MEDIA_URL` setting.
  125.20  
  125.21 +django.core.context_processors.csrf
  125.22 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  125.23 +
  125.24 +.. versionadded:: 1.2
  125.25 +
  125.26 +This processor adds a token that is needed by the ``csrf_token`` template tag
  125.27 +for protection against :ref:`Cross Site Request Forgeries <ref-contrib-csrf>`.
  125.28 +
  125.29  django.core.context_processors.request
  125.30  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  125.31  
   126.1 --- a/docs/ref/templates/builtins.txt	Sat Sep 12 18:53:56 2009 -0500
   126.2 +++ b/docs/ref/templates/builtins.txt	Fri Nov 13 11:27:59 2009 -0600
   126.3 @@ -53,6 +53,16 @@
   126.4  
   126.5  .. templatetag:: cycle
   126.6  
   126.7 +csrf_token
   126.8 +~~~~~~~~~~
   126.9 +
  126.10 +.. versionadded:: 1.1.2
  126.11 +
  126.12 +In the Django 1.1.X series, this is a no-op tag that returns an empty string for
  126.13 +future compatibility purposes.  In Django 1.2 and later, it is used for CSRF
  126.14 +protection, as described in the documentation for :ref:`Cross Site Request
  126.15 +Forgeries <ref-contrib-csrf>`.
  126.16 +
  126.17  cycle
  126.18  ~~~~~
  126.19  
   127.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   127.2 +++ b/docs/releases/1.2-alpha.txt	Fri Nov 13 11:27:59 2009 -0600
   127.3 @@ -0,0 +1,52 @@
   127.4 +
   127.5 +Backwards-incompatible changes
   127.6 +==============================
   127.7 +
   127.8 +CSRF Protection
   127.9 +---------------
  127.10 +
  127.11 +There have been large changes to the way that CSRF protection works, detailed in
  127.12 +:ref:`the CSRF documentaton <ref-contrib-csrf>`.  The following are the major
  127.13 +changes that developers must be aware of:
  127.14 +
  127.15 + * ``CsrfResponseMiddleware`` and ``CsrfMiddleware`` have been deprecated, and
  127.16 +   will be removed completely in Django 1.4, in favour of a template tag that
  127.17 +   should be inserted into forms.
  127.18 +
  127.19 + * All contrib apps use a ``csrf_protect`` decorator to protect the view.  This
  127.20 +   requires the use of the csrf_token template tag in the template, so if you
  127.21 +   have used custom templates for contrib views, you MUST READ THE UPGRADE
  127.22 +   INSTRUCTIONS to fix those templates.
  127.23 +
  127.24 + * ``CsrfViewMiddleware`` is included in :setting:`MIDDLEWARE_CLASSES` by
  127.25 +   default. This turns on CSRF protection by default, so that views that accept
  127.26 +   POST requests need to be written to work with the middleware.  Instructions
  127.27 +   on how to do this are found in the CSRF docs.
  127.28 +
  127.29 + * All of the CSRF has moved from contrib to core (with backwards compatible
  127.30 +   imports in the old locations, which are deprecated).
  127.31 +
  127.32 +LazyObject
  127.33 +----------
  127.34 +
  127.35 +``LazyObject`` is an undocumented utility class used for lazily wrapping other
  127.36 +objects of unknown type.  In Django 1.1 and earlier, it handled introspection in
  127.37 +a non-standard way, depending on wrapped objects implementing a public method
  127.38 +``get_all_members()``. Since this could easily lead to name clashes, it has been
  127.39 +changed to use the standard method, involving ``__members__`` and ``__dir__()``.
  127.40 +If you used ``LazyObject`` in your own code, and implemented the
  127.41 +``get_all_members()`` method for wrapped objects, you need to make the following
  127.42 +changes:
  127.43 +
  127.44 + * If your class does not have special requirements for introspection (i.e. you
  127.45 +   have not implemented ``__getattr__()`` or other methods that allow for
  127.46 +   attributes not discoverable by normal mechanisms), you can simply remove the
  127.47 +   ``get_all_members()`` method.  The default implementation on ``LazyObject``
  127.48 +   will do the right thing.
  127.49 +
  127.50 + * If you have more complex requirements for introspection, first rename the
  127.51 +   ``get_all_members()`` method to ``__dir__()``.  This is the standard method,
  127.52 +   from Python 2.6 onwards, for supporting introspection.  If you are require
  127.53 +   support for Python < 2.6, add the following code to the class::
  127.54 +
  127.55 +       __members__ = property(lambda self: self.__dir__())
   128.1 --- a/docs/topics/auth.txt	Sat Sep 12 18:53:56 2009 -0500
   128.2 +++ b/docs/topics/auth.txt	Fri Nov 13 11:27:59 2009 -0600
   128.3 @@ -212,14 +212,15 @@
   128.4      .. method:: models.User.has_perm(perm)
   128.5  
   128.6          Returns ``True`` if the user has the specified permission, where perm is
   128.7 -        in the format ``"<application name>.<lowercased model name>"``. If the
   128.8 -        user is inactive, this method will always return ``False``.
   128.9 +        in the format ``"<app label>.<permission codename>"``.
  128.10 +        If the user is inactive, this method will always return ``False``.
  128.11  
  128.12      .. method:: models.User.has_perms(perm_list)
  128.13  
  128.14          Returns ``True`` if the user has each of the specified permissions,
  128.15 -        where each perm is in the format ``"package.codename"``. If the user is
  128.16 -        inactive, this method will always return ``False``.
  128.17 +        where each perm is in the format 
  128.18 +        ``"<app label>.<permission codename>"``. If the user is inactive,
  128.19 +        this method will always return ``False``.
  128.20  
  128.21      .. method:: models.User.has_module_perms(package_name)
  128.22  
  128.23 @@ -261,8 +262,8 @@
  128.24          Creates, saves and returns a :class:`~django.contrib.auth.models.User`.
  128.25          The :attr:`~django.contrib.auth.models.User.username`,
  128.26          :attr:`~django.contrib.auth.models.User.email` and
  128.27 -        :attr:`~django.contrib.auth.models.User.password` are set as given, and
  128.28 -        the :class:`~django.contrib.auth.models.User` gets ``is_active=True``.
  128.29 +        :attr:`~django.contrib.auth.models.User.password` are set as given, and the
  128.30 +        :class:`~django.contrib.auth.models.User` gets ``is_active=True``.
  128.31  
  128.32          If no password is provided,
  128.33          :meth:`~django.contrib.auth.models.User.set_unusable_password()` will
  128.34 @@ -689,8 +690,10 @@
  128.35  
  128.36          * If the user isn't logged in, redirect to
  128.37            :setting:`settings.LOGIN_URL <LOGIN_URL>` (``/accounts/login/`` by
  128.38 -          default), passing the current absolute URL in the query string as
  128.39 -          ``next`` or the value of ``redirect_field_name``. For example:
  128.40 +          default), passing the current absolute URL in the query string. The
  128.41 +          name of the GET argument is determined by the ``redirect_field_name``
  128.42 +          argument provided to the decorator. The default argument name is
  128.43 +          ``next``. For example:
  128.44            ``/accounts/login/?next=/polls/3/``.
  128.45  
  128.46          * If the user is logged in, execute the view normally. The view code is
  128.47 @@ -702,7 +705,7 @@
  128.48  
  128.49      (r'^accounts/login/$', 'django.contrib.auth.views.login'),
  128.50  
  128.51 -.. function:: views.login(request, [template_name, redirect_field_name])
  128.52 +.. function:: views.login(request, [template_name, redirect_field_name, authentication_form])
  128.53  
  128.54      Here's what ``django.contrib.auth.views.login`` does:
  128.55  
  128.56 @@ -726,14 +729,14 @@
  128.57  
  128.58          * ``next``: The URL to redirect to after successful login. This may
  128.59            contain a query string, too.
  128.60 -        
  128.61 +
  128.62          * ``site``: The current :class:`~django.contrib.sites.models.Site`,
  128.63            according to the :setting:`SITE_ID` setting. If you don't have the
  128.64            site framework installed, this will be set to an instance of
  128.65            :class:`~django.contrib.sites.models.RequestSite`, which derives the
  128.66            site name and domain from the current
  128.67            :class:`~django.http.HttpRequest`.
  128.68 -        
  128.69 +
  128.70          * ``site_name``: An alias for ``site.name``. If you don't have the site
  128.71            framework installed, this will be set to the value of
  128.72            :attr:`request.META['SERVER_NAME'] <django.http.HttpRequest.META>`.
  128.73 @@ -745,11 +748,11 @@
  128.74      :file:`myapp/login.html` instead::
  128.75  
  128.76          (r'^accounts/login/$', 'django.contrib.auth.views.login', {'template_name': 'myapp/login.html'}),
  128.77 -        
  128.78 +
  128.79      You can also specify the name of the ``GET`` field which contains the URL
  128.80      to redirect to after login by passing ``redirect_field_name`` to the view.
  128.81      By default, the field is called ``next``.
  128.82 -        
  128.83 +
  128.84      Here's a sample :file:`registration/login.html` template you can use as a
  128.85      starting point. It assumes you have a :file:`base.html` template that
  128.86      defines a ``content`` block:
  128.87 @@ -764,7 +767,7 @@
  128.88          <p>Your username and password didn't match. Please try again.</p>
  128.89          {% endif %}
  128.90  
  128.91 -        <form method="post" action="{% url django.contrib.auth.views.login %}">
  128.92 +        <form method="post" action="{% url django.contrib.auth.views.login %}">{% csrf_token %}
  128.93          <table>
  128.94          <tr>
  128.95              <td>{{ form.username.label_tag }}</td>
  128.96 @@ -782,6 +785,15 @@
  128.97  
  128.98          {% endblock %}
  128.99  
 128.100 +    .. versionadded:: 1.2
 128.101 +
 128.102 +    If you are using alternate authentication (see
 128.103 +    :ref:`authentication-backends`) you can pass a custom authentication form
 128.104 +    to the login view via the ``authentication_form`` parameter. This form must
 128.105 +    accept a ``request`` keyword argument in its ``__init__`` method, and
 128.106 +    provide a ``get_user`` argument which returns the authenticated user object
 128.107 +    (this method is only ever called after successful form validation).
 128.108 +
 128.109      .. _forms documentation: ../forms/
 128.110      .. _site framework docs: ../sites/
 128.111  
 128.112 @@ -803,7 +815,7 @@
 128.113          * ``template_name``: The full name of a template to display after
 128.114            logging the user out. This will default to
 128.115            :file:`registration/logged_out.html` if no argument is supplied.
 128.116 -          
 128.117 +
 128.118          * ``redirect_field_name``: The name of a ``GET`` field containing the
 128.119            URL to redirect to after log out. Overrides ``next_page`` if the given
 128.120            ``GET`` parameter is passed.
 128.121 @@ -821,7 +833,7 @@
 128.122          * ``login_url``: The URL of the login page to redirect to. This will
 128.123            default to :setting:`settings.LOGIN_URL <LOGIN_URL>` if not supplied.
 128.124  
 128.125 -.. function:: views.password_change(request[, template_name, post_change_redirect])
 128.126 +.. function:: views.password_change(request[, template_name, post_change_redirect, password_change_form])
 128.127  
 128.128      Allows a user to change their password.
 128.129  
 128.130 @@ -834,6 +846,13 @@
 128.131          * ``post_change_redirect``: The URL to redirect to after a successful
 128.132            password change.
 128.133  
 128.134 +        * .. versionadded:: 1.2
 128.135 +
 128.136 +          ``password_change_form``: A custom "change password" form which must
 128.137 +          accept a ``user`` keyword argument. The form is responsible for
 128.138 +          actually changing the user's password.
 128.139 +
 128.140 +
 128.141      **Template context:**
 128.142  
 128.143          * ``form``: The password change form.
 128.144 @@ -862,17 +881,17 @@
 128.145          * ``email_template_name``: The full name of a template to use for
 128.146            generating the e-mail with the new password. This will default to
 128.147            :file:`registration/password_reset_email.html` if not supplied.
 128.148 -        
 128.149 +
 128.150          * ``password_reset_form``: Form that will be used to set the password.
 128.151            Defaults to ``SetPasswordForm``.
 128.152 -        
 128.153 +
 128.154          * ``token_generator``: Instance of the class to check the password. This
 128.155            will default to ``default_token_generator``, it's an instance of
 128.156            ``django.contrib.auth.tokens.PasswordResetTokenGenerator``.
 128.157 -          
 128.158 +
 128.159          * ``post_reset_redirect``: The URL to redirect to after a successful
 128.160            password change.
 128.161 -          
 128.162 +
 128.163      **Template context:**
 128.164  
 128.165          * ``form``: The form for resetting the user's password.
 128.166 @@ -900,11 +919,11 @@
 128.167  
 128.168          * ``login_url``: The URL of the login page to redirect to. This will
 128.169            default to :setting:`settings.LOGIN_URL <LOGIN_URL>` if not supplied.
 128.170 -          
 128.171 +
 128.172          * ``redirect_field_name``: The name of a ``GET`` field containing the
 128.173            URL to redirect to after log out. Overrides ``next`` if the given
 128.174            ``GET`` parameter is passed.
 128.175 -          
 128.176 +
 128.177  .. function:: password_reset_confirm(request[, uidb36, token, template_name, token_generator, set_password_form, post_reset_redirect])
 128.178  
 128.179      Presents a form for entering a new password.
 128.180 @@ -929,7 +948,7 @@
 128.181     Presents a view which informs the user that the password has been
 128.182     successfully changed.
 128.183  
 128.184 -   **Optional arguments:** 
 128.185 +   **Optional arguments:**
 128.186  
 128.187         * ``template_name``: The full name of a template to display the view.
 128.188           This will default to :file:`registration/password_reset_complete.html`.
 128.189 @@ -1027,15 +1046,7 @@
 128.190      optional ``login_url`` argument, which lets you specify the URL for your
 128.191      login page (:setting:`settings.LOGIN_URL <LOGIN_URL>` by default).
 128.192  
 128.193 -    Example in Python 2.3 syntax::
 128.194 -
 128.195 -        from django.contrib.auth.decorators import user_passes_test
 128.196 -
 128.197 -        def my_view(request):
 128.198 -            # ...
 128.199 -        my_view = user_passes_test(lambda u: u.has_perm('polls.can_vote'), login_url='/login/')(my_view)
 128.200 -
 128.201 -    Example in Python 2.4 syntax::
 128.202 +    For example::
 128.203  
 128.204          from django.contrib.auth.decorators import user_passes_test
 128.205  
 128.206 @@ -1060,8 +1071,8 @@
 128.207          my_view = permission_required('polls.can_vote')(my_view)
 128.208  
 128.209      As for the :meth:`User.has_perm` method, permission names take the form
 128.210 -    ``"<application name>.<lowercased model name>"`` (i.e. ``polls.choice`` for
 128.211 -    a ``Choice`` model in the ``polls`` application).
 128.212 +    ``"<app label>.<permission codename>"`` (i.e. ``polls.can_vote`` for a
 128.213 +    permission on a model in the ``polls`` application).
 128.214  
 128.215      Note that :func:`~django.contrib.auth.decorators.permission_required()`
 128.216      also takes an optional ``login_url`` parameter. Example::
   129.1 --- a/docs/topics/cache.txt	Sat Sep 12 18:53:56 2009 -0500
   129.2 +++ b/docs/topics/cache.txt	Fri Nov 13 11:27:59 2009 -0600
   129.3 @@ -361,6 +361,17 @@
   129.4  you may expect. But once a particular URL (e.g., ``/foo/23/``) has been
   129.5  requested, subsequent requests to that URL will use the cache.
   129.6  
   129.7 +``cache_page`` can also take an optional keyword argument, ``key_prefix``, which
   129.8 +works in the same way as the ``CACHE_MIDDLEWARE_KEY_PREFIX`` setting for the
   129.9 +middleware.  It can be used like this::
  129.10 +
  129.11 +    my_view = cache_page(my_view, 60 * 15, key_prefix="site1")
  129.12 +
  129.13 +Or, using Python 2.4's decorator syntax::
  129.14 +
  129.15 +    @cache_page(60 * 15, key_prefix="site1")
  129.16 +    def my_view(request):
  129.17 +
  129.18  Specifying per-view cache in the URLconf
  129.19  ----------------------------------------
  129.20  
  129.21 @@ -605,12 +616,6 @@
  129.22  
  129.23      from django.views.decorators.vary import vary_on_headers
  129.24  
  129.25 -    # Python 2.3 syntax.
  129.26 -    def my_view(request):
  129.27 -        # ...
  129.28 -    my_view = vary_on_headers(my_view, 'User-Agent')
  129.29 -
  129.30 -    # Python 2.4+ decorator syntax.
  129.31      @vary_on_headers('User-Agent')
  129.32      def my_view(request):
  129.33          # ...
   130.1 --- a/docs/topics/conditional-view-processing.txt	Sat Sep 12 18:53:56 2009 -0500
   130.2 +++ b/docs/topics/conditional-view-processing.txt	Fri Nov 13 11:27:59 2009 -0600
   130.3 @@ -95,13 +95,6 @@
   130.4      def front_page(request, blog_id):
   130.5          ...
   130.6  
   130.7 -Of course, if you're using Python 2.3 or prefer not to use the decorator
   130.8 -syntax, you can write the same code as follows, there is no difference::
   130.9 -
  130.10 -    def front_page(request, blog_id):
  130.11 -        ...
  130.12 -    front_page = condition(last_modified_func=latest_entry)(front_page)
  130.13 -
  130.14  Shortcuts for only computing one value
  130.15  ======================================
  130.16  
   131.1 --- a/docs/topics/db/queries.txt	Sat Sep 12 18:53:56 2009 -0500
   131.2 +++ b/docs/topics/db/queries.txt	Fri Nov 13 11:27:59 2009 -0600
   131.3 @@ -622,6 +622,8 @@
   131.4      >>> print [p.headline for p in queryset] # Evaluate the query set.
   131.5      >>> print [p.pub_date for p in queryset] # Re-use the cache from the evaluation.
   131.6  
   131.7 +.. _complex-lookups-with-q:
   131.8 +
   131.9  Complex lookups with Q objects
  131.10  ==============================
  131.11  
   132.1 --- a/docs/topics/email.txt	Sat Sep 12 18:53:56 2009 -0500
   132.2 +++ b/docs/topics/email.txt	Fri Nov 13 11:27:59 2009 -0600
   132.3 @@ -7,11 +7,13 @@
   132.4  .. module:: django.core.mail
   132.5     :synopsis: Helpers to easily send e-mail.
   132.6  
   132.7 -Although Python makes sending e-mail relatively easy via the `smtplib library`_,
   132.8 -Django provides a couple of light wrappers over it, to make sending e-mail
   132.9 -extra quick.
  132.10 +Although Python makes sending e-mail relatively easy via the `smtplib
  132.11 +library`_, Django provides a couple of light wrappers over it. These wrappers
  132.12 +are provided to make sending e-mail extra quick, to make it easy to test
  132.13 +email sending during development, and to provide support for platforms that
  132.14 +can't use SMTP.
  132.15  
  132.16 -The code lives in a single module: ``django.core.mail``.
  132.17 +The code lives in the ``django.core.mail`` module.
  132.18  
  132.19  .. _smtplib library: http://docs.python.org/library/smtplib.html
  132.20  
  132.21 @@ -25,11 +27,11 @@
  132.22      send_mail('Subject here', 'Here is the message.', 'from@example.com',
  132.23          ['to@example.com'], fail_silently=False)
  132.24  
  132.25 -Mail is sent using the SMTP host and port specified in the :setting:`EMAIL_HOST`
  132.26 -and :setting:`EMAIL_PORT` settings. The :setting:`EMAIL_HOST_USER` and
  132.27 -:setting:`EMAIL_HOST_PASSWORD` settings, if set, are used to authenticate to the
  132.28 -SMTP server, and the :setting:`EMAIL_USE_TLS` setting controls whether a secure
  132.29 -connection is used.
  132.30 +Mail is sent using the SMTP host and port specified in the
  132.31 +:setting:`EMAIL_HOST` and :setting:`EMAIL_PORT` settings. The
  132.32 +:setting:`EMAIL_HOST_USER` and :setting:`EMAIL_HOST_PASSWORD` settings, if
  132.33 +set, are used to authenticate to the SMTP server, and the
  132.34 +:setting:`EMAIL_USE_TLS` setting controls whether a secure connection is used.
  132.35  
  132.36  .. note::
  132.37  
  132.38 @@ -42,7 +44,7 @@
  132.39  The simplest way to send e-mail is using the function
  132.40  ``django.core.mail.send_mail()``. Here's its definition:
  132.41  
  132.42 -    .. function:: send_mail(subject, message, from_email, recipient_list, fail_silently=False, auth_user=None, auth_password=None)
  132.43 +    .. function:: send_mail(subject, message, from_email, recipient_list, fail_silently=False, auth_user=None, auth_password=None, connection=None)
  132.44  
  132.45  The ``subject``, ``message``, ``from_email`` and ``recipient_list`` parameters
  132.46  are required.
  132.47 @@ -62,6 +64,10 @@
  132.48      * ``auth_password``: The optional password to use to authenticate to the
  132.49        SMTP server. If this isn't provided, Django will use the value of the
  132.50        ``EMAIL_HOST_PASSWORD`` setting.
  132.51 +    * ``connection``: The optional email backend to use to send the mail.
  132.52 +      If unspecified, an instance of the default backend will be used.
  132.53 +      See the documentation on :ref:`E-mail backends <topic-email-backends>`
  132.54 +      for more details.
  132.55  
  132.56  .. _smtplib docs: http://docs.python.org/library/smtplib.html
  132.57  
  132.58 @@ -71,26 +77,29 @@
  132.59  ``django.core.mail.send_mass_mail()`` is intended to handle mass e-mailing.
  132.60  Here's the definition:
  132.61  
  132.62 -    .. function:: send_mass_mail(datatuple, fail_silently=False, auth_user=None, auth_password=None)
  132.63 +    .. function:: send_mass_mail(datatuple, fail_silently=False, auth_user=None, auth_password=None, connection=None)
  132.64  
  132.65  ``datatuple`` is a tuple in which each element is in this format::
  132.66  
  132.67      (subject, message, from_email, recipient_list)
  132.68  
  132.69  ``fail_silently``, ``auth_user`` and ``auth_password`` have the same functions
  132.70 -as in ``send_mail()``.
  132.71 +as in :meth:`~django.core.mail.send_mail()`.
  132.72  
  132.73  Each separate element of ``datatuple`` results in a separate e-mail message.
  132.74 -As in ``send_mail()``, recipients in the same ``recipient_list`` will all see
  132.75 -the other addresses in the e-mail messages' "To:" field.
  132.76 +As in :meth:`~django.core.mail.send_mail()`, recipients in the same
  132.77 +``recipient_list`` will all see the other addresses in the e-mail messages'
  132.78 +"To:" field.
  132.79  
  132.80  send_mass_mail() vs. send_mail()
  132.81  --------------------------------
  132.82  
  132.83 -The main difference between ``send_mass_mail()`` and ``send_mail()`` is that
  132.84 -``send_mail()`` opens a connection to the mail server each time it's executed,
  132.85 -while ``send_mass_mail()`` uses a single connection for all of its messages.
  132.86 -This makes ``send_mass_mail()`` slightly more efficient.
  132.87 +The main difference between :meth:`~django.core.mail.send_mass_mail()` and
  132.88 +:meth:`~django.core.mail.send_mail()` is that
  132.89 +:meth:`~django.core.mail.send_mail()` opens a connection to the mail server
  132.90 +each time it's executed, while :meth:`~django.core.mail.send_mass_mail()` uses
  132.91 +a single connection for all of its messages. This makes
  132.92 +:meth:`~django.core.mail.send_mass_mail()` slightly more efficient.
  132.93  
  132.94  mail_admins()
  132.95  =============
  132.96 @@ -98,7 +107,7 @@
  132.97  ``django.core.mail.mail_admins()`` is a shortcut for sending an e-mail to the
  132.98  site admins, as defined in the :setting:`ADMINS` setting. Here's the definition:
  132.99  
 132.100 -    .. function:: mail_admins(subject, message, fail_silently=False)
 132.101 +    .. function:: mail_admins(subject, message, fail_silently=False, connection=None)
 132.102  
 132.103  ``mail_admins()`` prefixes the subject with the value of the
 132.104  :setting:`EMAIL_SUBJECT_PREFIX` setting, which is ``"[Django] "`` by default.
 132.105 @@ -115,7 +124,7 @@
 132.106  sends an e-mail to the site managers, as defined in the :setting:`MANAGERS`
 132.107  setting. Here's the definition:
 132.108  
 132.109 -    .. function:: mail_managers(subject, message, fail_silently=False)
 132.110 +    .. function:: mail_managers(subject, message, fail_silently=False, connection=None)
 132.111  
 132.112  Examples
 132.113  ========
 132.114 @@ -145,7 +154,7 @@
 132.115  The Django e-mail functions outlined above all protect against header injection
 132.116  by forbidding newlines in header values. If any ``subject``, ``from_email`` or
 132.117  ``recipient_list`` contains a newline (in either Unix, Windows or Mac style),
 132.118 -the e-mail function (e.g. ``send_mail()``) will raise
 132.119 +the e-mail function (e.g. :meth:`~django.core.mail.send_mail()`) will raise
 132.120  ``django.core.mail.BadHeaderError`` (a subclass of ``ValueError``) and, hence,
 132.121  will not send the e-mail. It's your responsibility to validate all data before
 132.122  passing it to the e-mail functions.
 132.123 @@ -178,41 +187,47 @@
 132.124  
 132.125  .. _emailmessage-and-smtpconnection:
 132.126  
 132.127 -The EmailMessage and SMTPConnection classes
 132.128 -===========================================
 132.129 +The EmailMessage class
 132.130 +======================
 132.131  
 132.132  .. versionadded:: 1.0
 132.133  
 132.134 -Django's ``send_mail()`` and ``send_mass_mail()`` functions are actually thin
 132.135 -wrappers that make use of the ``EmailMessage`` and ``SMTPConnection`` classes
 132.136 -in ``django.core.mail``.  If you ever need to customize the way Django sends
 132.137 -e-mail, you can subclass these two classes to suit your needs.
 132.138 +Django's :meth:`~django.core.mail.send_mail()` and
 132.139 +:meth:`~django.core.mail.send_mass_mail()` functions are actually thin
 132.140 +wrappers that make use of the :class:`~django.core.mail.EmailMessage` class.
 132.141 +
 132.142 +Not all features of the :class:`~django.core.mail.EmailMessage` class are
 132.143 +available through the :meth:`~django.core.mail.send_mail()` and related
 132.144 +wrapper functions. If you wish to use advanced features, such as BCC'ed
 132.145 +recipients, file attachments, or multi-part e-mail, you'll need to create
 132.146 +:class:`~django.core.mail.EmailMessage` instances directly.
 132.147  
 132.148  .. note::
 132.149 -    Not all features of the ``EmailMessage`` class are available through the
 132.150 -    ``send_mail()`` and related wrapper functions. If you wish to use advanced
 132.151 -    features, such as BCC'ed recipients, file attachments, or multi-part
 132.152 -    e-mail, you'll need to create ``EmailMessage`` instances directly.
 132.153 +    This is a design feature. :meth:`~django.core.mail.send_mail()` and
 132.154 +    related functions were originally the only interface Django provided.
 132.155 +    However, the list of parameters they accepted was slowly growing over
 132.156 +    time. It made sense to move to a more object-oriented design for e-mail
 132.157 +    messages and retain the original functions only for backwards
 132.158 +    compatibility.
 132.159  
 132.160 -    This is a design feature. ``send_mail()`` and related functions were
 132.161 -    originally the only interface Django provided. However, the list of
 132.162 -    parameters they accepted was slowly growing over time. It made sense to
 132.163 -    move to a more object-oriented design for e-mail messages and retain the
 132.164 -    original functions only for backwards compatibility.
 132.165 +:class:`~django.core.mail.EmailMessage` is responsible for creating the e-mail
 132.166 +message itself. The :ref:`e-mail backend <topic-email-backends>` is then
 132.167 +responsible for sending the e-mail.
 132.168  
 132.169 -In general, ``EmailMessage`` is responsible for creating the e-mail message
 132.170 -itself. ``SMTPConnection`` is responsible for the network connection side of
 132.171 -the operation. This means you can reuse the same connection (an
 132.172 -``SMTPConnection`` instance) for multiple messages.
 132.173 +For convenience, :class:`~django.core.mail.EmailMessage` provides a simple
 132.174 +``send()`` method for sending a single email. If you need to send multiple
 132.175 +messages, the email backend API :ref:`provides an alternative
 132.176 +<topics-sending-multiple-emails>`.
 132.177  
 132.178  EmailMessage Objects
 132.179  --------------------
 132.180  
 132.181  .. class:: EmailMessage
 132.182  
 132.183 -The ``EmailMessage`` class is initialized with the following parameters (in
 132.184 -the given order, if positional arguments are used). All parameters are
 132.185 -optional and can be set at any time prior to calling the ``send()`` method.
 132.186 +The :class:`~django.core.mail.EmailMessage` class is initialized with the
 132.187 +following parameters (in the given order, if positional arguments are used).
 132.188 +All parameters are optional and can be set at any time prior to calling the
 132.189 +``send()`` method.
 132.190  
 132.191      * ``subject``: The subject line of the e-mail.
 132.192  
 132.193 @@ -227,7 +242,7 @@
 132.194      * ``bcc``: A list or tuple of addresses used in the "Bcc" header when
 132.195        sending the e-mail.
 132.196  
 132.197 -    * ``connection``: An ``SMTPConnection`` instance. Use this parameter if
 132.198 +    * ``connection``: An e-mail backend instance. Use this parameter if
 132.199        you want to use the same connection for multiple messages. If omitted, a
 132.200        new connection is created when ``send()`` is called.
 132.201  
 132.202 @@ -248,18 +263,18 @@
 132.203  
 132.204  The class has the following methods:
 132.205  
 132.206 -    * ``send(fail_silently=False)`` sends the message, using either
 132.207 -      the connection that is specified in the ``connection``
 132.208 -      attribute, or creating a new connection if none already
 132.209 -      exists. If the keyword argument ``fail_silently`` is ``True``,
 132.210 -      exceptions raised while sending the message will be quashed.
 132.211 +    * ``send(fail_silently=False)`` sends the message. If a connection was
 132.212 +      specified when the email was constructed, that connection will be used.
 132.213 +      Otherwise, an instance of the default backend will be instantiated and
 132.214 +      used. If the keyword argument ``fail_silently`` is ``True``, exceptions
 132.215 +      raised while sending the message will be quashed.
 132.216  
 132.217      * ``message()`` constructs a ``django.core.mail.SafeMIMEText`` object (a
 132.218        subclass of Python's ``email.MIMEText.MIMEText`` class) or a
 132.219 -      ``django.core.mail.SafeMIMEMultipart`` object holding the
 132.220 -      message to be sent. If you ever need to extend the ``EmailMessage`` class,
 132.221 -      you'll probably want to override this method to put the content you want
 132.222 -      into the MIME object.
 132.223 +      ``django.core.mail.SafeMIMEMultipart`` object holding the message to be
 132.224 +      sent. If you ever need to extend the
 132.225 +      :class:`~django.core.mail.EmailMessage` class, you'll probably want to
 132.226 +      override this method to put the content you want into the MIME object.
 132.227  
 132.228      * ``recipients()`` returns a list of all the recipients of the message,
 132.229        whether they're recorded in the ``to`` or ``bcc`` attributes. This is
 132.230 @@ -299,13 +314,13 @@
 132.231  Sending alternative content types
 132.232  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 132.233  
 132.234 -It can be useful to include multiple versions of the content in an e-mail;
 132.235 -the classic example is to send both text and HTML versions of a message. With
 132.236 +It can be useful to include multiple versions of the content in an e-mail; the
 132.237 +classic example is to send both text and HTML versions of a message. With
 132.238  Django's e-mail library, you can do this using the ``EmailMultiAlternatives``
 132.239 -class. This subclass of ``EmailMessage`` has an ``attach_alternative()`` method
 132.240 -for including extra versions of the message body in the e-mail. All the other
 132.241 -methods (including the class initialization) are inherited directly from
 132.242 -``EmailMessage``.
 132.243 +class. This subclass of :class:`~django.core.mail.EmailMessage` has an
 132.244 +``attach_alternative()`` method for including extra versions of the message
 132.245 +body in the e-mail. All the other methods (including the class initialization)
 132.246 +are inherited directly from :class:`~django.core.mail.EmailMessage`.
 132.247  
 132.248  To send a text and HTML combination, you could write::
 132.249  
 132.250 @@ -318,41 +333,231 @@
 132.251      msg.attach_alternative(html_content, "text/html")
 132.252      msg.send()
 132.253  
 132.254 -By default, the MIME type of the ``body`` parameter in an ``EmailMessage`` is
 132.255 -``"text/plain"``. It is good practice to leave this alone, because it
 132.256 -guarantees that any recipient will be able to read the e-mail, regardless of
 132.257 -their mail client. However, if you are confident that your recipients can
 132.258 -handle an alternative content type, you can use the ``content_subtype``
 132.259 -attribute on the ``EmailMessage`` class to change the main content type. The
 132.260 -major type will always be ``"text"``, but you can change it to the subtype. For
 132.261 -example::
 132.262 +By default, the MIME type of the ``body`` parameter in an
 132.263 +:class:`~django.core.mail.EmailMessage` is ``"text/plain"``. It is good
 132.264 +practice to leave this alone, because it guarantees that any recipient will be
 132.265 +able to read the e-mail, regardless of their mail client. However, if you are
 132.266 +confident that your recipients can handle an alternative content type, you can
 132.267 +use the ``content_subtype`` attribute on the
 132.268 +:class:`~django.core.mail.EmailMessage` class to change the main content type.
 132.269 +The major type will always be ``"text"``, but you can change it to the
 132.270 +subtype. For example::
 132.271  
 132.272      msg = EmailMessage(subject, html_content, from_email, [to])
 132.273      msg.content_subtype = "html"  # Main content is now text/html
 132.274      msg.send()
 132.275  
 132.276 -SMTPConnection Objects
 132.277 -----------------------
 132.278 +.. _topic-email-backends:
 132.279  
 132.280 -.. class:: SMTPConnection
 132.281 +E-Mail Backends
 132.282 +===============
 132.283  
 132.284 -The ``SMTPConnection`` class is initialized with the host, port, username and
 132.285 -password for the SMTP server. If you don't specify one or more of those
 132.286 -options, they are read from your settings file.
 132.287 +.. versionadded:: 1.2
 132.288  
 132.289 -If you're sending lots of messages at once, the ``send_messages()`` method of
 132.290 -the ``SMTPConnection`` class is useful. It takes a list of ``EmailMessage``
 132.291 -instances (or subclasses) and sends them over a single connection. For example,
 132.292 -if you have a function called ``get_notification_email()`` that returns a
 132.293 -list of ``EmailMessage`` objects representing some periodic e-mail you wish to
 132.294 -send out, you could send this with::
 132.295 +The actual sending of an e-mail is handled by the e-mail backend.
 132.296  
 132.297 -    connection = SMTPConnection()   # Use default settings for connection
 132.298 +The e-mail backend class has the following methods:
 132.299 +
 132.300 +    * ``open()`` instantiates an long-lived email-sending connection.
 132.301 +
 132.302 +    * ``close()`` closes the current email-sending connection.
 132.303 +
 132.304 +    * ``send_messages(email_messages)`` sends a list of
 132.305 +      :class:`~django.core.mail.EmailMessage` objects. If the connection is
 132.306 +      not open, this call will implicitly open the connection, and close the
 132.307 +      connection afterwards. If the connection is already open, it will be
 132.308 +      left open after mail has been sent.
 132.309 +
 132.310 +Obtaining an instance of an e-mail backend
 132.311 +------------------------------------------
 132.312 +
 132.313 +The :meth:`get_connection` function in ``django.core.mail`` returns an
 132.314 +instance of the e-mail backend that you can use.
 132.315 +
 132.316 +.. currentmodule:: django.core.mail
 132.317 +
 132.318 +.. function:: get_connection(backend=None, fail_silently=False, *args, **kwargs)
 132.319 +
 132.320 +By default, a call to ``get_connection()`` will return an instance of the
 132.321 +email backend specified in :setting:`EMAIL_BACKEND`. If you specify the
 132.322 +``backend`` argument, an instance of that backend will be instantiated.
 132.323 +
 132.324 +The ``fail_silently`` argument controls how the backend should handle errors.
 132.325 +If ``fail_silently`` is True, exceptions during the email sending process
 132.326 +will be silently ignored.
 132.327 +
 132.328 +All other arguments are passed directly to the constructor of the
 132.329 +e-mail backend.
 132.330 +
 132.331 +Django ships with several e-mail sending backends. With the exception of the
 132.332 +SMTP backend (which is the default), these backends are only useful during
 132.333 +testing and development. If you have special email sending requirements, you
 132.334 +can :ref:`write your own email backend <topic-custom-email-backend>`.
 132.335 +
 132.336 +.. _topic-email-smtp-backend:
 132.337 +
 132.338 +SMTP backend
 132.339 +~~~~~~~~~~~~
 132.340 +
 132.341 +This is the default backend. E-mail will be sent through a SMTP server.
 132.342 +The server address and authentication credentials are set in the
 132.343 +:setting:`EMAIL_HOST`, :setting:`EMAIL_POST`, :setting:`EMAIL_HOST_USER`,
 132.344 +:setting:`EMAIL_HOST_PASSWORD` and :setting:`EMAIL_USE_TLS` settings in your
 132.345 +settings file.
 132.346 +
 132.347 +The SMTP backend is the default configuration inherited by Django. If you
 132.348 +want to specify it explicitly, put the following in your settings::
 132.349 +
 132.350 +    EMAIL_BACKEND = 'django.core.mail.backends.smtp'
 132.351 +
 132.352 +.. admonition:: SMTPConnection objects
 132.353 +
 132.354 +    Prior to version 1.2, Django provided a
 132.355 +    :class:`~django.core.mail.SMTPConnection` class. This class provided a way
 132.356 +    to directly control the use of SMTP to send email. This class has been
 132.357 +    deprecated in favor of the generic email backend API.
 132.358 +
 132.359 +    For backwards compatibility :class:`~django.core.mail.SMTPConnection` is
 132.360 +    still available in ``django.core.mail`` as an alias for the SMTP backend.
 132.361 +    New code should use :meth:`~django.core.mail.get_connection` instead.
 132.362 +
 132.363 +Console backend
 132.364 +~~~~~~~~~~~~~~~
 132.365 +
 132.366 +Instead of sending out real e-mails the console backend just writes the
 132.367 +e-mails that would be send to the standard output. By default, the console
 132.368 +backend writes to ``stdout``. You can use a different stream-like object by
 132.369 +providing the ``stream`` keyword argument when constructing the connection.
 132.370 +
 132.371 +To specify this backend, put the following in your settings::
 132.372 +
 132.373 +    EMAIL_BACKEND = 'django.core.mail.backends.console'
 132.374 +
 132.375 +This backend is not intended for use in production -- it is provided as a
 132.376 +convenience that can be used during development.
 132.377 +
 132.378 +File backend
 132.379 +~~~~~~~~~~~~
 132.380 +
 132.381 +The file backend writes e-mails to a file. A new file is created for each new
 132.382 +session that is opened on this backend. The directory to which the files are
 132.383 +written is either taken from the :setting:`EMAIL_FILE_PATH` setting or from
 132.384 +the ``file_path`` keyword when creating a connection with
 132.385 +:meth:`~django.core.mail.get_connection`.
 132.386 +
 132.387 +To specify this backend, put the following in your settings::
 132.388 +
 132.389 +    EMAIL_BACKEND = 'django.core.mail.backends.filebased'
 132.390 +    EMAIL_FILE_PATH = '/tmp/app-messages' # change this to a proper location
 132.391 +
 132.392 +This backend is not intended for use in production -- it is provided as a
 132.393 +convenience that can be used during development.
 132.394 +
 132.395 +In-memory backend
 132.396 +~~~~~~~~~~~~~~~~~
 132.397 +
 132.398 +The ``'locmem'`` backend stores messages in a special attribute of the
 132.399 +``django.core.mail`` module. The ``outbox`` attribute is created when the
 132.400 +first message is send. It's a list with an
 132.401 +:class:`~django.core.mail.EmailMessage` instance for each message that would
 132.402 +be send.
 132.403 +
 132.404 +To specify this backend, put the following in your settings::
 132.405 +
 132.406 +  EMAIL_BACKEND = 'django.core.mail.backends.locmem'
 132.407 +
 132.408 +This backend is not intended for use in production -- it is provided as a
 132.409 +convenience that can be used during development and testing.
 132.410 +
 132.411 +Dummy backend
 132.412 +~~~~~~~~~~~~~
 132.413 +
 132.414 +As the name suggests the dummy backend does nothing with your messages. To
 132.415 +specify this backend, put the following in your settings::
 132.416 +
 132.417 +   EMAIL_BACKEND = 'django.core.mail.backends.dummy'
 132.418 +
 132.419 +This backend is not intended for use in production -- it is provided as a
 132.420 +convenience that can be used during development.
 132.421 +
 132.422 +.. _topic-custom-email-backend:
 132.423 +
 132.424 +Defining a custom e-mail backend
 132.425 +--------------------------------
 132.426 +
 132.427 +If you need to change how e-mails are send you can write your own e-mail
 132.428 +backend. The ``EMAIL_BACKEND`` setting in your settings file is then the
 132.429 +Python import path for your backend.
 132.430 +
 132.431 +Custom e-mail backends should subclass ``BaseEmailBackend`` that is located in
 132.432 +the ``django.core.mail.backends.base`` module. A custom e-mail backend must
 132.433 +implement the ``send_messages(email_messages)`` method. This method receives a
 132.434 +list of :class:`~django.core.mail.EmailMessage` instances and returns the
 132.435 +number of successfully delivered messages. If your backend has any concept of
 132.436 +a persistent session or connection, you should also implement the ``open()``
 132.437 +and ``close()`` methods. Refer to ``SMTPEmailBackend`` for a reference
 132.438 +implementation.
 132.439 +
 132.440 +.. _topics-sending-multiple-emails:
 132.441 +
 132.442 +Sending multiple emails
 132.443 +-----------------------
 132.444 +
 132.445 +Establishing and closing an SMTP connection (or any other network connection,
 132.446 +for that matter) is an expensive process. If you have a lot of emails to send,
 132.447 +it makes sense to reuse an SMTP connection, rather than creating and
 132.448 +destroying a connection every time you want to send an email.
 132.449 +
 132.450 +There are two ways you tell an email backend to reuse a connection.
 132.451 +
 132.452 +Firstly, you can use the ``send_messages()`` method. ``send_messages()`` takes
 132.453 +a list of :class:`~django.core.mail.EmailMessage` instances (or subclasses),
 132.454 +and sends them all using a single connection.
 132.455 +
 132.456 +For example, if you have a function called ``get_notification_email()`` that
 132.457 +returns a list of :class:`~django.core.mail.EmailMessage` objects representing
 132.458 +some periodic e-mail you wish to send out, you could send these emails using
 132.459 +a single call to send_messages::
 132.460 +
 132.461 +    from django.core import mail
 132.462 +    connection = mail.get_connection()   # Use default email connection
 132.463      messages = get_notification_email()
 132.464      connection.send_messages(messages)
 132.465  
 132.466 +In this example, the call to ``send_messages()`` opens a connection on the
 132.467 +backend, sends the list of messages, and then closes the connection again.
 132.468 +
 132.469 +The second approach is to use the ``open()`` and ``close()`` methods on the
 132.470 +email backend to manually control the connection. ``send_messages()`` will not
 132.471 +manually open or close the connection if it is already open, so if you
 132.472 +manually open the connection, you can control when it is closed. For example::
 132.473 +
 132.474 +    from django.core import mail
 132.475 +    connection = mail.get_connection()
 132.476 +
 132.477 +    # Manually open the connection
 132.478 +    connection.open()
 132.479 +
 132.480 +    # Construct an email message that uses the connection
 132.481 +    email1 = mail.EmailMessage('Hello', 'Body goes here', 'from@example.com',
 132.482 +                              ['to1@example.com'], connection=connection)
 132.483 +    email1.send() # Send the email
 132.484 +
 132.485 +    # Construct two more messages
 132.486 +    email2 = mail.EmailMessage('Hello', 'Body goes here', 'from@example.com',
 132.487 +                              ['to2@example.com'])
 132.488 +    email3 = mail.EmailMessage('Hello', 'Body goes here', 'from@example.com',
 132.489 +                              ['to3@example.com'])
 132.490 +
 132.491 +    # Send the two emails in a single call -
 132.492 +    connection.send_messages([email2, email3])
 132.493 +    # The connection was already open so send_messages() doesn't close it.
 132.494 +    # We need to manually close the connection.
 132.495 +    connection.close()
 132.496 +
 132.497 +
 132.498  Testing e-mail sending
 132.499 -----------------------
 132.500 +======================
 132.501  
 132.502  The are times when you do not want Django to send e-mails at all. For example,
 132.503  while developing a website, you probably don't want to send out thousands of
 132.504 @@ -360,19 +565,41 @@
 132.505  people under the right conditions, and that those e-mails will contain the
 132.506  correct content.
 132.507  
 132.508 -The easiest way to test your project's use of e-mail is to use a "dumb" e-mail
 132.509 -server that receives the e-mails locally and displays them to the terminal,
 132.510 -but does not actually send anything. Python has a built-in way to accomplish
 132.511 -this with a single command::
 132.512 +The easiest way to test your project's use of e-mail is to use the ``console``
 132.513 +email backend. This backend redirects all email to stdout, allowing you to
 132.514 +inspect the content of mail.
 132.515 +
 132.516 +The ``file`` email backend can also be useful during development -- this backend
 132.517 +dumps the contents of every SMTP connection to a file that can be inspected
 132.518 +at your leisure.
 132.519 +
 132.520 +Another approach is to use a "dumb" SMTP server that receives the e-mails
 132.521 +locally and displays them to the terminal, but does not actually send
 132.522 +anything. Python has a built-in way to accomplish this with a single command::
 132.523  
 132.524      python -m smtpd -n -c DebuggingServer localhost:1025
 132.525  
 132.526  This command will start a simple SMTP server listening on port 1025 of
 132.527 -localhost. This server simply prints to standard output all email headers and
 132.528 -the email body. You then only need to set the :setting:`EMAIL_HOST` and
 132.529 +localhost. This server simply prints to standard output all e-mail headers and
 132.530 +the e-mail body. You then only need to set the :setting:`EMAIL_HOST` and
 132.531  :setting:`EMAIL_PORT` accordingly, and you are set.
 132.532  
 132.533 -For more entailed testing and processing of e-mails locally, see the Python
 132.534 -documentation on the `SMTP Server`_.
 132.535 +For a more detailed discussion of testing and processing of e-mails locally,
 132.536 +see the Python documentation on the `SMTP Server`_.
 132.537  
 132.538  .. _SMTP Server: http://docs.python.org/library/smtpd.html
 132.539 +
 132.540 +SMTPConnection
 132.541 +==============
 132.542 +
 132.543 +.. class:: SMTPConnection
 132.544 +
 132.545 +.. deprecated:: 1.2
 132.546 +
 132.547 +The ``SMTPConnection`` class has been deprecated in favor of the generic email
 132.548 +backend API.
 132.549 +
 132.550 +For backwards compatibility ``SMTPConnection`` is still available in
 132.551 +``django.core.mail`` as an alias for the :ref:`SMTP backend
 132.552 +<topic-email-smtp-backend>`. New code should use
 132.553 +:meth:`~django.core.mail.get_connection` instead.
   133.1 --- a/docs/topics/forms/modelforms.txt	Sat Sep 12 18:53:56 2009 -0500
   133.2 +++ b/docs/topics/forms/modelforms.txt	Fri Nov 13 11:27:59 2009 -0600
   133.3 @@ -371,6 +371,35 @@
   133.4     ...     class Meta:
   133.5     ...         model = Article
   133.6  
   133.7 +.. note::
   133.8 +
   133.9 +    If you explicitly instantiate a form field like this, Django assumes that you
  133.10 +    want to completely define its behavior; therefore, default attributes (such as
  133.11 +    ``max_length`` or ``required``) are not drawn from the corresponding model. If
  133.12 +    you want to maintain the behavior specified in the model, you must set the
  133.13 +    relevant arguments explicitly when declaring the form field.
  133.14 +
  133.15 +    For example, if the ``Article`` model looks like this::
  133.16 +
  133.17 +        class Article(models.Model):
  133.18 +            headline = models.CharField(max_length=200, null=True, blank=True,
  133.19 +                                        help_text="Use puns liberally")
  133.20 +            content = models.TextField()
  133.21 +
  133.22 +    and you want to do some custom validation for ``headline``, while keeping
  133.23 +    the ``blank`` and ``help_text`` values as specified, you might define
  133.24 +    ``ArticleForm`` like this::
  133.25 +
  133.26 +        class ArticleForm(ModelForm):
  133.27 +            headline = MyFormField(max_length=200, required=False,
  133.28 +                                   help_text="Use puns liberally")
  133.29 +
  133.30 +            class Meta:
  133.31 +                model = Article
  133.32 +
  133.33 +    See the :ref:`form field documentation <ref-forms-fields>` for more information
  133.34 +    on fields and their arguments.
  133.35 +
  133.36  Changing the order of fields
  133.37  ----------------------------
  133.38  
  133.39 @@ -512,6 +541,12 @@
  133.40  
  133.41      >>> AuthorFormSet = modelformset_factory(Author, formset=BaseAuthorFormSet)
  133.42  
  133.43 +If you want to return a formset that doesn't include *any* pre-existing
  133.44 +instances of the model, you can specify an empty QuerySet::
  133.45 +
  133.46 +   >>> AuthorFormSet(queryset=Author.objects.none())
  133.47 +
  133.48 +
  133.49  Controlling which fields are used with ``fields`` and ``exclude``
  133.50  -----------------------------------------------------------------
  133.51  
   134.1 --- a/docs/topics/generic-views.txt	Sat Sep 12 18:53:56 2009 -0500
   134.2 +++ b/docs/topics/generic-views.txt	Fri Nov 13 11:27:59 2009 -0600
   134.3 @@ -64,7 +64,7 @@
   134.4  the extra-parameters dictionary and uses that information when rendering the
   134.5  view.
   134.6  
   134.7 -Because this generic view -- and all the others -- is a regular view functions
   134.8 +Because this generic view -- and all the others -- is a regular view function
   134.9  like any other, we can reuse it inside our own views. As an example, let's
  134.10  extend our "about" example to map URLs of the form ``/about/<whatever>/`` to
  134.11  statically rendered ``about/<whatever>.html``. We'll do this by first modifying
   135.1 --- a/docs/topics/http/middleware.txt	Sat Sep 12 18:53:56 2009 -0500
   135.2 +++ b/docs/topics/http/middleware.txt	Fri Nov 13 11:27:59 2009 -0600
   135.3 @@ -29,6 +29,7 @@
   135.4      MIDDLEWARE_CLASSES = (
   135.5          'django.middleware.common.CommonMiddleware',
   135.6          'django.contrib.sessions.middleware.SessionMiddleware',
   135.7 +        'django.middleware.csrf.CsrfViewMiddleware',
   135.8          'django.contrib.auth.middleware.AuthenticationMiddleware',
   135.9      )
  135.10  
   136.1 --- a/docs/topics/http/sessions.txt	Sat Sep 12 18:53:56 2009 -0500
   136.2 +++ b/docs/topics/http/sessions.txt	Fri Nov 13 11:27:59 2009 -0600
   136.3 @@ -453,6 +453,20 @@
   136.4  
   136.5  The name of the cookie to use for sessions. This can be whatever you want.
   136.6  
   136.7 +SESSION_COOKIE_PATH
   136.8 +-------------------
   136.9 +
  136.10 +.. versionadded:: 1.0
  136.11 +
  136.12 +Default: ``'/'``
  136.13 +
  136.14 +The path set on the session cookie. This should either match the URL path of
  136.15 +your Django installation or be parent of that path.
  136.16 +
  136.17 +This is useful if you have multiple Django instances running under the same
  136.18 +hostname. They can use different cookie paths, and each instance will only see
  136.19 +its own session cookie.
  136.20 +
  136.21  SESSION_COOKIE_SECURE
  136.22  ---------------------
  136.23  
   137.1 --- a/docs/topics/install.txt	Sat Sep 12 18:53:56 2009 -0500
   137.2 +++ b/docs/topics/install.txt	Fri Nov 13 11:27:59 2009 -0600
   137.3 @@ -11,7 +11,7 @@
   137.4  
   137.5  Being a Python Web framework, Django requires Python.
   137.6  
   137.7 -It works with any Python version from 2.3 to 2.6 (due to backwards
   137.8 +It works with any Python version from 2.4 to 2.6 (due to backwards
   137.9  incompatibilities in Python 3.0, Django does not currently work with
  137.10  Python 3.0; see :ref:`the Django FAQ <faq-install>` for more
  137.11  information on supported Python versions and the 3.0 transition).
  137.12 @@ -93,10 +93,10 @@
  137.13    will also want to read the database-specific notes for the :ref:`MySQL
  137.14    backend <ref-databases>`.
  137.15  
  137.16 -* If you're using SQLite and either Python 2.3 or Python 2.4, you'll need
  137.17 -  pysqlite_. Use version 2.0.3 or higher. Python 2.5 ships with an SQLite
  137.18 -  wrapper in the standard library, so you don't need to install anything extra
  137.19 -  in that case. Please read the SQLite backend :ref:`notes<sqlite-notes>`.
  137.20 +* If you're using SQLite and Python 2.4, you'll need pysqlite_. Use version
  137.21 +  2.0.3 or higher. Python 2.5 ships with an SQLite wrapper in the standard
  137.22 +  library, so you don't need to install anything extra in that case. Please
  137.23 +  read the SQLite backend :ref:`notes<sqlite-notes>`.
  137.24  
  137.25  * If you're using Oracle, you'll need a copy of cx_Oracle_, but please
  137.26    read the database-specific notes for the
   138.1 --- a/docs/topics/testing.txt	Sat Sep 12 18:53:56 2009 -0500
   138.2 +++ b/docs/topics/testing.txt	Fri Nov 13 11:27:59 2009 -0600
   138.3 @@ -515,7 +515,7 @@
   138.4          >>> c = Client()
   138.5          >>> c.get('/customers/details/?name=fred&age=7')
   138.6  
   138.7 -        If you provide URL both an encoded GET data and a data argument,
   138.8 +        If you provide a URL with both an encoded GET data and a data argument,
   138.9          the data argument will take precedence.
  138.10  
  138.11          If you set ``follow`` to ``True`` the client will follow any redirects
  138.12 @@ -627,7 +627,7 @@
  138.13  
  138.14          .. versionadded:: 1.1
  138.15  
  138.16 -        Makes an PUT request on the provided ``path`` and returns a
  138.17 +        Makes a PUT request on the provided ``path`` and returns a
  138.18          ``Response`` object. Useful for testing RESTful interfaces. Acts just
  138.19          like :meth:`Client.post` except with the PUT request method.
  138.20  
  138.21 @@ -1104,6 +1104,8 @@
  138.22      ``target_status_code`` will be the url and status code for the final
  138.23      point of the redirect chain.
  138.24  
  138.25 +.. _topics-testing-email:
  138.26 +
  138.27  E-mail services
  138.28  ---------------
  138.29  
  138.30 @@ -1117,7 +1119,7 @@
  138.31  contents of each message -- without actually sending the messages.
  138.32  
  138.33  The test runner accomplishes this by transparently replacing the normal
  138.34 -:class:`~django.core.mail.SMTPConnection` class with a different version.
  138.35 +email backend with a testing backend.
  138.36  (Don't worry -- this has no effect on any other e-mail senders outside of
  138.37  Django, such as your machine's mail server, if you're running one.)
  138.38  
  138.39 @@ -1127,15 +1129,9 @@
  138.40  
  138.41  During test running, each outgoing e-mail is saved in
  138.42  ``django.core.mail.outbox``. This is a simple list of all
  138.43 -:class:`<~django.core.mail.EmailMessage>` instances that have been sent.
  138.44 -It does not exist under normal execution conditions, i.e., when you're not
  138.45 -running unit tests. The outbox is created during test setup, along with the
  138.46 -dummy :class:`<~django.core.mail.SMTPConnection>`. When the test framework is
  138.47 -torn down, the standard :class:`<~django.core.mail.SMTPConnection>` class is
  138.48 -restored, and the test outbox is destroyed.
  138.49 -
  138.50 +:class:`~django.core.mail.EmailMessage` instances that have been sent.
  138.51  The ``outbox`` attribute is a special attribute that is created *only* when
  138.52 -the tests are run. It doesn't normally exist as part of the
  138.53 +the ``locmem`` e-mail backend is used. It doesn't normally exist as part of the
  138.54  :mod:`django.core.mail` module and you can't import it directly. The code
  138.55  below shows how to access this attribute correctly.
  138.56  
   139.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   139.2 +++ b/extras/csrf_migration_helper.py	Fri Nov 13 11:27:59 2009 -0600
   139.3 @@ -0,0 +1,369 @@
   139.4 +#!/usr/bin/env python
   139.5 +
   139.6 +# This script aims to help developers locate forms and view code that needs to
   139.7 +# use the new CSRF protection in Django 1.2.  It tries to find all the code that
   139.8 +# may need the steps described in the CSRF documentation.  It does not modify
   139.9 +# any code directly, it merely attempts to locate it.  Developers should be
  139.10 +# aware of its limitations, described below.
  139.11 +#
  139.12 +# For each template that contains at least one POST form, the following info is printed:
  139.13 +#
  139.14 +# <Absolute path to template>
  139.15 +#   AKA: <Aliases (relative to template directory/directories that contain it)>
  139.16 +#   POST forms: <Number of POST forms>
  139.17 +#   With token: <Number of POST forms with the CSRF token already added>
  139.18 +#   Without token:
  139.19 +#     <File name and line number of form without token>
  139.20 +#
  139.21 +#   Searching for:
  139.22 +#     <Template names that need to be searched for in view code
  139.23 +#      (includes templates that 'include' current template)>
  139.24 +#
  139.25 +#   Found:
  139.26 +#     <File name and line number of any view code found>
  139.27 +#
  139.28 +# The format used allows this script to be used in Emacs grep mode:
  139.29 +#   M-x grep
  139.30 +#   Run grep (like this): /path/to/my/virtualenv/python /path/to/django/src/extras/csrf_migration_helper.py --settings=mysettings /path/to/my/srcs
  139.31 +
  139.32 +
  139.33 +# Limitations
  139.34 +# ===========
  139.35 +#
  139.36 +# - All templates must be stored on disk in '.html' or '.htm' files.
  139.37 +#   (extensions configurable below)
  139.38 +#
  139.39 +# - All Python code must be stored on disk in '.py' files.  (extensions
  139.40 +#   configurable below)
  139.41 +#
  139.42 +# - All templates must be accessible from TEMPLATE_DIRS or from the 'templates/'
  139.43 +#   directory in apps specified in INSTALLED_APPS.  Non-file based template
  139.44 +#   loaders are out of the picture, because there is no way to ask them to
  139.45 +#   return all templates.
  139.46 +#
  139.47 +# - If you put the {% csrf_token %} tag on the same line as the <form> tag it
  139.48 +#   will be detected, otherwise it will be assumed that the form does not have
  139.49 +#   the token.
  139.50 +#
  139.51 +# - It's impossible to programmatically determine which forms should and should
  139.52 +#   not have the token added.  The developer must decide when to do this,
  139.53 +#   ensuring that the token is only added to internally targetted forms.
  139.54 +#
  139.55 +# - It's impossible to programmatically work out when a template is used.  The
  139.56 +#   attempts to trace back to view functions are guesses, and could easily fail
  139.57 +#   in the following ways:
  139.58 +#
  139.59 +#   * If the 'include' template tag is used with a variable
  139.60 +#     i.e. {% include tname %} where tname is a variable containing the actual
  139.61 +#     template name, rather than {% include "my_template.html" %}.
  139.62 +#
  139.63 +#   * If the template name has been built up by view code instead of as a simple
  139.64 +#     string.  For example, generic views and the admin both do this.  (These
  139.65 +#     apps are both contrib and both use RequestContext already, as it happens).
  139.66 +#
  139.67 +#   * If the 'ssl' tag (or any template tag other than 'include') is used to
  139.68 +#     include the template in another template.
  139.69 +#
  139.70 +# - All templates belonging to apps referenced in INSTALLED_APPS will be
  139.71 +#   searched, which may include third party apps or Django contrib.  In some
  139.72 +#   cases, this will be a good thing, because even if the templates of these
  139.73 +#   apps have been fixed by someone else, your own view code may reference the
  139.74 +#   same template and may need to be updated.
  139.75 +#
  139.76 +#   You may, however, wish to comment out some entries in INSTALLED_APPS or
  139.77 +#   TEMPLATE_DIRS before running this script.
  139.78 +
  139.79 +# Improvements to this script are welcome!
  139.80 +
  139.81 +# Configuration
  139.82 +# =============
  139.83 +
  139.84 +TEMPLATE_EXTENSIONS = [
  139.85 +    ".html",
  139.86 +    ".htm",
  139.87 +    ]
  139.88 +
  139.89 +PYTHON_SOURCE_EXTENSIONS = [
  139.90 +    ".py",
  139.91 +    ]
  139.92 +
  139.93 +TEMPLATE_ENCODING = "UTF-8"
  139.94 +
  139.95 +PYTHON_ENCODING = "UTF-8"
  139.96 +
  139.97 +# Method
  139.98 +# ======
  139.99 +
 139.100 +# Find templates:
 139.101 +#  - template dirs
 139.102 +#  - installed apps
 139.103 +#
 139.104 +# Search for POST forms
 139.105 +#  - Work out what the name of the template is, as it would appear in an
 139.106 +#    'include' or get_template() call. This can be done by comparing template
 139.107 +#    filename to all template dirs.  Some templates can have more than one
 139.108 +#    'name' e.g.  if a directory and one of its child directories are both in
 139.109 +#    TEMPLATE_DIRS.  This is actually a common hack used for
 139.110 +#    overriding-and-extending admin templates.
 139.111 +#
 139.112 +# For each POST form,
 139.113 +# - see if it already contains '{% csrf_token %}' immediately after <form>
 139.114 +# - work back to the view function(s):
 139.115 +#   - First, see if the form is included in any other templates, then
 139.116 +#     recursively compile a list of affected templates.
 139.117 +#   - Find any code function that references that template.  This is just a
 139.118 +#     brute force text search that can easily return false positives
 139.119 +#     and fail to find real instances.
 139.120 +
 139.121 +
 139.122 +import os
 139.123 +import sys
 139.124 +import re
 139.125 +try:
 139.126 +    set
 139.127 +except NameError:
 139.128 +    from sets import Set as set
 139.129 +
 139.130 +
 139.131 +USAGE = """
 139.132 +This tool helps to locate forms that need CSRF tokens added and the
 139.133 +corresponding view code.  This processing is NOT fool proof, and you should read
 139.134 +the help contained in the script itself.  Also, this script may need configuring
 139.135 +(by editing the script) before use.
 139.136 +
 139.137 +Usage:
 139.138 +
 139.139 +python csrf_migration_helper.py [--settings=path.to.your.settings] /path/to/python/code [more paths...]
 139.140 +
 139.141 +  Paths can be specified as relative paths.
 139.142 +
 139.143 +  With no arguments, this help is printed.
 139.144 +"""
 139.145 +
 139.146 +_POST_FORM_RE = \
 139.147 +    re.compile(r'(<form\W[^>]*\bmethod\s*=\s*(\'|"|)POST(\'|"|)\b[^>]*>)', re.IGNORECASE)
 139.148 +_TOKEN_RE = re.compile('\{% csrf_token')
 139.149 +
 139.150 +def get_template_dirs():
 139.151 +    """
 139.152 +    Returns a set of all directories that contain project templates.
 139.153 +    """
 139.154 +    from django.conf import settings
 139.155 +    dirs = set()
 139.156 +    if 'django.template.loaders.filesystem.load_template_source' in settings.TEMPLATE_LOADERS:
 139.157 +        dirs.update(map(unicode, settings.TEMPLATE_DIRS))
 139.158 +
 139.159 +    if 'django.template.loaders.app_directories.load_template_source' in settings.TEMPLATE_LOADERS:
 139.160 +        from django.template.loaders.app_directories import app_template_dirs
 139.161 +        dirs.update(app_template_dirs)
 139.162 +    return dirs
 139.163 +
 139.164 +def make_template_info(filename, root_dirs):
 139.165 +    """
 139.166 +    Creates a Template object for a filename, calculating the possible
 139.167 +    relative_filenames from the supplied filename and root template directories
 139.168 +    """
 139.169 +    return Template(filename,
 139.170 +                    [filename[len(d)+1:] for d in root_dirs if filename.startswith(d)])
 139.171 +
 139.172 +
 139.173 +class Template(object):
 139.174 +    def __init__(self, absolute_filename, relative_filenames):
 139.175 +        self.absolute_filename, self.relative_filenames = absolute_filename, relative_filenames
 139.176 +
 139.177 +    def content(self):
 139.178 +        try:
 139.179 +            return self._content
 139.180 +        except AttributeError:
 139.181 +            fd = open(self.absolute_filename)
 139.182 +            content = fd.read().decode(TEMPLATE_ENCODING)
 139.183 +            fd.close()
 139.184 +            self._content = content
 139.185 +            return content
 139.186 +    content = property(content)
 139.187 +
 139.188 +    def post_form_info(self):
 139.189 +        """
 139.190 +        Get information about any POST forms in the template.
 139.191 +        Returns [(linenumber, csrf_token added)]
 139.192 +        """
 139.193 +        matches = []
 139.194 +        for ln, line in enumerate(self.content.split("\n")):
 139.195 +            m = _POST_FORM_RE.search(line)
 139.196 +            if m is not None:
 139.197 +                matches.append((ln + 1, _TOKEN_RE.search(line) is not None))
 139.198 +        return matches
 139.199 +
 139.200 +    def includes_template(self, t):
 139.201 +        """
 139.202 +        Returns true if this template includes template 't' (via {% include %})
 139.203 +        """
 139.204 +        for r in t.relative_filenames:
 139.205 +            if re.search(r'\{%\s*include\s+"' + re.escape(r) + r'"\s*%\}', self.content):
 139.206 +                return True
 139.207 +        return False
 139.208 +
 139.209 +    def related_templates(self):
 139.210 +        """
 139.211 +        Returns all templates that include this one, recursively.  (starting
 139.212 +        with this one)
 139.213 +        """
 139.214 +        try:
 139.215 +            return self._related_templates
 139.216 +        except AttributeError:
 139.217 +            pass
 139.218 +
 139.219 +        retval = set([self])
 139.220 +        for r in self.relative_filenames:
 139.221 +            for t in self.all_templates:
 139.222 +                if t.includes_template(self):
 139.223 +                    # If two templates mutually include each other, directly or
 139.224 +                    # indirectly, we have a problem here...
 139.225 +                    retval = retval.union(t.related_templates())
 139.226 +
 139.227 +        self._related_templates = retval
 139.228 +        return retval
 139.229 +
 139.230 +    def __repr__(self):
 139.231 +        return repr(self.absolute_filename)
 139.232 +
 139.233 +    def __eq__(self, other):
 139.234 +        return self.absolute_filename == other.absolute_filename
 139.235 +
 139.236 +    def __hash__(self):
 139.237 +        return hash(self.absolute_filename)
 139.238 +
 139.239 +def get_templates(dirs):
 139.240 +    """
 139.241 +    Returns all files in dirs that have template extensions, as Template
 139.242 +    objects.
 139.243 +    """
 139.244 +    templates = set()
 139.245 +    for root in dirs:
 139.246 +        for (dirpath, dirnames, filenames) in os.walk(root):
 139.247 +            for f in filenames:
 139.248 +                if len([True for e in TEMPLATE_EXTENSIONS if f.endswith(e)]) > 0:
 139.249 +                    t = make_template_info(os.path.join(dirpath, f), dirs)
 139.250 +                    # templates need to be able to search others:
 139.251 +                    t.all_templates = templates
 139.252 +                    templates.add(t)
 139.253 +    return templates
 139.254 +
 139.255 +def get_python_code(paths):
 139.256 +    """
 139.257 +    Returns all Python code, as a list of tuples, each one being:
 139.258 +     (filename, list of lines)
 139.259 +    """
 139.260 +    retval = []
 139.261 +    for p in paths:
 139.262 +        for (dirpath, dirnames, filenames) in os.walk(p):
 139.263 +            for f in filenames:
 139.264 +                if len([True for e in PYTHON_SOURCE_EXTENSIONS if f.endswith(e)]) > 0:
 139.265 +                    fn = os.path.join(dirpath, f)
 139.266 +                    fd = open(fn)
 139.267 +                    content = [l.decode(PYTHON_ENCODING) for l in fd.readlines()]
 139.268 +                    fd.close()
 139.269 +                    retval.append((fn, content))
 139.270 +    return retval
 139.271 +
 139.272 +def search_python_list(python_code, template_names):
 139.273 +    """
 139.274 +    Searches python code for a list of template names.
 139.275 +    Returns a list of tuples, each one being:
 139.276 +     (filename, line number)
 139.277 +    """
 139.278 +    retval = []
 139.279 +    for tn in template_names:
 139.280 +        retval.extend(search_python(python_code, tn))
 139.281 +    retval = list(set(retval))
 139.282 +    retval.sort()
 139.283 +    return retval
 139.284 +
 139.285 +def search_python(python_code, template_name):
 139.286 +    """
 139.287 +    Searches Python code for a template name.
 139.288 +    Returns a list of tuples, each one being:
 139.289 +     (filename, line number)
 139.290 +    """
 139.291 +    retval = []
 139.292 +    for fn, content in python_code:
 139.293 +        for ln, line in enumerate(content):
 139.294 +            if ((u'"%s"' % template_name) in line) or \
 139.295 +               ((u"'%s'" % template_name) in line):
 139.296 +                retval.append((fn, ln + 1))
 139.297 +    return retval
 139.298 +
 139.299 +def main(pythonpaths):
 139.300 +    template_dirs = get_template_dirs()
 139.301 +    templates = get_templates(template_dirs)
 139.302 +    python_code = get_python_code(pythonpaths)
 139.303 +    for t in templates:
 139.304 +        # Logic
 139.305 +        form_matches = t.post_form_info()
 139.306 +        num_post_forms = len(form_matches)
 139.307 +        form_lines_without_token = [ln for (ln, has_token) in form_matches if not has_token]
 139.308 +        if num_post_forms == 0:
 139.309 +            continue
 139.310 +        to_search = [rf for rt in t.related_templates() for rf in rt.relative_filenames]
 139.311 +        found = search_python_list(python_code, to_search)
 139.312 +
 139.313 +        # Display:
 139.314 +        print t.absolute_filename
 139.315 +        for r in t.relative_filenames:
 139.316 +            print u"  AKA %s" % r
 139.317 +        print u"  POST forms: %s" % num_post_forms
 139.318 +        print u"  With token: %s" % (num_post_forms - len(form_lines_without_token))
 139.319 +        if form_lines_without_token:
 139.320 +            print u"  Without token:"
 139.321 +            for ln in form_lines_without_token:
 139.322 +                print "%s:%d:" % (t.absolute_filename, ln)
 139.323 +        print
 139.324 +        print u"  Searching for:"
 139.325 +        for r in to_search:
 139.326 +            print u"    " + r
 139.327 +        print
 139.328 +        print u"  Found:"
 139.329 +        if len(found) == 0:
 139.330 +            print "    Nothing"
 139.331 +        else:
 139.332 +            for fn, ln in found:
 139.333 +                print "%s:%d:" % (fn, ln)
 139.334 +
 139.335 +        print
 139.336 +        print "----"
 139.337 +
 139.338 +
 139.339 +if __name__ == '__main__':
 139.340 +    # Hacky argument parsing, one day I'll learn OptParse...
 139.341 +    args = list(sys.argv[1:])
 139.342 +    if len(args) > 0:
 139.343 +        if args[0] in ['--help', '-h', '-?', '--usage']:
 139.344 +            print USAGE
 139.345 +            sys.exit(0)
 139.346 +        else:
 139.347 +            if args[0].startswith('--settings='):
 139.348 +                module = args[0][len('--settings='):]
 139.349 +                os.environ["DJANGO_SETTINGS_MODULE"] = module
 139.350 +                args = args[1:]
 139.351 +
 139.352 +            if args[0].startswith('-'):
 139.353 +                print "Unknown option: %s" % args[0]
 139.354 +                print USAGE
 139.355 +                sys.exit(1)
 139.356 +
 139.357 +            pythonpaths = args
 139.358 +
 139.359 +            if os.environ.get("DJANGO_SETTINGS_MODULE", None) is None:
 139.360 +                print "You need to set DJANGO_SETTINGS_MODULE or use the '--settings' parameter"
 139.361 +                sys.exit(1)
 139.362 +            if len(pythonpaths) == 0:
 139.363 +                print "Unrecognised command: %s" % command
 139.364 +                print USAGE
 139.365 +                sys.exit(1)
 139.366 +
 139.367 +            main(pythonpaths)
 139.368 +
 139.369 +    else:
 139.370 +        # no args
 139.371 +        print USAGE
 139.372 +        sys.exit(0)
   140.1 --- a/tests/modeltests/expressions/models.py	Sat Sep 12 18:53:56 2009 -0500
   140.2 +++ b/tests/modeltests/expressions/models.py	Fri Nov 13 11:27:59 2009 -0600
   140.3 @@ -56,6 +56,16 @@
   140.4  >>> company_query
   140.5  [{'num_chairs': 2302, 'name': u'Example Inc.', 'num_employees': 2300}, {'num_chairs': 5, 'name': u'Foobar Ltd.', 'num_employees': 3}, {'num_chairs': 34, 'name': u'Test GmbH', 'num_employees': 32}]
   140.6  
   140.7 +# Law of order of operations is followed
   140.8 +>>> _ =company_query.update(num_chairs=F('num_employees') + 2 * F('num_employees'))
   140.9 +>>> company_query
  140.10 +[{'num_chairs': 6900, 'name': u'Example Inc.', 'num_employees': 2300}, {'num_chairs': 9, 'name': u'Foobar Ltd.', 'num_employees': 3}, {'num_chairs': 96, 'name': u'Test GmbH', 'num_employees': 32}]
  140.11 +
  140.12 +# Law of order of operations can be overridden by parentheses
  140.13 +>>> _ =company_query.update(num_chairs=((F('num_employees') + 2) * F('num_employees')))
  140.14 +>>> company_query
  140.15 +[{'num_chairs': 5294600, 'name': u'Example Inc.', 'num_employees': 2300}, {'num_chairs': 15, 'name': u'Foobar Ltd.', 'num_employees': 3}, {'num_chairs': 1088, 'name': u'Test GmbH', 'num_employees': 32}]
  140.16 +
  140.17  # The relation of a foreign key can become copied over to an other foreign key.
  140.18  >>> Company.objects.update(point_of_contact=F('ceo'))
  140.19  3
   141.1 --- a/tests/modeltests/invalid_models/models.py	Sat Sep 12 18:53:56 2009 -0500
   141.2 +++ b/tests/modeltests/invalid_models/models.py	Fri Nov 13 11:27:59 2009 -0600
   141.3 @@ -182,6 +182,7 @@
   141.4      """ Model to test for unique ManyToManyFields, which are invalid. """
   141.5      unique_people = models.ManyToManyField( Person, unique=True )
   141.6  
   141.7 +
   141.8  model_errors = """invalid_models.fielderrors: "charfield": CharFields require a "max_length" attribute.
   141.9  invalid_models.fielderrors: "decimalfield": DecimalFields require a "decimal_places" attribute.
  141.10  invalid_models.fielderrors: "decimalfield": DecimalFields require a "max_digits" attribute.
   142.1 --- a/tests/modeltests/lookup/models.py	Sat Sep 12 18:53:56 2009 -0500
   142.2 +++ b/tests/modeltests/lookup/models.py	Fri Nov 13 11:27:59 2009 -0600
   142.3 @@ -17,6 +17,10 @@
   142.4          return self.headline
   142.5  
   142.6  __test__ = {'API_TESTS': r"""
   142.7 +# We can use .exists() to check that there are none yet
   142.8 +>>> Article.objects.exists()
   142.9 +False
  142.10 +
  142.11  # Create a couple of Articles.
  142.12  >>> from datetime import datetime
  142.13  >>> a1 = Article(headline='Article 1', pub_date=datetime(2005, 7, 26))
  142.14 @@ -33,6 +37,10 @@
  142.15  >>> a6.save()
  142.16  >>> a7 = Article(headline='Article 7', pub_date=datetime(2005, 7, 27))
  142.17  >>> a7.save()
  142.18 +
  142.19 +# There should be some now!
  142.20 +>>> Article.objects.exists()
  142.21 +True
  142.22  """}
  142.23  
  142.24  if settings.DATABASE_ENGINE in ('postgresql', 'postgresql_pysycopg2'):
   143.1 --- a/tests/modeltests/m2m_through/models.py	Sat Sep 12 18:53:56 2009 -0500
   143.2 +++ b/tests/modeltests/m2m_through/models.py	Fri Nov 13 11:27:59 2009 -0600
   143.3 @@ -133,7 +133,7 @@
   143.4  >>> rock.members.create(name='Anne')
   143.5  Traceback (most recent call last):
   143.6  ...
   143.7 -AttributeError: Cannot use create() on a ManyToManyField which specifies an intermediary model. Use Membership's Manager instead.
   143.8 +AttributeError: Cannot use create() on a ManyToManyField which specifies an intermediary model. Use m2m_through.Membership's Manager instead.
   143.9  
  143.10  # Remove has similar complications, and is not provided either.
  143.11  >>> rock.members.remove(jim)
  143.12 @@ -160,7 +160,7 @@
  143.13  >>> rock.members = backup
  143.14  Traceback (most recent call last):
  143.15  ...
  143.16 -AttributeError: Cannot set values on a ManyToManyField which specifies an intermediary model.  Use Membership's Manager instead.
  143.17 +AttributeError: Cannot set values on a ManyToManyField which specifies an intermediary model.  Use m2m_through.Membership's Manager instead.
  143.18  
  143.19  # Let's re-save those instances that we've cleared.
  143.20  >>> m1.save()
  143.21 @@ -184,7 +184,7 @@
  143.22  >>> bob.group_set.create(name='Funk')
  143.23  Traceback (most recent call last):
  143.24  ...
  143.25 -AttributeError: Cannot use create() on a ManyToManyField which specifies an intermediary model. Use Membership's Manager instead.
  143.26 +AttributeError: Cannot use create() on a ManyToManyField which specifies an intermediary model. Use m2m_through.Membership's Manager instead.
  143.27  
  143.28  # Remove has similar complications, and is not provided either.
  143.29  >>> jim.group_set.remove(rock)
  143.30 @@ -209,7 +209,7 @@
  143.31  >>> jim.group_set = backup
  143.32  Traceback (most recent call last):
  143.33  ...
  143.34 -AttributeError: Cannot set values on a ManyToManyField which specifies an intermediary model.  Use Membership's Manager instead.
  143.35 +AttributeError: Cannot set values on a ManyToManyField which specifies an intermediary model.  Use m2m_through.Membership's Manager instead.
  143.36  
  143.37  # Let's re-save those instances that we've cleared.
  143.38  >>> m1.save()
  143.39 @@ -334,4 +334,4 @@
  143.40  # QuerySet's distinct() method can correct this problem.
  143.41  >>> Person.objects.filter(membership__date_joined__gt=datetime(2004, 1, 1)).distinct()
  143.42  [<Person: Jane>, <Person: Jim>]
  143.43 -"""}
  143.44 \ No newline at end of file
  143.45 +"""}
   144.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   144.2 +++ b/tests/modeltests/model_package/__init__.py	Fri Nov 13 11:27:59 2009 -0600
   144.3 @@ -0,0 +1,1 @@
   144.4 +
   145.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   145.2 +++ b/tests/modeltests/model_package/models/__init__.py	Fri Nov 13 11:27:59 2009 -0600
   145.3 @@ -0,0 +1,3 @@
   145.4 +# Import all the models from subpackages
   145.5 +from article import Article
   145.6 +from publication import Publication
   146.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   146.2 +++ b/tests/modeltests/model_package/models/article.py	Fri Nov 13 11:27:59 2009 -0600
   146.3 @@ -0,0 +1,10 @@
   146.4 +from django.db import models
   146.5 +from django.contrib.sites.models import Site
   146.6 +
   146.7 +class Article(models.Model):
   146.8 +    sites = models.ManyToManyField(Site)
   146.9 +    headline = models.CharField(max_length=100)
  146.10 +    publications = models.ManyToManyField("model_package.Publication", null=True, blank=True,)
  146.11 +
  146.12 +    class Meta:
  146.13 +        app_label = 'model_package'
   147.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   147.2 +++ b/tests/modeltests/model_package/models/publication.py	Fri Nov 13 11:27:59 2009 -0600
   147.3 @@ -0,0 +1,7 @@
   147.4 +from django.db import models
   147.5 +
   147.6 +class Publication(models.Model):
   147.7 +    title = models.CharField(max_length=30)
   147.8 +
   147.9 +    class Meta:
  147.10 +        app_label = 'model_package'
   148.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   148.2 +++ b/tests/modeltests/model_package/tests.py	Fri Nov 13 11:27:59 2009 -0600
   148.3 @@ -0,0 +1,34 @@
   148.4 +"""
   148.5 +>>> from models.publication import Publication
   148.6 +>>> from models.article import Article
   148.7 +>>> from django.contrib.auth.views import Site
   148.8 +
   148.9 +>>> p = Publication(title="FooBar")
  148.10 +>>> p.save()
  148.11 +>>> p
  148.12 +<Publication: Publication object>
  148.13 +
  148.14 +>>> from django.contrib.sites.models import Site
  148.15 +>>> current_site = Site.objects.get_current()
  148.16 +>>> current_site
  148.17 +<Site: example.com>
  148.18 +
  148.19 +# Regression for #12168: models split into subpackages still get M2M tables
  148.20 +
  148.21 +>>> a = Article(headline="a foo headline")
  148.22 +>>> a.save()
  148.23 +>>> a.publications.add(p)
  148.24 +>>> a.sites.add(current_site)
  148.25 +>>> a.save()
  148.26 +
  148.27 +>>> a = Article.objects.get(id=1)
  148.28 +>>> a
  148.29 +<Article: Article object>
  148.30 +>>> a.id
  148.31 +1
  148.32 +>>> a.sites.count()
  148.33 +1
  148.34 +
  148.35 +"""
  148.36 +
  148.37 +
   149.1 --- a/tests/regressiontests/admin_validation/models.py	Sat Sep 12 18:53:56 2009 -0500
   149.2 +++ b/tests/regressiontests/admin_validation/models.py	Fri Nov 13 11:27:59 2009 -0600
   149.3 @@ -4,9 +4,11 @@
   149.4  
   149.5  from django.db import models
   149.6  
   149.7 +
   149.8  class Album(models.Model):
   149.9      title = models.CharField(max_length=150)
  149.10  
  149.11 +
  149.12  class Song(models.Model):
  149.13      title = models.CharField(max_length=150)
  149.14      album = models.ForeignKey(Album)
  149.15 @@ -17,11 +19,19 @@
  149.16      def __unicode__(self):
  149.17          return self.title
  149.18  
  149.19 +
  149.20 +class TwoAlbumFKAndAnE(models.Model):
  149.21 +    album1 = models.ForeignKey(Album, related_name="album1_set")
  149.22 +    album2 = models.ForeignKey(Album, related_name="album2_set")
  149.23 +    e = models.CharField(max_length=1)
  149.24 +
  149.25 +
  149.26 +
  149.27  __test__ = {'API_TESTS':"""
  149.28  
  149.29  >>> from django import forms
  149.30  >>> from django.contrib import admin
  149.31 ->>> from django.contrib.admin.validation import validate
  149.32 +>>> from django.contrib.admin.validation import validate, validate_inline
  149.33  
  149.34  # Regression test for #8027: custom ModelForms with fields/fieldsets
  149.35  
  149.36 @@ -58,4 +68,31 @@
  149.37      ...
  149.38  ImproperlyConfigured: SongInline cannot exclude the field 'album' - this is the foreign key to the parent model Album.
  149.39  
  149.40 +# Regression test for #11709 - when testing for fk excluding (when exclude is
  149.41 +# given) make sure fk_name is honored or things blow up when there is more
  149.42 +# than one fk to the parent model.
  149.43 +
  149.44 +>>> class TwoAlbumFKAndAnEInline(admin.TabularInline):
  149.45 +...     model = TwoAlbumFKAndAnE
  149.46 +...     exclude = ("e",)
  149.47 +...     fk_name = "album1"
  149.48 +
  149.49 +>>> validate_inline(TwoAlbumFKAndAnEInline, None, Album)
  149.50 +
  149.51 +# Ensure inlines validate that they can be used correctly.
  149.52 +
  149.53 +>>> class TwoAlbumFKAndAnEInline(admin.TabularInline):
  149.54 +...     model = TwoAlbumFKAndAnE
  149.55 +
  149.56 +>>> validate_inline(TwoAlbumFKAndAnEInline, None, Album)
  149.57 +Traceback (most recent call last):
  149.58 +    ...
  149.59 +Exception: <class 'regressiontests.admin_validation.models.TwoAlbumFKAndAnE'> has more than 1 ForeignKey to <class 'regressiontests.admin_validation.models.Album'>
  149.60 +
  149.61 +>>> class TwoAlbumFKAndAnEInline(admin.TabularInline):
  149.62 +...     model = TwoAlbumFKAndAnE
  149.63 +...     fk_name = "album1"
  149.64 +
  149.65 +>>> validate_inline(TwoAlbumFKAndAnEInline, None, Album)
  149.66 +
  149.67  """}
   150.1 --- a/tests/regressiontests/admin_views/fixtures/admin-views-person.xml	Sat Sep 12 18:53:56 2009 -0500
   150.2 +++ b/tests/regressiontests/admin_views/fixtures/admin-views-person.xml	Fri Nov 13 11:27:59 2009 -0600
   150.3 @@ -6,7 +6,7 @@
   150.4          <field type="BooleanField" name="alive">True</field>
   150.5      </object>
   150.6      <object pk="2" model="admin_views.person">
   150.7 -        <field type="CharField" name="name">Grace Hooper</field>
   150.8 +        <field type="CharField" name="name">Grace Hopper</field>
   150.9          <field type="IntegerField" name="gender">1</field>
  150.10          <field type="BooleanField" name="alive">False</field>
  150.11      </object>
   151.1 --- a/tests/regressiontests/admin_views/tests.py	Sat Sep 12 18:53:56 2009 -0500
   151.2 +++ b/tests/regressiontests/admin_views/tests.py	Fri Nov 13 11:27:59 2009 -0600
   151.3 @@ -885,8 +885,9 @@
   151.4          # 4 action inputs (3 regular checkboxes, 1 checkbox to select all)
   151.5          # main form submit button = 1
   151.6          # search field and search submit button = 2
   151.7 -        # 6 + 2 + 1 + 2 = 11 inputs
   151.8 -        self.failUnlessEqual(response.content.count("<input"), 15)
   151.9 +        # CSRF field = 1
  151.10 +        # 6 + 2 + 4 + 1 + 2 + 1 = 16 inputs
  151.11 +        self.failUnlessEqual(response.content.count("<input"), 16)
  151.12          # 1 select per object = 3 selects
  151.13          self.failUnlessEqual(response.content.count("<select"), 4)
  151.14  
  151.15 @@ -908,7 +909,7 @@
  151.16          self.client.post('/test_admin/admin/admin_views/person/', data)
  151.17  
  151.18          self.failUnlessEqual(Person.objects.get(name="John Mauchly").alive, False)
  151.19 -        self.failUnlessEqual(Person.objects.get(name="Grace Hooper").gender, 2)
  151.20 +        self.failUnlessEqual(Person.objects.get(name="Grace Hopper").gender, 2)
  151.21  
  151.22          # test a filtered page
  151.23          data = {
  151.24 @@ -1140,6 +1141,16 @@
  151.25              '<input type="checkbox" class="action-select"' not in response.content,
  151.26              "Found an unexpected action toggle checkboxbox in response"
  151.27          )
  151.28 +        self.assert_('action-checkbox-column' not in response.content,
  151.29 +            "Found unexpected action-checkbox-column class in response")
  151.30 +
  151.31 +    def test_action_column_class(self):
  151.32 +        "Tests that the checkbox column class is present in the response"
  151.33 +        response = self.client.get('/test_admin/admin/admin_views/subscriber/')
  151.34 +        self.assertNotEquals(response.context["action_form"], None)
  151.35 +        self.assert_('action-checkbox-column' in response.content,
  151.36 +            "Expected an action-checkbox-column in response")
  151.37 +
  151.38  
  151.39      def test_multiple_actions_form(self):
  151.40          """
  151.41 @@ -1616,4 +1627,3 @@
  151.42          "Check the never-cache status of the Javascript i18n view"
  151.43          response = self.client.get('/test_admin/jsi18n/')
  151.44          self.failUnlessEqual(get_max_age(response), None)
  151.45 -
   152.1 --- a/tests/regressiontests/cache/models.py	Sat Sep 12 18:53:56 2009 -0500
   152.2 +++ b/tests/regressiontests/cache/models.py	Fri Nov 13 11:27:59 2009 -0600
   152.3 @@ -0,0 +1,11 @@
   152.4 +from django.db import models
   152.5 +from datetime import datetime
   152.6 +
   152.7 +def expensive_calculation():
   152.8 +    expensive_calculation.num_runs += 1
   152.9 +    return datetime.now()
  152.10 +
  152.11 +class Poll(models.Model):
  152.12 +    question = models.CharField(max_length=200)
  152.13 +    answer = models.CharField(max_length=200)
  152.14 +    pub_date = models.DateTimeField('date published', default=expensive_calculation)
   153.1 --- a/tests/regressiontests/cache/tests.py	Sat Sep 12 18:53:56 2009 -0500
   153.2 +++ b/tests/regressiontests/cache/tests.py	Fri Nov 13 11:27:59 2009 -0600
   153.3 @@ -16,6 +16,7 @@
   153.4  from django.http import HttpResponse, HttpRequest
   153.5  from django.utils.cache import patch_vary_headers, get_cache_key, learn_cache_key
   153.6  from django.utils.hashcompat import md5_constructor
   153.7 +from regressiontests.cache.models import Poll, expensive_calculation
   153.8  
   153.9  # functions/classes for complex data type tests
  153.10  def f():
  153.11 @@ -211,6 +212,47 @@
  153.12          self.cache.set("stuff", stuff)
  153.13          self.assertEqual(self.cache.get("stuff"), stuff)
  153.14  
  153.15 +    def test_cache_read_for_model_instance(self):
  153.16 +        # Don't want fields with callable as default to be called on cache read
  153.17 +        expensive_calculation.num_runs = 0
  153.18 +        Poll.objects.all().delete()
  153.19 +        my_poll = Poll.objects.create(question="Well?")
  153.20 +        self.assertEqual(Poll.objects.count(), 1)
  153.21 +        pub_date = my_poll.pub_date
  153.22 +        self.cache.set('question', my_poll)
  153.23 +        cached_poll = self.cache.get('question')
  153.24 +        self.assertEqual(cached_poll.pub_date, pub_date)
  153.25 +        # We only want the default expensive calculation run once
  153.26 +        self.assertEqual(expensive_calculation.num_runs, 1)
  153.27 +
  153.28 +    def test_cache_write_for_model_instance_with_deferred(self):
  153.29 +        # Don't want fields with callable as default to be called on cache write
  153.30 +        expensive_calculation.num_runs = 0
  153.31 +        Poll.objects.all().delete()
  153.32 +        my_poll = Poll.objects.create(question="What?")
  153.33 +        self.assertEqual(expensive_calculation.num_runs, 1)
  153.34 +        defer_qs = Poll.objects.all().defer('question')
  153.35 +        self.assertEqual(defer_qs.count(), 1)
  153.36 +        self.assertEqual(expensive_calculation.num_runs, 1)
  153.37 +        self.cache.set('deferred_queryset', defer_qs)
  153.38 +        # cache set should not re-evaluate default functions
  153.39 +        self.assertEqual(expensive_calculation.num_runs, 1)
  153.40 +
  153.41 +    def test_cache_read_for_model_instance_with_deferred(self):
  153.42 +        # Don't want fields with callable as default to be called on cache read
  153.43 +        expensive_calculation.num_runs = 0
  153.44 +        Poll.objects.all().delete()
  153.45 +        my_poll = Poll.objects.create(question="What?")
  153.46 +        self.assertEqual(expensive_calculation.num_runs, 1)
  153.47 +        defer_qs = Poll.objects.all().defer('question')
  153.48 +        self.assertEqual(defer_qs.count(), 1)
  153.49 +        self.cache.set('deferred_queryset', defer_qs)
  153.50 +        self.assertEqual(expensive_calculation.num_runs, 1)
  153.51 +        runs_before_cache_read = expensive_calculation.num_runs
  153.52 +        cached_polls = self.cache.get('deferred_queryset')
  153.53 +        # We only want the default expensive calculation run on creation and set
  153.54 +        self.assertEqual(expensive_calculation.num_runs, runs_before_cache_read)
  153.55 +
  153.56      def test_expiration(self):
  153.57          # Cache values can be set to expire
  153.58          self.cache.set('expire1', 'very quickly', 1)
   154.1 --- a/tests/regressiontests/comment_tests/tests/moderation_view_tests.py	Sat Sep 12 18:53:56 2009 -0500
   154.2 +++ b/tests/regressiontests/comment_tests/tests/moderation_view_tests.py	Fri Nov 13 11:27:59 2009 -0600
   154.3 @@ -159,31 +159,32 @@
   154.4          response = self.client.get("/approved/", data={"c":pk})
   154.5          self.assertTemplateUsed(response, "comments/approved.html")
   154.6  
   154.7 +class AdminActionsTests(CommentTestCase):
   154.8 +    urls = "regressiontests.comment_tests.urls_admin"
   154.9 +    
  154.10 +    def setUp(self):
  154.11 +        super(AdminActionsTests, self).setUp()
  154.12 +        
  154.13 +        # Make "normaluser" a moderator
  154.14 +        u = User.objects.get(username="normaluser")
  154.15 +        u.is_staff = True
  154.16 +        perms = Permission.objects.filter(
  154.17 +            content_type__app_label = 'comments', 
  154.18 +            codename__endswith = 'comment'
  154.19 +        )
  154.20 +        for perm in perms:
  154.21 +            u.user_permissions.add(perm)
  154.22 +        u.save()
  154.23  
  154.24 -class ModerationQueueTests(CommentTestCase):
  154.25 +    def testActionsNonModerator(self):
  154.26 +        comments = self.createSomeComments()
  154.27 +        self.client.login(username="normaluser", password="normaluser")
  154.28 +        response = self.client.get("/admin/comments/comment/")
  154.29 +        self.assertEquals("approve_comments" in response.content, False)
  154.30  
  154.31 -    def testModerationQueuePermissions(self):
  154.32 -        """Only moderators can view the moderation queue"""
  154.33 -        self.client.login(username="normaluser", password="normaluser")
  154.34 -        response = self.client.get("/moderate/")
  154.35 -        self.assertEqual(response["Location"], "http://testserver/accounts/login/?next=/moderate/")
  154.36 -
  154.37 -        makeModerator("normaluser")
  154.38 -        response = self.client.get("/moderate/")
  154.39 -        self.assertEqual(response.status_code, 200)
  154.40 -
  154.41 -    def testModerationQueueContents(self):
  154.42 -        """Moderation queue should display non-public, non-removed comments."""
  154.43 -        c1, c2, c3, c4 = self.createSomeComments()
  154.44 +    def testActionsModerator(self):
  154.45 +        comments = self.createSomeComments()
  154.46          makeModerator("normaluser")
  154.47          self.client.login(username="normaluser", password="normaluser")
  154.48 -
  154.49 -        c1.is_public = c2.is_public = False
  154.50 -        c1.save(); c2.save()
  154.51 -        response = self.client.get("/moderate/")
  154.52 -        self.assertEqual(list(response.context[0]["comments"]), [c1, c2])
  154.53 -
  154.54 -        c2.is_removed = True
  154.55 -        c2.save()
  154.56 -        response = self.client.get("/moderate/")
  154.57 -        self.assertEqual(list(response.context[0]["comments"]), [c1])
  154.58 +        response = self.client.get("/admin/comments/comment/")
  154.59 +        self.assertEquals("approve_comments" in response.content, True)
   155.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   155.2 +++ b/tests/regressiontests/comment_tests/urls_admin.py	Fri Nov 13 11:27:59 2009 -0600
   155.3 @@ -0,0 +1,13 @@
   155.4 +from django.conf.urls.defaults import *
   155.5 +from django.contrib import admin
   155.6 +from django.contrib.comments.admin import CommentsAdmin
   155.7 +from django.contrib.comments.models import Comment
   155.8 +
   155.9 +# Make a new AdminSite to avoid picking up the deliberately broken admin
  155.10 +# modules in other tests.
  155.11 +admin_site = admin.AdminSite()
  155.12 +admin_site.register(Comment, CommentsAdmin)
  155.13 +
  155.14 +urlpatterns = patterns('',
  155.15 +    (r'^admin/', include(admin_site.urls)),
  155.16 +)
   156.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   156.2 +++ b/tests/regressiontests/context_processors/fixtures/context-processors-users.xml	Fri Nov 13 11:27:59 2009 -0600
   156.3 @@ -0,0 +1,17 @@
   156.4 +<?xml version="1.0" encoding="utf-8"?>
   156.5 +<django-objects version="1.0">
   156.6 +    <object pk="100" model="auth.user">
   156.7 +        <field type="CharField" name="username">super</field>
   156.8 +        <field type="CharField" name="first_name">Super</field>
   156.9 +        <field type="CharField" name="last_name">User</field>
  156.10 +        <field type="CharField" name="email">super@example.com</field>
  156.11 +        <field type="CharField" name="password">sha1$995a3$6011485ea3834267d719b4c801409b8b1ddd0158</field>
  156.12 +        <field type="BooleanField" name="is_staff">True</field>
  156.13 +        <field type="BooleanField" name="is_active">True</field>
  156.14 +        <field type="BooleanField" name="is_superuser">True</field>
  156.15 +        <field type="DateTimeField" name="last_login">2007-05-30 13:20:10</field>
  156.16 +        <field type="DateTimeField" name="date_joined">2007-05-30 13:20:10</field>
  156.17 +        <field to="auth.group" name="groups" rel="ManyToManyRel"></field>
  156.18 +        <field to="auth.permission" name="user_permissions" rel="ManyToManyRel"></field>
  156.19 +    </object>
  156.20 +</django-objects>
   157.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   157.2 +++ b/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_access.html	Fri Nov 13 11:27:59 2009 -0600
   157.3 @@ -0,0 +1,1 @@
   157.4 +{{ user }}
   158.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   158.2 +++ b/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_messages.html	Fri Nov 13 11:27:59 2009 -0600
   158.3 @@ -0,0 +1,1 @@
   158.4 +{% for m in messages %}{{ m }}{% endfor %}
   159.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   159.2 +++ b/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_no_access.html	Fri Nov 13 11:27:59 2009 -0600
   159.3 @@ -0,0 +1,1 @@
   159.4 + 
   160.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   160.2 +++ b/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_perms.html	Fri Nov 13 11:27:59 2009 -0600
   160.3 @@ -0,0 +1,1 @@
   160.4 +{% if perms.auth %}Has auth permissions{% endif %}
   161.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   161.2 +++ b/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_test_access.html	Fri Nov 13 11:27:59 2009 -0600
   161.3 @@ -0,0 +1,1 @@
   161.4 +{% if session_accessed %}Session accessed{% else %}Session not accessed{% endif %}
   162.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   162.2 +++ b/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_user.html	Fri Nov 13 11:27:59 2009 -0600
   162.3 @@ -0,0 +1,4 @@
   162.4 +unicode: {{ user }}
   162.5 +id: {{ user.id }}
   162.6 +username: {{ user.username }}
   162.7 +url: {% url userpage user %}
   163.1 --- a/tests/regressiontests/context_processors/tests.py	Sat Sep 12 18:53:56 2009 -0500
   163.2 +++ b/tests/regressiontests/context_processors/tests.py	Fri Nov 13 11:27:59 2009 -0600
   163.3 @@ -3,8 +3,10 @@
   163.4  """
   163.5  
   163.6  from django.conf import settings
   163.7 +from django.contrib.auth import authenticate
   163.8 +from django.db.models import Q
   163.9  from django.test import TestCase
  163.10 -
  163.11 +from django.template import Template
  163.12  
  163.13  class RequestContextProcessorTests(TestCase):
  163.14      """
  163.15 @@ -36,3 +38,75 @@
  163.16          self.assertContains(response, url)
  163.17          response = self.client.post(url, {'path': '/blah/'})
  163.18          self.assertContains(response, url)
  163.19 +
  163.20 +class AuthContextProcessorTests(TestCase):
  163.21 +    """
  163.22 +    Tests for the ``django.core.context_processors.auth`` processor
  163.23 +    """
  163.24 +    urls = 'regressiontests.context_processors.urls'
  163.25 +    fixtures = ['context-processors-users.xml']
  163.26 +
  163.27 +    def test_session_not_accessed(self):
  163.28 +        """
  163.29 +        Tests that the session is not accessed simply by including
  163.30 +        the auth context processor
  163.31 +        """
  163.32 +        response = self.client.get('/auth_processor_no_attr_access/')
  163.33 +        self.assertContains(response, "Session not accessed")
  163.34 +
  163.35 +    def test_session_is_accessed(self):
  163.36 +        """
  163.37 +        Tests that the session is accessed if the auth context processor
  163.38 +        is used and relevant attributes accessed.
  163.39 +        """
  163.40 +        response = self.client.get('/auth_processor_attr_access/')
  163.41 +        self.assertContains(response, "Session accessed")
  163.42 +
  163.43 +    def test_perms_attrs(self):
  163.44 +        self.client.login(username='super', password='secret')
  163.45 +        response = self.client.get('/auth_processor_perms/')
  163.46 +        self.assertContains(response, "Has auth permissions")
  163.47 +
  163.48 +    def test_message_attrs(self):
  163.49 +        self.client.login(username='super', password='secret')
  163.50 +        response = self.client.get('/auth_processor_messages/')
  163.51 +        self.assertContains(response, "Message 1")
  163.52 +
  163.53 +    def test_user_attrs(self):
  163.54 +        """
  163.55 +        Test that the lazy objects returned behave just like the wrapped objects.
  163.56 +        """
  163.57 +        # These are 'functional' level tests for common use cases.  Direct
  163.58 +        # testing of the implementation (SimpleLazyObject) is in the 'utils'
  163.59 +        # tests.
  163.60 +        self.client.login(username='super', password='secret')
  163.61 +        user = authenticate(username='super', password='secret')
  163.62 +        response = self.client.get('/auth_processor_user/')
  163.63 +        self.assertContains(response, "unicode: super")
  163.64 +        self.assertContains(response, "id: 100")
  163.65 +        self.assertContains(response, "username: super")
  163.66 +        # bug #12037 is tested by the {% url %} in the template:
  163.67 +        self.assertContains(response, "url: /userpage/super/")
  163.68 +
  163.69 +        # See if this object can be used for queries where a Q() comparing
  163.70 +        # a user can be used with another Q() (in an AND or OR fashion).
  163.71 +        # This simulates what a template tag might do with the user from the
  163.72 +        # context. Note that we don't need to execute a query, just build it.
  163.73 +        #
  163.74 +        # The failure case (bug #12049) on Python 2.4 with a LazyObject-wrapped
  163.75 +        # User is a fatal TypeError: "function() takes at least 2 arguments
  163.76 +        # (0 given)" deep inside deepcopy().
  163.77 +        #
  163.78 +        # Python 2.5 and 2.6 succeeded, but logged internally caught exception
  163.79 +        # spew:
  163.80 +        #
  163.81 +        #    Exception RuntimeError: 'maximum recursion depth exceeded while
  163.82 +        #    calling a Python object' in <type 'exceptions.AttributeError'>
  163.83 +        #    ignored"
  163.84 +        query = Q(user=response.context['user']) & Q(someflag=True)
  163.85 +
  163.86 +        # Tests for user equality.  This is hard because User defines
  163.87 +        # equality in a non-duck-typing way
  163.88 +        # See bug #12060
  163.89 +        self.assertEqual(response.context['user'], user)
  163.90 +        self.assertEqual(user, response.context['user'])
   164.1 --- a/tests/regressiontests/context_processors/urls.py	Sat Sep 12 18:53:56 2009 -0500
   164.2 +++ b/tests/regressiontests/context_processors/urls.py	Fri Nov 13 11:27:59 2009 -0600
   164.3 @@ -5,4 +5,10 @@
   164.4  
   164.5  urlpatterns = patterns('',
   164.6      (r'^request_attrs/$', views.request_processor),
   164.7 +    (r'^auth_processor_no_attr_access/$', views.auth_processor_no_attr_access),
   164.8 +    (r'^auth_processor_attr_access/$', views.auth_processor_attr_access),
   164.9 +    (r'^auth_processor_user/$', views.auth_processor_user),
  164.10 +    (r'^auth_processor_perms/$', views.auth_processor_perms),
  164.11 +    (r'^auth_processor_messages/$', views.auth_processor_messages),
  164.12 +    url(r'^userpage/(.+)/$', views.userpage, name="userpage"),
  164.13  )
   165.1 --- a/tests/regressiontests/context_processors/views.py	Sat Sep 12 18:53:56 2009 -0500
   165.2 +++ b/tests/regressiontests/context_processors/views.py	Fri Nov 13 11:27:59 2009 -0600
   165.3 @@ -6,3 +6,32 @@
   165.4  def request_processor(request):
   165.5      return render_to_response('context_processors/request_attrs.html',
   165.6          RequestContext(request, {}, processors=[context_processors.request]))
   165.7 +
   165.8 +def auth_processor_no_attr_access(request):
   165.9 +    r1 = render_to_response('context_processors/auth_attrs_no_access.html',
  165.10 +        RequestContext(request, {}, processors=[context_processors.auth]))
  165.11 +    # *After* rendering, we check whether the session was accessed
  165.12 +    return render_to_response('context_processors/auth_attrs_test_access.html',
  165.13 +        {'session_accessed':request.session.accessed})
  165.14 +
  165.15 +def auth_processor_attr_access(request):
  165.16 +    r1 = render_to_response('context_processors/auth_attrs_access.html',
  165.17 +        RequestContext(request, {}, processors=[context_processors.auth]))
  165.18 +    return render_to_response('context_processors/auth_attrs_test_access.html',
  165.19 +        {'session_accessed':request.session.accessed})
  165.20 +
  165.21 +def auth_processor_user(request):
  165.22 +    return render_to_response('context_processors/auth_attrs_user.html',
  165.23 +        RequestContext(request, {}, processors=[context_processors.auth]))
  165.24 +
  165.25 +def auth_processor_perms(request):
  165.26 +    return render_to_response('context_processors/auth_attrs_perms.html',
  165.27 +        RequestContext(request, {}, processors=[context_processors.auth]))
  165.28 +
  165.29 +def auth_processor_messages(request):
  165.30 +    request.user.message_set.create(message="Message 1")
  165.31 +    return render_to_response('context_processors/auth_attrs_messages.html',
  165.32 +         RequestContext(request, {}, processors=[context_processors.auth]))
  165.33 +
  165.34 +def userpage(request):
  165.35 +    pass
   166.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   166.2 +++ b/tests/regressiontests/csrf_tests/models.py	Fri Nov 13 11:27:59 2009 -0600
   166.3 @@ -0,0 +1,1 @@
   166.4 +# models.py file for tests to run.
   167.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   167.2 +++ b/tests/regressiontests/csrf_tests/tests.py	Fri Nov 13 11:27:59 2009 -0600
   167.3 @@ -0,0 +1,324 @@
   167.4 +# -*- coding: utf-8 -*-
   167.5 +
   167.6 +from django.test import TestCase
   167.7 +from django.http import HttpRequest, HttpResponse
   167.8 +from django.middleware.csrf import CsrfMiddleware, CsrfViewMiddleware
   167.9 +from django.views.decorators.csrf import csrf_exempt
  167.10 +from django.core.context_processors import csrf
  167.11 +from django.contrib.sessions.middleware import SessionMiddleware
  167.12 +from django.utils.importlib import import_module
  167.13 +from django.conf import settings
  167.14 +from django.template import RequestContext, Template
  167.15 +
  167.16 +# Response/views used for CsrfResponseMiddleware and CsrfViewMiddleware tests
  167.17 +def post_form_response():
  167.18 +    resp = HttpResponse(content="""
  167.19 +<html><body><form method="POST"><input type="text" /></form></body></html>
  167.20 +""", mimetype="text/html")
  167.21 +    return resp
  167.22 +
  167.23 +def post_form_response_non_html():
  167.24 +    resp = post_form_response()
  167.25 +    resp["Content-Type"] = "application/xml"
  167.26 +    return resp
  167.27 +
  167.28 +def post_form_view(request):
  167.29 +    """A view that returns a POST form (without a token)"""
  167.30 +    return post_form_response()
  167.31 +
  167.32 +# Response/views used for template tag tests
  167.33 +def _token_template():
  167.34 +    return Template("{% csrf_token %}")
  167.35 +
  167.36 +def _render_csrf_token_template(req):
  167.37 +    context = RequestContext(req, processors=[csrf])
  167.38 +    template = _token_template()
  167.39 +    return template.render(context)
  167.40 +
  167.41 +def token_view(request):
  167.42 +    """A view that uses {% csrf_token %}"""
  167.43 +    return HttpResponse(_render_csrf_token_template(request))
  167.44 +
  167.45 +def non_token_view_using_request_processor(request):
  167.46 +    """
  167.47 +    A view that doesn't use the token, but does use the csrf view processor.
  167.48 +    """
  167.49 +    context = RequestContext(request, processors=[csrf])
  167.50 +    template = Template("")
  167.51 +    return HttpResponse(template.render(context))
  167.52 +
  167.53 +class TestingHttpRequest(HttpRequest):
  167.54 +    """
  167.55 +    A version of HttpRequest that allows us to change some things
  167.56 +    more easily
  167.57 +    """
  167.58 +    def is_secure(self):
  167.59 +        return getattr(self, '_is_secure', False)
  167.60 +
  167.61 +class CsrfMiddlewareTest(TestCase):
  167.62 +    _csrf_id = "1"
  167.63 +
  167.64 +    # This is a valid session token for this ID and secret key.  This was generated using
  167.65 +    # the old code that we're to be backwards-compatible with.  Don't use the CSRF code
  167.66 +    # to generate this hash, or we're merely testing the code against itself and not
  167.67 +    # checking backwards-compatibility.  This is also the output of (echo -n test1 | md5sum).
  167.68 +    _session_token = "5a105e8b9d40e1329780d62ea2265d8a"
  167.69 +    _session_id = "1"
  167.70 +    _secret_key_for_session_test= "test"
  167.71 +
  167.72 +    def _get_GET_no_csrf_cookie_request(self):
  167.73 +        return TestingHttpRequest()
  167.74 +
  167.75 +    def _get_GET_csrf_cookie_request(self):
  167.76 +        req = TestingHttpRequest()
  167.77 +        req.COOKIES[settings.CSRF_COOKIE_NAME] = self._csrf_id
  167.78 +        return req
  167.79 +
  167.80 +    def _get_POST_csrf_cookie_request(self):
  167.81 +        req = self._get_GET_csrf_cookie_request()
  167.82 +        req.method = "POST"
  167.83 +        return req
  167.84 +
  167.85 +    def _get_POST_no_csrf_cookie_request(self):
  167.86 +        req = self._get_GET_no_csrf_cookie_request()
  167.87 +        req.method = "POST"
  167.88 +        return req
  167.89 +
  167.90 +    def _get_POST_request_with_token(self):
  167.91 +        req = self._get_POST_csrf_cookie_request()
  167.92 +        req.POST['csrfmiddlewaretoken'] = self._csrf_id
  167.93 +        return req
  167.94 +
  167.95 +    def _get_POST_session_request_with_token(self):
  167.96 +        req = self._get_POST_no_csrf_cookie_request()
  167.97 +        req.COOKIES[settings.SESSION_COOKIE_NAME] = self._session_id
  167.98 +        req.POST['csrfmiddlewaretoken'] = self._session_token
  167.99 +        return req
 167.100 +
 167.101 +    def _get_POST_session_request_no_token(self):
 167.102 +        req = self._get_POST_no_csrf_cookie_request()
 167.103 +        req.COOKIES[settings.SESSION_COOKIE_NAME] = self._session_id
 167.104 +        return req
 167.105 +
 167.106 +    def _check_token_present(self, response, csrf_id=None):
 167.107 +        self.assertContains(response, "name='csrfmiddlewaretoken' value='%s'" % (csrf_id or self._csrf_id))
 167.108 +
 167.109 +    # Check the post processing and outgoing cookie
 167.110 +    def test_process_response_no_csrf_cookie(self):
 167.111 +        """
 167.112 +        When no prior CSRF cookie exists, check that the cookie is created and a
 167.113 +        token is inserted.
 167.114 +        """
 167.115 +        req = self._get_GET_no_csrf_cookie_request()
 167.116 +        CsrfMiddleware().process_view(req, post_form_view, (), {})
 167.117 +
 167.118 +        resp = post_form_response()
 167.119 +        resp_content = resp.content # needed because process_response modifies resp
 167.120 +        resp2 = CsrfMiddleware().process_response(req, resp)
 167.121 +
 167.122 +        csrf_cookie = resp2.cookies.get(settings.CSRF_COOKIE_NAME, False)
 167.123 +        self.assertNotEqual(csrf_cookie, False)
 167.124 +        self.assertNotEqual(resp_content, resp2.content)
 167.125 +        self._check_token_present(resp2, csrf_cookie.value)
 167.126 +        # Check the Vary header got patched correctly
 167.127 +        self.assert_('Cookie' in resp2.get('Vary',''))
 167.128 +
 167.129 +    def test_process_response_no_csrf_cookie_view_only_get_token_used(self):
 167.130 +        """
 167.131 +        When no prior CSRF cookie exists, check that the cookie is created, even
 167.132 +        if only CsrfViewMiddleware is used.
 167.133 +        """
 167.134 +        # This is checking that CsrfViewMiddleware has the cookie setting
 167.135 +        # code. Most of the other tests use CsrfMiddleware.
 167.136 +        req = self._get_GET_no_csrf_cookie_request()
 167.137 +        # token_view calls get_token() indirectly
 167.138 +        CsrfViewMiddleware().process_view(req, token_view, (), {})
 167.139 +        resp = token_view(req)
 167.140 +        resp2 = CsrfViewMiddleware().process_response(req, resp)
 167.141 +
 167.142 +        csrf_cookie = resp2.cookies.get(settings.CSRF_COOKIE_NAME, False)
 167.143 +        self.assertNotEqual(csrf_cookie, False)
 167.144 +
 167.145 +    def test_process_response_get_token_not_used(self):
 167.146 +        """
 167.147 +        Check that if get_token() is not called, the view middleware does not
 167.148 +        add a cookie.
 167.149 +        """
 167.150 +        # This is important to make pages cacheable.  Pages which do call
 167.151 +        # get_token(), assuming they use the token, are not cacheable because
 167.152 +        # the token is specific to the user
 167.153 +        req = self._get_GET_no_csrf_cookie_request()
 167.154 +        # non_token_view_using_request_processor does not call get_token(), but
 167.155 +        # does use the csrf request processor.  By using this, we are testing
 167.156 +        # that the view processor is properly lazy and doesn't call get_token()
 167.157 +        # until needed.
 167.158 +        CsrfViewMiddleware().process_view(req, non_token_view_using_request_processor, (), {})
 167.159 +        resp = non_token_view_using_request_processor(req)
 167.160 +        resp2 = CsrfViewMiddleware().process_response(req, resp)
 167.161 +
 167.162 +        csrf_cookie = resp2.cookies.get(settings.CSRF_COOKIE_NAME, False)
 167.163 +        self.assertEqual(csrf_cookie, False)
 167.164 +
 167.165 +    def test_process_response_existing_csrf_cookie(self):
 167.166 +        """
 167.167 +        Check that the token is inserted when a prior CSRF cookie exists
 167.168 +        """
 167.169 +        req = self._get_GET_csrf_cookie_request()
 167.170 +        CsrfMiddleware().process_view(req, post_form_view, (), {})
 167.171 +
 167.172 +        resp = post_form_response()
 167.173 +        resp_content = resp.content # needed because process_response modifies resp
 167.174 +        resp2 = CsrfMiddleware().process_response(req, resp)
 167.175 +        self.assertNotEqual(resp_content, resp2.content)
 167.176 +        self._check_token_present(resp2)
 167.177 +
 167.178 +    def test_process_response_non_html(self):
 167.179 +        """
 167.180 +        Check the the post-processor does nothing for content-types not in _HTML_TYPES.
 167.181 +        """
 167.182 +        req = self._get_GET_no_csrf_cookie_request()
 167.183 +        CsrfMiddleware().process_view(req, post_form_view, (), {})
 167.184 +        resp = post_form_response_non_html()
 167.185 +        resp_content = resp.content # needed because process_response modifies resp
 167.186 +        resp2 = CsrfMiddleware().process_response(req, resp)
 167.187 +        self.assertEquals(resp_content, resp2.content)
 167.188 +
 167.189 +    def test_process_response_exempt_view(self):
 167.190 +        """
 167.191 +        Check that no post processing is done for an exempt view
 167.192 +        """
 167.193 +        req = self._get_POST_csrf_cookie_request()
 167.194 +        resp = csrf_exempt(post_form_view)(req)
 167.195 +        resp_content = resp.content
 167.196 +        resp2 = CsrfMiddleware().process_response(req, resp)
 167.197 +        self.assertEquals(resp_content, resp2.content)
 167.198 +
 167.199 +    # Check the request processing
 167.200 +    def test_process_request_no_session_no_csrf_cookie(self):
 167.201 +        """
 167.202 +        Check that if neither a CSRF cookie nor a session cookie are present,
 167.203 +        the middleware rejects the incoming request.  This will stop login CSRF.
 167.204 +        """
 167.205 +        req = self._get_POST_no_csrf_cookie_request()
 167.206 +        req2 = CsrfMiddleware().process_view(req, post_form_view, (), {})
 167.207 +        self.assertEquals(403, req2.status_code)
 167.208 +
 167.209 +    def test_process_request_csrf_cookie_no_token(self):
 167.210 +        """
 167.211 +        Check that if a CSRF cookie is present but no token, the middleware
 167.212 +        rejects the incoming request.
 167.213 +        """
 167.214 +        req = self._get_POST_csrf_cookie_request()
 167.215 +        req2 = CsrfMiddleware().process_view(req, post_form_view, (), {})
 167.216 +        self.assertEquals(403, req2.status_code)
 167.217 +
 167.218 +    def test_process_request_csrf_cookie_and_token(self):
 167.219 +        """
 167.220 +        Check that if both a cookie and a token is present, the middleware lets it through.
 167.221 +        """
 167.222 +        req = self._get_POST_request_with_token()
 167.223 +        req2 = CsrfMiddleware().process_view(req, post_form_view, (), {})
 167.224 +        self.assertEquals(None, req2)
 167.225 +
 167.226 +    def test_process_request_session_cookie_no_csrf_cookie_token(self):
 167.227 +        """
 167.228 +        When no CSRF cookie exists, but the user has a session, check that a token
 167.229 +        using the session cookie as a legacy CSRF cookie is accepted.
 167.230 +        """
 167.231 +        orig_secret_key = settings.SECRET_KEY
 167.232 +        settings.SECRET_KEY = self._secret_key_for_session_test
 167.233 +        try:
 167.234 +            req = self._get_POST_session_request_with_token()
 167.235 +            req2 = CsrfMiddleware().process_view(req, post_form_view, (), {})
 167.236 +            self.assertEquals(None, req2)
 167.237 +        finally:
 167.238 +            settings.SECRET_KEY = orig_secret_key
 167.239 +
 167.240 +    def test_process_request_session_cookie_no_csrf_cookie_no_token(self):
 167.241 +        """
 167.242 +        Check that if a session cookie is present but no token and no CSRF cookie,
 167.243 +        the request is rejected.
 167.244 +        """
 167.245 +        req = self._get_POST_session_request_no_token()
 167.246 +        req2 = CsrfMiddleware().process_view(req, post_form_view, (), {})
 167.247 +        self.assertEquals(403, req2.status_code)
 167.248 +
 167.249 +    def test_process_request_csrf_cookie_no_token_exempt_view(self):
 167.250 +        """
 167.251 +        Check that if a CSRF cookie is present and no token, but the csrf_exempt
 167.252 +        decorator has been applied to the view, the middleware lets it through
 167.253 +        """
 167.254 +        req = self._get_POST_csrf_cookie_request()
 167.255 +        req2 = CsrfMiddleware().process_view(req, csrf_exempt(post_form_view), (), {})
 167.256 +        self.assertEquals(None, req2)
 167.257 +
 167.258 +    def test_ajax_exemption(self):
 167.259 +        """
 167.260 +        Check that AJAX requests are automatically exempted.
 167.261 +        """
 167.262 +        req = self._get_POST_csrf_cookie_request()
 167.263 +        req.META['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
 167.264 +        req2 = CsrfMiddleware().process_view(req, post_form_view, (), {})
 167.265 +        self.assertEquals(None, req2)
 167.266 +
 167.267 +    # Tests for the template tag method
 167.268 +    def test_token_node_no_csrf_cookie(self):
 167.269 +        """
 167.270 +        Check that CsrfTokenNode works when no CSRF cookie is set
 167.271 +        """
 167.272 +        req = self._get_GET_no_csrf_cookie_request()
 167.273 +        resp = token_view(req)
 167.274 +        self.assertEquals(u"", resp.content)
 167.275 +
 167.276 +    def test_token_node_with_csrf_cookie(self):
 167.277 +        """
 167.278 +        Check that CsrfTokenNode works when a CSRF cookie is set
 167.279 +        """
 167.280 +        req = self._get_GET_csrf_cookie_request()
 167.281 +        CsrfViewMiddleware().process_view(req, token_view, (), {})
 167.282 +        resp = token_view(req)
 167.283 +        self._check_token_present(resp)
 167.284 +
 167.285 +    def test_token_node_with_new_csrf_cookie(self):
 167.286 +        """
 167.287 +        Check that CsrfTokenNode works when a CSRF cookie is created by
 167.288 +        the middleware (when one was not already present)
 167.289 +        """
 167.290 +        req = self._get_GET_no_csrf_cookie_request()
 167.291 +        CsrfViewMiddleware().process_view(req, token_view, (), {})
 167.292 +        resp = token_view(req)
 167.293 +        resp2 = CsrfViewMiddleware().process_response(req, resp)
 167.294 +        csrf_cookie = resp2.cookies[settings.CSRF_COOKIE_NAME]
 167.295 +        self._check_token_present(resp, csrf_id=csrf_cookie.value)
 167.296 +
 167.297 +    def test_response_middleware_without_view_middleware(self):
 167.298 +        """
 167.299 +        Check that CsrfResponseMiddleware finishes without error if the view middleware
 167.300 +        has not been called, as is the case if a request middleware returns a response.
 167.301 +        """
 167.302 +        req = self._get_GET_no_csrf_cookie_request()
 167.303 +        resp = post_form_view(req)
 167.304 +        CsrfMiddleware().process_response(req, resp)
 167.305 +
 167.306 +    def test_https_bad_referer(self):
 167.307 +        """
 167.308 +        Test that a POST HTTPS request with a bad referer is rejected
 167.309 +        """
 167.310 +        req = self._get_POST_request_with_token()
 167.311 +        req._is_secure = True
 167.312 +        req.META['HTTP_HOST'] = 'www.example.com'
 167.313 +        req.META['HTTP_REFERER'] = 'https://www.evil.org/somepage'
 167.314 +        req2 = CsrfViewMiddleware().process_view(req, post_form_view, (), {})
 167.315 +        self.assertNotEqual(None, req2)
 167.316 +        self.assertEquals(403, req2.status_code)
 167.317 +
 167.318 +    def test_https_good_referer(self):
 167.319 +        """
 167.320 +        Test that a POST HTTPS request with a good referer is accepted
 167.321 +        """
 167.322 +        req = self._get_POST_request_with_token()
 167.323 +        req._is_secure = True
 167.324 +        req.META['HTTP_HOST'] = 'www.example.com'
 167.325 +        req.META['HTTP_REFERER'] = 'https://www.example.com/somepage'
 167.326 +        req2 = CsrfViewMiddleware().process_view(req, post_form_view, (), {})
 167.327 +        self.assertEquals(None, req2)
   168.1 --- a/tests/regressiontests/dateformat/tests.py	Sat Sep 12 18:53:56 2009 -0500
   168.2 +++ b/tests/regressiontests/dateformat/tests.py	Fri Nov 13 11:27:59 2009 -0600
   168.3 @@ -1,90 +1,92 @@
   168.4 -r"""
   168.5 ->>> format(my_birthday, '')
   168.6 -u''
   168.7 ->>> format(my_birthday, 'a')
   168.8 -u'p.m.'
   168.9 ->>> format(my_birthday, 'A')
  168.10 -u'PM'
  168.11 ->>> format(my_birthday, 'd')
  168.12 -u'08'
  168.13 ->>> format(my_birthday, 'j')
  168.14 -u'8'
  168.15 ->>> format(my_birthday, 'l')
  168.16 -u'Sunday'
  168.17 ->>> format(my_birthday, 'L')
  168.18 -u'False'
  168.19 ->>> format(my_birthday, 'm')
  168.20 -u'07'
  168.21 ->>> format(my_birthday, 'M')
  168.22 -u'Jul'
  168.23 ->>> format(my_birthday, 'b')
  168.24 -u'jul'
  168.25 ->>> format(my_birthday, 'n')
  168.26 -u'7'
  168.27 ->>> format(my_birthday, 'N')
  168.28 -u'July'
  168.29 ->>> no_tz or format(my_birthday, 'O') == '+0100'
  168.30 -True
  168.31 ->>> format(my_birthday, 'P')
  168.32 -u'10 p.m.'
  168.33 ->>> no_tz or format(my_birthday, 'r') == 'Sun, 8 Jul 1979 22:00:00 +0100'
  168.34 -True
  168.35 ->>> format(my_birthday, 's')
  168.36 -u'00'
  168.37 ->>> format(my_birthday, 'S')
  168.38 -u'th'
  168.39 ->>> format(my_birthday, 't')
  168.40 -u'31'
  168.41 ->>> no_tz or format(my_birthday, 'T') == 'CET'
  168.42 -True
  168.43 ->>> no_tz or format(my_birthday, 'U') == '300315600'
  168.44 -True
  168.45 ->>> format(my_birthday, 'w')
  168.46 -u'0'
  168.47 ->>> format(my_birthday, 'W')
  168.48 -u'27'
  168.49 ->>> format(my_birthday, 'y')
  168.50 -u'79'
  168.51 ->>> format(my_birthday, 'Y')
  168.52 -u'1979'
  168.53 ->>> format(my_birthday, 'z')
  168.54 -u'189'
  168.55 ->>> no_tz or format(my_birthday, 'Z') == '3600'
  168.56 -True
  168.57 -
  168.58 ->>> no_tz or format(summertime, 'I') == '1'
  168.59 -True
  168.60 ->>> no_tz or format(summertime, 'O') == '+0200'
  168.61 -True
  168.62 ->>> no_tz or format(wintertime, 'I') == '0'
  168.63 -True
  168.64 ->>> no_tz or format(wintertime, 'O') == '+0100'
  168.65 -True
  168.66 -
  168.67 ->>> format(my_birthday, r'Y z \C\E\T')
  168.68 -u'1979 189 CET'
  168.69 -
  168.70 ->>> format(my_birthday, r'jS o\f F')
  168.71 -u'8th of July'
  168.72 -
  168.73 ->>> format(the_future, r'Y')
  168.74 -u'2100'
  168.75 -"""
  168.76  
  168.77  from django.utils import dateformat, translation
  168.78 +from unittest import TestCase
  168.79  import datetime, os, time
  168.80  
  168.81 -format = dateformat.format
  168.82 -os.environ['TZ'] = 'Europe/Copenhagen'
  168.83 -translation.activate('en-us')
  168.84 +class DateFormatTests(TestCase):
  168.85 +    def setUp(self):
  168.86 +        self.old_TZ = os.environ.get('TZ')
  168.87 +        os.environ['TZ'] = 'Europe/Copenhagen'
  168.88 +        translation.activate('en-us')
  168.89  
  168.90 -try:
  168.91 -    time.tzset()
  168.92 -    no_tz = False
  168.93 -except AttributeError:
  168.94 -    no_tz = True
  168.95 +        try:
  168.96 +            # Check if a timezone has been set
  168.97 +            time.tzset()
  168.98 +            self.tz_tests = True
  168.99 +        except AttributeError:
 168.100 +            # No timezone available. Don't run the tests that require a TZ
 168.101 +            self.tz_tests = False
 168.102  
 168.103 -my_birthday = datetime.datetime(1979, 7, 8, 22, 00)
 168.104 -summertime = datetime.datetime(2005, 10, 30, 1, 00)
 168.105 -wintertime = datetime.datetime(2005, 10, 30, 4, 00)
 168.106 -the_future = datetime.datetime(2100, 10, 25, 0, 00)
 168.107 +    def tearDown(self):
 168.108 +        if self.old_TZ is None:
 168.109 +            del os.environ['TZ']
 168.110 +        else:
 168.111 +            os.environ['TZ'] = self.old_TZ
 168.112 +
 168.113 +        # Cleanup - force re-evaluation of TZ environment variable.
 168.114 +        if self.tz_tests:
 168.115 +            time.tzset()
 168.116 +
 168.117 +    def test_empty_format(self):
 168.118 +        my_birthday = datetime.datetime(1979, 7, 8, 22, 00)
 168.119 +
 168.120 +        self.assertEquals(dateformat.format(my_birthday, ''), u'')
 168.121 +
 168.122 +    def test_am_pm(self):
 168.123 +        my_birthday = datetime.datetime(1979, 7, 8, 22, 00)
 168.124 +
 168.125 +        self.assertEquals(dateformat.format(my_birthday, 'a'), u'p.m.')
 168.126 +
 168.127 +    def test_date_formats(self):
 168.128 +        my_birthday = datetime.datetime(1979, 7, 8, 22, 00)
 168.129 +
 168.130 +        self.assertEquals(dateformat.format(my_birthday, 'A'), u'PM')
 168.131 +        self.assertEquals(dateformat.format(my_birthday, 'd'), u'08')
 168.132 +        self.assertEquals(dateformat.format(my_birthday, 'j'), u'8')
 168.133 +        self.assertEquals(dateformat.format(my_birthday, 'l'), u'Sunday')
 168.134 +        self.assertEquals(dateformat.format(my_birthday, 'L'), u'False')
 168.135 +        self.assertEquals(dateformat.format(my_birthday, 'm'), u'07')
 168.136 +        self.assertEquals(dateformat.format(my_birthday, 'M'), u'Jul')
 168.137 +        self.assertEquals(dateformat.format(my_birthday, 'b'), u'jul')
 168.138 +        self.assertEquals(dateformat.format(my_birthday, 'n'), u'7')
 168.139 +        self.assertEquals(dateformat.format(my_birthday, 'N'), u'July')
 168.140 +
 168.141 +    def test_time_formats(self):
 168.142 +        my_birthday = datetime.datetime(1979, 7, 8, 22, 00)
 168.143 +
 168.144 +        self.assertEquals(dateformat.format(my_birthday, 'P'), u'10 p.m.')
 168.145 +        self.assertEquals(dateformat.format(my_birthday, 's'), u'00')
 168.146 +        self.assertEquals(dateformat.format(my_birthday, 'S'), u'th')
 168.147 +        self.assertEquals(dateformat.format(my_birthday, 't'), u'31')
 168.148 +        self.assertEquals(dateformat.format(my_birthday, 'w'), u'0')
 168.149 +        self.assertEquals(dateformat.format(my_birthday, 'W'), u'27')
 168.150 +        self.assertEquals(dateformat.format(my_birthday, 'y'), u'79')
 168.151 +        self.assertEquals(dateformat.format(my_birthday, 'Y'), u'1979')
 168.152 +        self.assertEquals(dateformat.format(my_birthday, 'z'), u'189')
 168.153 +
 168.154 +    def test_dateformat(self):
 168.155 +        my_birthday = datetime.datetime(1979, 7, 8, 22, 00)
 168.156 +
 168.157 +        self.assertEquals(dateformat.format(my_birthday, r'Y z \C\E\T'), u'1979 189 CET')
 168.158 +
 168.159 +        self.assertEquals(dateformat.format(my_birthday, r'jS o\f F'), u'8th of July')
 168.160 +
 168.161 +    def test_futuredates(self):
 168.162 +        the_future = datetime.datetime(2100, 10, 25, 0, 00)
 168.163 +        self.assertEquals(dateformat.format(the_future, r'Y'), u'2100')
 168.164 +
 168.165 +    def test_timezones(self):
 168.166 +        my_birthday = datetime.datetime(1979, 7, 8, 22, 00)
 168.167 +        summertime = datetime.datetime(2005, 10, 30, 1, 00)
 168.168 +        wintertime = datetime.datetime(2005, 10, 30, 4, 00)
 168.169 +
 168.170 +        if self.tz_tests:
 168.171 +            self.assertEquals(dateformat.format(my_birthday, 'O'), u'+0100')
 168.172 +            self.assertEquals(dateformat.format(my_birthday, 'r'), u'Sun, 8 Jul 1979 22:00:00 +0100')
 168.173 +            self.assertEquals(dateformat.format(my_birthday, 'T'), u'CET')
 168.174 +            self.assertEquals(dateformat.format(my_birthday, 'U'), u'300315600')
 168.175 +            self.assertEquals(dateformat.format(my_birthday, 'Z'), u'3600')
 168.176 +            self.assertEquals(dateformat.format(summertime, 'I'), u'1')
 168.177 +            self.assertEquals(dateformat.format(summertime, 'O'), u'+0200')
 168.178 +            self.assertEquals(dateformat.format(wintertime, 'I'), u'0')
 168.179 +            self.assertEquals(dateformat.format(wintertime, 'O'), u'+0100')
   169.1 --- a/tests/regressiontests/decorators/tests.py	Sat Sep 12 18:53:56 2009 -0500
   169.2 +++ b/tests/regressiontests/decorators/tests.py	Fri Nov 13 11:27:59 2009 -0600
   169.3 @@ -1,11 +1,16 @@
   169.4  from unittest import TestCase
   169.5  from sys import version_info
   169.6 +try:
   169.7 +    from functools import wraps
   169.8 +except ImportError:
   169.9 +    from django.utils.functional import wraps  # Python 2.3, 2.4 fallback.
  169.10  
  169.11 -from django.http import HttpResponse
  169.12 +from django.http import HttpResponse, HttpRequest
  169.13  from django.utils.functional import allow_lazy, lazy, memoize
  169.14  from django.views.decorators.http import require_http_methods, require_GET, require_POST
  169.15  from django.views.decorators.vary import vary_on_headers, vary_on_cookie
  169.16  from django.views.decorators.cache import cache_page, never_cache, cache_control
  169.17 +from django.utils.decorators import auto_adapt_to_methods
  169.18  from django.contrib.auth.decorators import login_required, permission_required, user_passes_test
  169.19  from django.contrib.admin.views.decorators import staff_member_required
  169.20  
  169.21 @@ -84,4 +89,65 @@
  169.22          response = callback(request)
  169.23          
  169.24          self.assertEqual(response, ['test2', 'test1'])
  169.25 -        
  169.26 +
  169.27 +    def test_cache_page_new_style(self):
  169.28 +        """
  169.29 +        Test that we can call cache_page the new way
  169.30 +        """
  169.31 +        def my_view(request):
  169.32 +            return "response"
  169.33 +        my_view_cached = cache_page(123)(my_view)
  169.34 +        self.assertEqual(my_view_cached(HttpRequest()), "response")
  169.35 +        my_view_cached2 = cache_page(123, key_prefix="test")(my_view)
  169.36 +        self.assertEqual(my_view_cached2(HttpRequest()), "response")
  169.37 +
  169.38 +    def test_cache_page_old_style(self):
  169.39 +        """
  169.40 +        Test that we can call cache_page the old way
  169.41 +        """
  169.42 +        def my_view(request):
  169.43 +            return "response"
  169.44 +        my_view_cached = cache_page(my_view, 123)
  169.45 +        self.assertEqual(my_view_cached(HttpRequest()), "response")
  169.46 +        my_view_cached2 = cache_page(my_view, 123, key_prefix="test")
  169.47 +        self.assertEqual(my_view_cached2(HttpRequest()), "response")
  169.48 +
  169.49 +class MethodDecoratorAdapterTests(TestCase):
  169.50 +    def test_auto_adapt_to_methods(self):
  169.51 +        """
  169.52 +        Test that auto_adapt_to_methods actually works.
  169.53 +        """
  169.54 +        # Need 2 decorators with auto_adapt_to_methods,
  169.55 +        # to check it plays nicely with composing itself.
  169.56 +
  169.57 +        def my_decorator(func):
  169.58 +            def wrapped(*args, **kwargs):
  169.59 +                # need to ensure that the first arg isn't 'self'
  169.60 +                self.assertEqual(args[0], "test")
  169.61 +                return "my_decorator:" + func(*args, **kwargs)
  169.62 +            wrapped.my_decorator_custom_attribute = True
  169.63 +            return wraps(func)(wrapped)
  169.64 +        my_decorator = auto_adapt_to_methods(my_decorator)
  169.65 +
  169.66 +        def my_decorator2(func):
  169.67 +            def wrapped(*args, **kwargs):
  169.68 +                # need to ensure that the first arg isn't 'self'
  169.69 +                self.assertEqual(args[0], "test")
  169.70 +                return "my_decorator2:" + func(*args, **kwargs)
  169.71 +            wrapped.my_decorator2_custom_attribute = True
  169.72 +            return wraps(func)(wrapped)
  169.73 +        my_decorator2 = auto_adapt_to_methods(my_decorator2)
  169.74 +
  169.75 +        class MyClass(object):
  169.76 +            def my_method(self, *args, **kwargs):
  169.77 +                return "my_method:%r %r" % (args, kwargs)
  169.78 +            my_method = my_decorator2(my_decorator(my_method))
  169.79 +
  169.80 +        obj = MyClass()
  169.81 +        self.assertEqual(obj.my_method("test", 123, name='foo'),
  169.82 +                         "my_decorator2:my_decorator:my_method:('test', 123) {'name': 'foo'}")
  169.83 +        self.assertEqual(obj.my_method.__name__, 'my_method')
  169.84 +        self.assertEqual(getattr(obj.my_method, 'my_decorator_custom_attribute', False),
  169.85 +                         True)
  169.86 +        self.assertEqual(getattr(obj.my_method, 'my_decorator2_custom_attribute', False),
  169.87 +                         True)
   170.1 --- a/tests/regressiontests/defer_regress/models.py	Sat Sep 12 18:53:56 2009 -0500
   170.2 +++ b/tests/regressiontests/defer_regress/models.py	Fri Nov 13 11:27:59 2009 -0600
   170.3 @@ -115,6 +115,23 @@
   170.4  >>> results[0].second_child.name
   170.5  u'c2'
   170.6  
   170.7 +# Test for #12163 - Pickling error saving session with unsaved model instances.
   170.8 +>>> from django.contrib.sessions.backends.db import SessionStore
   170.9 +>>> SESSION_KEY = '2b1189a188b44ad18c35e1baac6ceead'
  170.10 +>>> item = Item()
  170.11 +>>> item._deferred
  170.12 +False
  170.13 +>>> s = SessionStore(SESSION_KEY)
  170.14 +>>> s.clear()
  170.15 +>>> s['item'] = item
  170.16 +>>> s.save()
  170.17 +>>> s = SessionStore(SESSION_KEY)
  170.18 +>>> s.modified = True
  170.19 +>>> s.save()
  170.20 +>>> i2 = s['item']
  170.21 +>>> i2._deferred # Item must still be non-deferred
  170.22 +False
  170.23 +
  170.24  # Finally, we need to flush the app cache for the defer module.
  170.25  # Using only/defer creates some artifical entries in the app cache
  170.26  # that messes up later tests. Purge all entries, just to be sure.
   171.1 --- a/tests/regressiontests/forms/fields.py	Sat Sep 12 18:53:56 2009 -0500
   171.2 +++ b/tests/regressiontests/forms/fields.py	Fri Nov 13 11:27:59 2009 -0600
   171.3 @@ -767,6 +767,13 @@
   171.4  >>> f.clean('example@valid-with-hyphens.com')
   171.5  u'example@valid-with-hyphens.com'
   171.6  
   171.7 +# Check for runaway regex security problem. This will take for-freeking-ever
   171.8 +# if the security fix isn't in place.
   171.9 +>>> f.clean('viewx3dtextx26qx3d@yahoo.comx26latlngx3d15854521645943074058')
  171.10 +Traceback (most recent call last):
  171.11 +    ...
  171.12 +ValidationError: [u'Enter a valid e-mail address.']
  171.13 +
  171.14  >>> f = EmailField(required=False)
  171.15  >>> f.clean('')
  171.16  u''
  171.17 @@ -972,6 +979,32 @@
  171.18  Traceback (most recent call last):
  171.19  ...
  171.20  ValidationError: [u'Enter a valid URL.']
  171.21 +>>> f.clean('.')
  171.22 +Traceback (most recent call last):
  171.23 +...
  171.24 +ValidationError: [u'Enter a valid URL.']
  171.25 +>>> f.clean('com.')
  171.26 +Traceback (most recent call last):
  171.27 +...
  171.28 +ValidationError: [u'Enter a valid URL.']
  171.29 +>>> f.clean('http://example.com.')
  171.30 +u'http://example.com./'
  171.31 +>>> f.clean('example.com.')
  171.32 +u'http://example.com./'
  171.33 +
  171.34 +# hangs "forever" if catastrophic backtracking in ticket:#11198 not fixed
  171.35 +>>> f.clean('http://%s' % ("X"*200,))
  171.36 +Traceback (most recent call last):
  171.37 +...
  171.38 +ValidationError: [u'Enter a valid URL.']
  171.39 +
  171.40 +# a second test, to make sure the problem is really addressed, even on 
  171.41 +# domains that don't fail the domain label length check in the regex
  171.42 +>>> f.clean('http://%s' % ("X"*60,))
  171.43 +Traceback (most recent call last):
  171.44 +...
  171.45 +ValidationError: [u'Enter a valid URL.']
  171.46 +
  171.47  >>> f.clean('http://.com')
  171.48  Traceback (most recent call last):
  171.49  ...
   172.1 --- a/tests/regressiontests/m2m_through_regress/models.py	Sat Sep 12 18:53:56 2009 -0500
   172.2 +++ b/tests/regressiontests/m2m_through_regress/models.py	Fri Nov 13 11:27:59 2009 -0600
   172.3 @@ -84,22 +84,22 @@
   172.4  >>> bob.group_set = []
   172.5  Traceback (most recent call last):
   172.6  ...
   172.7 -AttributeError: Cannot set values on a ManyToManyField which specifies an intermediary model.  Use Membership's Manager instead.
   172.8 +AttributeError: Cannot set values on a ManyToManyField which specifies an intermediary model.  Use m2m_through_regress.Membership's Manager instead.
   172.9  
  172.10  >>> roll.members = []
  172.11  Traceback (most recent call last):
  172.12  ...
  172.13 -AttributeError: Cannot set values on a ManyToManyField which specifies an intermediary model.  Use Membership's Manager instead.
  172.14 +AttributeError: Cannot set values on a ManyToManyField which specifies an intermediary model.  Use m2m_through_regress.Membership's Manager instead.
  172.15  
  172.16  >>> rock.members.create(name='Anne')
  172.17  Traceback (most recent call last):
  172.18  ...
  172.19 -AttributeError: Cannot use create() on a ManyToManyField which specifies an intermediary model.  Use Membership's Manager instead.
  172.20 +AttributeError: Cannot use create() on a ManyToManyField which specifies an intermediary model.  Use m2m_through_regress.Membership's Manager instead.
  172.21  
  172.22  >>> bob.group_set.create(name='Funk')
  172.23  Traceback (most recent call last):
  172.24  ...
  172.25 -AttributeError: Cannot use create() on a ManyToManyField which specifies an intermediary model.  Use Membership's Manager instead.
  172.26 +AttributeError: Cannot use create() on a ManyToManyField which specifies an intermediary model.  Use m2m_through_regress.Membership's Manager instead.
  172.27  
  172.28  # Now test that the intermediate with a relationship outside
  172.29  # the current app (i.e., UserMembership) workds
   173.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   173.2 +++ b/tests/regressiontests/mail/custombackend.py	Fri Nov 13 11:27:59 2009 -0600
   173.3 @@ -0,0 +1,15 @@
   173.4 +"""A custom backend for testing."""
   173.5 +
   173.6 +from django.core.mail.backends.base import BaseEmailBackend
   173.7 +
   173.8 +
   173.9 +class EmailBackend(BaseEmailBackend):
  173.10 +
  173.11 +    def __init__(self, *args, **kwargs):
  173.12 +        super(EmailBackend, self).__init__(*args, **kwargs)
  173.13 +        self.test_outbox = []
  173.14 +
  173.15 +    def send_messages(self, email_messages):
  173.16 +        # Messages are stored in a instance variable for testing.
  173.17 +        self.test_outbox.extend(email_messages)
  173.18 +        return len(email_messages)
   174.1 --- a/tests/regressiontests/mail/tests.py	Sat Sep 12 18:53:56 2009 -0500
   174.2 +++ b/tests/regressiontests/mail/tests.py	Fri Nov 13 11:27:59 2009 -0600
   174.3 @@ -1,10 +1,18 @@
   174.4  # coding: utf-8
   174.5 +
   174.6  r"""
   174.7  # Tests for the django.core.mail.
   174.8  
   174.9 +>>> import os
  174.10 +>>> import shutil
  174.11 +>>> import tempfile
  174.12 +>>> from StringIO import StringIO
  174.13  >>> from django.conf import settings
  174.14  >>> from django.core import mail
  174.15  >>> from django.core.mail import EmailMessage, mail_admins, mail_managers, EmailMultiAlternatives
  174.16 +>>> from django.core.mail import send_mail, send_mass_mail
  174.17 +>>> from django.core.mail.backends.base import BaseEmailBackend
  174.18 +>>> from django.core.mail.backends import console, dummy, locmem, filebased, smtp
  174.19  >>> from django.utils.translation import ugettext_lazy
  174.20  
  174.21  # Test normal ascii character case:
  174.22 @@ -85,8 +93,6 @@
  174.23  >>> mail_managers('hi','there')
  174.24  >>> len(mail.outbox)
  174.25  1
  174.26 ->>> settings.ADMINS = old_admins
  174.27 ->>> settings.MANAGERS = old_managers
  174.28  
  174.29  # Make sure we can manually set the From header (#9214)
  174.30  
  174.31 @@ -95,6 +101,17 @@
  174.32  >>> message['From']
  174.33  'from@example.com'
  174.34  
  174.35 +# Regression for #11144 - When a to/from/cc header contains unicode,
  174.36 +# make sure the email addresses are parsed correctly (especially
  174.37 +# with regards to commas)
  174.38 +>>> email = EmailMessage('Subject', 'Content', 'from@example.com', ['"Firstname Sürname" <to@example.com>','other@example.com'])
  174.39 +>>> email.message()['To']
  174.40 +'=?utf-8?q?Firstname_S=C3=BCrname?= <to@example.com>, other@example.com'
  174.41 +
  174.42 +>>> email = EmailMessage('Subject', 'Content', 'from@example.com', ['"Sürname, Firstname" <to@example.com>','other@example.com'])
  174.43 +>>> email.message()['To']
  174.44 +'=?utf-8?q?S=C3=BCrname=2C_Firstname?= <to@example.com>, other@example.com'
  174.45 +
  174.46  # Handle attachments within an multipart/alternative mail correctly (#9367)
  174.47  # (test is not as precise/clear as it could be w.r.t. email tree structure,
  174.48  #  but it's good enough.)
  174.49 @@ -138,4 +155,217 @@
  174.50  JVBERi0xLjQuJS4uLg==
  174.51  ...
  174.52  
  174.53 +# Make sure that the console backend writes to stdout by default
  174.54 +>>> connection = console.EmailBackend()
  174.55 +>>> email = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'})
  174.56 +>>> connection.send_messages([email])
  174.57 +Content-Type: text/plain; charset="utf-8"
  174.58 +MIME-Version: 1.0
  174.59 +Content-Transfer-Encoding: quoted-printable
  174.60 +Subject: Subject
  174.61 +From: from@example.com
  174.62 +To: to@example.com
  174.63 +Date: ...
  174.64 +Message-ID: ...
  174.65 +
  174.66 +Content
  174.67 +-------------------------------------------------------------------------------
  174.68 +1
  174.69 +
  174.70 +# Test that the console backend can be pointed at an arbitrary stream
  174.71 +>>> s = StringIO()
  174.72 +>>> connection = mail.get_connection('django.core.mail.backends.console', stream=s)
  174.73 +>>> send_mail('Subject', 'Content', 'from@example.com', ['to@example.com'], connection=connection)
  174.74 +1
  174.75 +>>> print s.getvalue()
  174.76 +Content-Type: text/plain; charset="utf-8"
  174.77 +MIME-Version: 1.0
  174.78 +Content-Transfer-Encoding: quoted-printable
  174.79 +Subject: Subject
  174.80 +From: from@example.com
  174.81 +To: to@example.com
  174.82 +Date: ...
  174.83 +Message-ID: ...
  174.84 +
  174.85 +Content
  174.86 +-------------------------------------------------------------------------------
  174.87 +
  174.88 +# Make sure that dummy backends returns correct number of sent messages
  174.89 +>>> connection = dummy.EmailBackend()
  174.90 +>>> email = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'})
  174.91 +>>> connection.send_messages([email, email, email])
  174.92 +3
  174.93 +
  174.94 +# Make sure that locmen backend populates the outbox
  174.95 +>>> mail.outbox = []
  174.96 +>>> connection = locmem.EmailBackend()
  174.97 +>>> email1 = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'})
  174.98 +>>> email2 = EmailMessage('Subject 2', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'})
  174.99 +>>> connection.send_messages([email1, email2])
 174.100 +2
 174.101 +>>> len(mail.outbox)
 174.102 +2
 174.103 +>>> mail.outbox[0].subject
 174.104 +'Subject'
 174.105 +>>> mail.outbox[1].subject
 174.106 +'Subject 2'
 174.107 +
 174.108 +# Make sure that multiple locmem connections share mail.outbox
 174.109 +>>> mail.outbox = []
 174.110 +>>> connection1 = locmem.EmailBackend()
 174.111 +>>> connection2 = locmem.EmailBackend()
 174.112 +>>> email = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'})
 174.113 +>>> connection1.send_messages([email])
 174.114 +1
 174.115 +>>> connection2.send_messages([email])
 174.116 +1
 174.117 +>>> len(mail.outbox)
 174.118 +2
 174.119 +
 174.120 +# Make sure that the file backend write to the right location
 174.121 +>>> tmp_dir = tempfile.mkdtemp()
 174.122 +>>> connection = filebased.EmailBackend(file_path=tmp_dir)
 174.123 +>>> email = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'})
 174.124 +>>> connection.send_messages([email])
 174.125 +1
 174.126 +>>> len(os.listdir(tmp_dir))
 174.127 +1
 174.128 +>>> print open(os.path.join(tmp_dir, os.listdir(tmp_dir)[0])).read()
 174.129 +Content-Type: text/plain; charset="utf-8"
 174.130 +MIME-Version: 1.0
 174.131 +Content-Transfer-Encoding: quoted-printable
 174.132 +Subject: Subject
 174.133 +From: from@example.com
 174.134 +To: to@example.com
 174.135 +Date: ...
 174.136 +Message-ID: ...
 174.137 +
 174.138 +Content
 174.139 +-------------------------------------------------------------------------------
 174.140 +
 174.141 +>>> connection2 = filebased.EmailBackend(file_path=tmp_dir)
 174.142 +>>> connection2.send_messages([email])
 174.143 +1
 174.144 +>>> len(os.listdir(tmp_dir))
 174.145 +2
 174.146 +>>> connection.send_messages([email])
 174.147 +1
 174.148 +>>> len(os.listdir(tmp_dir))
 174.149 +2
 174.150 +>>> email.connection = filebased.EmailBackend(file_path=tmp_dir)
 174.151 +>>> connection_created = connection.open()
 174.152 +>>> num_sent = email.send()
 174.153 +>>> len(os.listdir(tmp_dir))
 174.154 +3
 174.155 +>>> num_sent = email.send()
 174.156 +>>> len(os.listdir(tmp_dir))
 174.157 +3
 174.158 +>>> connection.close()
 174.159 +>>> shutil.rmtree(tmp_dir)
 174.160 +
 174.161 +# Make sure that get_connection() accepts arbitrary keyword that might be
 174.162 +# used with custom backends.
 174.163 +>>> c = mail.get_connection(fail_silently=True, foo='bar')
 174.164 +>>> c.fail_silently
 174.165 +True
 174.166 +
 174.167 +# Test custom backend defined in this suite.
 174.168 +>>> conn = mail.get_connection('regressiontests.mail.custombackend')
 174.169 +>>> hasattr(conn, 'test_outbox')
 174.170 +True
 174.171 +>>> email = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'})
 174.172 +>>> conn.send_messages([email])
 174.173 +1
 174.174 +>>> len(conn.test_outbox)
 174.175 +1
 174.176 +
 174.177 +# Test backend argument of mail.get_connection()
 174.178 +>>> isinstance(mail.get_connection('django.core.mail.backends.smtp'), smtp.EmailBackend)
 174.179 +True
 174.180 +>>> isinstance(mail.get_connection('django.core.mail.backends.locmem'), locmem.EmailBackend)
 174.181 +True
 174.182 +>>> isinstance(mail.get_connection('django.core.mail.backends.dummy'), dummy.EmailBackend)
 174.183 +True
 174.184 +>>> isinstance(mail.get_connection('django.core.mail.backends.console'), console.EmailBackend)
 174.185 +True
 174.186 +>>> tmp_dir = tempfile.mkdtemp()
 174.187 +>>> isinstance(mail.get_connection('django.core.mail.backends.filebased', file_path=tmp_dir), filebased.EmailBackend)
 174.188 +True
 174.189 +>>> shutil.rmtree(tmp_dir)
 174.190 +>>> isinstance(mail.get_connection(), locmem.EmailBackend)
 174.191 +True
 174.192 +
 174.193 +# Test connection argument of send_mail() et al
 174.194 +>>> connection = mail.get_connection('django.core.mail.backends.console')
 174.195 +>>> send_mail('Subject', 'Content', 'from@example.com', ['to@example.com'], connection=connection)
 174.196 +Content-Type: text/plain; charset="utf-8"
 174.197 +MIME-Version: 1.0
 174.198 +Content-Transfer-Encoding: quoted-printable
 174.199 +Subject: Subject
 174.200 +From: from@example.com
 174.201 +To: to@example.com
 174.202 +Date: ...
 174.203 +Message-ID: ...
 174.204 +
 174.205 +Content
 174.206 +-------------------------------------------------------------------------------
 174.207 +1
 174.208 +
 174.209 +>>> send_mass_mail([
 174.210 +...         ('Subject1', 'Content1', 'from1@example.com', ['to1@example.com']),
 174.211 +...         ('Subject2', 'Content2', 'from2@example.com', ['to2@example.com'])
 174.212 +...     ], connection=connection)
 174.213 +Content-Type: text/plain; charset="utf-8"
 174.214 +MIME-Version: 1.0
 174.215 +Content-Transfer-Encoding: quoted-printable
 174.216 +Subject: Subject1
 174.217 +From: from1@example.com
 174.218 +To: to1@example.com
 174.219 +Date: ...
 174.220 +Message-ID: ...
 174.221 +
 174.222 +Content1
 174.223 +-------------------------------------------------------------------------------
 174.224 +Content-Type: text/plain; charset="utf-8"
 174.225 +MIME-Version: 1.0
 174.226 +Content-Transfer-Encoding: quoted-printable
 174.227 +Subject: Subject2
 174.228 +From: from2@example.com
 174.229 +To: to2@example.com
 174.230 +Date: ...
 174.231 +Message-ID: ...
 174.232 +
 174.233 +Content2
 174.234 +-------------------------------------------------------------------------------
 174.235 +2
 174.236 +
 174.237 +>>> mail_admins('Subject', 'Content', connection=connection)
 174.238 +Content-Type: text/plain; charset="utf-8"
 174.239 +MIME-Version: 1.0
 174.240 +Content-Transfer-Encoding: quoted-printable
 174.241 +Subject: [Django] Subject
 174.242 +From: root@localhost
 174.243 +To: nobody@example.com
 174.244 +Date: ...
 174.245 +Message-ID: ...
 174.246 +
 174.247 +Content
 174.248 +-------------------------------------------------------------------------------
 174.249 +
 174.250 +>>> mail_managers('Subject', 'Content', connection=connection)
 174.251 +Content-Type: text/plain; charset="utf-8"
 174.252 +MIME-Version: 1.0
 174.253 +Content-Transfer-Encoding: quoted-printable
 174.254 +Subject: [Django] Subject
 174.255 +From: root@localhost
 174.256 +To: nobody@example.com
 174.257 +Date: ...
 174.258 +Message-ID: ...
 174.259 +
 174.260 +Content
 174.261 +-------------------------------------------------------------------------------
 174.262 +
 174.263 +>>> settings.ADMINS = old_admins
 174.264 +>>> settings.MANAGERS = old_managers
 174.265 +
 174.266  """
   175.1 --- a/tests/regressiontests/many_to_one_regress/models.py	Sat Sep 12 18:53:56 2009 -0500
   175.2 +++ b/tests/regressiontests/many_to_one_regress/models.py	Fri Nov 13 11:27:59 2009 -0600
   175.3 @@ -167,4 +167,10 @@
   175.4      ...
   175.5  ValueError: Cannot assign "<Child: Child object>": "Child.parent" must be a "Parent" instance.
   175.6  
   175.7 +# Regression for #12190 -- Should be able to instantiate a FK
   175.8 +# outside of a model, and interrogate its related field.
   175.9 +>>> cat = models.ForeignKey(Category)
  175.10 +>>> cat.rel.get_related_field().name
  175.11 +'id'
  175.12 +
  175.13  """}
   176.1 --- a/tests/regressiontests/model_formsets_regress/tests.py	Sat Sep 12 18:53:56 2009 -0500
   176.2 +++ b/tests/regressiontests/model_formsets_regress/tests.py	Fri Nov 13 11:27:59 2009 -0600
   176.3 @@ -140,3 +140,13 @@
   176.4              self.assertEqual(manager[1]['name'], 'Terry Gilliam')
   176.5          else:
   176.6              self.fail('Errors found on formset:%s' % form_set.errors)
   176.7 +
   176.8 +    def test_formset_with_none_instance(self):
   176.9 +        "A formset with instance=None can be created. Regression for #11872"
  176.10 +        Form = modelform_factory(User)
  176.11 +        FormSet = inlineformset_factory(User, UserSite)
  176.12 +
  176.13 +        # Instantiate the Form and FormSet to prove
  176.14 +        # you can create a formset with an instance of None
  176.15 +        form = Form(instance=None)
  176.16 +        formset = FormSet(instance=None)
   177.1 --- a/tests/regressiontests/model_inheritance_regress/models.py	Sat Sep 12 18:53:56 2009 -0500
   177.2 +++ b/tests/regressiontests/model_inheritance_regress/models.py	Fri Nov 13 11:27:59 2009 -0600
   177.3 @@ -110,6 +110,36 @@
   177.4          return "PK = %d, base_name = %s, derived_name = %s" \
   177.5                  % (self.customPK, self.base_name, self.derived_name)
   177.6  
   177.7 +# Check that abstract classes don't get m2m tables autocreated.
   177.8 +class Person(models.Model):
   177.9 +    name = models.CharField(max_length=100)
  177.10 +
  177.11 +    class Meta:
  177.12 +        ordering = ('name',)
  177.13 +
  177.14 +    def __unicode__(self):
  177.15 +        return self.name
  177.16 +
  177.17 +class AbstractEvent(models.Model):
  177.18 +    name = models.CharField(max_length=100)
  177.19 +    attendees = models.ManyToManyField(Person, related_name="%(class)s_set")
  177.20 +
  177.21 +    class Meta:
  177.22 +        abstract = True
  177.23 +        ordering = ('name',)
  177.24 +
  177.25 +    def __unicode__(self):
  177.26 +        return self.name
  177.27 +
  177.28 +class BirthdayParty(AbstractEvent):
  177.29 +    pass
  177.30 +
  177.31 +class BachelorParty(AbstractEvent):
  177.32 +    pass
  177.33 +
  177.34 +class MessyBachelorParty(BachelorParty):
  177.35 +    pass
  177.36 +
  177.37  __test__ = {'API_TESTS':"""
  177.38  # Regression for #7350, #7202
  177.39  # Check that when you create a Parent object with a specific reference to an
  177.40 @@ -318,5 +348,41 @@
  177.41  >>> ParkingLot3._meta.get_ancestor_link(Place).name  # the child->parent link
  177.42  "parent"
  177.43  
  177.44 +# Check that many-to-many relations defined on an abstract base class
  177.45 +# are correctly inherited (and created) on the child class.
  177.46 +>>> p1 = Person.objects.create(name='Alice')
  177.47 +>>> p2 = Person.objects.create(name='Bob')
  177.48 +>>> p3 = Person.objects.create(name='Carol')
  177.49 +>>> p4 = Person.objects.create(name='Dave')
  177.50 +
  177.51 +>>> birthday = BirthdayParty.objects.create(name='Birthday party for Alice')
  177.52 +>>> birthday.attendees = [p1, p3]
  177.53 +
  177.54 +>>> bachelor = BachelorParty.objects.create(name='Bachelor party for Bob')
  177.55 +>>> bachelor.attendees = [p2, p4]
  177.56 +
  177.57 +>>> print p1.birthdayparty_set.all()
  177.58 +[<BirthdayParty: Birthday party for Alice>]
  177.59 +
  177.60 +>>> print p1.bachelorparty_set.all()
  177.61 +[]
  177.62 +
  177.63 +>>> print p2.bachelorparty_set.all()
  177.64 +[<BachelorParty: Bachelor party for Bob>]
  177.65 +
  177.66 +# Check that a subclass of a subclass of an abstract model
  177.67 +# doesn't get it's own accessor.
  177.68 +>>> p2.messybachelorparty_set.all()
  177.69 +Traceback (most recent call last):
  177.70 +...
  177.71 +AttributeError: 'Person' object has no attribute 'messybachelorparty_set'
  177.72 +
  177.73 +# ... but it does inherit the m2m from it's parent
  177.74 +>>> messy = MessyBachelorParty.objects.create(name='Bachelor party for Dave')
  177.75 +>>> messy.attendees = [p4]
  177.76 +
  177.77 +>>> p4.bachelorparty_set.all()
  177.78 +[<BachelorParty: Bachelor party for Bob>, <BachelorParty: Bachelor party for Dave>]
  177.79 +
  177.80  """}
  177.81  
   178.1 --- a/tests/regressiontests/serializers_regress/models.py	Sat Sep 12 18:53:56 2009 -0500
   178.2 +++ b/tests/regressiontests/serializers_regress/models.py	Fri Nov 13 11:27:59 2009 -0600
   178.3 @@ -105,6 +105,9 @@
   178.4  
   178.5      data = models.CharField(max_length=30)
   178.6  
   178.7 +    class Meta:
   178.8 +        ordering = ('id',)
   178.9 +
  178.10  class UniqueAnchor(models.Model):
  178.11      """This is a model that can be used as
  178.12      something for other models to point at"""
  178.13 @@ -135,7 +138,7 @@
  178.14  
  178.15  class M2MIntermediateData(models.Model):
  178.16      data = models.ManyToManyField(Anchor, null=True, through='Intermediate')
  178.17 -    
  178.18 +
  178.19  class Intermediate(models.Model):
  178.20      left = models.ForeignKey(M2MIntermediateData)
  178.21      right = models.ForeignKey(Anchor)
  178.22 @@ -242,7 +245,7 @@
  178.23  
  178.24  class InheritAbstractModel(AbstractBaseModel):
  178.25      child_data = models.IntegerField()
  178.26 -    
  178.27 +
  178.28  class BaseModel(models.Model):
  178.29      parent_data = models.IntegerField()
  178.30  
  178.31 @@ -252,4 +255,3 @@
  178.32  class ExplicitInheritBaseModel(BaseModel):
  178.33      parent = models.OneToOneField(BaseModel)
  178.34      child_data = models.IntegerField()
  178.35 -    
  178.36 \ No newline at end of file
   179.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   179.2 +++ b/tests/regressiontests/signals_regress/models.py	Fri Nov 13 11:27:59 2009 -0600
   179.3 @@ -0,0 +1,89 @@
   179.4 +"""
   179.5 +Testing signals before/after saving and deleting.
   179.6 +"""
   179.7 +
   179.8 +from django.db import models
   179.9 +
  179.10 +class Author(models.Model):
  179.11 +    name = models.CharField(max_length=20)
  179.12 +
  179.13 +    def __unicode__(self):
  179.14 +        return self.name
  179.15 +
  179.16 +class Book(models.Model):
  179.17 +    name = models.CharField(max_length=20)
  179.18 +    authors = models.ManyToManyField(Author)
  179.19 +
  179.20 +    def __unicode__(self):
  179.21 +        return self.name
  179.22 +
  179.23 +def pre_save_test(signal, sender, instance, **kwargs):
  179.24 +    print 'pre_save signal,', instance
  179.25 +    if kwargs.get('raw'):
  179.26 +        print 'Is raw'
  179.27 +
  179.28 +def post_save_test(signal, sender, instance, **kwargs):
  179.29 +    print 'post_save signal,', instance
  179.30 +    if 'created' in kwargs:
  179.31 +        if kwargs['created']:
  179.32 +            print 'Is created'
  179.33 +        else:
  179.34 +            print 'Is updated'
  179.35 +    if kwargs.get('raw'):
  179.36 +        print 'Is raw'
  179.37 +
  179.38 +def pre_delete_test(signal, sender, instance, **kwargs):
  179.39 +    print 'pre_delete signal,', instance
  179.40 +    print 'instance.id is not None: %s' % (instance.id != None)
  179.41 +
  179.42 +def post_delete_test(signal, sender, instance, **kwargs):
  179.43 +    print 'post_delete signal,', instance
  179.44 +    print 'instance.id is not None: %s' % (instance.id != None)
  179.45 +
  179.46 +__test__ = {'API_TESTS':"""
  179.47 +
  179.48 +# Save up the number of connected signals so that we can check at the end
  179.49 +# that all the signals we register get properly unregistered (#9989)
  179.50 +>>> pre_signals = (len(models.signals.pre_save.receivers),
  179.51 +...                len(models.signals.post_save.receivers),
  179.52 +...                len(models.signals.pre_delete.receivers),
  179.53 +...                len(models.signals.post_delete.receivers))
  179.54 +
  179.55 +>>> models.signals.pre_save.connect(pre_save_test)
  179.56 +>>> models.signals.post_save.connect(post_save_test)
  179.57 +>>> models.signals.pre_delete.connect(pre_delete_test)
  179.58 +>>> models.signals.post_delete.connect(post_delete_test)
  179.59 +
  179.60 +>>> a1 = Author(name='Neal Stephenson')
  179.61 +>>> a1.save()
  179.62 +pre_save signal, Neal Stephenson
  179.63 +post_save signal, Neal Stephenson
  179.64 +Is created
  179.65 +
  179.66 +>>> b1 = Book(name='Snow Crash')
  179.67 +>>> b1.save()
  179.68 +pre_save signal, Snow Crash
  179.69 +post_save signal, Snow Crash
  179.70 +Is created
  179.71 +
  179.72 +# Assigning to m2m shouldn't generate an m2m signal
  179.73 +>>> b1.authors = [a1]
  179.74 +
  179.75 +# Removing an author from an m2m shouldn't generate an m2m signal
  179.76 +>>> b1.authors = []
  179.77 +
  179.78 +>>> models.signals.post_delete.disconnect(post_delete_test)
  179.79 +>>> models.signals.pre_delete.disconnect(pre_delete_test)
  179.80 +>>> models.signals.post_save.disconnect(post_save_test)
  179.81 +>>> models.signals.pre_save.disconnect(pre_save_test)
  179.82 +
  179.83 +# Check that all our signals got disconnected properly.
  179.84 +>>> post_signals = (len(models.signals.pre_save.receivers),
  179.85 +...                 len(models.signals.post_save.receivers),
  179.86 +...                 len(models.signals.pre_delete.receivers),
  179.87 +...                 len(models.signals.post_delete.receivers))
  179.88 +
  179.89 +>>> pre_signals == post_signals
  179.90 +True
  179.91 +
  179.92 +"""}
   180.1 --- a/tests/regressiontests/test_client_regress/models.py	Sat Sep 12 18:53:56 2009 -0500
   180.2 +++ b/tests/regressiontests/test_client_regress/models.py	Fri Nov 13 11:27:59 2009 -0600
   180.3 @@ -574,6 +574,23 @@
   180.4          self.assertEqual(response.status_code, 200)
   180.5          self.assertEqual(response.content, 'request method: DELETE')
   180.6  
   180.7 +class RequestMethodStringDataTests(TestCase):
   180.8 +    def test_post(self):
   180.9 +        "Request a view with string data via request method POST"
  180.10 +        # Regression test for #11371
  180.11 +        data = u'{"test": "json"}'
  180.12 +        response = self.client.post('/test_client_regress/request_methods/', data=data, content_type='application/json')
  180.13 +        self.assertEqual(response.status_code, 200)
  180.14 +        self.assertEqual(response.content, 'request method: POST')
  180.15 +
  180.16 +    def test_put(self):
  180.17 +        "Request a view with string data via request method PUT"
  180.18 +        # Regression test for #11371
  180.19 +        data = u'{"test": "json"}'
  180.20 +        response = self.client.put('/test_client_regress/request_methods/', data=data, content_type='application/json')
  180.21 +        self.assertEqual(response.status_code, 200)
  180.22 +        self.assertEqual(response.content, 'request method: PUT')
  180.23 +
  180.24  class QueryStringTests(TestCase):
  180.25      def test_get_like_requests(self):
  180.26          for method_name in ('get','head','options','put','delete'):
   181.1 --- a/tests/regressiontests/utils/dateformat.py	Sat Sep 12 18:53:56 2009 -0500
   181.2 +++ b/tests/regressiontests/utils/dateformat.py	Fri Nov 13 11:27:59 2009 -0600
   181.3 @@ -1,48 +1,35 @@
   181.4 -"""
   181.5 ->>> from datetime import datetime, date
   181.6 ->>> from django.utils.dateformat import format
   181.7 ->>> from django.utils.tzinfo import FixedOffset, LocalTimezone
   181.8 +import os
   181.9 +from unittest import TestCase
  181.10 +from datetime import datetime, date
  181.11 +from django.utils.dateformat import format
  181.12 +from django.utils.tzinfo import FixedOffset, LocalTimezone
  181.13  
  181.14 -# date
  181.15 ->>> d = date(2009, 5, 16)
  181.16 ->>> date.fromtimestamp(int(format(d, 'U'))) == d
  181.17 -True
  181.18 +class DateFormatTests(TestCase):
  181.19 +    def test_date(self):
  181.20 +        d = date(2009, 5, 16)
  181.21 +        self.assertEquals(date.fromtimestamp(int(format(d, 'U'))), d)
  181.22  
  181.23 -# Naive datetime
  181.24 ->>> dt = datetime(2009, 5, 16, 5, 30, 30)
  181.25 ->>> datetime.fromtimestamp(int(format(dt, 'U'))) == dt
  181.26 -True
  181.27 +    def test_naive_datetime(self):
  181.28 +        dt = datetime(2009, 5, 16, 5, 30, 30)
  181.29 +        self.assertEquals(datetime.fromtimestamp(int(format(dt, 'U'))), dt)
  181.30  
  181.31 -# datetime with local tzinfo
  181.32 ->>> ltz = LocalTimezone(datetime.now())
  181.33 ->>> dt = datetime(2009, 5, 16, 5, 30, 30, tzinfo=ltz)
  181.34 ->>> datetime.fromtimestamp(int(format(dt, 'U')), ltz) == dt
  181.35 -True
  181.36 ->>> datetime.fromtimestamp(int(format(dt, 'U'))) == dt.replace(tzinfo=None)
  181.37 -True
  181.38 +    def test_datetime_with_local_tzinfo(self):
  181.39 +        ltz = LocalTimezone(datetime.now())
  181.40 +        dt = datetime(2009, 5, 16, 5, 30, 30, tzinfo=ltz)
  181.41 +        self.assertEquals(datetime.fromtimestamp(int(format(dt, 'U')), ltz), dt)
  181.42 +        self.assertEquals(datetime.fromtimestamp(int(format(dt, 'U'))), dt.replace(tzinfo=None))
  181.43  
  181.44 -# datetime with arbitrary tzinfo
  181.45 ->>> tz = FixedOffset(-510)
  181.46 ->>> ltz = LocalTimezone(datetime.now())
  181.47 ->>> dt = datetime(2009, 5, 16, 5, 30, 30, tzinfo=tz)
  181.48 ->>> datetime.fromtimestamp(int(format(dt, 'U')), tz) == dt
  181.49 -True
  181.50 ->>> datetime.fromtimestamp(int(format(dt, 'U')), ltz) == dt
  181.51 -True
  181.52 ->>> datetime.fromtimestamp(int(format(dt, 'U'))) == dt.astimezone(ltz).replace(tzinfo=None)
  181.53 -True
  181.54 ->>> datetime.fromtimestamp(int(format(dt, 'U')), tz).utctimetuple() == dt.utctimetuple()
  181.55 -True
  181.56 ->>> datetime.fromtimestamp(int(format(dt, 'U')), ltz).utctimetuple() == dt.utctimetuple()
  181.57 -True
  181.58 +    def test_datetime_with_tzinfo(self):
  181.59 +        tz = FixedOffset(-510)
  181.60 +        ltz = LocalTimezone(datetime.now())
  181.61 +        dt = datetime(2009, 5, 16, 5, 30, 30, tzinfo=tz)
  181.62 +        self.assertEquals(datetime.fromtimestamp(int(format(dt, 'U')), tz), dt)
  181.63 +        self.assertEquals(datetime.fromtimestamp(int(format(dt, 'U')), ltz), dt)
  181.64 +        self.assertEquals(datetime.fromtimestamp(int(format(dt, 'U'))), dt.astimezone(ltz).replace(tzinfo=None))
  181.65 +        self.assertEquals(datetime.fromtimestamp(int(format(dt, 'U')), tz).utctimetuple(), dt.utctimetuple())
  181.66 +        self.assertEquals(datetime.fromtimestamp(int(format(dt, 'U')), ltz).utctimetuple(), dt.utctimetuple())
  181.67  
  181.68 -# Epoch
  181.69 ->>> utc = FixedOffset(0)
  181.70 ->>> udt = datetime(1970, 1, 1, tzinfo=utc)
  181.71 ->>> format(udt, 'U')
  181.72 -u'0'
  181.73 -"""
  181.74 -
  181.75 -if __name__ == "__main__":
  181.76 -    import doctest
  181.77 -    doctest.testmod()
  181.78 +    def test_epoch(self):
  181.79 +        utc = FixedOffset(0)
  181.80 +        udt = datetime(1970, 1, 1, tzinfo=utc)
  181.81 +        self.assertEquals(format(udt, 'U'), u'0')
   182.1 --- a/tests/regressiontests/utils/tests.py	Sat Sep 12 18:53:56 2009 -0500
   182.2 +++ b/tests/regressiontests/utils/tests.py	Fri Nov 13 11:27:59 2009 -0600
   182.3 @@ -5,6 +5,7 @@
   182.4  from unittest import TestCase
   182.5  
   182.6  from django.utils import html, checksums
   182.7 +from django.utils.functional import SimpleLazyObject
   182.8  
   182.9  import timesince
  182.10  import datastructures
  182.11 @@ -23,10 +24,11 @@
  182.12  __test__ = {
  182.13      'timesince': timesince,
  182.14      'datastructures': datastructures,
  182.15 -    'dateformat': dateformat,
  182.16      'itercompat': itercompat,
  182.17  }
  182.18  
  182.19 +from dateformat import *
  182.20 +
  182.21  class TestUtilsHtml(TestCase):
  182.22  
  182.23      def check_output(self, function, value, output=None):
  182.24 @@ -161,6 +163,80 @@
  182.25          for value, output in items:
  182.26              self.check_output(f, value, output)
  182.27  
  182.28 +class _ComplexObject(object):
  182.29 +    def __init__(self, name):
  182.30 +        self.name = name
  182.31 +
  182.32 +    def __eq__(self, other):
  182.33 +        return self.name == other.name
  182.34 +
  182.35 +    def __hash__(self):
  182.36 +        return hash(self.name)
  182.37 +
  182.38 +    def __str__(self):
  182.39 +        return "I am _ComplexObject(%r)" % self.name
  182.40 +
  182.41 +    def __unicode__(self):
  182.42 +        return unicode(self.name)
  182.43 +
  182.44 +    def __repr__(self):
  182.45 +        return "_ComplexObject(%r)" % self.name
  182.46 +
  182.47 +complex_object = lambda: _ComplexObject("joe")
  182.48 +
  182.49 +class TestUtilsSimpleLazyObject(TestCase):
  182.50 +    """
  182.51 +    Tests for SimpleLazyObject
  182.52 +    """
  182.53 +    # Note that concrete use cases for SimpleLazyObject are also found in the
  182.54 +    # auth context processor tests (unless the implementation of that function
  182.55 +    # is changed).
  182.56 +
  182.57 +    def test_equality(self):
  182.58 +        self.assertEqual(complex_object(), SimpleLazyObject(complex_object))
  182.59 +        self.assertEqual(SimpleLazyObject(complex_object), complex_object())
  182.60 +
  182.61 +    def test_hash(self):
  182.62 +        # hash() equality would not be true for many objects, but it should be
  182.63 +        # for _ComplexObject
  182.64 +        self.assertEqual(hash(complex_object()),
  182.65 +                         hash(SimpleLazyObject(complex_object)))
  182.66 +
  182.67 +    def test_repr(self):
  182.68 +        # For debugging, it will really confuse things if there is no clue that
  182.69 +        # SimpleLazyObject is actually a proxy object. So we don't
  182.70 +        # proxy __repr__
  182.71 +        self.assert_("SimpleLazyObject" in repr(SimpleLazyObject(complex_object)))
  182.72 +
  182.73 +    def test_str(self):
  182.74 +        self.assertEqual("I am _ComplexObject('joe')", str(SimpleLazyObject(complex_object)))
  182.75 +
  182.76 +    def test_unicode(self):
  182.77 +        self.assertEqual(u"joe", unicode(SimpleLazyObject(complex_object)))
  182.78 +
  182.79 +    def test_class(self):
  182.80 +        # This is important for classes that use __class__ in things like
  182.81 +        # equality tests.
  182.82 +        self.assertEqual(_ComplexObject, SimpleLazyObject(complex_object).__class__)
  182.83 +
  182.84 +    def test_deepcopy(self):
  182.85 +        import copy
  182.86 +        # Check that we *can* do deep copy, and that it returns the right
  182.87 +        # objects.
  182.88 +
  182.89 +        # First, for an unevaluated SimpleLazyObject
  182.90 +        s = SimpleLazyObject(complex_object)
  182.91 +        assert s._wrapped is None
  182.92 +        s2 = copy.deepcopy(s)
  182.93 +        assert s._wrapped is None # something has gone wrong is s is evaluated
  182.94 +        self.assertEqual(s2, complex_object())
  182.95 +
  182.96 +        # Second, for an evaluated SimpleLazyObject
  182.97 +        name = s.name # evaluate
  182.98 +        assert s._wrapped is not None
  182.99 +        s3 = copy.deepcopy(s)
 182.100 +        self.assertEqual(s3, complex_object())
 182.101 +
 182.102  if __name__ == "__main__":
 182.103      import doctest
 182.104      doctest.testmod()
   183.1 --- a/tests/regressiontests/views/tests/generic/date_based.py	Sat Sep 12 18:53:56 2009 -0500
   183.2 +++ b/tests/regressiontests/views/tests/generic/date_based.py	Fri Nov 13 11:27:59 2009 -0600
   183.3 @@ -100,7 +100,7 @@
   183.4  
   183.5          now = datetime.now()
   183.6          prev_month = now.date().replace(day=1)
   183.7 -        if prev_month.month == 11:
   183.8 +        if prev_month.month == 1:
   183.9              prev_month = prev_month.replace(year=prev_month.year-1, month=12)
  183.10          else:
  183.11              prev_month = prev_month.replace(month=prev_month.month-1)
  183.12 @@ -121,4 +121,4 @@
  183.13          article = Article.objects.create(title="example", author=author, date_created=datetime(2004, 1, 21, 0, 0, 1))
  183.14          response = self.client.get('/views/date_based/archive_day/2004/1/21/')
  183.15          self.assertEqual(response.status_code, 200)
  183.16 -        self.assertEqual(response.context['object_list'][0], article)
  183.17 \ No newline at end of file
  183.18 +        self.assertEqual(response.context['object_list'][0], article)