From 3fb2662edcdfb03ebb4b2ab60ffe309104809246 Mon Sep 17 00:00:00 2001 From: Martey Dodoo Date: Sun, 9 Sep 2012 00:35:40 -0400 Subject: [PATCH 01/21] Fixes #18933. Fixes code example in docstring. Makes code example of silent keyword docstring in cycle templatetag method the same as in the documentation. --- django/template/defaulttags.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py index cb2ecd26d8..ea1dd0281e 100644 --- a/django/template/defaulttags.py +++ b/django/template/defaulttags.py @@ -531,11 +531,9 @@ def cycle(parser, token): The optional flag "silent" can be used to prevent the cycle declaration from returning any value:: - {% cycle 'row1' 'row2' as rowcolors silent %}{# no value here #} {% for o in some_list %} - {# first value will be "row1" #} - ... - + {% cycle 'row1' 'row2' as rowcolors silent %} + {% include "subtemplate.html " %} {% endfor %} """ From 9ca17f883d7a637581befe8092120242d0534673 Mon Sep 17 00:00:00 2001 From: Florian Apolloner Date: Sun, 9 Sep 2012 13:00:10 +0200 Subject: [PATCH 02/21] Replace nested try/finally try/except with try/except/finally. --- django/core/handlers/wsgi.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/django/core/handlers/wsgi.py b/django/core/handlers/wsgi.py index e445d07a61..7a32a3dac7 100644 --- a/django/core/handlers/wsgi.py +++ b/django/core/handlers/wsgi.py @@ -223,18 +223,17 @@ class WSGIHandler(base.BaseHandler): set_script_prefix(base.get_script_name(environ)) signals.request_started.send(sender=self.__class__) try: - try: - request = self.request_class(environ) - except UnicodeDecodeError: - logger.warning('Bad Request (UnicodeDecodeError)', - exc_info=sys.exc_info(), - extra={ - 'status_code': 400, - } - ) - response = http.HttpResponseBadRequest() - else: - response = self.get_response(request) + request = self.request_class(environ) + except UnicodeDecodeError: + logger.warning('Bad Request (UnicodeDecodeError)', + exc_info=sys.exc_info(), + extra={ + 'status_code': 400, + } + ) + response = http.HttpResponseBadRequest() + else: + response = self.get_response(request) finally: signals.request_finished.send(sender=self.__class__) From ffe8bc00bf9b94a366ea3d1998a2712a908a9f1b Mon Sep 17 00:00:00 2001 From: Karen Tracey Date: Sun, 9 Sep 2012 12:53:32 -0400 Subject: [PATCH 03/21] Replaced backwards-incompatible changes reference The previously-referenced wiki page documents backwards-incompatible changes from .96 to 1.0. Changed that referece to point to current in-development release notes, which is where such changes are now documented. --- docs/releases/index.txt | 1 + docs/topics/install.txt | 7 +++---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/releases/index.txt b/docs/releases/index.txt index fa55a4d206..2329d1effa 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -14,6 +14,7 @@ up to and including the new version. Final releases ============== +.. _development_release_notes: 1.5 release ----------- diff --git a/docs/topics/install.txt b/docs/topics/install.txt index 890c5e3195..39b9a93c04 100644 --- a/docs/topics/install.txt +++ b/docs/topics/install.txt @@ -257,15 +257,14 @@ Installing the development version If you decide to use the latest development version of Django, you'll want to pay close attention to `the development timeline`_, - and you'll want to keep an eye on `the list of - backwards-incompatible changes`_. This will help you stay on top - of any new features you might want to use, as well as any changes + and you'll want to keep an eye on the :ref:`release notes for the + upcoming release `. This will help you stay + on top of any new features you might want to use, as well as any changes you'll need to make to your code when updating your copy of Django. (For stable releases, any necessary changes are documented in the release notes.) .. _the development timeline: https://code.djangoproject.com/timeline -.. _the list of backwards-incompatible changes: https://code.djangoproject.com/wiki/BackwardsIncompatibleChanges If you'd like to be able to update your Django code occasionally with the latest bug fixes and improvements, follow these instructions: From 75ef980e20fc0441aedb0645aa471c9fe606e3b0 Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Sun, 9 Sep 2012 11:37:16 -0600 Subject: [PATCH 04/21] Fix Python 3 test failure introduced in a78dd109. --- django/contrib/auth/decorators.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/django/contrib/auth/decorators.py b/django/contrib/auth/decorators.py index 0fc9f3754a..11518193e7 100644 --- a/django/contrib/auth/decorators.py +++ b/django/contrib/auth/decorators.py @@ -24,7 +24,9 @@ def user_passes_test(test_func, login_url=None, redirect_field_name=REDIRECT_FIE if test_func(request.user): return view_func(request, *args, **kwargs) path = request.build_absolute_uri() - resolved_login_url = resolve_url(login_url or settings.LOGIN_URL) + # urlparse chokes on lazy objects in Python 3, force to str + resolved_login_url = force_str( + resolve_url(login_url or settings.LOGIN_URL)) # If the login url is the same scheme and net location then just # use the path as the "next" url. login_scheme, login_netloc = urlparse(resolved_login_url)[:2] @@ -33,7 +35,8 @@ def user_passes_test(test_func, login_url=None, redirect_field_name=REDIRECT_FIE (not login_netloc or login_netloc == current_netloc)): path = request.get_full_path() from django.contrib.auth.views import redirect_to_login - return redirect_to_login(path, login_url, redirect_field_name) + return redirect_to_login( + path, resolved_login_url, redirect_field_name) return _wrapped_view return decorator From fcec904e4f3582a45d4d8e309e71e9f0c4d79a0c Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Sun, 9 Sep 2012 12:13:42 -0600 Subject: [PATCH 05/21] Fix an HTML-parser test that's failed in Python 2.6.8 since 5c79dd58. The problem description in #18239 asserted that http://bugs.python.org/issue670664 was fixed in Python 2.6.8, but based on http://bugs.python.org/issue670664#msg146770 it appears that's not correct; the fix was only applied in 2.7, 3.2, and Python trunk. Therefore we must use our patched HTMLParser subclass in all Python 2.6 versions. --- django/utils/html_parser.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/django/utils/html_parser.py b/django/utils/html_parser.py index d7311f253b..6ccb665249 100644 --- a/django/utils/html_parser.py +++ b/django/utils/html_parser.py @@ -5,8 +5,7 @@ import sys current_version = sys.version_info use_workaround = ( - (current_version < (2, 6, 8)) or - (current_version >= (2, 7) and current_version < (2, 7, 3)) or + (current_version < (2, 7, 3)) or (current_version >= (3, 0) and current_version < (3, 2, 3)) ) From 2e928583861bc35f1ea137cb6f3353867f160bdb Mon Sep 17 00:00:00 2001 From: Karen Tracey Date: Sun, 9 Sep 2012 15:07:04 -0400 Subject: [PATCH 06/21] Fixed a couple of test failures on Windows. --- tests/regressiontests/utils/os_utils.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/tests/regressiontests/utils/os_utils.py b/tests/regressiontests/utils/os_utils.py index a78f348cf5..a205d67431 100644 --- a/tests/regressiontests/utils/os_utils.py +++ b/tests/regressiontests/utils/os_utils.py @@ -1,21 +1,26 @@ +import os + from django.utils import unittest from django.utils._os import safe_join class SafeJoinTests(unittest.TestCase): def test_base_path_ends_with_sep(self): + drive, path = os.path.splitdrive(safe_join("/abc/", "abc")) self.assertEqual( - safe_join("/abc/", "abc"), - "/abc/abc", + path, + "{0}abc{0}abc".format(os.path.sep) ) def test_root_path(self): + drive, path = os.path.splitdrive(safe_join("/", "path")) self.assertEqual( - safe_join("/", "path"), - "/path", + path, + "{0}path".format(os.path.sep), ) + drive, path = os.path.splitdrive(safe_join("/", "")) self.assertEqual( - safe_join("/", ""), - "/", + path, + os.path.sep, ) From cb1614f7b30f336db2a807b43696e20fdab7b78c Mon Sep 17 00:00:00 2001 From: Mike Grouchy Date: Mon, 10 Sep 2012 13:35:21 +0200 Subject: [PATCH 07/21] Fixed #18611 -- Display current date/time when running runserver --- django/core/management/commands/runserver.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/django/core/management/commands/runserver.py b/django/core/management/commands/runserver.py index c067c9c322..9c24701d2e 100644 --- a/django/core/management/commands/runserver.py +++ b/django/core/management/commands/runserver.py @@ -1,4 +1,5 @@ from optparse import make_option +from datetime import datetime import os import re import sys @@ -90,10 +91,12 @@ class Command(BaseCommand): self.stdout.write("Validating models...\n\n") self.validate(display_num_errors=True) self.stdout.write(( + "%(started_at)s\n" "Django version %(version)s, using settings %(settings)r\n" "Development server is running at http://%(addr)s:%(port)s/\n" "Quit the server with %(quit_command)s.\n" ) % { + "started_at": datetime.now().strftime('%B %d, %Y - %X'), "version": self.get_version(), "settings": settings.SETTINGS_MODULE, "addr": self._raw_ipv6 and '[%s]' % self.addr or self.addr, From f416ea9c8d8a5a7224236e429d51ce5606c95c5b Mon Sep 17 00:00:00 2001 From: Collin Anderson Date: Mon, 10 Sep 2012 12:11:24 -0300 Subject: [PATCH 08/21] fixed rfc comment typo in middleware/csrf.py --- django/middleware/csrf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/middleware/csrf.py b/django/middleware/csrf.py index 305b20e1c4..c9e8d73c82 100644 --- a/django/middleware/csrf.py +++ b/django/middleware/csrf.py @@ -105,7 +105,7 @@ class CsrfViewMiddleware(object): if getattr(callback, 'csrf_exempt', False): return None - # Assume that anything not defined as 'safe' by RC2616 needs protection + # Assume that anything not defined as 'safe' by RFC2616 needs protection if request.method not in ('GET', 'HEAD', 'OPTIONS', 'TRACE'): if getattr(request, '_dont_enforce_csrf_checks', False): # Mechanism to turn off CSRF checks for test suite. From f1bdfbd24bcc76d21c4bf7442959bdf630ac4dec Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Mon, 10 Sep 2012 19:21:29 +0200 Subject: [PATCH 09/21] Document and test 'type' usage in Widget attrs Refs #16630. --- django/forms/fields.py | 2 +- django/forms/widgets.py | 18 +++++++++++------- docs/ref/forms/widgets.txt | 11 ++++++----- tests/regressiontests/forms/tests/widgets.py | 18 +++++++++--------- 4 files changed, 27 insertions(+), 22 deletions(-) diff --git a/django/forms/fields.py b/django/forms/fields.py index 7f0d26d1aa..124e4f669a 100644 --- a/django/forms/fields.py +++ b/django/forms/fields.py @@ -199,7 +199,7 @@ class CharField(Field): def widget_attrs(self, widget): attrs = super(CharField, self).widget_attrs(widget) - if self.max_length is not None and isinstance(widget, (TextInput, PasswordInput)): + if self.max_length is not None and isinstance(widget, TextInput): # The HTML attribute is maxlength, not max_length. attrs.update({'maxlength': str(self.max_length)}) return attrs diff --git a/django/forms/widgets.py b/django/forms/widgets.py index 7651efccd0..763da0cff2 100644 --- a/django/forms/widgets.py +++ b/django/forms/widgets.py @@ -260,10 +260,17 @@ class Input(Widget): final_attrs['value'] = force_text(self._format_value(value)) return format_html('', flatatt(final_attrs)) + class TextInput(Input): input_type = 'text' -class PasswordInput(Input): + def __init__(self, attrs=None): + if attrs is not None: + self.input_type = attrs.pop('type', self.input_type) + super(TextInput, self).__init__(attrs) + + +class PasswordInput(TextInput): input_type = 'password' def __init__(self, attrs=None, render_value=False): @@ -400,9 +407,8 @@ class Textarea(Widget): flatatt(final_attrs), force_text(value)) -class DateInput(Input): - input_type = 'text' +class DateInput(TextInput): def __init__(self, attrs=None, format=None): super(DateInput, self).__init__(attrs) if format: @@ -431,9 +437,8 @@ class DateInput(Input): pass return super(DateInput, self)._has_changed(self._format_value(initial), data) -class DateTimeInput(Input): - input_type = 'text' +class DateTimeInput(TextInput): def __init__(self, attrs=None, format=None): super(DateTimeInput, self).__init__(attrs) if format: @@ -462,9 +467,8 @@ class DateTimeInput(Input): pass return super(DateTimeInput, self)._has_changed(self._format_value(initial), data) -class TimeInput(Input): - input_type = 'text' +class TimeInput(TextInput): def __init__(self, attrs=None, format=None): super(TimeInput, self).__init__(attrs) if format: diff --git a/docs/ref/forms/widgets.txt b/docs/ref/forms/widgets.txt index eab314a4cd..1935cb23bc 100644 --- a/docs/ref/forms/widgets.txt +++ b/docs/ref/forms/widgets.txt @@ -126,8 +126,9 @@ provided for each widget will be rendered exactly the same:: On a real Web page, you probably don't want every widget to look the same. You might want a larger input element for the comment, and you might want the -'name' widget to have some special CSS class. To do this, you use the -:attr:`Widget.attrs` argument when creating the widget: +'name' widget to have some special CSS class. It is also possible to specify +the 'type' attribute to take advantage of the new HTML5 input types. To do +this, you use the :attr:`Widget.attrs` argument when creating the widget: For example:: @@ -245,7 +246,7 @@ commonly used groups of widgets: Date input as a simple text box: ```` - Takes one optional argument: + Takes same arguments as :class:`TextInput`, with one more optional argument: .. attribute:: DateInput.format @@ -262,7 +263,7 @@ commonly used groups of widgets: Date/time input as a simple text box: ```` - Takes one optional argument: + Takes same arguments as :class:`TextInput`, with one more optional argument: .. attribute:: DateTimeInput.format @@ -279,7 +280,7 @@ commonly used groups of widgets: Time input as a simple text box: ```` - Takes one optional argument: + Takes same arguments as :class:`TextInput`, with one more optional argument: .. attribute:: TimeInput.format diff --git a/tests/regressiontests/forms/tests/widgets.py b/tests/regressiontests/forms/tests/widgets.py index 544ca41642..104144b288 100644 --- a/tests/regressiontests/forms/tests/widgets.py +++ b/tests/regressiontests/forms/tests/widgets.py @@ -31,9 +31,9 @@ class FormsWidgetTestCase(TestCase): self.assertHTMLEqual(w.render('email', 'ŠĐĆŽćžšđ', attrs={'class': 'fun'}), '') # You can also pass 'attrs' to the constructor: - w = TextInput(attrs={'class': 'fun'}) - self.assertHTMLEqual(w.render('email', ''), '') - self.assertHTMLEqual(w.render('email', 'foo@example.com'), '') + w = TextInput(attrs={'class': 'fun', 'type': 'email'}) + self.assertHTMLEqual(w.render('email', ''), '') + self.assertHTMLEqual(w.render('email', 'foo@example.com'), '') # 'attrs' passed to render() get precedence over those passed to the constructor: w = TextInput(attrs={'class': 'pretty'}) @@ -915,8 +915,8 @@ beatle J R Ringo False""") self.assertHTMLEqual(w.render('date', datetime.datetime(2007, 9, 17, 12, 51)), '') # Use 'format' to change the way a value is displayed. - w = DateTimeInput(format='%d/%m/%Y %H:%M') - self.assertHTMLEqual(w.render('date', d), '') + w = DateTimeInput(format='%d/%m/%Y %H:%M', attrs={'type': 'datetime'}) + self.assertHTMLEqual(w.render('date', d), '') self.assertFalse(w._has_changed(d, '17/09/2007 12:51')) # Make sure a custom format works with _has_changed. The hidden input will use @@ -938,8 +938,8 @@ beatle J R Ringo False""") self.assertHTMLEqual(w.render('date', '2007-09-17'), '') # Use 'format' to change the way a value is displayed. - w = DateInput(format='%d/%m/%Y') - self.assertHTMLEqual(w.render('date', d), '') + w = DateInput(format='%d/%m/%Y', attrs={'type': 'date'}) + self.assertHTMLEqual(w.render('date', d), '') self.assertFalse(w._has_changed(d, '17/09/2007')) # Make sure a custom format works with _has_changed. The hidden input will use @@ -963,8 +963,8 @@ beatle J R Ringo False""") self.assertHTMLEqual(w.render('time', '13:12:11'), '') # Use 'format' to change the way a value is displayed. - w = TimeInput(format='%H:%M') - self.assertHTMLEqual(w.render('time', t), '') + w = TimeInput(format='%H:%M', attrs={'type': 'time'}) + self.assertHTMLEqual(w.render('time', t), '') self.assertFalse(w._has_changed(t, '12:51')) # Make sure a custom format works with _has_changed. The hidden input will use From 6eb4f2569266391e223410ab7eaa2555ac0a4f10 Mon Sep 17 00:00:00 2001 From: Julien Phalip Date: Mon, 10 Sep 2012 17:19:35 -0700 Subject: [PATCH 10/21] Removed an obsolete documentation page about admin style customization. --- docs/contents.txt | 11 -- docs/faq/admin.txt | 5 +- docs/intro/whatsnext.txt | 3 +- docs/obsolete/_images/formrow.png | Bin 16601 -> 0 bytes docs/obsolete/_images/module.png | Bin 10072 -> 0 bytes docs/obsolete/_images/objecttools_01.png | Bin 1398 -> 0 bytes docs/obsolete/_images/objecttools_02.png | Bin 2268 -> 0 bytes docs/obsolete/admin-css.txt | 186 ----------------------- docs/obsolete/index.txt | 12 -- 9 files changed, 3 insertions(+), 214 deletions(-) delete mode 100644 docs/obsolete/_images/formrow.png delete mode 100644 docs/obsolete/_images/module.png delete mode 100644 docs/obsolete/_images/objecttools_01.png delete mode 100644 docs/obsolete/_images/objecttools_02.png delete mode 100644 docs/obsolete/admin-css.txt delete mode 100644 docs/obsolete/index.txt diff --git a/docs/contents.txt b/docs/contents.txt index 9bf0d685c4..736e1f62bf 100644 --- a/docs/contents.txt +++ b/docs/contents.txt @@ -28,14 +28,3 @@ Indices, glossary and tables * :ref:`genindex` * :ref:`modindex` * :ref:`glossary` - -Deprecated/obsolete documentation -================================= - -The following documentation covers features that have been deprecated or that -have been replaced in newer versions of Django. - -.. toctree:: - :maxdepth: 2 - - obsolete/index diff --git a/docs/faq/admin.txt b/docs/faq/admin.txt index 8ec7491903..ea6aa2e74e 100644 --- a/docs/faq/admin.txt +++ b/docs/faq/admin.txt @@ -91,8 +91,7 @@ The dynamically-generated admin site is ugly! How can I change it? We like it, but if you don't agree, you can modify the admin site's presentation by editing the CSS stylesheet and/or associated image files. The site is built using semantic HTML and plenty of CSS hooks, so any changes you'd -like to make should be possible by editing the stylesheet. We've got a -:doc:`guide to the CSS used in the admin ` to get you started. +like to make should be possible by editing the stylesheet. What browsers are supported for using the admin? ------------------------------------------------ @@ -104,5 +103,5 @@ There *may* be minor stylistic differences between supported browsers—for example, some browsers may not support rounded corners. These are considered acceptable variations in rendering. -.. _YUI's A-grade: http://yuilibrary.com/yui/docs/tutorials/gbs/ +.. _YUI's A-grade: http://yuilibrary.com/yui/docs/tutorials/gbs/ diff --git a/docs/intro/whatsnext.txt b/docs/intro/whatsnext.txt index cc793c8129..ea4b18de03 100644 --- a/docs/intro/whatsnext.txt +++ b/docs/intro/whatsnext.txt @@ -67,8 +67,7 @@ different needs: whathaveyou. * Finally, there's some "specialized" documentation not usually relevant to - most developers. This includes the :doc:`release notes `, - :doc:`documentation of obsolete features `, + most developers. This includes the :doc:`release notes ` and :doc:`internals documentation ` for those who want to add code to Django itself, and a :doc:`few other things that simply don't fit elsewhere `. diff --git a/docs/obsolete/_images/formrow.png b/docs/obsolete/_images/formrow.png deleted file mode 100644 index 164dd262b5f8b39891b77a1406a2df01184771e2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16601 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

