From 060ac8e7115b51374de7d5e34c784d9df5764d0b Mon Sep 17 00:00:00 2001 From: Issac Kelly Date: Fri, 10 Aug 2012 23:07:15 -0700 Subject: Create headings and expand CBV docs so that the "Built-In CBV" docs include a complete list. --- docs/ref/class-based-views/base.txt | 35 ++++++++++++++-------- docs/ref/class-based-views/generic-date-based.txt | 24 ++++++++++++++- docs/ref/class-based-views/generic-display.txt | 10 +++++-- docs/ref/class-based-views/generic-editing.txt | 27 ++++++++++++----- docs/ref/class-based-views/index.txt | 2 +- docs/ref/class-based-views/mixins-date-based.txt | 18 +++++++++++ docs/ref/class-based-views/mixins-editing.txt | 14 +++++++-- .../class-based-views/mixins-multiple-object.txt | 6 ++++ docs/ref/class-based-views/mixins-simple.txt | 6 ++++ .../ref/class-based-views/mixins-single-object.txt | 6 ++++ 10 files changed, 121 insertions(+), 27 deletions(-) (limited to 'docs/ref') diff --git a/docs/ref/class-based-views/base.txt b/docs/ref/class-based-views/base.txt index 5e0360c88f..3f82b44f46 100644 --- a/docs/ref/class-based-views/base.txt +++ b/docs/ref/class-based-views/base.txt @@ -8,6 +8,9 @@ themselves or inherited from. They may not provide all the capabilities required for projects, in which case there are Mixins and Generic class-based views. +View +---- + .. class:: django.views.generic.base.View The master class-based base view. All other class-based views inherit from @@ -31,13 +34,13 @@ views. **Example urls.py**:: from django.conf.urls import patterns, url - + from myapp.views import MyView - + urlpatterns = patterns('', url(r'^mine/$', MyView.as_view(), name='my-view'), ) - + **Methods** .. method:: dispatch(request, *args, **kwargs) @@ -61,13 +64,16 @@ views. The default implementation returns ``HttpResponseNotAllowed`` with list of allowed methods in plain text. - - .. note:: + + .. note:: Documentation on class-based views is a work in progress. As yet, only the methods defined directly on the class are documented here, not methods defined on superclasses. +TemplateView +------------ + .. class:: django.views.generic.base.TemplateView Renders a given template, passing it a ``{{ params }}`` template variable, @@ -84,22 +90,22 @@ views. 1. :meth:`dispatch()` 2. :meth:`http_method_not_allowed()` 3. :meth:`get_context_data()` - + **Example views.py**:: from django.views.generic.base import TemplateView - + from articles.models import Article class HomePageView(TemplateView): template_name = "home.html" - + def get_context_data(self, **kwargs): context = super(HomePageView, self).get_context_data(**kwargs) context['latest_articles'] = Article.objects.all()[:5] return context - + **Example urls.py**:: from django.conf.urls import patterns, url @@ -126,12 +132,15 @@ views. * ``params``: The dictionary of keyword arguments captured from the URL pattern that served the view. - .. note:: + .. note:: Documentation on class-based views is a work in progress. As yet, only the methods defined directly on the class are documented here, not methods defined on superclasses. +RedirectView +------------ + .. class:: django.views.generic.base.RedirectView Redirects to a given URL. @@ -159,7 +168,7 @@ views. from django.shortcuts import get_object_or_404 from django.views.generic.base import RedirectView - + from articles.models import Article class ArticleCounterRedirectView(RedirectView): @@ -176,7 +185,7 @@ views. from django.conf.urls import patterns, url from django.views.generic.base import RedirectView - + from article.views import ArticleCounterRedirectView urlpatterns = patterns('', @@ -217,7 +226,7 @@ views. behavior they wish, as long as the method returns a redirect-ready URL string. - .. note:: + .. note:: Documentation on class-based views is a work in progress. As yet, only the methods defined directly on the class are documented here, not methods diff --git a/docs/ref/class-based-views/generic-date-based.txt b/docs/ref/class-based-views/generic-date-based.txt index 69a98df77b..6730923d1e 100644 --- a/docs/ref/class-based-views/generic-date-based.txt +++ b/docs/ref/class-based-views/generic-date-based.txt @@ -5,6 +5,9 @@ Generic date views Date-based generic views (in the module :mod:`django.views.generic.dates`) are views for displaying drilldown pages for date-based data. +ArchiveIndexView +---------------- + .. class:: django.views.generic.dates.ArchiveIndexView A top-level index page showing the "latest" objects, by date. Objects with @@ -21,12 +24,15 @@ are views for displaying drilldown pages for date-based data. * :class:`django.views.generic.list.MultipleObjectMixin` * :class:`django.views.generic.dates.DateMixin` * :class:`django.views.generic.base.View` - + **Notes** * Uses a default ``context_object_name`` of ``latest``. * Uses a default ``template_name_suffix`` of ``_archive``. +YearArchiveView +--------------- + .. class:: django.views.generic.dates.YearArchiveView A yearly archive page showing all available months in a given year. Objects @@ -86,6 +92,9 @@ are views for displaying drilldown pages for date-based data. * Uses a default ``template_name_suffix`` of ``_archive_year``. +MonthArchiveView +---------------- + .. class:: django.views.generic.dates.MonthArchiveView A monthly archive page showing all objects in a given month. Objects with a @@ -134,6 +143,9 @@ are views for displaying drilldown pages for date-based data. * Uses a default ``template_name_suffix`` of ``_archive_month``. +WeekArchiveView +--------------- + .. class:: django.views.generic.dates.WeekArchiveView A weekly archive page showing all objects in a given week. Objects with a @@ -175,6 +187,9 @@ are views for displaying drilldown pages for date-based data. * Uses a default ``template_name_suffix`` of ``_archive_week``. +DayArchiveView +-------------- + .. class:: django.views.generic.dates.DayArchiveView A day archive page showing all objects in a given day. Days in the future @@ -225,6 +240,9 @@ are views for displaying drilldown pages for date-based data. * Uses a default ``template_name_suffix`` of ``_archive_day``. +TodayArchiveView +---------------- + .. class:: django.views.generic.dates.TodayArchiveView A day archive page showing all objects for *today*. This is exactly the @@ -246,6 +264,10 @@ are views for displaying drilldown pages for date-based data. * :class:`django.views.generic.dates.DateMixin` * :class:`django.views.generic.base.View` + +DateDetailView +-------------- + .. class:: django.views.generic.dates.DateDetailView A page representing an individual object. If the object has a date value in diff --git a/docs/ref/class-based-views/generic-display.txt b/docs/ref/class-based-views/generic-display.txt index bbf0d4f05a..ef3bc179ee 100644 --- a/docs/ref/class-based-views/generic-display.txt +++ b/docs/ref/class-based-views/generic-display.txt @@ -5,6 +5,9 @@ Generic display views The two following generic class-based views are designed to display data. On many projects they are typically the most commonly used views. +DetailView +---------- + .. class:: django.views.generic.detail.DetailView While this view is executing, ``self.object`` will contain the object that @@ -39,7 +42,7 @@ many projects they are typically the most commonly used views. from articles.models import Article class ArticleDetailView(DetailView): - + model = Article def get_context_data(self, **kwargs): @@ -55,7 +58,10 @@ many projects they are typically the most commonly used views. urlpatterns = patterns('', url(r'^(?P[-_\w]+)/$', ArticleDetailView.as_view(), name='article-detail'), - ) + ) + +ListView +-------- .. class:: django.views.generic.list.ListView diff --git a/docs/ref/class-based-views/generic-editing.txt b/docs/ref/class-based-views/generic-editing.txt index a65a59bc8b..2fac06ee02 100644 --- a/docs/ref/class-based-views/generic-editing.txt +++ b/docs/ref/class-based-views/generic-editing.txt @@ -2,7 +2,7 @@ Generic editing views ===================== -The following views are described on this page and provide a foundation for +The following views are described on this page and provide a foundation for editing content: * :class:`django.views.generic.edit.FormView` @@ -13,7 +13,7 @@ editing content: .. note:: Some of the examples on this page assume that a model titled 'Author' - has been defined. For these cases we assume the following has been defined + has been defined. For these cases we assume the following has been defined in `myapp/models.py`:: from django import models @@ -25,6 +25,9 @@ editing content: def get_absolute_url(self): return reverse('author-detail', kwargs={'pk': self.pk}) +FormView +-------- + .. class:: django.views.generic.edit.FormView A view that displays a form. On error, redisplays the form with validation @@ -69,6 +72,8 @@ editing content: form.send_email() return super(ContactView, self).form_valid(form) +CreateView +---------- .. class:: django.views.generic.edit.CreateView @@ -94,7 +99,7 @@ editing content: .. attribute:: template_name_suffix The CreateView page displayed to a GET request uses a - ``template_name_suffix`` of ``'_form.html'``. For + ``template_name_suffix`` of ``'_form.html'``. For example, changing this attribute to ``'_create_form.html'`` for a view creating objects for the the example `Author` model would cause the the default `template_name` to be ``'myapp/author_create_form.html'``. @@ -107,6 +112,9 @@ editing content: class AuthorCreate(CreateView): model = Author +UpdateView +---------- + .. class:: django.views.generic.edit.UpdateView A view that displays a form for editing an existing object, redisplaying @@ -133,10 +141,10 @@ editing content: .. attribute:: template_name_suffix The UpdateView page displayed to a GET request uses a - ``template_name_suffix`` of ``'_form.html'``. For + ``template_name_suffix`` of ``'_form.html'``. For example, changing this attribute to ``'_update_form.html'`` for a view updating objects for the the example `Author` model would cause the the - default `template_name` to be ``'myapp/author_update_form.html'``. + default `template_name` to be ``'myapp/author_update_form.html'``. **Example views.py**:: @@ -146,6 +154,9 @@ editing content: class AuthorUpdate(UpdateView): model = Author +DeleteView +---------- + .. class:: django.views.generic.edit.DeleteView A view that displays a confirmation page and deletes an existing object. @@ -171,10 +182,10 @@ editing content: .. attribute:: template_name_suffix The DeleteView page displayed to a GET request uses a - ``template_name_suffix`` of ``'_confirm_delete.html'``. For + ``template_name_suffix`` of ``'_confirm_delete.html'``. For example, changing this attribute to ``'_check_delete.html'`` for a view deleting objects for the the example `Author` model would cause the the - default `template_name` to be ``'myapp/author_check_delete.html'``. + default `template_name` to be ``'myapp/author_check_delete.html'``. **Example views.py**:: @@ -185,4 +196,4 @@ editing content: class AuthorDelete(DeleteView): model = Author - success_url = reverse_lazy('author-list') + success_url = reverse_lazy('author-list') diff --git a/docs/ref/class-based-views/index.txt b/docs/ref/class-based-views/index.txt index f2271d2506..9ed0762533 100644 --- a/docs/ref/class-based-views/index.txt +++ b/docs/ref/class-based-views/index.txt @@ -6,7 +6,7 @@ Class-based views API reference. For introductory material, see :doc:`/topics/class-based-views/index`. .. toctree:: - :maxdepth: 1 + :maxdepth: 3 base generic-display diff --git a/docs/ref/class-based-views/mixins-date-based.txt b/docs/ref/class-based-views/mixins-date-based.txt index a65471e68d..6bf6f10b5d 100644 --- a/docs/ref/class-based-views/mixins-date-based.txt +++ b/docs/ref/class-based-views/mixins-date-based.txt @@ -3,6 +3,9 @@ Date-based mixins ================= +YearMixin +--------- + .. class:: django.views.generic.dates.YearMixin A mixin that can be used to retrieve and provide parsing information for a @@ -36,6 +39,9 @@ Date-based mixins Raises a 404 if no valid year specification can be found. +MonthMixin +---------- + .. class:: django.views.generic.dates.MonthMixin A mixin that can be used to retrieve and provide parsing information for a @@ -82,6 +88,9 @@ Date-based mixins date provided. If ``allow_empty = False``, returns the previous month that contained data. +DayMixin +-------- + .. class:: django.views.generic.dates.DayMixin A mixin that can be used to retrieve and provide parsing information for a @@ -127,6 +136,9 @@ Date-based mixins Returns a date object containing the previous day. If ``allow_empty = False``, returns the previous day that contained data. +WeekMixin +--------- + .. class:: django.views.generic.dates.WeekMixin A mixin that can be used to retrieve and provide parsing information for a @@ -161,6 +173,9 @@ Date-based mixins Raises a 404 if no valid week specification can be found. +DateMixin +--------- + .. class:: django.views.generic.dates.DateMixin A mixin class providing common behavior for all date-based views. @@ -204,6 +219,9 @@ Date-based mixins is greater than the current date/time. Returns :attr:`DateMixin.allow_future` by default. +BaseDateListView +---------------- + .. class:: django.views.generic.dates.BaseDateListView A base class that provides common behavior for all date-based views. There diff --git a/docs/ref/class-based-views/mixins-editing.txt b/docs/ref/class-based-views/mixins-editing.txt index 89610889db..95dd24f442 100644 --- a/docs/ref/class-based-views/mixins-editing.txt +++ b/docs/ref/class-based-views/mixins-editing.txt @@ -14,6 +14,9 @@ The following mixins are used to construct Django's editing views: Examples of how these are combined into editing views can be found at the documentation on ``Generic editing views``. +FormMixin +--------- + .. class:: django.views.generic.edit.FormMixin A mixin class that provides facilities for creating and displaying forms. @@ -90,6 +93,9 @@ The following mixins are used to construct Django's editing views: :meth:`~django.views.generic.FormMixin.form_invalid`. +ModelFormMixin +-------------- + .. class:: django.views.generic.edit.ModelFormMixin A form mixin that works on ModelForms, rather than a standalone form. @@ -147,12 +153,16 @@ The following mixins are used to construct Django's editing views: .. method:: form_invalid() Renders a response, providing the invalid form as context. - + + +ProcessFormView +--------------- + .. class:: django.views.generic.edit.ProcessFormView A mixin that provides basic HTTP GET and POST workflow. - .. note:: + .. note:: This is named 'ProcessFormView' and inherits directly from :class:`django.views.generic.base.View`, but breaks if used diff --git a/docs/ref/class-based-views/mixins-multiple-object.txt b/docs/ref/class-based-views/mixins-multiple-object.txt index b414355c6b..8bc613b887 100644 --- a/docs/ref/class-based-views/mixins-multiple-object.txt +++ b/docs/ref/class-based-views/mixins-multiple-object.txt @@ -2,6 +2,9 @@ Multiple object mixins ====================== +MultipleObjectMixin +------------------- + .. class:: django.views.generic.list.MultipleObjectMixin A mixin that can be used to display a list of objects. @@ -148,6 +151,9 @@ Multiple object mixins this context variable will be ``None``. +MultipleObjectTemplateResponseMixin +----------------------------------- + .. class:: django.views.generic.list.MultipleObjectTemplateResponseMixin A mixin class that performs template-based response rendering for views diff --git a/docs/ref/class-based-views/mixins-simple.txt b/docs/ref/class-based-views/mixins-simple.txt index 33d0db3134..085c8a5fc7 100644 --- a/docs/ref/class-based-views/mixins-simple.txt +++ b/docs/ref/class-based-views/mixins-simple.txt @@ -2,6 +2,9 @@ Simple mixins ============= +ContextMixin +------------ + .. class:: django.views.generic.base.ContextMixin .. versionadded:: 1.5 @@ -17,6 +20,9 @@ Simple mixins Returns a dictionary representing the template context. The keyword arguments provided will make up the returned context. +TemplateResponseMixin +--------------------- + .. class:: django.views.generic.base.TemplateResponseMixin Provides a mechanism to construct a diff --git a/docs/ref/class-based-views/mixins-single-object.txt b/docs/ref/class-based-views/mixins-single-object.txt index a31608dbd4..77f52b96c6 100644 --- a/docs/ref/class-based-views/mixins-single-object.txt +++ b/docs/ref/class-based-views/mixins-single-object.txt @@ -2,6 +2,9 @@ Single object mixins ==================== +SingleObjectMixin +----------------- + .. class:: django.views.generic.detail.SingleObjectMixin Provides a mechanism for looking up an object associated with the @@ -86,6 +89,9 @@ Single object mixins ``context_object_name`` is specified, that variable will also be set in the context, with the same value as ``object``. +SingleObjectTemplateResponseMixin +--------------------------------- + .. class:: django.views.generic.detail.SingleObjectTemplateResponseMixin A mixin class that performs template-based response rendering for views -- cgit v1.3 From a0a0203a392f67832ba7a8a2f099e70d7db2d19e Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sat, 11 Aug 2012 15:34:51 +0200 Subject: [py3] Added python_2_unicode_compatible decorator. --- django/utils/encoding.py | 13 +++++++++++++ docs/ref/utils.txt | 8 ++++++++ 2 files changed, 21 insertions(+) (limited to 'docs/ref') diff --git a/django/utils/encoding.py b/django/utils/encoding.py index eb60cfde8b..7030ab1db0 100644 --- a/django/utils/encoding.py +++ b/django/utils/encoding.py @@ -39,6 +39,19 @@ class StrAndUnicode(object): def __str__(self): return self.__unicode__().encode('utf-8') +def python_2_unicode_compatible(klass): + """ + A decorator that defines __unicode__ and __str__ methods under Python 2. + Under Python 3 it does nothing. + + To support Python 2 and 3 with a single code base, define a __str__ method + returning text and apply this decorator to the class. + """ + if not six.PY3: + klass.__unicode__ = klass.__str__ + klass.__str__ = lambda self: self.__unicode__().encode('utf-8') + return klass + def smart_text(s, encoding='utf-8', strings_only=False, errors='strict'): """ Returns a text object representing 's' -- unicode on Python 2 and str on diff --git a/docs/ref/utils.txt b/docs/ref/utils.txt index b6cb1035d3..97643c29e9 100644 --- a/docs/ref/utils.txt +++ b/docs/ref/utils.txt @@ -187,6 +187,14 @@ The functions defined in this module share the following properties: Useful as a mix-in. If you support Python 2 and 3 with a single code base, you can inherit this mix-in and just define ``__unicode__``. +.. function:: python_2_unicode_compatible + + A decorator that defines ``__unicode__`` and ``__str__`` methods under + Python 2. Under Python 3 it does nothing. + + To support Python 2 and 3 with a single code base, define a ``__str__`` + method returning text and apply this decorator to the class. + .. function:: smart_text(s, encoding='utf-8', strings_only=False, errors='strict') .. versionadded:: 1.5 -- cgit v1.3 From 99321e30cebbffeafc6ae19f4f92a0a665cbf19b Mon Sep 17 00:00:00 2001 From: Andrei Antoukh Date: Sun, 12 Aug 2012 22:17:54 +0300 Subject: Fixed #18306 -- Made deferred models issue update_fields on save Deferred models now automatically update only the fields which are loaded from the db (with .only() or .defer()). In addition, any field set manually after the load is updated on save. --- django/db/models/base.py | 18 ++++ docs/ref/models/instances.txt | 6 ++ docs/ref/models/querysets.txt | 16 ++++ docs/releases/1.5.txt | 4 + tests/modeltests/update_only_fields/models.py | 1 + tests/modeltests/update_only_fields/tests.py | 101 +++++++++++++++++++++++ tests/regressiontests/multiple_database/tests.py | 15 ++++ 7 files changed, 161 insertions(+) (limited to 'docs/ref') diff --git a/django/db/models/base.py b/django/db/models/base.py index 30e07be3a7..1e1a138714 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -475,6 +475,7 @@ class Model(six.with_metaclass(ModelBase, object)): that the "save" must be an SQL insert or update (or equivalent for non-SQL backends), respectively. Normally, they should not be set. """ + using = using or router.db_for_write(self.__class__, instance=self) if force_insert and (force_update or update_fields): raise ValueError("Cannot force both insert and updating in model saving.") @@ -502,6 +503,23 @@ class Model(six.with_metaclass(ModelBase, object)): "model or are m2m fields: %s" % ', '.join(non_model_fields)) + # If saving to the same database, and this model is deferred, then + # automatically do a "update_fields" save on the loaded fields. + elif not force_insert and self._deferred and using == self._state.db: + field_names = set() + for field in self._meta.fields: + if not field.primary_key and not hasattr(field, 'through'): + field_names.add(field.attname) + deferred_fields = [ + f.attname for f in self._meta.fields + if f.attname not in self.__dict__ + and isinstance(self.__class__.__dict__[f.attname], + DeferredAttribute)] + + loaded_fields = field_names.difference(deferred_fields) + if loaded_fields: + update_fields = frozenset(loaded_fields) + self.save_base(using=using, force_insert=force_insert, force_update=force_update, update_fields=update_fields) save.alters_data = True diff --git a/docs/ref/models/instances.txt b/docs/ref/models/instances.txt index 14541ad0d1..b7ae3890cf 100644 --- a/docs/ref/models/instances.txt +++ b/docs/ref/models/instances.txt @@ -386,6 +386,12 @@ perform an update on all fields. Specifying ``update_fields`` will force an update. +When saving a model fetched through deferred model loading +(:meth:`~Model.only()` or :meth:`~Model.defer()`) only the fields loaded from +the DB will get updated. In effect there is an automatic ``update_fields`` in +this case. If you assign or change any deferred field value, these fields will +be added to the updated fields. + Deleting objects ================ diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt index 8c188c67c3..b59b2ece82 100644 --- a/docs/ref/models/querysets.txt +++ b/docs/ref/models/querysets.txt @@ -1110,6 +1110,14 @@ one, doing so will result in an error. reader, is slightly faster and consumes a little less memory in the Python process. +.. versionchanged:: 1.5 + +.. note:: + + When calling :meth:`~Model.save()` for instances with deferred fields, + only the loaded fields will be saved. See :meth:`~Model.save()` for more + details. + only ~~~~ @@ -1154,6 +1162,14 @@ All of the cautions in the note for the :meth:`defer` documentation apply to options. Also note that using :meth:`only` and omitting a field requested using :meth:`select_related` is an error as well. +.. versionchanged:: 1.5 + +.. note:: + + When calling :meth:`~Model.save()` for instances with deferred fields, + only the loaded fields will be saved. See :meth:`~Model.save()` for more + details. + using ~~~~~ diff --git a/docs/releases/1.5.txt b/docs/releases/1.5.txt index 4aeb61af94..2896acb36b 100644 --- a/docs/releases/1.5.txt +++ b/docs/releases/1.5.txt @@ -42,6 +42,10 @@ keyword argument ``update_fields``. By using this argument it is possible to save only a select list of model's fields. This can be useful for performance reasons or when trying to avoid overwriting concurrent changes. +Deferred instances (those loaded by .only() or .defer()) will automatically +save just the loaded fields. If any field is set manually after load, that +field will also get updated on save. + See the :meth:`Model.save() ` documentation for more details. diff --git a/tests/modeltests/update_only_fields/models.py b/tests/modeltests/update_only_fields/models.py index 01184f715b..bf5dd99166 100644 --- a/tests/modeltests/update_only_fields/models.py +++ b/tests/modeltests/update_only_fields/models.py @@ -15,6 +15,7 @@ class Account(models.Model): class Person(models.Model): name = models.CharField(max_length=20) gender = models.CharField(max_length=1, choices=GENDER_CHOICES) + pid = models.IntegerField(null=True, default=None) def __str__(self): return self.name diff --git a/tests/modeltests/update_only_fields/tests.py b/tests/modeltests/update_only_fields/tests.py index bce53ca621..c904c97236 100644 --- a/tests/modeltests/update_only_fields/tests.py +++ b/tests/modeltests/update_only_fields/tests.py @@ -18,6 +18,107 @@ class UpdateOnlyFieldsTests(TestCase): self.assertEqual(s.gender, 'F') self.assertEqual(s.name, 'Ian') + def test_update_fields_deferred(self): + s = Person.objects.create(name='Sara', gender='F', pid=22) + self.assertEqual(s.gender, 'F') + + s1 = Person.objects.defer("gender", "pid").get(pk=s.pk) + s1.name = "Emily" + s1.gender = "M" + + with self.assertNumQueries(1): + s1.save() + + s2 = Person.objects.get(pk=s1.pk) + self.assertEqual(s2.name, "Emily") + self.assertEqual(s2.gender, "M") + + def test_update_fields_only_1(self): + s = Person.objects.create(name='Sara', gender='F') + self.assertEqual(s.gender, 'F') + + s1 = Person.objects.only('name').get(pk=s.pk) + s1.name = "Emily" + s1.gender = "M" + + with self.assertNumQueries(1): + s1.save() + + s2 = Person.objects.get(pk=s1.pk) + self.assertEqual(s2.name, "Emily") + self.assertEqual(s2.gender, "M") + + def test_update_fields_only_2(self): + s = Person.objects.create(name='Sara', gender='F', pid=22) + self.assertEqual(s.gender, 'F') + + s1 = Person.objects.only('name').get(pk=s.pk) + s1.name = "Emily" + s1.gender = "M" + + with self.assertNumQueries(2): + s1.save(update_fields=['pid']) + + s2 = Person.objects.get(pk=s1.pk) + self.assertEqual(s2.name, "Sara") + self.assertEqual(s2.gender, "F") + + def test_update_fields_only_repeated(self): + s = Person.objects.create(name='Sara', gender='F') + self.assertEqual(s.gender, 'F') + + s1 = Person.objects.only('name').get(pk=s.pk) + s1.gender = 'M' + with self.assertNumQueries(1): + s1.save() + # Test that the deferred class does not remember that gender was + # set, instead the instace should remember this. + s1 = Person.objects.only('name').get(pk=s.pk) + with self.assertNumQueries(1): + s1.save() + + def test_update_fields_inheritance_defer(self): + profile_boss = Profile.objects.create(name='Boss', salary=3000) + e1 = Employee.objects.create(name='Sara', gender='F', + employee_num=1, profile=profile_boss) + e1 = Employee.objects.only('name').get(pk=e1.pk) + e1.name = 'Linda' + with self.assertNumQueries(1): + e1.save() + self.assertEqual(Employee.objects.get(pk=e1.pk).name, + 'Linda') + + def test_update_fields_fk_defer(self): + profile_boss = Profile.objects.create(name='Boss', salary=3000) + profile_receptionist = Profile.objects.create(name='Receptionist', salary=1000) + e1 = Employee.objects.create(name='Sara', gender='F', + employee_num=1, profile=profile_boss) + e1 = Employee.objects.only('profile').get(pk=e1.pk) + e1.profile = profile_receptionist + with self.assertNumQueries(1): + e1.save() + self.assertEqual(Employee.objects.get(pk=e1.pk).profile, profile_receptionist) + e1.profile_id = profile_boss.pk + with self.assertNumQueries(1): + e1.save() + self.assertEqual(Employee.objects.get(pk=e1.pk).profile, profile_boss) + + def test_select_related_only_interaction(self): + profile_boss = Profile.objects.create(name='Boss', salary=3000) + e1 = Employee.objects.create(name='Sara', gender='F', + employee_num=1, profile=profile_boss) + e1 = Employee.objects.only('profile__salary').select_related('profile').get(pk=e1.pk) + profile_boss.name = 'Clerk' + profile_boss.salary = 1000 + profile_boss.save() + # The loaded salary of 3000 gets saved, the name of 'Clerk' isn't + # overwritten. + with self.assertNumQueries(1): + e1.profile.save() + reloaded_profile = Profile.objects.get(pk=profile_boss.pk) + self.assertEqual(reloaded_profile.name, profile_boss.name) + self.assertEqual(reloaded_profile.salary, 3000) + def test_update_fields_m2m(self): profile_boss = Profile.objects.create(name='Boss', salary=3000) e1 = Employee.objects.create(name='Sara', gender='F', diff --git a/tests/regressiontests/multiple_database/tests.py b/tests/regressiontests/multiple_database/tests.py index 74a5f2f550..782fe2bfc6 100644 --- a/tests/regressiontests/multiple_database/tests.py +++ b/tests/regressiontests/multiple_database/tests.py @@ -1546,6 +1546,21 @@ class RouterTestCase(TestCase): # If you evaluate the query, it should work, running on 'other' self.assertEqual(list(qs.values_list('title', flat=True)), ['Dive into Python']) + def test_deferred_models(self): + mark_def = Person.objects.using('default').create(name="Mark Pilgrim") + mark_other = Person.objects.using('other').create(name="Mark Pilgrim") + orig_b = Book.objects.using('other').create(title="Dive into Python", + published=datetime.date(2009, 5, 4), + editor=mark_other) + b = Book.objects.using('other').only('title').get(pk=orig_b.pk) + self.assertEqual(b.published, datetime.date(2009, 5, 4)) + b = Book.objects.using('other').only('title').get(pk=orig_b.pk) + b.editor = mark_def + b.save(using='default') + self.assertEqual(Book.objects.using('default').get(pk=b.pk).published, + datetime.date(2009, 5, 4)) + + class AuthTestCase(TestCase): multi_db = True -- cgit v1.3 From 383da137851bd5fbe4f5f22e13d6237f3532a5bb Mon Sep 17 00:00:00 2001 From: Tom Mortimer-Jones Date: Wed, 15 Aug 2012 19:41:16 +0200 Subject: Fixed comment to match the code --- docs/ref/contrib/admin/index.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs/ref') diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index 4d39981a4d..7ca102c529 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -623,7 +623,7 @@ subclass:: provided in the query string and retrievable via `self.value()`. """ - # Compare the requested value (either '80s' or 'other') + # Compare the requested value (either '80s' or '90s') # to decide how to filter the queryset. if self.value() == '80s': return queryset.filter(birthday__gte=date(1980, 1, 1), -- cgit v1.3 From e38112d882a8aec0aaf6d52ab6d07fa1a408a3aa Mon Sep 17 00:00:00 2001 From: Piet Delport Date: Mon, 13 Aug 2012 14:10:40 +0200 Subject: Fixed #18759 -- updated SECRET_KEY documentation Document SECRET_KEY becoming required in 1.5. Also expand the description slightly, and add a more prominent warning about the security implications of running with an exposed SECRET_KEY. --- docs/ref/settings.txt | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) (limited to 'docs/ref') diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index 531ff33da2..f319a9bee9 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -1537,9 +1537,23 @@ SECRET_KEY Default: ``''`` (Empty string) -A secret key for this particular Django installation. Used to provide a seed in -secret-key hashing algorithms. Set this to a random string -- the longer, the -better. ``django-admin.py startproject`` creates one automatically. +A secret key for a particular Django installation. This is used to provide +:doc:`cryptographic signing `, and should be set to a unique, +unpredictable value. + +:djadmin:`django-admin.py startproject ` automatically adds a +randomly-generated ``SECRET_KEY`` to each new project. + +.. warning:: + + **Keep this value secret.** + + Running Django with a known :setting:`SECRET_KEY` defeats many of Django's + security protections, and can lead to privilege escalation and remote code + execution vulnerabilities. + +.. versionchanged:: 1.5 + Django will now refuse to start if :setting:`SECRET_KEY` is not set. .. setting:: SECURE_PROXY_SSL_HEADER -- cgit v1.3 From d1d514af044209a6af790ba012c4637232c5335d Mon Sep 17 00:00:00 2001 From: Simon Meers Date: Thu, 16 Aug 2012 10:13:28 +1000 Subject: Marked up a few raw values in the settings documentation. --- docs/ref/settings.txt | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) (limited to 'docs/ref') diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index f319a9bee9..0eac229c16 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -747,14 +747,15 @@ Did you catch that? NEVER deploy a site into production with :setting:`DEBUG` turned on. One of the main features of debug mode is the display of detailed error pages. -If your app raises an exception when ``DEBUG`` is ``True``, Django will display -a detailed traceback, including a lot of metadata about your environment, such -as all the currently defined Django settings (from ``settings.py``). +If your app raises an exception when :setting:`DEBUG` is ``True``, Django will +display a detailed traceback, including a lot of metadata about your +environment, such as all the currently defined Django settings (from +``settings.py``). As a security measure, Django will *not* include settings that might be -sensitive (or offensive), such as ``SECRET_KEY`` or ``PROFANITIES_LIST``. -Specifically, it will exclude any setting whose name includes any of the -following: +sensitive (or offensive), such as :setting:`SECRET_KEY` or +:setting:`PROFANITIES_LIST`. Specifically, it will exclude any setting whose +name includes any of the following: * API * KEY @@ -1356,7 +1357,7 @@ MANAGERS Default: ``()`` (Empty tuple) A tuple in the same format as :setting:`ADMINS` that specifies who should get -broken-link notifications when ``SEND_BROKEN_LINK_EMAILS=True``. +broken-link notifications when :setting:`SEND_BROKEN_LINK_EMAILS` is ``True``. .. setting:: MEDIA_ROOT -- cgit v1.3 From 2079b730f139685bcedf0c92d5ed9a3f64b51e9f Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 16 Aug 2012 16:05:41 -0400 Subject: Fixed #18223 - Corrected default transaction behavior in postgresql docs. Thanks philipn for the report and mateusgondim for the patch. --- docs/ref/databases.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'docs/ref') diff --git a/docs/ref/databases.txt b/docs/ref/databases.txt index 92b5665bea..3e256e9d9e 100644 --- a/docs/ref/databases.txt +++ b/docs/ref/databases.txt @@ -56,10 +56,10 @@ will do some additional queries to set these parameters. Transaction handling --------------------- -:doc:`By default `, Django starts a transaction when a -database connection is first used and commits the result at the end of the -request/response handling. The PostgreSQL backends normally operate the same -as any other Django backend in this respect. +:doc:`By default `, Django runs with an open +transaction which it commits automatically when any built-in, data-altering +model function is called. The PostgreSQL backends normally operate the same as +any other Django backend in this respect. .. _postgresql-autocommit-mode: -- cgit v1.3 From 2a36a1a07145860bad45d9c416281313ff53b910 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Fri, 17 Aug 2012 11:06:38 -0400 Subject: Fixed #18696 - Clarified WizardView heading; thanks sergzach. --- docs/ref/contrib/formtools/form-wizard.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'docs/ref') diff --git a/docs/ref/contrib/formtools/form-wizard.txt b/docs/ref/contrib/formtools/form-wizard.txt index 7d229a5d66..3cc8c7aed4 100644 --- a/docs/ref/contrib/formtools/form-wizard.txt +++ b/docs/ref/contrib/formtools/form-wizard.txt @@ -86,8 +86,8 @@ the message itself. Here's what the :file:`forms.py` might look like:: section :ref:`Handling files ` below to learn more about what to do. -Creating a ``WizardView`` class -------------------------------- +Creating a ``WizardView`` subclass +---------------------------------- The next step is to create a :class:`django.contrib.formtools.wizard.views.WizardView` subclass. You can -- cgit v1.3 From 547b181046539f548839e42544521503fbe4d821 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sat, 18 Aug 2012 16:04:06 +0200 Subject: [py3] Ported django.utils.safestring. Backwards compatibility aliases were created under Python 2. --- django/db/backends/mysql/base.py | 8 +-- django/db/backends/postgresql_psycopg2/base.py | 6 +-- django/db/backends/sqlite3/base.py | 4 +- django/utils/encoding.py | 4 +- django/utils/safestring.py | 74 +++++++++++++++----------- docs/howto/custom-template-tags.txt | 6 +-- docs/ref/utils.txt | 22 ++++++-- tests/regressiontests/i18n/tests.py | 12 ++--- 8 files changed, 82 insertions(+), 54 deletions(-) (limited to 'docs/ref') diff --git a/django/db/backends/mysql/base.py b/django/db/backends/mysql/base.py index 2222f89cf0..cec3b04f7e 100644 --- a/django/db/backends/mysql/base.py +++ b/django/db/backends/mysql/base.py @@ -38,7 +38,7 @@ 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.functional import cached_property -from django.utils.safestring import SafeString, SafeUnicode +from django.utils.safestring import SafeBytes, SafeText from django.utils import six from django.utils import timezone @@ -75,7 +75,7 @@ def adapt_datetime_with_timezone_support(value, conv): # MySQLdb-1.2.1 returns TIME columns as timedelta -- they are more like # timedelta in terms of actual behavior as they are signed and include days -- # and Django expects time, so we still need to override that. We also need to -# add special handling for SafeUnicode and SafeString as MySQLdb's type +# add special handling for SafeText and SafeBytes as MySQLdb's type # checking is too tight to catch those (see Django ticket #6052). # Finally, MySQLdb always returns naive datetime objects. However, when # timezone support is active, Django expects timezone-aware datetime objects. @@ -402,8 +402,8 @@ class DatabaseWrapper(BaseDatabaseWrapper): kwargs['client_flag'] = CLIENT.FOUND_ROWS kwargs.update(settings_dict['OPTIONS']) self.connection = Database.connect(**kwargs) - self.connection.encoders[SafeUnicode] = self.connection.encoders[six.text_type] - self.connection.encoders[SafeString] = self.connection.encoders[bytes] + self.connection.encoders[SafeText] = self.connection.encoders[six.text_type] + self.connection.encoders[SafeBytes] = self.connection.encoders[bytes] connection_created.send(sender=self.__class__, connection=self) cursor = self.connection.cursor() if new_connection: diff --git a/django/db/backends/postgresql_psycopg2/base.py b/django/db/backends/postgresql_psycopg2/base.py index d553594ec1..f6f534da8c 100644 --- a/django/db/backends/postgresql_psycopg2/base.py +++ b/django/db/backends/postgresql_psycopg2/base.py @@ -14,7 +14,7 @@ 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.log import getLogger -from django.utils.safestring import SafeUnicode, SafeString +from django.utils.safestring import SafeText, SafeBytes from django.utils import six from django.utils.timezone import utc @@ -29,8 +29,8 @@ DatabaseError = Database.DatabaseError IntegrityError = Database.IntegrityError psycopg2.extensions.register_type(psycopg2.extensions.UNICODE) -psycopg2.extensions.register_adapter(SafeString, psycopg2.extensions.QuotedString) -psycopg2.extensions.register_adapter(SafeUnicode, psycopg2.extensions.QuotedString) +psycopg2.extensions.register_adapter(SafeBytes, psycopg2.extensions.QuotedString) +psycopg2.extensions.register_adapter(SafeText, psycopg2.extensions.QuotedString) logger = getLogger('django.db.backends') diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py index 775ca20e45..31a16b6a2b 100644 --- a/django/db/backends/sqlite3/base.py +++ b/django/db/backends/sqlite3/base.py @@ -20,7 +20,7 @@ from django.db.backends.sqlite3.creation import DatabaseCreation from django.db.backends.sqlite3.introspection import DatabaseIntrospection from django.utils.dateparse import parse_date, parse_datetime, parse_time from django.utils.functional import cached_property -from django.utils.safestring import SafeString +from django.utils.safestring import SafeBytes from django.utils import six from django.utils import timezone @@ -80,7 +80,7 @@ if Database.version_info >= (2, 4, 1): # slow-down, this adapter is only registered for sqlite3 versions # needing it (Python 2.6 and up). Database.register_adapter(str, lambda s: s.decode('utf-8')) - Database.register_adapter(SafeString, lambda s: s.decode('utf-8')) + Database.register_adapter(SafeBytes, lambda s: s.decode('utf-8')) class DatabaseFeatures(BaseDatabaseFeatures): # SQLite cannot handle us only partially reading from a cursor's result set diff --git a/django/utils/encoding.py b/django/utils/encoding.py index 7b80f13496..3ac2c508b6 100644 --- a/django/utils/encoding.py +++ b/django/utils/encoding.py @@ -119,8 +119,8 @@ def force_text(s, encoding='utf-8', strings_only=False, errors='strict'): errors) for arg in s]) else: # Note: We use .decode() here, instead of six.text_type(s, encoding, - # errors), so that if s is a SafeString, it ends up being a - # SafeUnicode at the end. + # errors), so that if s is a SafeBytes, it ends up being a + # SafeText at the end. s = s.decode(encoding, errors) except UnicodeDecodeError as e: if not isinstance(s, Exception): diff --git a/django/utils/safestring.py b/django/utils/safestring.py index bfaefd07ee..07e0bf4cea 100644 --- a/django/utils/safestring.py +++ b/django/utils/safestring.py @@ -10,36 +10,43 @@ from django.utils import six class EscapeData(object): pass -class EscapeString(bytes, EscapeData): +class EscapeBytes(bytes, EscapeData): """ - A string that should be HTML-escaped when output. + A byte string that should be HTML-escaped when output. """ pass -class EscapeUnicode(six.text_type, EscapeData): +class EscapeText(six.text_type, EscapeData): """ - A unicode object that should be HTML-escaped when output. + A unicode string object that should be HTML-escaped when output. """ pass +if six.PY3: + EscapeString = EscapeText +else: + EscapeString = EscapeBytes + # backwards compatibility for Python 2 + EscapeUnicode = EscapeText + class SafeData(object): pass -class SafeString(bytes, SafeData): +class SafeBytes(bytes, SafeData): """ - A string subclass that has been specifically marked as "safe" (requires no + A bytes subclass that has been specifically marked as "safe" (requires no further escaping) for HTML output purposes. """ def __add__(self, rhs): """ - Concatenating a safe string with another safe string or safe unicode - object is safe. Otherwise, the result is no longer safe. + Concatenating a safe byte string with another safe byte string or safe + unicode string is safe. Otherwise, the result is no longer safe. """ - t = super(SafeString, self).__add__(rhs) - if isinstance(rhs, SafeUnicode): - return SafeUnicode(t) - elif isinstance(rhs, SafeString): - return SafeString(t) + t = super(SafeBytes, self).__add__(rhs) + if isinstance(rhs, SafeText): + return SafeText(t) + elif isinstance(rhs, SafeBytes): + return SafeBytes(t) return t def _proxy_method(self, *args, **kwargs): @@ -51,25 +58,25 @@ class SafeString(bytes, SafeData): method = kwargs.pop('method') data = method(self, *args, **kwargs) if isinstance(data, bytes): - return SafeString(data) + return SafeBytes(data) else: - return SafeUnicode(data) + return SafeText(data) decode = curry(_proxy_method, method=bytes.decode) -class SafeUnicode(six.text_type, SafeData): +class SafeText(six.text_type, SafeData): """ - A unicode subclass that has been specifically marked as "safe" for HTML - output purposes. + A unicode (Python 2) / str (Python 3) subclass that has been specifically + marked as "safe" for HTML output purposes. """ def __add__(self, rhs): """ - Concatenating a safe unicode object with another safe string or safe - unicode object is safe. Otherwise, the result is no longer safe. + Concatenating a safe unicode string with another safe byte string or + safe unicode string is safe. Otherwise, the result is no longer safe. """ - t = super(SafeUnicode, self).__add__(rhs) + t = super(SafeText, self).__add__(rhs) if isinstance(rhs, SafeData): - return SafeUnicode(t) + return SafeText(t) return t def _proxy_method(self, *args, **kwargs): @@ -81,12 +88,19 @@ class SafeUnicode(six.text_type, SafeData): method = kwargs.pop('method') data = method(self, *args, **kwargs) if isinstance(data, bytes): - return SafeString(data) + return SafeBytes(data) else: - return SafeUnicode(data) + return SafeText(data) encode = curry(_proxy_method, method=six.text_type.encode) +if six.PY3: + SafeString = SafeText +else: + SafeString = SafeBytes + # backwards compatibility for Python 2 + SafeUnicode = SafeText + def mark_safe(s): """ Explicitly mark a string as safe for (HTML) output purposes. The returned @@ -97,10 +111,10 @@ def mark_safe(s): if isinstance(s, SafeData): return s if isinstance(s, bytes) or (isinstance(s, Promise) and s._delegate_bytes): - return SafeString(s) + return SafeBytes(s) if isinstance(s, (six.text_type, Promise)): - return SafeUnicode(s) - return SafeString(bytes(s)) + return SafeText(s) + return SafeString(str(s)) def mark_for_escaping(s): """ @@ -113,8 +127,8 @@ def mark_for_escaping(s): if isinstance(s, (SafeData, EscapeData)): return s if isinstance(s, bytes) or (isinstance(s, Promise) and s._delegate_bytes): - return EscapeString(s) + return EscapeBytes(s) if isinstance(s, (six.text_type, Promise)): - return EscapeUnicode(s) - return EscapeString(bytes(s)) + return EscapeText(s) + return EscapeBytes(bytes(s)) diff --git a/docs/howto/custom-template-tags.txt b/docs/howto/custom-template-tags.txt index 054c831fad..5b27af82d6 100644 --- a/docs/howto/custom-template-tags.txt +++ b/docs/howto/custom-template-tags.txt @@ -189,7 +189,7 @@ passed around inside the template code: They're commonly used for output that contains raw HTML that is intended to be interpreted as-is on the client side. - Internally, these strings are of type ``SafeString`` or ``SafeUnicode``. + Internally, these strings are of type ``SafeBytes`` or ``SafeText``. They share a common base class of ``SafeData``, so you can test for them using code like: @@ -204,8 +204,8 @@ passed around inside the template code: not. These strings are only escaped once, however, even if auto-escaping applies. - Internally, these strings are of type ``EscapeString`` or - ``EscapeUnicode``. Generally you don't have to worry about these; they + Internally, these strings are of type ``EscapeBytes`` or + ``EscapeText``. Generally you don't have to worry about these; they exist for the implementation of the :tfilter:`escape` filter. Template filter code falls into one of two situations: diff --git a/docs/ref/utils.txt b/docs/ref/utils.txt index 97643c29e9..20775fcc81 100644 --- a/docs/ref/utils.txt +++ b/docs/ref/utils.txt @@ -560,15 +560,29 @@ string" means that the producer of the string has already turned characters that should not be interpreted by the HTML engine (e.g. '<') into the appropriate entities. +.. class:: SafeBytes + + .. versionadded:: 1.5 + + A :class:`bytes` subclass that has been specifically marked as "safe" + (requires no further escaping) for HTML output purposes. + .. class:: SafeString - A string subclass that has been specifically marked as "safe" (requires no - further escaping) for HTML output purposes. + A :class:`str` subclass that has been specifically marked as "safe" + (requires no further escaping) for HTML output purposes. This is + :class:`SafeBytes` on Python 2 and :class:`SafeText` on Python 3. + +.. class:: SafeText + + .. versionadded:: 1.5 + + A :class:`str` (in Python 3) or :class:`unicode` (in Python 2) subclass + that has been specifically marked as "safe" for HTML output purposes. .. class:: SafeUnicode - A unicode subclass that has been specifically marked as "safe" for HTML - output purposes. + Historical name of :class:`SafeText`. Only available under Python 2. .. function:: mark_safe(s) diff --git a/tests/regressiontests/i18n/tests.py b/tests/regressiontests/i18n/tests.py index 1a8ff25ff3..c13c1c6f86 100644 --- a/tests/regressiontests/i18n/tests.py +++ b/tests/regressiontests/i18n/tests.py @@ -18,7 +18,7 @@ from django.utils.formats import (get_format, date_format, time_format, number_format) from django.utils.importlib import import_module from django.utils.numberformat import format as nformat -from django.utils.safestring import mark_safe, SafeString, SafeUnicode +from django.utils.safestring import mark_safe, SafeBytes, SafeString, SafeText from django.utils import six from django.utils.six import PY3 from django.utils.translation import (ugettext, ugettext_lazy, activate, @@ -235,9 +235,9 @@ class TranslationTests(TestCase): s = mark_safe(str('Password')) self.assertEqual(SafeString, type(s)) with translation.override('de', deactivate=True): - self.assertEqual(SafeUnicode, type(ugettext(s))) - self.assertEqual('aPassword', SafeString('a') + s) - self.assertEqual('Passworda', s + SafeString('a')) + self.assertEqual(SafeText, type(ugettext(s))) + self.assertEqual('aPassword', SafeText('a') + s) + self.assertEqual('Passworda', s + SafeText('a')) self.assertEqual('Passworda', s + mark_safe('a')) self.assertEqual('aPassword', mark_safe('a') + s) self.assertEqual('as', mark_safe('a') + mark_safe('s')) @@ -897,9 +897,9 @@ class TestModels(TestCase): def test_safestr(self): c = Company(cents_paid=12, products_delivered=1) - c.name = SafeUnicode('Iñtërnâtiônàlizætiøn1') + c.name = SafeText('Iñtërnâtiônàlizætiøn1') c.save() - c.name = SafeString('Iñtërnâtiônàlizætiøn1'.encode('utf-8')) + c.name = SafeBytes('Iñtërnâtiônàlizætiøn1'.encode('utf-8')) c.save() -- cgit v1.3 From 58683e9c82d6e7c5fbb7acef79eef9408b776ab0 Mon Sep 17 00:00:00 2001 From: Marc Tamlyn Date: Sat, 18 Aug 2012 14:46:17 +0100 Subject: Fixed #16744 -- Class based view should have the view object in the context Updated the most recent patch from @claudep to apply again and updated the documentation location. --- django/views/generic/base.py | 2 ++ docs/ref/class-based-views/mixins-simple.txt | 16 ++++++++++++++-- docs/ref/templates/api.txt | 2 ++ docs/releases/1.5.txt | 6 ++++++ tests/regressiontests/generic_views/base.py | 2 ++ tests/regressiontests/generic_views/detail.py | 2 ++ tests/regressiontests/generic_views/edit.py | 5 +++++ tests/regressiontests/generic_views/list.py | 2 ++ 8 files changed, 35 insertions(+), 2 deletions(-) (limited to 'docs/ref') diff --git a/django/views/generic/base.py b/django/views/generic/base.py index 6554e898ca..ca039ae9db 100644 --- a/django/views/generic/base.py +++ b/django/views/generic/base.py @@ -18,6 +18,8 @@ class ContextMixin(object): """ def get_context_data(self, **kwargs): + if 'view' not in kwargs: + kwargs['view'] = self return kwargs diff --git a/docs/ref/class-based-views/mixins-simple.txt b/docs/ref/class-based-views/mixins-simple.txt index 085c8a5fc7..61fc945cd3 100644 --- a/docs/ref/class-based-views/mixins-simple.txt +++ b/docs/ref/class-based-views/mixins-simple.txt @@ -17,8 +17,20 @@ ContextMixin .. method:: get_context_data(**kwargs) - Returns a dictionary representing the template context. The - keyword arguments provided will make up the returned context. + Returns a dictionary representing the template context. The keyword + arguments provided will make up the returned context. + + The template context of all class-based generic views include a + ``view`` variable that points to the ``View`` instance. + + .. admonition:: Use ``alters_data`` where appropriate + + Note that having the view instance in the template context may + expose potentially hazardous methods to template authors. To + prevent methods like this from being called in the template, set + ``alters_data=True`` on those methods. For more information, read + the documentation on :ref:`rendering a template context + `. TemplateResponseMixin --------------------- diff --git a/docs/ref/templates/api.txt b/docs/ref/templates/api.txt index bd2b4c6e9d..c52194e6b7 100644 --- a/docs/ref/templates/api.txt +++ b/docs/ref/templates/api.txt @@ -198,6 +198,8 @@ straight lookups. Here are some things to keep in mind: * A variable can only be called if it has no required arguments. Otherwise, the system will return an empty string. +.. _alters-data-description: + * Obviously, there can be side effects when calling some variables, and it'd be either foolish or a security hole to allow the template system to access them. diff --git a/docs/releases/1.5.txt b/docs/releases/1.5.txt index 2896acb36b..5f27b7ccbb 100644 --- a/docs/releases/1.5.txt +++ b/docs/releases/1.5.txt @@ -84,6 +84,12 @@ By passing ``False`` using this argument it is now possible to retreive the :class:`ContentType ` associated with proxy models. +New ``view`` variable in class-based views context +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +In all :doc:`generic class-based views ` +(or any class-based view inheriting from ``ContextMixin``), the context dictionary +contains a ``view`` variable that points to the ``View`` instance. + Minor features ~~~~~~~~~~~~~~ diff --git a/tests/regressiontests/generic_views/base.py b/tests/regressiontests/generic_views/base.py index 5296f6941a..b9f64888fc 100644 --- a/tests/regressiontests/generic_views/base.py +++ b/tests/regressiontests/generic_views/base.py @@ -276,6 +276,7 @@ class TemplateViewTest(TestCase): response = self.client.get('/template/simple/bar/') self.assertEqual(response.status_code, 200) self.assertEqual(response.context['params'], {'foo': 'bar'}) + self.assertTrue(isinstance(response.context['view'], View)) def test_extra_template_params(self): """ @@ -285,6 +286,7 @@ class TemplateViewTest(TestCase): self.assertEqual(response.status_code, 200) self.assertEqual(response.context['params'], {'foo': 'bar'}) self.assertEqual(response.context['key'], 'value') + self.assertTrue(isinstance(response.context['view'], View)) def test_cached_views(self): """ diff --git a/tests/regressiontests/generic_views/detail.py b/tests/regressiontests/generic_views/detail.py index 60c5f7afe9..c8f1999a2a 100644 --- a/tests/regressiontests/generic_views/detail.py +++ b/tests/regressiontests/generic_views/detail.py @@ -2,6 +2,7 @@ from __future__ import absolute_import from django.core.exceptions import ImproperlyConfigured from django.test import TestCase +from django.views.generic.base import View from .models import Artist, Author, Page @@ -14,6 +15,7 @@ class DetailViewTest(TestCase): res = self.client.get('/detail/obj/') self.assertEqual(res.status_code, 200) self.assertEqual(res.context['object'], {'foo': 'bar'}) + self.assertTrue(isinstance(res.context['view'], View)) self.assertTemplateUsed(res, 'generic_views/detail.html') def test_detail_by_pk(self): diff --git a/tests/regressiontests/generic_views/edit.py b/tests/regressiontests/generic_views/edit.py index 651e14fff3..16f4da8efe 100644 --- a/tests/regressiontests/generic_views/edit.py +++ b/tests/regressiontests/generic_views/edit.py @@ -5,6 +5,7 @@ from django.core.urlresolvers import reverse from django import forms from django.test import TestCase from django.utils.unittest import expectedFailure +from django.views.generic.base import View from django.views.generic.edit import FormMixin from . import views @@ -31,6 +32,7 @@ class CreateViewTests(TestCase): res = self.client.get('/edit/authors/create/') self.assertEqual(res.status_code, 200) self.assertTrue(isinstance(res.context['form'], forms.ModelForm)) + self.assertTrue(isinstance(res.context['view'], View)) self.assertFalse('object' in res.context) self.assertFalse('author' in res.context) self.assertTemplateUsed(res, 'generic_views/author_form.html') @@ -101,6 +103,7 @@ class CreateViewTests(TestCase): self.assertEqual(res.status_code, 302) self.assertRedirects(res, 'http://testserver/accounts/login/?next=/edit/authors/create/restricted/') + class UpdateViewTests(TestCase): urls = 'regressiontests.generic_views.urls' @@ -226,6 +229,7 @@ class UpdateViewTests(TestCase): res = self.client.get('/edit/author/update/') self.assertEqual(res.status_code, 200) self.assertTrue(isinstance(res.context['form'], forms.ModelForm)) + self.assertTrue(isinstance(res.context['view'], View)) self.assertEqual(res.context['object'], Author.objects.get(pk=a.pk)) self.assertEqual(res.context['author'], Author.objects.get(pk=a.pk)) self.assertTemplateUsed(res, 'generic_views/author_form.html') @@ -237,6 +241,7 @@ class UpdateViewTests(TestCase): self.assertRedirects(res, 'http://testserver/list/authors/') self.assertQuerysetEqual(Author.objects.all(), ['']) + class DeleteViewTests(TestCase): urls = 'regressiontests.generic_views.urls' diff --git a/tests/regressiontests/generic_views/list.py b/tests/regressiontests/generic_views/list.py index b925758524..14dc1d725d 100644 --- a/tests/regressiontests/generic_views/list.py +++ b/tests/regressiontests/generic_views/list.py @@ -2,6 +2,7 @@ from __future__ import absolute_import from django.core.exceptions import ImproperlyConfigured from django.test import TestCase +from django.views.generic.base import View from .models import Author, Artist @@ -21,6 +22,7 @@ class ListViewTests(TestCase): self.assertEqual(res.status_code, 200) self.assertTemplateUsed(res, 'generic_views/author_list.html') self.assertEqual(list(res.context['object_list']), list(Author.objects.all())) + self.assertTrue(isinstance(res.context['view'], View)) self.assertIs(res.context['author_list'], res.context['object_list']) self.assertIsNone(res.context['paginator']) self.assertIsNone(res.context['page_obj']) -- cgit v1.3 From 212b9826bdda5c3c2eb680e6f9c5b046b4172300 Mon Sep 17 00:00:00 2001 From: Marc Tamlyn Date: Sat, 18 Aug 2012 13:53:22 +0100 Subject: Fixed #14516 -- Extract methods from removetags and slugify template filters Patch by @jphalip updated to apply, documentation and release notes added. I've documented strip_tags as well as remove_tags as the difference between the two wouldn't be immediately obvious. --- django/template/defaultfilters.py | 10 ++++----- django/utils/html.py | 11 +++++++++ django/utils/text.py | 12 ++++++++++ docs/ref/utils.txt | 45 +++++++++++++++++++++++++++++++++++++ docs/releases/1.5.txt | 4 ++++ tests/regressiontests/utils/html.py | 9 ++++++++ tests/regressiontests/utils/text.py | 8 +++++++ 7 files changed, 94 insertions(+), 5 deletions(-) (limited to 'docs/ref') diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py index 628b6627d2..d8b0c3439a 100644 --- a/django/template/defaultfilters.py +++ b/django/template/defaultfilters.py @@ -231,12 +231,12 @@ def make_list(value): @stringfilter def slugify(value): """ - Normalizes string, converts to lowercase, removes non-alpha characters, - and converts spaces to hyphens. + Converts to lowercase, removes non-word characters (alphanumerics and + underscores) and converts spaces to hyphens. Also strips leading and + trailing whitespace. """ - value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore').decode() - value = re.sub('[^\w\s-]', '', value).strip().lower() - return mark_safe(re.sub('[-\s]+', '-', value)) + from django.utils.text import slugify + return slugify(value) @register.filter(is_safe=True) def stringformat(value, arg): diff --git a/django/utils/html.py b/django/utils/html.py index 647982a15f..13954ce195 100644 --- a/django/utils/html.py +++ b/django/utils/html.py @@ -123,6 +123,17 @@ def strip_tags(value): return re.sub(r'<[^>]*?>', '', force_text(value)) strip_tags = allow_lazy(strip_tags) +def remove_tags(html, tags): + """Returns the given HTML with given tags removed.""" + tags = [re.escape(tag) for tag in tags.split()] + tags_re = u'(%s)' % u'|'.join(tags) + starttag_re = re.compile(ur'<%s(/?>|(\s+[^>]*>))' % tags_re, re.U) + endtag_re = re.compile(u'' % tags_re) + html = starttag_re.sub(u'', html) + html = endtag_re.sub(u'', html) + return html +remove_tags = allow_lazy(remove_tags, unicode) + def strip_spaces_between_tags(value): """Returns the given HTML with spaces between tags removed.""" return re.sub(r'>\s+<', '><', force_text(value)) diff --git a/django/utils/text.py b/django/utils/text.py index ddeb29f2d2..cbafab0032 100644 --- a/django/utils/text.py +++ b/django/utils/text.py @@ -16,6 +16,7 @@ if not six.PY3: from django.utils.functional import allow_lazy, SimpleLazyObject from django.utils import six from django.utils.translation import ugettext_lazy, ugettext as _, pgettext +from django.utils.safestring import mark_safe # Capitalizes the first letter of a string. capfirst = lambda x: x and force_text(x)[0].upper() + force_text(x)[1:] @@ -383,3 +384,14 @@ def unescape_string_literal(s): quote = s[0] return s[1:-1].replace(r'\%s' % quote, quote).replace(r'\\', '\\') unescape_string_literal = allow_lazy(unescape_string_literal) + +def slugify(value): + """ + Converts to lowercase, removes non-word characters (alphanumerics and + underscores) and converts spaces to hyphens. Also strips leading and + trailing whitespace. + """ + value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore') + value = unicode(re.sub('[^\w\s-]', '', value).strip().lower()) + return mark_safe(re.sub('[-\s]+', '-', value)) +slugify = allow_lazy(slugify, unicode) diff --git a/docs/ref/utils.txt b/docs/ref/utils.txt index 20775fcc81..775d70738b 100644 --- a/docs/ref/utils.txt +++ b/docs/ref/utils.txt @@ -486,6 +486,33 @@ escaping HTML. through :func:`conditional_escape` which (ultimately) calls :func:`~django.utils.encoding.force_text` on the values. +.. function:: strip_tags(value) + + Removes anything that looks like an html tag from the string, that is + anything contained within ``<>``. + + For example:: + + strip_tags(value) + + If ``value`` is ``"Joel a slug"`` the + return value will be ``"Joel is a slug"``. + +.. function:: remove_tags(value, tags) + + Removes a list of [X]HTML tag names from the output. + + For example:: + + remove_tags(value, ["b", "span"]) + + If ``value`` is ``"Joel a slug"`` the + return value will be ``"Joel a slug"``. + + Note that this filter is case-sensitive. + + If ``value`` is ``"Joel a slug"`` the + return value will be ``"Joel a slug"``. .. _str.format: http://docs.python.org/library/stdtypes.html#str.format @@ -599,6 +626,24 @@ appropriate entities. Can be called multiple times on a single string (the resulting escaping is only applied once). +``django.utils.text`` +===================== + +.. module:: django.utils.text + :synopsis: Text manipulation. + +.. function:: slugify + + Converts to lowercase, removes non-word characters (alphanumerics and + underscores) and converts spaces to hyphens. Also strips leading and trailing + whitespace. + + For example:: + + slugify(value) + + If ``value`` is ``"Joel is a slug"``, the output will be ``"joel-is-a-slug"``. + ``django.utils.translation`` ============================ diff --git a/docs/releases/1.5.txt b/docs/releases/1.5.txt index 5f27b7ccbb..29cbd45c49 100644 --- a/docs/releases/1.5.txt +++ b/docs/releases/1.5.txt @@ -267,6 +267,10 @@ Miscellaneous * :func:`~django.utils.http.int_to_base36` properly raises a :exc:`TypeError` instead of :exc:`ValueError` for non-integer inputs. +* The ``slugify`` template filter is now available as a standard python + function at :func:`django.utils.text.slugify`. Similarly, ``remove_tags`` is + available at :func:`django.utils.html.remove_tags`. + Features deprecated in 1.5 ========================== diff --git a/tests/regressiontests/utils/html.py b/tests/regressiontests/utils/html.py index fe40d4eaae..98df80a5e2 100644 --- a/tests/regressiontests/utils/html.py +++ b/tests/regressiontests/utils/html.py @@ -146,3 +146,12 @@ class TestUtilsHtml(unittest.TestCase): ) for value, output in items: self.check_output(f, value, output) + + def test_remove_tags(self): + f = html.remove_tags + items = ( + ("Yes", "b i", "Yes"), + ("x