J)M$@o6R|ZZj=T)4)Y}97lK;c$jfz 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 diff --git a/docs/obsolete/_images/module.png b/docs/obsolete/_images/module.png deleted file mode 100644 index 6acda978091070ec62c96c9d335b5bb0fdfb06e4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10072 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 Ef&~hkumAu6 diff --git a/docs/obsolete/_images/objecttools_02.png b/docs/obsolete/_images/objecttools_02.png deleted file mode 100644 index 06a854009bcef5dd425f9ca50b6239a60cd09b7f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2268 zcmV<22qX82P)e5Szl8VSr)(0-H=QOn~*V*HEf*DLITdf$Pz`&0v431 zVoR;9ota84I!_i~z>nagQ*hMS%A${(-=bk>lU!Okr_D$KFH*Zj>l^7_1f${=uN+u-+Xa-8TQp$ru z87SpSp-h$R@LUwYK4xWQ#pQCndi5$C4(Bra|LQReQ!14Q4jeF>%{4VOWo2b~;(0F+ z<%xfrL+8(*zj*OtE+;sSt5hnIBwJfswOXwtlhV18PNyF}eE9U~(_F6ih@PS-$YW?u zr?auK@z}9rfLSuO_XUw;@H?VRCX;#m06)KMwnvX1wY9a`Y&J2I5^E4l8y+6s`-vhz z4go{h5WGMS-S-rc7|3=s8yg#v)5SUAa5%1AyC!B*m@81IA3S&v2m~bk?r|lA4Z#ae z0p4pw{{zC?+uLF35!`5ge*X6D+X5zexdLOMPzd%Yf|lH^z(Bvvlrb>b`UeKS zZc-wp$p-qqCbi^5{o&qIokq!_?JFU8fWU$*4iRp&L)-&@BN2VVGL(xW*D)MD+WezGsbjFNXJ5`*pTqT1PA~mlflIza!oB*dPrTP3C)bjjb;r(DZk5CraY2? zgXD*MA=N>R!t>)d?=AQKy-&S5@y$(#srRccl9;$L=!iAhzp|qFABOJEvSCTV?6mh7 zW5I|*t07P-=o~{N7GEM8rzc#B8j#m)yoCy?(gqBv5n8NTW zR7bUmtR|?4@2!afz5xPTIg3RU_e=*VglUYh41Lm62j09>ZWc~$JDn&IpL4<;R2eL% zj$+FRB-4?4Ty3mVA*3J=)~f&5P>rG0g+&_XX3GInP9Q?1m3f_WF^bTdk2lGg2?&XJ zt7T$Ha|sm2b6mKNWIYxjDHMt`XU=d9BasNpBuoE!tyrvq|l925y1;h15f8;@YKz%ha0p6qWsg|?7MpK50DU4B8h!eL9ezdBk6Vd z;CiU-Gc{d0k>Wt(=XBKxJ7=S;<2*3^W5Xl=y*uWkiQ|6ki+N+bQCtE=@zitjjhRvK zH5Q9qzkWTBId2UDJE%o4C(i`&P-5|?;X8D_HTL%R(9bm6yrXx<(Gf%K#~PJNU)}yi zRr;Tvj6wFk>2=yZK~dRh2(7QtF{A@O&m|6#WJf0@d~94}feaA1nqm>nPJ66mv%b{= zP9pA^WrUQhJ32;8QB@UB#{GUj6%W65FGkw+hGsL^&ighD`uci$*>Xb$37RBKYI8l! zaKT+#rFh%&hvOUqMezu3nyY*H@?~}i4y8IeIueP*)vH&z3lJDUrD4rL5Z0A_`}T2% z3zC_=EzStctjbRNmlpHySEhfsH$KlO=_{dbXm6@r2}XeWmcG8W!<~PzK>bVwmsc}p z7|IYBot_DTxeP;*xhZ&Tg){*KLHF$*0?y6%91K&>r|H7Rw&mA!0f?=5pZk+qovwqHo@86f4 zDfU&MMy^!iC>@Qm3qP(@%Hz@KRyyEvr5w+8IZknO!p`TI2t_z2 z=8%jFr6yMagqM;L>2KQ~NidxUb|h(};V ztJM%ZKwuou*BwWcGhFUn#d2<#V%v-+_lgp0O$;%aZ7t71b z;SXEb?@Ufk?#x*WFR6{%DLAN<%xgq3^g<~ zSglr#M#E(i!sF!%^nqMB#e;vPEfz~%T^%e&us42(Lzf*v9$yW3@>VxEh=JUP{D7sW zwzd}jRfpi&M9IXkrx5K3ExX|aRwDT70S+NifJ|v7x%-vf2%_ZkgJFR89rVGVDgoII q21>cI8?1hqK1;dsVLsR0K=m89nOK7bs>~Sx0000 -

  • Add redirect
  • - - -.. image:: _images/objecttools_01.png - :alt: Object tools on a changelist page - -and from a form page: - -.. code-block:: html+django - - - -.. image:: _images/objecttools_02.png - :alt: Object tools on a form page - -Form Styles -=========== - -Fieldsets ---------- - -Admin forms are broken up into groups by ``fieldset`` elements. Each form fieldset -should have a class ``.module``. Each fieldset should have a header ``h2`` within the -fieldset at the top (except the first group in the form, and in some cases where the -group of fields doesn't have a logical label). - -Each fieldset can also take extra classes in addition to ``.module`` to apply -appropriate formatting to the group of fields. - -.aligned - This will align the labels and inputs side by side on the same line. -.wide - Used in combination with ``.aligned`` to widen the space available for the - labels. - -Form Rows ---------- - -Each row of the form (within the ``fieldset``) should be enclosed in a ``div`` -with class ``form-row``. If the field in the row is required, a class of -``required`` should also be added to the ``div.form-row``. - -.. image:: _images/formrow.png - :alt: Example use of form-row class - -Labels ------- - -Form labels should always precede the field, except in the case -of checkboxes and radio buttons, where the ``input`` should come first. Any -explanation or help text should follow the ``label`` in a ``p`` with class -``.help``. diff --git a/docs/obsolete/index.txt b/docs/obsolete/index.txt deleted file mode 100644 index ddc86237cc..0000000000 --- a/docs/obsolete/index.txt +++ /dev/null @@ -1,12 +0,0 @@ -Deprecated/obsolete documentation -================================= - -These documents cover features that have been deprecated or that have been -replaced in newer versions of Django. They're preserved here for folks using old -versions of Django or those still using deprecated APIs. No new code based on -these APIs should be written. - -.. toctree:: - :maxdepth: 1 - - admin-css \ No newline at end of file From 0133d66734f5b0470436a22f5b85bdf7266bf1cf Mon Sep 17 00:00:00 2001 From: Julien Phalip Date: Mon, 10 Sep 2012 17:27:50 -0700 Subject: [PATCH 11/21] Removed a colloquialism ("and then some") from the documentation index page that would be confusing to non-native English speakers. --- docs/index.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.txt b/docs/index.txt index 3f4e30385c..8b29c95fa2 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -5,7 +5,7 @@ Django documentation ==================== -.. rubric:: Everything you need to know about Django (and then some). +.. rubric:: Everything you need to know about Django. Getting help ============ From 859aa2a6c4496a0feafebef7c7ea8fc57d9913f4 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Wed, 12 Sep 2012 10:16:49 +0200 Subject: [PATCH 12/21] Fixed #18790 -- Encoded database password on Python 2 Thanks thcourbon@gmail.com for the report. --- django/db/backends/mysql/base.py | 3 ++- django/db/backends/postgresql_psycopg2/base.py | 3 ++- tests/regressiontests/backends/tests.py | 13 +++++++++++++ 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/django/db/backends/mysql/base.py b/django/db/backends/mysql/base.py index cec3b04f7e..4043014b8e 100644 --- a/django/db/backends/mysql/base.py +++ b/django/db/backends/mysql/base.py @@ -37,6 +37,7 @@ from django.db.backends.mysql.client import DatabaseClient from django.db.backends.mysql.creation import DatabaseCreation from django.db.backends.mysql.introspection import DatabaseIntrospection from django.db.backends.mysql.validation import DatabaseValidation +from django.utils.encoding import force_str from django.utils.functional import cached_property from django.utils.safestring import SafeBytes, SafeText from django.utils import six @@ -390,7 +391,7 @@ class DatabaseWrapper(BaseDatabaseWrapper): if settings_dict['NAME']: kwargs['db'] = settings_dict['NAME'] if settings_dict['PASSWORD']: - kwargs['passwd'] = settings_dict['PASSWORD'] + kwargs['passwd'] = force_str(settings_dict['PASSWORD']) if settings_dict['HOST'].startswith('/'): kwargs['unix_socket'] = settings_dict['HOST'] elif settings_dict['HOST']: diff --git a/django/db/backends/postgresql_psycopg2/base.py b/django/db/backends/postgresql_psycopg2/base.py index f6f534da8c..c8b88d5619 100644 --- a/django/db/backends/postgresql_psycopg2/base.py +++ b/django/db/backends/postgresql_psycopg2/base.py @@ -13,6 +13,7 @@ from django.db.backends.postgresql_psycopg2.client import DatabaseClient from django.db.backends.postgresql_psycopg2.creation import DatabaseCreation from django.db.backends.postgresql_psycopg2.version import get_version from django.db.backends.postgresql_psycopg2.introspection import DatabaseIntrospection +from django.utils.encoding import force_str from django.utils.log import getLogger from django.utils.safestring import SafeText, SafeBytes from django.utils import six @@ -172,7 +173,7 @@ class DatabaseWrapper(BaseDatabaseWrapper): if settings_dict['USER']: conn_params['user'] = settings_dict['USER'] if settings_dict['PASSWORD']: - conn_params['password'] = settings_dict['PASSWORD'] + conn_params['password'] = force_str(settings_dict['PASSWORD']) if settings_dict['HOST']: conn_params['host'] = settings_dict['HOST'] if settings_dict['PORT']: diff --git a/tests/regressiontests/backends/tests.py b/tests/regressiontests/backends/tests.py index e53b02032e..cfa298253c 100644 --- a/tests/regressiontests/backends/tests.py +++ b/tests/regressiontests/backends/tests.py @@ -401,6 +401,19 @@ class BackendTestCase(TestCase): self.assertEqual(list(cursor.fetchmany(2)), [('Jane', 'Doe'), ('John', 'Doe')]) self.assertEqual(list(cursor.fetchall()), [('Mary', 'Agnelline'), ('Peter', 'Parker')]) + def test_unicode_password(self): + old_password = connection.settings_dict['PASSWORD'] + connection.settings_dict['PASSWORD'] = "françois" + try: + cursor = connection.cursor() + except backend.Database.DatabaseError: + # As password is probably wrong, a database exception is expected + pass + except Exception as e: + self.fail("Unexpected error raised with unicode password: %s" % e) + finally: + connection.settings_dict['PASSWORD'] = old_password + def test_database_operations_helper_class(self): # Ticket #13630 self.assertTrue(hasattr(connection, 'ops')) From 703c266682be39f7153498ad0d8031231f12ee79 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Wed, 12 Sep 2012 11:21:58 +0200 Subject: [PATCH 13/21] Fixed #18182 -- Made is_usable_password check if hashing algorithm is correct The display of the ReadOnlyPasswordHashWidget has also been improved to distinguish empty/unusable password from erroneous password. Fixed #18453 also. Thanks danielr and Leo for the reports and Moritz Sichert for the initial patch. --- django/contrib/auth/forms.py | 28 ++++++++++++++-------------- django/contrib/auth/hashers.py | 8 +++++++- django/contrib/auth/tests/forms.py | 19 +++++++++++++------ django/contrib/auth/tests/hashers.py | 4 ++++ 4 files changed, 38 insertions(+), 21 deletions(-) diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py index 75b3ca4ece..08488237c7 100644 --- a/django/contrib/auth/forms.py +++ b/django/contrib/auth/forms.py @@ -11,7 +11,7 @@ from django.utils.translation import ugettext, ugettext_lazy as _ from django.contrib.auth import authenticate from django.contrib.auth.models import User -from django.contrib.auth.hashers import UNUSABLE_PASSWORD, is_password_usable, identify_hasher +from django.contrib.auth.hashers import UNUSABLE_PASSWORD, identify_hasher from django.contrib.auth.tokens import default_token_generator from django.contrib.sites.models import get_current_site @@ -24,22 +24,22 @@ mask_password = lambda p: "%s%s" % (p[:UNMASKED_DIGITS_TO_SHOW], "*" * max(len(p class ReadOnlyPasswordHashWidget(forms.Widget): def render(self, name, value, attrs): encoded = value - - if not is_password_usable(encoded): - return "None" - final_attrs = self.build_attrs(attrs) - try: - hasher = identify_hasher(encoded) - except ValueError: - summary = mark_safe("Invalid password format or unknown hashing algorithm.") + if encoded == '' or encoded == UNUSABLE_PASSWORD: + summary = mark_safe("%s" % ugettext("No password set.")) else: - summary = format_html_join('', - "{0}: {1} ", - ((ugettext(key), value) - for key, value in hasher.safe_summary(encoded).items()) - ) + try: + hasher = identify_hasher(encoded) + except ValueError: + summary = mark_safe("%s" % ugettext( + "Invalid password format or unknown hashing algorithm.")) + else: + summary = format_html_join('', + "{0}: {1} ", + ((ugettext(key), value) + for key, value in hasher.safe_summary(encoded).items()) + ) return format_html("{1}", flatatt(final_attrs), summary) diff --git a/django/contrib/auth/hashers.py b/django/contrib/auth/hashers.py index bd0c6778c9..c628059d34 100644 --- a/django/contrib/auth/hashers.py +++ b/django/contrib/auth/hashers.py @@ -28,7 +28,13 @@ def reset_hashers(**kwargs): def is_password_usable(encoded): - return (encoded is not None and encoded != UNUSABLE_PASSWORD) + if encoded is None or encoded == UNUSABLE_PASSWORD: + return False + try: + hasher = identify_hasher(encoded) + except ValueError: + return False + return True def check_password(password, encoded, setter=None, preferred='default'): diff --git a/django/contrib/auth/tests/forms.py b/django/contrib/auth/tests/forms.py index 594b55c633..8fee3501c5 100644 --- a/django/contrib/auth/tests/forms.py +++ b/django/contrib/auth/tests/forms.py @@ -236,23 +236,30 @@ class UserChangeFormTest(TestCase): # Just check we can create it form = MyUserForm({}) + def test_unsuable_password(self): + user = User.objects.get(username='empty_password') + user.set_unusable_password() + user.save() + form = UserChangeForm(instance=user) + self.assertIn(_("No password set."), form.as_table()) + def test_bug_17944_empty_password(self): user = User.objects.get(username='empty_password') form = UserChangeForm(instance=user) - # Just check that no error is raised. - form.as_table() + self.assertIn(_("Invalid password format or unknown hashing algorithm."), + form.as_table()) def test_bug_17944_unmanageable_password(self): user = User.objects.get(username='unmanageable_password') form = UserChangeForm(instance=user) - # Just check that no error is raised. - form.as_table() + self.assertIn(_("Invalid password format or unknown hashing algorithm."), + form.as_table()) def test_bug_17944_unknown_password_algorithm(self): user = User.objects.get(username='unknown_password') form = UserChangeForm(instance=user) - # Just check that no error is raised. - form.as_table() + self.assertIn(_("Invalid password format or unknown hashing algorithm."), + form.as_table()) @override_settings(USE_TZ=False, PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) diff --git a/django/contrib/auth/tests/hashers.py b/django/contrib/auth/tests/hashers.py index 673263b566..d867a57d98 100644 --- a/django/contrib/auth/tests/hashers.py +++ b/django/contrib/auth/tests/hashers.py @@ -100,6 +100,10 @@ class TestUtilsHashPass(unittest.TestCase): self.assertRaises(ValueError, doit) self.assertRaises(ValueError, identify_hasher, "lolcat$salt$hash") + def test_bad_encoded(self): + self.assertFalse(is_password_usable('letmein_badencoded')) + self.assertFalse(is_password_usable('')) + def test_low_level_pkbdf2(self): hasher = PBKDF2PasswordHasher() encoded = hasher.encode('letmein', 'seasalt') From 1aa218b857ccd5e7db3718e2c0b4d4739dcb075c Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Wed, 12 Sep 2012 11:56:58 +0200 Subject: [PATCH 14/21] Fixed test output check when password is blank --- django/contrib/auth/tests/forms.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/django/contrib/auth/tests/forms.py b/django/contrib/auth/tests/forms.py index 8fee3501c5..74aa47e199 100644 --- a/django/contrib/auth/tests/forms.py +++ b/django/contrib/auth/tests/forms.py @@ -246,8 +246,7 @@ class UserChangeFormTest(TestCase): def test_bug_17944_empty_password(self): user = User.objects.get(username='empty_password') form = UserChangeForm(instance=user) - self.assertIn(_("Invalid password format or unknown hashing algorithm."), - form.as_table()) + self.assertIn(_("No password set."), form.as_table()) def test_bug_17944_unmanageable_password(self): user = User.objects.get(username='unmanageable_password') From c2c8d4044e2c6345f6e8d0f8617755dec7cd55d5 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Wed, 12 Sep 2012 14:50:16 +0200 Subject: [PATCH 15/21] Made minimal changes to make gis test suite start with Python 3 --- django/contrib/gis/geos/libgeos.py | 2 +- django/contrib/gis/geos/mutable_list.py | 9 ++++++--- django/contrib/gis/geos/tests/__init__.py | 3 ++- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/django/contrib/gis/geos/libgeos.py b/django/contrib/gis/geos/libgeos.py index a4f5adf4d0..aed6cf366c 100644 --- a/django/contrib/gis/geos/libgeos.py +++ b/django/contrib/gis/geos/libgeos.py @@ -110,7 +110,7 @@ def geos_version_info(): is a release candidate (and what number release candidate), and the C API version. """ - ver = geos_version() + ver = geos_version().decode() m = version_regex.match(ver) if not m: raise GEOSException('Could not parse version info string "%s"' % ver) return dict((key, m.group(key)) for key in ('version', 'release_candidate', 'capi_version', 'major', 'minor', 'subminor')) diff --git a/django/contrib/gis/geos/mutable_list.py b/django/contrib/gis/geos/mutable_list.py index 69e50e6b3f..820cdfa5a4 100644 --- a/django/contrib/gis/geos/mutable_list.py +++ b/django/contrib/gis/geos/mutable_list.py @@ -215,15 +215,18 @@ class ListMixin(object): "Standard list reverse method" self[:] = self[-1::-1] - def sort(self, cmp=cmp, key=None, reverse=False): + def sort(self, cmp=None, key=None, reverse=False): "Standard list sort method" if key: temp = [(key(v),v) for v in self] - temp.sort(cmp=cmp, key=lambda x: x[0], reverse=reverse) + temp.sort(key=lambda x: x[0], reverse=reverse) self[:] = [v[1] for v in temp] else: temp = list(self) - temp.sort(cmp=cmp, reverse=reverse) + if cmp is not None: + temp.sort(cmp=cmp, reverse=reverse) + else: + temp.sort(reverse=reverse) self[:] = temp ### Private routines ### diff --git a/django/contrib/gis/geos/tests/__init__.py b/django/contrib/gis/geos/tests/__init__.py index ccf960c68f..6b715d8c59 100644 --- a/django/contrib/gis/geos/tests/__init__.py +++ b/django/contrib/gis/geos/tests/__init__.py @@ -16,7 +16,8 @@ test_suites = [ def suite(): "Builds a test suite for the GEOS tests." s = TestSuite() - map(s.addTest, test_suites) + for suite in test_suites: + s.addTest(suite) return s def run(verbosity=1): From fbd4b3a5188d0b488648f1fdd2339e1eac6722a2 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Wed, 12 Sep 2012 16:13:58 +0200 Subject: [PATCH 16/21] [py3] Fixed GeoDjango mutable list tests --- .../contrib/gis/geos/tests/test_mutable_list.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/django/contrib/gis/geos/tests/test_mutable_list.py b/django/contrib/gis/geos/tests/test_mutable_list.py index cd174d7cfa..675505f0f9 100644 --- a/django/contrib/gis/geos/tests/test_mutable_list.py +++ b/django/contrib/gis/geos/tests/test_mutable_list.py @@ -55,14 +55,14 @@ class ListMixinTest(unittest.TestCase): def lists_of_len(self, length=None): if length is None: length = self.limit - pl = range(length) + pl = list(range(length)) return pl, self.listType(pl) def limits_plus(self, b): return range(-self.limit - b, self.limit + b) def step_range(self): - return range(-1 - self.limit, 0) + range(1, 1 + self.limit) + return list(range(-1 - self.limit, 0)) + list(range(1, 1 + self.limit)) def test01_getslice(self): 'Slice retrieval' @@ -160,13 +160,13 @@ class ListMixinTest(unittest.TestCase): del pl[i:j] del ul[i:j] self.assertEqual(pl[:], ul[:], 'del slice [%d:%d]' % (i,j)) - for k in range(-Len - 1,0) + range(1,Len): + for k in list(range(-Len - 1, 0)) + list(range(1, Len)): pl, ul = self.lists_of_len(Len) del pl[i:j:k] del ul[i:j:k] self.assertEqual(pl[:], ul[:], 'del slice [%d:%d:%d]' % (i,j,k)) - for k in range(-Len - 1,0) + range(1,Len): + for k in list(range(-Len - 1, 0)) + list(range(1, Len)): pl, ul = self.lists_of_len(Len) del pl[:i:k] del ul[:i:k] @@ -177,7 +177,7 @@ class ListMixinTest(unittest.TestCase): del ul[i::k] self.assertEqual(pl[:], ul[:], 'del slice [%d::%d]' % (i,k)) - for k in range(-Len - 1,0) + range(1,Len): + for k in list(range(-Len - 1, 0)) + list(range(1, Len)): pl, ul = self.lists_of_len(Len) del pl[::k] del ul[::k] @@ -320,7 +320,7 @@ class ListMixinTest(unittest.TestCase): pl.sort() ul.sort() self.assertEqual(pl[:], ul[:], 'sort') - mid = pl[len(pl) / 2] + mid = pl[len(pl) // 2] pl.sort(key=lambda x: (mid-x)**2) ul.sort(key=lambda x: (mid-x)**2) self.assertEqual(pl[:], ul[:], 'sort w/ key') @@ -330,7 +330,7 @@ class ListMixinTest(unittest.TestCase): pl.sort(reverse=True) ul.sort(reverse=True) self.assertEqual(pl[:], ul[:], 'sort w/ reverse') - mid = pl[len(pl) / 2] + mid = pl[len(pl) // 2] pl.sort(key=lambda x: (mid-x)**2) ul.sort(key=lambda x: (mid-x)**2) self.assertEqual(pl[:], ul[:], 'sort w/ key') @@ -338,7 +338,7 @@ class ListMixinTest(unittest.TestCase): def test_12_arithmetic(self): 'Arithmetic' pl, ul = self.lists_of_len() - al = range(10,14) + al = list(range(10,14)) self.assertEqual(list(pl + al), list(ul + al), 'add') self.assertEqual(type(ul), type(ul + al), 'type of add result') self.assertEqual(list(al + pl), list(al + ul), 'radd') From 9db7652eef7152d06e177a4886b3484f658c1101 Mon Sep 17 00:00:00 2001 From: Jeremy Dunck Date: Sun, 9 Sep 2012 01:36:38 -0400 Subject: [PATCH 17/21] Fixed my terribly outdated profile in committers.txt in celebration of my commit bit. --- AUTHORS | 2 +- docs/internals/committers.txt | 24 ++++++++++++++---------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/AUTHORS b/AUTHORS index 0a3699d516..af84cbff0b 100644 --- a/AUTHORS +++ b/AUTHORS @@ -31,6 +31,7 @@ The PRIMARY AUTHORS are (and/or have been): * Claude Paroz * Anssi Kääriäinen * Florian Apolloner + * Jeremy Dunck More information on the main contributors to Django can be found in docs/internals/committers.txt. @@ -167,7 +168,6 @@ answer newbie questions, and generally made Django that much better: dready Maximillian Dornseif Daniel Duan - Jeremy Dunck Andrew Durdin dusk@woofle.net Andy Dustman diff --git a/docs/internals/committers.txt b/docs/internals/committers.txt index 2faace99a5..63c8f5d618 100644 --- a/docs/internals/committers.txt +++ b/docs/internals/committers.txt @@ -379,6 +379,20 @@ Florian Apolloner .. _Graz University of Technology: http://tugraz.at/ .. _Ubuntuusers webteam: http://wiki.ubuntuusers.de/ubuntuusers/Webteam +Jeremy Dunck + Jeremy was rescued from corporate IT drudgery by Free Software and, in part, + Django. Many of Jeremy's interests center around access to information. + + Jeremy was the lead developer of Pegasus News, one of the first uses of + Django outside World Online, and has since joined Votizen, a startup intent + on reducing the influence of money in politics. + + He serves as DSF Secretary, organizes and helps organize sprints, cares + about the health and equity of the Django community. He has gone an + embarrassingly long time without a working blog. + + Jeremy lives in Mountain View, CA, USA. + Specialists ----------- @@ -403,16 +417,6 @@ Ian Kelly Matt Boersma Matt is also responsible for Django's Oracle support. -Jeremy Dunck - Jeremy is the lead developer of Pegasus News, a personalized local site based - in Dallas, Texas. An early contributor to Greasemonkey and Django, he sees - technology as a tool for communication and access to knowledge. - - Jeremy helped kick off GeoDjango development, and is mostly responsible for - the serious speed improvements that signals received in Django 1.0. - - Jeremy lives in Dallas, Texas, USA. - `Simon Meers`_ Simon discovered Django 0.96 during his Computer Science PhD research and has been developing with it full-time ever since. His core code From 7e5ebcce5365a172d802c76b57771c321430317f Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Thu, 13 Sep 2012 16:17:32 +0200 Subject: [PATCH 18/21] Fixed #18795 -- Fixed failing GeoDjango tests Proj.4 and SRS strings may slightly vary depending on the installed libraries. Made some tests pass again with recent Proj.4/GDAL lib versions. --- django/contrib/gis/gdal/tests/test_ds.py | 6 +++- django/contrib/gis/gdal/tests/test_geom.py | 6 ++-- django/contrib/gis/geos/tests/test_geos.py | 6 ++-- .../contrib/gis/tests/test_spatialrefsys.py | 32 +++++++------------ 4 files changed, 25 insertions(+), 25 deletions(-) diff --git a/django/contrib/gis/gdal/tests/test_ds.py b/django/contrib/gis/gdal/tests/test_ds.py index 71d22a0a27..22394a2888 100644 --- a/django/contrib/gis/gdal/tests/test_ds.py +++ b/django/contrib/gis/gdal/tests/test_ds.py @@ -181,7 +181,11 @@ class DataSourceTest(unittest.TestCase): # Making sure the SpatialReference is as expected. if hasattr(source, 'srs_wkt'): - self.assertEqual(source.srs_wkt, g.srs.wkt) + self.assertEqual( + source.srs_wkt, + # Depending on lib versions, WGS_84 might be WGS_1984 + g.srs.wkt.replace('SPHEROID["WGS_84"', 'SPHEROID["WGS_1984"') + ) def test06_spatial_filter(self): "Testing the Layer.spatial_filter property." diff --git a/django/contrib/gis/gdal/tests/test_geom.py b/django/contrib/gis/gdal/tests/test_geom.py index a0b2593605..dda22036e3 100644 --- a/django/contrib/gis/gdal/tests/test_geom.py +++ b/django/contrib/gis/gdal/tests/test_geom.py @@ -1,3 +1,4 @@ +import json from binascii import b2a_hex try: from django.utils.six.moves import cPickle as pickle @@ -111,8 +112,9 @@ class OGRGeomTest(unittest.TestCase, TestDataMixin): for g in self.geometries.json_geoms: geom = OGRGeometry(g.wkt) if not hasattr(g, 'not_equal'): - self.assertEqual(g.json, geom.json) - self.assertEqual(g.json, geom.geojson) + # Loading jsons to prevent decimal differences + self.assertEqual(json.loads(g.json), json.loads(geom.json)) + self.assertEqual(json.loads(g.json), json.loads(geom.geojson)) self.assertEqual(OGRGeometry(g.wkt), OGRGeometry(geom.json)) def test02_points(self): diff --git a/django/contrib/gis/geos/tests/test_geos.py b/django/contrib/gis/geos/tests/test_geos.py index d621c6b4d4..7300ab9c63 100644 --- a/django/contrib/gis/geos/tests/test_geos.py +++ b/django/contrib/gis/geos/tests/test_geos.py @@ -1,4 +1,5 @@ import ctypes +import json import random from django.contrib.gis.geos import (GEOSException, GEOSIndexError, GEOSGeometry, @@ -204,8 +205,9 @@ class GEOSTest(unittest.TestCase, TestDataMixin): for g in self.geometries.json_geoms: geom = GEOSGeometry(g.wkt) if not hasattr(g, 'not_equal'): - self.assertEqual(g.json, geom.json) - self.assertEqual(g.json, geom.geojson) + # Loading jsons to prevent decimal differences + self.assertEqual(json.loads(g.json), json.loads(geom.json)) + self.assertEqual(json.loads(g.json), json.loads(geom.geojson)) self.assertEqual(GEOSGeometry(g.wkt), GEOSGeometry(geom.json)) def test_fromfile(self): diff --git a/django/contrib/gis/tests/test_spatialrefsys.py b/django/contrib/gis/tests/test_spatialrefsys.py index 5cdc68a74d..7f7a0111f1 100644 --- a/django/contrib/gis/tests/test_spatialrefsys.py +++ b/django/contrib/gis/tests/test_spatialrefsys.py @@ -8,9 +8,11 @@ from django.utils import unittest test_srs = ({'srid' : 4326, 'auth_name' : ('EPSG', True), 'auth_srid' : 4326, - 'srtext' : 'GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],TOWGS84[0,0,0,0,0,0,0],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.01745329251994328,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]]', - 'srtext14' : 'GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.01745329251994328,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]]', - 'proj4' : '+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs ', + # Only the beginning, because there are differences depending on installed libs + 'srtext' : 'GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84"', + 'proj4' : ['+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs ', + # +ellps=WGS84 has been removed in the 4326 proj string in proj-4.8 + '+proj=longlat +datum=WGS84 +no_defs '], 'spheroid' : 'WGS 84', 'name' : 'WGS 84', 'geographic' : True, 'projected' : False, 'spatialite' : True, 'ellipsoid' : (6378137.0, 6356752.3, 298.257223563), # From proj's "cs2cs -le" and Wikipedia (semi-minor only) @@ -19,9 +21,9 @@ test_srs = ({'srid' : 4326, {'srid' : 32140, 'auth_name' : ('EPSG', False), 'auth_srid' : 32140, - 'srtext' : 'PROJCS["NAD83 / Texas South Central",GEOGCS["NAD83",DATUM["North_American_Datum_1983",SPHEROID["GRS 1980",6378137,298.257222101,AUTHORITY["EPSG","7019"]],AUTHORITY["EPSG","6269"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.01745329251994328,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4269"]],PROJECTION["Lambert_Conformal_Conic_2SP"],PARAMETER["standard_parallel_1",30.28333333333333],PARAMETER["standard_parallel_2",28.38333333333333],PARAMETER["latitude_of_origin",27.83333333333333],PARAMETER["central_meridian",-99],PARAMETER["false_easting",600000],PARAMETER["false_northing",4000000],UNIT["metre",1,AUTHORITY["EPSG","9001"]],AUTHORITY["EPSG","32140"]]', - 'srtext14': 'PROJCS["NAD83 / Texas South Central",GEOGCS["NAD83",DATUM["North_American_Datum_1983",SPHEROID["GRS 1980",6378137,298.257222101,AUTHORITY["EPSG","7019"]],AUTHORITY["EPSG","6269"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.01745329251994328,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4269"]],UNIT["metre",1,AUTHORITY["EPSG","9001"]],PROJECTION["Lambert_Conformal_Conic_2SP"],PARAMETER["standard_parallel_1",30.28333333333333],PARAMETER["standard_parallel_2",28.38333333333333],PARAMETER["latitude_of_origin",27.83333333333333],PARAMETER["central_meridian",-99],PARAMETER["false_easting",600000],PARAMETER["false_northing",4000000],AUTHORITY["EPSG","32140"],AXIS["X",EAST],AXIS["Y",NORTH]]', - 'proj4' : '+proj=lcc +lat_1=30.28333333333333 +lat_2=28.38333333333333 +lat_0=27.83333333333333 +lon_0=-99 +x_0=600000 +y_0=4000000 +ellps=GRS80 +datum=NAD83 +units=m +no_defs ', + 'srtext' : 'PROJCS["NAD83 / Texas South Central",GEOGCS["NAD83",DATUM["North_American_Datum_1983",SPHEROID["GRS 1980"', + 'proj4' : ['+proj=lcc +lat_1=30.28333333333333 +lat_2=28.38333333333333 +lat_0=27.83333333333333 +lon_0=-99 +x_0=600000 +y_0=4000000 +ellps=GRS80 +datum=NAD83 +units=m +no_defs ', + '+proj=lcc +lat_1=30.28333333333333 +lat_2=28.38333333333333 +lat_0=27.83333333333333 +lon_0=-99 +x_0=600000 +y_0=4000000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs '], 'spheroid' : 'GRS 1980', 'name' : 'NAD83 / Texas South Central', 'geographic' : False, 'projected' : True, 'spatialite' : False, 'ellipsoid' : (6378137.0, 6356752.31414, 298.257222101), # From proj's "cs2cs -le" and Wikipedia (semi-minor only) @@ -51,17 +53,12 @@ class SpatialRefSysTest(unittest.TestCase): # No proj.4 and different srtext on oracle backends :( if postgis: - if connection.ops.spatial_version >= (1, 4, 0): - srtext = sd['srtext14'] - else: - srtext = sd['srtext'] - self.assertEqual(srtext, srs.wkt) - self.assertEqual(sd['proj4'], srs.proj4text) + self.assertTrue(srs.wkt.startswith(sd['srtext'])) + self.assertTrue(srs.proj4text in sd['proj4']) @no_mysql def test02_osr(self): "Testing getting OSR objects from SpatialRefSys model objects." - from django.contrib.gis.gdal import GDAL_VERSION for sd in test_srs: sr = SpatialRefSys.objects.get(srid=sd['srid']) self.assertEqual(True, sr.spheroid.startswith(sd['spheroid'])) @@ -76,15 +73,10 @@ class SpatialRefSysTest(unittest.TestCase): # Testing the SpatialReference object directly. if postgis or spatialite: srs = sr.srs - if GDAL_VERSION <= (1, 8): - self.assertEqual(sd['proj4'], srs.proj4) + self.assertTrue(srs.proj4 in sd['proj4']) # No `srtext` field in the `spatial_ref_sys` table in SpatiaLite if not spatialite: - if connection.ops.spatial_version >= (1, 4, 0): - srtext = sd['srtext14'] - else: - srtext = sd['srtext'] - self.assertEqual(srtext, srs.wkt) + self.assertTrue(srs.wkt.startswith(sd['srtext'])) @no_mysql def test03_ellipsoid(self): From 690170a8b9bbcea1ae51e4020aea49987fefdbd2 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Thu, 13 Sep 2012 20:17:52 +0200 Subject: [PATCH 19/21] Removed unused quoting/encoding in gis db backend --- django/contrib/gis/db/backends/base.py | 2 -- django/contrib/gis/db/backends/util.py | 14 -------------- 2 files changed, 16 deletions(-) diff --git a/django/contrib/gis/db/backends/base.py b/django/contrib/gis/db/backends/base.py index f7af420a8d..2b8924d92e 100644 --- a/django/contrib/gis/db/backends/base.py +++ b/django/contrib/gis/db/backends/base.py @@ -90,8 +90,6 @@ class BaseSpatialOperations(object): # For quoting column values, rather than columns. def geo_quote_name(self, name): - if isinstance(name, six.text_type): - name = name.encode('ascii') return "'%s'" % name # GeometryField operations diff --git a/django/contrib/gis/db/backends/util.py b/django/contrib/gis/db/backends/util.py index 648fcfe963..2fc9123d26 100644 --- a/django/contrib/gis/db/backends/util.py +++ b/django/contrib/gis/db/backends/util.py @@ -3,20 +3,6 @@ A collection of utility routines and classes used by the spatial backends. """ -from django.utils import six - -def gqn(val): - """ - The geographic quote name function; used for quoting tables and - geometries (they use single rather than the double quotes of the - backend quotename function). - """ - if isinstance(val, six.string_types): - if isinstance(val, six.text_type): val = val.encode('ascii') - return "'%s'" % val - else: - return str(val) - class SpatialOperation(object): """ Base class for generating spatial SQL. From a01bce1e26ebd6f67809455282a26789d09cc1df Mon Sep 17 00:00:00 2001 From: Bryan Veloso Date: Fri, 14 Sep 2012 19:24:39 -0700 Subject: [PATCH 20/21] Adding myself to the committers list. --- AUTHORS | 1 + docs/internals/committers.txt | 14 ++++++++++++++ 2 files changed, 15 insertions(+) diff --git a/AUTHORS b/AUTHORS index af84cbff0b..6a7f22ada4 100644 --- a/AUTHORS +++ b/AUTHORS @@ -32,6 +32,7 @@ The PRIMARY AUTHORS are (and/or have been): * Anssi Kääriäinen * Florian Apolloner * Jeremy Dunck + * Bryan Veloso More information on the main contributors to Django can be found in docs/internals/committers.txt. diff --git a/docs/internals/committers.txt b/docs/internals/committers.txt index 63c8f5d618..f12ced2c07 100644 --- a/docs/internals/committers.txt +++ b/docs/internals/committers.txt @@ -393,6 +393,20 @@ Jeremy Dunck Jeremy lives in Mountain View, CA, USA. +`Bryan Veloso`_ + Bryan found Django 0.96 through a fellow designer who was evangelizing + its use. It was his for foray outside of the land that was PHP-based + templating. Although he has only ever used Django for personal projects, + it is the very reason he considers himself a designer/developer + hybrid and is working to further design within the Django community. + + Bryan works as a designer at GitHub by day, and masquerades as a `vlogger`_ + and `shoutcaster`_ in the after-hours. Bryan lives in Los Angeles, CA, USA. + +.. _bryan veloso: http://avalonstar.com/ +.. _vlogger: http://youtube.com/bryanveloso/ +.. _shoutcaster: http://twitch.tv/vlogalonstar/ + Specialists ----------- From abfba3bb65c9a28ca3756ec5d091e2e8561fc450 Mon Sep 17 00:00:00 2001 From: Bryan Veloso Date: Fri, 14 Sep 2012 19:26:11 -0700 Subject: [PATCH 21/21] Correcting my English. --- docs/internals/committers.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/internals/committers.txt b/docs/internals/committers.txt index f12ced2c07..ca56d36880 100644 --- a/docs/internals/committers.txt +++ b/docs/internals/committers.txt @@ -395,7 +395,7 @@ Jeremy Dunck `Bryan Veloso`_ Bryan found Django 0.96 through a fellow designer who was evangelizing - its use. It was his for foray outside of the land that was PHP-based + its use. It was his first foray outside of the land that was PHP-based templating. Although he has only ever used Django for personal projects, it is the very reason he considers himself a designer/developer hybrid and is working to further design within the Django community.