y

", "a b", "x

y

"), + ) + for value, tags, output in items: + self.assertEquals(f(value, tags), output) diff --git a/tests/regressiontests/utils/text.py b/tests/regressiontests/utils/text.py index dd6de63841..9fa86d515c 100644 --- a/tests/regressiontests/utils/text.py +++ b/tests/regressiontests/utils/text.py @@ -113,3 +113,11 @@ class TestUtilsText(SimpleTestCase): self.assertEqual(text.wrap(long_word, 20), long_word) self.assertEqual(text.wrap('a %s word' % long_word, 10), 'a\n%s\nword' % long_word) + + def test_slugify(self): + items = ( + (u'Hello, World!', 'hello-world'), + (u'spam & eggs', 'spam-eggs'), + ) + for value, output in items: + self.assertEqual(text.slugify(value), output) -- cgit v1.3 From a120fac65a17137bc8ac710477478474e3f9973e Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sat, 18 Aug 2012 16:36:55 +0200 Subject: Introduced force_bytes and force_str. This is consistent with the smart_* series of functions and it's going to be used by the next commit. --- django/utils/encoding.py | 19 +++++++++++++++++++ docs/ref/utils.txt | 17 ++++++++++++++++- 2 files changed, 35 insertions(+), 1 deletion(-) (limited to 'docs/ref') diff --git a/django/utils/encoding.py b/django/utils/encoding.py index 3ac2c508b6..7027b82a61 100644 --- a/django/utils/encoding.py +++ b/django/utils/encoding.py @@ -139,6 +139,19 @@ def smart_bytes(s, encoding='utf-8', strings_only=False, errors='strict'): """ Returns a bytestring version of 's', encoded as specified in 'encoding'. + If strings_only is True, don't convert (some) non-string-like objects. + """ + if isinstance(s, Promise): + # The input is the result of a gettext_lazy() call. + return s + return force_bytes(s, encoding, strings_only, errors) + + +def force_bytes(s, encoding='utf-8', strings_only=False, errors='strict'): + """ + Similar to smart_bytes, except that lazy instances are resolved to + strings, rather than kept as lazy objects. + If strings_only is True, don't convert (some) non-string-like objects. """ if isinstance(s, bytes): @@ -169,8 +182,10 @@ def smart_bytes(s, encoding='utf-8', strings_only=False, errors='strict'): if six.PY3: smart_str = smart_text + force_str = force_text else: smart_str = smart_bytes + force_str = force_bytes # backwards compatibility for Python 2 smart_unicode = smart_text force_unicode = force_text @@ -181,6 +196,10 @@ Apply smart_text in Python 3 and smart_bytes in Python 2. This is suitable for writing to sys.stdout (for instance). """ +force_str.__doc__ = """\ +Apply force_text in Python 3 and force_bytes in Python 2. +""" + def iri_to_uri(iri): """ Convert an Internationalized Resource Identifier (IRI) portion to a URI diff --git a/docs/ref/utils.txt b/docs/ref/utils.txt index 775d70738b..de19578cac 100644 --- a/docs/ref/utils.txt +++ b/docs/ref/utils.txt @@ -240,14 +240,29 @@ The functions defined in this module share the following properties: If ``strings_only`` is ``True``, don't convert (some) non-string-like objects. +.. function:: force_bytes(s, encoding='utf-8', strings_only=False, errors='strict') + + .. versionadded:: 1.5 + + Similar to ``smart_bytes``, except that lazy instances are resolved to + bytestrings, rather than kept as lazy objects. + + If ``strings_only`` is ``True``, don't convert (some) non-string-like + objects. + .. function:: smart_str(s, encoding='utf-8', strings_only=False, errors='strict') Alias of :func:`smart_bytes` on Python 2 and :func:`smart_text` on Python - 3. This function always returns a :class:`str`. + 3. This function returns a :class:`str` or a lazy string. For instance, this is suitable for writing to :attr:`sys.stdout` on Python 2 and 3. +.. function:: force_str(s, encoding='utf-8', strings_only=False, errors='strict') + + Alias of :func:`force_bytes` on Python 2 and :func:`force_text` on Python + 3. This function always returns a :class:`str`. + .. function:: iri_to_uri(iri) Convert an Internationalized Resource Identifier (IRI) portion to a URI -- cgit v1.3 From 8d5c11caad7f87d14b486a46890782fa0b66bb74 Mon Sep 17 00:00:00 2001 From: Marc Tamlyn Date: Sat, 18 Aug 2012 16:28:17 +0100 Subject: Fixed #3542 -- Add support for changing granularity on ArchiveView. Resolving the concept from a very old ticket in a more class-based-view manner. --- django/views/generic/dates.py | 17 +++++++++++++---- docs/ref/class-based-views/generic-date-based.txt | 3 +++ tests/regressiontests/generic_views/dates.py | 6 +++++- tests/regressiontests/generic_views/urls.py | 2 ++ 4 files changed, 23 insertions(+), 5 deletions(-) (limited to 'docs/ref') diff --git a/django/views/generic/dates.py b/django/views/generic/dates.py index 08b7318738..d44246f0b7 100644 --- a/django/views/generic/dates.py +++ b/django/views/generic/dates.py @@ -327,6 +327,7 @@ class BaseDateListView(MultipleObjectMixin, DateMixin, View): Abstract base class for date-based views displaying a list of objects. """ allow_empty = False + date_list_period = 'year' def get(self, request, *args, **kwargs): self.date_list, self.object_list, extra_context = self.get_dated_items() @@ -370,13 +371,18 @@ class BaseDateListView(MultipleObjectMixin, DateMixin, View): return qs - def get_date_list(self, queryset, date_type): + def get_date_list_period(self): + return self.date_list_period + + def get_date_list(self, queryset, date_type=None): """ Get a date list by calling `queryset.dates()`, checking along the way for empty lists that aren't allowed. """ date_field = self.get_date_field() allow_empty = self.get_allow_empty() + if date_type is None: + date_type = self.get_date_list_period() date_list = queryset.dates(date_field, date_type)[::-1] if date_list is not None and not date_list and not allow_empty: @@ -400,7 +406,7 @@ class BaseArchiveIndexView(BaseDateListView): Return (date_list, items, extra_context) for this request. """ qs = self.get_dated_queryset(ordering='-%s' % self.get_date_field()) - date_list = self.get_date_list(qs, 'year') + date_list = self.get_date_list(qs) if not date_list: qs = qs.none() @@ -419,6 +425,7 @@ class BaseYearArchiveView(YearMixin, BaseDateListView): """ List of objects published in a given year. """ + date_list_period = 'month' make_object_list = False def get_dated_items(self): @@ -438,7 +445,7 @@ class BaseYearArchiveView(YearMixin, BaseDateListView): } qs = self.get_dated_queryset(ordering='-%s' % date_field, **lookup_kwargs) - date_list = self.get_date_list(qs, 'month') + date_list = self.get_date_list(qs) if not self.get_make_object_list(): # We need this to be a queryset since parent classes introspect it @@ -470,6 +477,8 @@ class BaseMonthArchiveView(YearMixin, MonthMixin, BaseDateListView): """ List of objects published in a given year. """ + date_list_period = 'day' + def get_dated_items(self): """ Return (date_list, items, extra_context) for this request. @@ -489,7 +498,7 @@ class BaseMonthArchiveView(YearMixin, MonthMixin, BaseDateListView): } qs = self.get_dated_queryset(**lookup_kwargs) - date_list = self.get_date_list(qs, 'day') + date_list = self.get_date_list(qs) return (date_list, qs, { 'month': date, diff --git a/docs/ref/class-based-views/generic-date-based.txt b/docs/ref/class-based-views/generic-date-based.txt index 6730923d1e..12776cbb94 100644 --- a/docs/ref/class-based-views/generic-date-based.txt +++ b/docs/ref/class-based-views/generic-date-based.txt @@ -29,6 +29,9 @@ ArchiveIndexView * Uses a default ``context_object_name`` of ``latest``. * Uses a default ``template_name_suffix`` of ``_archive``. + * Defaults to providing ``date_list`` by year, but this can be altered to + month or day using the attribute ``date_list_period``. This also applies + to all subclass views. YearArchiveView --------------- diff --git a/tests/regressiontests/generic_views/dates.py b/tests/regressiontests/generic_views/dates.py index d18d10e603..c2fa71b376 100644 --- a/tests/regressiontests/generic_views/dates.py +++ b/tests/regressiontests/generic_views/dates.py @@ -60,7 +60,6 @@ class ArchiveIndexViewTests(TestCase): res = self.client.get('/dates/books/allow_empty/') self.assertEqual(res.status_code, 200) self.assertEqual(list(res.context['date_list']), []) - self.assertEqual(list(res.context['date_list']), []) self.assertTemplateUsed(res, 'generic_views/book_archive.html') def test_archive_view_template(self): @@ -80,6 +79,11 @@ class ArchiveIndexViewTests(TestCase): def test_archive_view_invalid(self): self.assertRaises(ImproperlyConfigured, self.client.get, '/dates/books/invalid/') + def test_archive_view_by_month(self): + res = self.client.get('/dates/books/by_month/') + self.assertEqual(res.status_code, 200) + self.assertEqual(res.context['date_list'], Book.objects.dates('pubdate', 'month')[::-1]) + def test_paginated_archive_view(self): self._make_books(20, base_date=datetime.date.today()) res = self.client.get('/dates/books/paginated/') diff --git a/tests/regressiontests/generic_views/urls.py b/tests/regressiontests/generic_views/urls.py index 5e15c6c9c1..c72bfecb65 100644 --- a/tests/regressiontests/generic_views/urls.py +++ b/tests/regressiontests/generic_views/urls.py @@ -113,6 +113,8 @@ urlpatterns = patterns('', views.BookArchive.as_view(paginate_by=10)), (r'^dates/books/reverse/$', views.BookArchive.as_view(queryset=models.Book.objects.order_by('pubdate'))), + (r'^dates/books/by_month/$', + views.BookArchive.as_view(date_list_period='month')), (r'^dates/booksignings/$', views.BookSigningArchive.as_view()), -- cgit v1.3 From cccbb6bff3be5fe5f9653e625cf839eb1aedc7f6 Mon Sep 17 00:00:00 2001 From: Julien Phalip Date: Sun, 19 Aug 2012 00:57:30 -0700 Subject: Made some minor fixes to the GeoDjango installation guide. --- docs/ref/contrib/gis/install.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'docs/ref') diff --git a/docs/ref/contrib/gis/install.txt b/docs/ref/contrib/gis/install.txt index 72bd72a6f8..127091724a 100644 --- a/docs/ref/contrib/gis/install.txt +++ b/docs/ref/contrib/gis/install.txt @@ -140,7 +140,7 @@ internal geometry representation used by GeoDjango (it's behind the "lazy" geometries). Specifically, the C API library is called (e.g., ``libgeos_c.so``) directly from Python using ctypes. -First, download GEOS 3.2 from the refractions Web site and untar the source +First, download GEOS 3.3.0 from the refractions Web site and untar the source archive:: $ wget http://download.osgeo.org/geos/geos-3.3.0.tar.bz2 @@ -421,7 +421,7 @@ After SQLite has been built with the R*Tree module enabled, get the latest SpatiaLite library source and tools bundle from the `download page`__:: $ wget http://www.gaia-gis.it/gaia-sins/libspatialite-sources/libspatialite-amalgamation-2.3.1.tar.gz - $ wget http://www.gaia-gis.it/gaia-sins/libspatialite-sources/spatialite-tools-2.3.1.tar.gz + $ wget http://www.gaia-gis.it/gaia-sins/spatialite-tools-sources/spatialite-tools-2.3.1.tar.gz $ tar xzf libspatialite-amalgamation-2.3.1.tar.gz $ tar xzf spatialite-tools-2.3.1.tar.gz @@ -605,7 +605,7 @@ http://www.gaia-gis.it/spatialite-2.4.0/ for 2.4):: Then, use the ``spatialite`` command to initialize a spatial database:: - $ spatialite geodjango.db < init_spatialite-2.X.sql + $ spatialite geodjango.db < init_spatialite-2.3.sql .. note:: -- cgit v1.3 From 3631db88cbae4b50b1d1b0fda0dfaa6a47f4eb0d Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sun, 19 Aug 2012 20:13:09 -0400 Subject: Fixed typo in form wizard docs. --- docs/ref/contrib/formtools/form-wizard.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs/ref') diff --git a/docs/ref/contrib/formtools/form-wizard.txt b/docs/ref/contrib/formtools/form-wizard.txt index 3cc8c7aed4..b8e585a4d2 100644 --- a/docs/ref/contrib/formtools/form-wizard.txt +++ b/docs/ref/contrib/formtools/form-wizard.txt @@ -528,7 +528,7 @@ We define our wizard in a ``views.py``:: We need to add the ``ContactWizard`` to our ``urls.py`` file:: - from django.conf.urls import pattern + from django.conf.urls import patterns from myapp.forms import ContactForm1, ContactForm2 from myapp.views import ContactWizard, show_message_form_condition -- cgit v1.3 From 26e0ba07aef4dcd6c57e1b03f2000c28672985e5 Mon Sep 17 00:00:00 2001 From: Ramiro Morales Date: Sun, 19 Aug 2012 22:03:50 -0300 Subject: Tweaked SpatiaLite GeoDjango docs. --- docs/ref/contrib/gis/install.txt | 18 +++++++----------- docs/ref/contrib/gis/testing.txt | 3 ++- 2 files changed, 9 insertions(+), 12 deletions(-) (limited to 'docs/ref') diff --git a/docs/ref/contrib/gis/install.txt b/docs/ref/contrib/gis/install.txt index 127091724a..3cd790212c 100644 --- a/docs/ref/contrib/gis/install.txt +++ b/docs/ref/contrib/gis/install.txt @@ -60,14 +60,14 @@ The geospatial libraries required for a GeoDjango installation depends on the spatial database used. The following lists the library requirements, supported versions, and any notes for each of the supported database backends: -================== ============================== ================== ========================================================== +================== ============================== ================== ========================================= Database Library Requirements Supported Versions Notes -================== ============================== ================== ========================================================== +================== ============================== ================== ========================================= PostgreSQL GEOS, PROJ.4, PostGIS 8.1+ Requires PostGIS. MySQL GEOS 5.x Not OGC-compliant; limited functionality. Oracle GEOS 10.2, 11 XE not supported; not tested with 9. -SQLite GEOS, GDAL, PROJ.4, SpatiaLite 3.6.+ Requires SpatiaLite 2.3+, pysqlite2 2.5+, and Django 1.1. -================== ============================== ================== ========================================================== +SQLite GEOS, GDAL, PROJ.4, SpatiaLite 3.6.+ Requires SpatiaLite 2.3+, pysqlite2 2.5+ +================== ============================== ================== ========================================= .. _geospatial_libs: @@ -467,8 +467,8 @@ pysqlite2 ^^^^^^^^^ Because SpatiaLite must be loaded as an external extension, it requires the -``enable_load_extension`` method, which is only available in versions 2.5+. -Thus, download pysqlite2 2.6, and untar:: +``enable_load_extension`` method, which is only available in versions 2.5+ of +pysqlite2. Thus, download pysqlite2 2.6, and untar:: $ wget http://pysqlite.googlecode.com/files/pysqlite-2.6.0.tar.gz $ tar xzf pysqlite-2.6.0.tar.gz @@ -583,7 +583,7 @@ After you've installed SpatiaLite, you'll need to create a number of spatial metadata tables in your database in order to perform spatial queries. If you're using SpatiaLite 3.0 or newer, use the ``spatialite`` utility to -call the ``InitSpatiaMetaData()`` function, like this:: +call the ``InitSpatialMetaData()`` function, like this:: $ spatialite geodjango.db "SELECT InitSpatialMetaData();" the SPATIAL_REF_SYS table already contains some row(s) @@ -643,10 +643,6 @@ Invoke the Django shell from your project and execute the >>> from django.contrib.gis.utils import add_srs_entry >>> add_srs_entry(900913) -.. note:: - - In Django 1.1 the name of this function is ``add_postgis_srs``. - This adds an entry for the 900913 SRID to the ``spatial_ref_sys`` (or equivalent) table, making it possible for the spatial database to transform coordinates in this projection. You only need to execute this command *once* per spatial database. diff --git a/docs/ref/contrib/gis/testing.txt b/docs/ref/contrib/gis/testing.txt index 78663b967c..18ffbdd7d8 100644 --- a/docs/ref/contrib/gis/testing.txt +++ b/docs/ref/contrib/gis/testing.txt @@ -121,7 +121,8 @@ Settings Only relevant when using a SpatiaLite version older than 3.0. -By default, the GeoDjango test runner looks for the SpatiaLite SQL in the +By default, the GeoDjango test runner looks for the :ref:`file containing the +SpatiaLite dababase-initialization SQL code ` in the same directory where it was invoked (by default the same directory where ``manage.py`` is located). To use a different location, add the following to your settings:: -- cgit v1.3 From 13d47c3f338e1e9a5da943b97b5334c0523d2e2c Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 21 Aug 2012 16:29:16 -0400 Subject: Fixed #18637 - Updated some documentation for aspects of models that are ModelForm specific, not admin specific. Thanks Ben Sturmfels for the patch. --- docs/ref/models/fields.txt | 90 +++++++++++++++++++++++----------------------- docs/topics/db/models.txt | 28 +++++++-------- 2 files changed, 58 insertions(+), 60 deletions(-) (limited to 'docs/ref') diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt index a43163c5e9..29cde19c78 100644 --- a/docs/ref/models/fields.txt +++ b/docs/ref/models/fields.txt @@ -70,8 +70,8 @@ If ``True``, the field is allowed to be blank. Default is ``False``. Note that this is different than :attr:`~Field.null`. :attr:`~Field.null` is purely database-related, whereas :attr:`~Field.blank` is validation-related. If -a field has ``blank=True``, validation on Django's admin site will allow entry -of an empty value. If a field has ``blank=False``, the field will be required. +a field has ``blank=True``, form validation will allow entry of an empty value. +If a field has ``blank=False``, the field will be required. .. _field-choices: @@ -81,14 +81,11 @@ of an empty value. If a field has ``blank=False``, the field will be required. .. attribute:: Field.choices An iterable (e.g., a list or tuple) of 2-tuples to use as choices for this -field. +field. If this is given, the default form widget will be a select box with +these choices instead of the standard text field. -If this is given, Django's admin will use a select box instead of the standard -text field and will limit choices to the choices given. - -A choices list is an iterable of 2-tuples; the first element in each -tuple is the actual value to be stored, and the second element is the -human-readable name. For example:: +The first element in each tuple is the actual value to be stored, and the +second element is the human-readable name. For example:: YEAR_IN_SCHOOL_CHOICES = ( ('FR', 'Freshman'), @@ -203,8 +200,8 @@ callable it will be called every time a new object is created. .. attribute:: Field.editable -If ``False``, the field will not be editable in the admin or via forms -automatically generated from the model class. Default is ``True``. +If ``False``, the field will not be displayed in the admin or any other +:class:`~django.forms.ModelForm`. Default is ``True``. ``error_messages`` ------------------ @@ -224,11 +221,11 @@ the `Field types`_ section below. .. attribute:: Field.help_text -Extra "help" text to be displayed under the field on the object's admin form. -It's useful for documentation even if your object doesn't have an admin form. +Extra "help" text to be displayed with the form widget. It's useful for +documentation even if your field isn't used on a form. -Note that this value is *not* HTML-escaped when it's displayed in the admin -interface. This lets you include HTML in :attr:`~Field.help_text` if you so +Note that this value is *not* HTML-escaped in automatically-generated +forms. This lets you include HTML in :attr:`~Field.help_text` if you so desire. For example:: help_text="Please use the following format: YYYY-MM-DD." @@ -259,7 +256,7 @@ Only one primary key is allowed on an object. If ``True``, this field must be unique throughout the table. -This is enforced at the database level and at the Django admin-form level. If +This is enforced at the database level and by model validation. If you try to save a model with a duplicate value in a :attr:`~Field.unique` field, a :exc:`django.db.IntegrityError` will be raised by the model's :meth:`~django.db.models.Model.save` method. @@ -279,7 +276,7 @@ For example, if you have a field ``title`` that has ``unique_for_date="pub_date"``, then Django wouldn't allow the entry of two records with the same ``title`` and ``pub_date``. -This is enforced at the Django admin-form level but not at the database level. +This is enforced by model validation but not at the database level. ``unique_for_month`` -------------------- @@ -337,7 +334,7 @@ otherwise. See :ref:`automatic-primary-key-fields`. A 64 bit integer, much like an :class:`IntegerField` except that it is guaranteed to fit numbers from -9223372036854775808 to 9223372036854775807. The -admin represents this as an ```` (a single-line input). +default form widget for this field is a :class:`~django.forms.TextInput`. ``BooleanField`` @@ -347,7 +344,8 @@ admin represents this as an ```` (a single-line input). A true/false field. -The admin represents this as a checkbox. +The default form widget for this field is a +:class:`~django.forms.CheckboxInput`. If you need to accept :attr:`~Field.null` values then use :class:`NullBooleanField` instead. @@ -361,7 +359,7 @@ A string field, for small- to large-sized strings. For large amounts of text, use :class:`~django.db.models.TextField`. -The admin represents this as an ```` (a single-line input). +The default form widget for this field is a :class:`~django.forms.TextInput`. :class:`CharField` has one extra required argument: @@ -414,9 +412,10 @@ optional arguments: for creation of timestamps. Note that the current date is *always* used; it's not just a default value that you can override. -The admin represents this as an ```` with a JavaScript -calendar, and a shortcut for "Today". Includes an additional ``invalid_date`` -error message key. +The default form widget for this field is a +:class:`~django.forms.TextInput`. The admin adds a JavaScript calendar, +and a shortcut for "Today". Includes an additional ``invalid_date`` error +message key. .. note:: As currently implemented, setting ``auto_now`` or ``auto_now_add`` to @@ -431,8 +430,9 @@ error message key. A date and time, represented in Python by a ``datetime.datetime`` instance. Takes the same extra arguments as :class:`DateField`. -The admin represents this as two ```` fields, with -JavaScript shortcuts. +The default form widget for this field is a single +:class:`~django.forms.TextInput`. The admin uses two separate +:class:`~django.forms.TextInput` widgets with JavaScript shortcuts. ``DecimalField`` ---------------- @@ -461,7 +461,7 @@ decimal places:: models.DecimalField(..., max_digits=19, decimal_places=10) -The admin represents this as an ```` (a single-line input). +The default form widget for this field is a :class:`~django.forms.TextInput`. .. note:: @@ -539,8 +539,8 @@ Also has one optional argument: Optional. A storage object, which handles the storage and retrieval of your files. See :doc:`/topics/files` for details on how to provide this object. -The admin represents this field as an ```` (a file-upload -widget). +The default form widget for this field is a +:class:`~django.forms.widgets.FileInput`. Using a :class:`FileField` or an :class:`ImageField` (see below) in a model takes a few steps: @@ -725,7 +725,7 @@ can change the maximum length using the :attr:`~CharField.max_length` argument. A floating-point number represented in Python by a ``float`` instance. -The admin represents this as an ```` (a single-line input). +The default form widget for this field is a :class:`~django.forms.TextInput`. .. _floatfield_vs_decimalfield: @@ -776,16 +776,16 @@ length using the :attr:`~CharField.max_length` argument. .. class:: IntegerField([**options]) -An integer. The admin represents this as an ```` (a -single-line input). +An integer. The default form widget for this field is a +:class:`~django.forms.TextInput`. ``IPAddressField`` ------------------ .. class:: IPAddressField([**options]) -An IP address, in string format (e.g. "192.0.2.30"). The admin represents this -as an ```` (a single-line input). +An IP address, in string format (e.g. "192.0.2.30"). The default form widget +for this field is a :class:`~django.forms.TextInput`. ``GenericIPAddressField`` ------------------------- @@ -795,8 +795,8 @@ as an ```` (a single-line input). .. versionadded:: 1.4 An IPv4 or IPv6 address, in string format (e.g. ``192.0.2.30`` or -``2a02:42fe::4``). The admin represents this as an ```` -(a single-line input). +``2a02:42fe::4``). The default form widget for this field is a +:class:`~django.forms.TextInput`. The IPv6 address normalization follows :rfc:`4291#section-2.2` section 2.2, including using the IPv4 format suggested in paragraph 3 of that section, like @@ -823,8 +823,8 @@ are converted to lowercase. .. class:: NullBooleanField([**options]) Like a :class:`BooleanField`, but allows ``NULL`` as one of the options. Use -this instead of a :class:`BooleanField` with ``null=True``. The admin represents -this as a ``