Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Fixes ticket 16319 #922

Closed
wants to merge 6 commits into from

3 participants

Paul Collins Preston Holmes Aymeric Augustin
Paul Collins

This is mainly a cleanup of the patch in 16319. I changed the formatting
of the success_message to use the .format() system instead.

paulcollinsiii added some commits
Paul Collins paulcollinsiii Fixes ticket 16319
This is mainly a cleanup of the patch in 16319. I changed the formatting
of the success_message to use the .format() system instead.
cc2d7dd
Paul Collins paulcollinsiii Merge branch 'master' into ticket_16319 564ee84
Paul Collins paulcollinsiii fixup comments for SuccessMessageMixin 0aca654
Paul Collins paulcollinsiii Updated per comments from claudep b0c2ce5
django/contrib/messages/views.py
((3 lines not shown))
+
+
+class SuccessMessageMixin(FormMixin):
+ """
+ A mixin that adds a success message on successful form submission
+ """
+ success_message = None
+
+ def form_valid(self, form):
+ success_message = self.get_success_message()
+ if success_message:
+ messages.success(self.request, success_message)
+ return super(SuccessMessageMixin, self).form_valid(form)
+
+ def get_success_message(self):
+ if hasattr(self, 'object') and self.object:
Aymeric Augustin Owner

Is this conditional really necessary?

If it is, it means that in some cases a success message with placeholders could be returned, that sounds bad.

What if the Mixin is being used on a non ModelForm style FormView?
I can change this further to signal what the string should be formatted with, but is there a preference on style for that?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
docs/ref/contrib/messages.txt
@@ -286,6 +286,33 @@ example::
use one of the ``add_message`` family of methods. It does not hide failures
that may occur for other reasons.
+Adding messages in Class Based Views
+------------------------------------
+
+If you're writing a form processing view and want to add a success message on
+valid form submission you can use the
+``'django.contrib.messages.views.SuccessMessageMixin'``.
Aymeric Augustin Owner

The single quotes aren't necessary.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Aymeric Augustin aaugustin commented on the diff
docs/ref/contrib/messages.txt
@@ -286,6 +286,33 @@ example::
use one of the ``add_message`` family of methods. It does not hide failures
that may occur for other reasons.
+Adding messages in Class Based Views
+------------------------------------
+
+If you're writing a form processing view and want to add a success message on
+valid form submission you can use the
+``'django.contrib.messages.views.SuccessMessageMixin'``.
+For example::
+
+ # views.py
+ from django.contrib.messages.views import SuccessMessageMixin
+ from django.views.generic.edit import CreateView
+ from myapp.models import Author
+
+
Aymeric Augustin Owner

Extra line break -- keep the docs compact.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
docs/ref/contrib/messages.txt
((8 lines not shown))
+valid form submission you can use the
+``'django.contrib.messages.views.SuccessMessageMixin'``.
+For example::
+
+ # views.py
+ from django.contrib.messages.views import SuccessMessageMixin
+ from django.views.generic.edit import CreateView
+ from myapp.models import Author
+
+
+ class AuthorCreate(CreateView, SuccessMessageMixin):
+ model = Author
+ success_url = '/success/'
+ success_message = "%(name)s was created successfully"
+
+.. note::
Aymeric Augustin Owner

Notes are pretty heavy in the rendered docs, this could be more readable as regular text.

Same comment for the warning below.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
docs/ref/contrib/messages.txt
((14 lines not shown))
+ from django.views.generic.edit import CreateView
+ from myapp.models import Author
+
+
+ class AuthorCreate(CreateView, SuccessMessageMixin):
+ model = Author
+ success_url = '/success/'
+ success_message = "%(name)s was created successfully"
+
+.. note::
+ In ``success_message`` fields from the ``model`` are available for string
+ interpolation using the ``%(field_name)s`` syntax.
+
+.. warning::
+ The order of the inherited classes matters. ``SuccessMessageMixin`` needs
+ to come after any form processing mixins
Aymeric Augustin Owner

Missing point.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
tests/generic_views/edit.py
@@ -315,3 +315,17 @@ def test_delete_without_redirect(self):
except ImproperlyConfigured:
pass
+class SuccessMessageMixinTests(TestCase):
+ urls = 'generic_views.urls'
+
+ def test_set_messages_success(self):
+ author = {'name': 'John Doe',
+ 'slug': 'success-msg'}
+ req = self.client.post('/edit/authors/create/msg/', author)
+ self.assertIn(views.AuthorCreateViewWithMsg.success_message % author,
+ req.cookies['messages'].value)
+
+ def test_set_message_false(self):
+ req = self.client.post('/edit/authors/create/msg/',
+ {'name': 'John Doe'})
+ self.assertFalse('messages' in req.cookies)
Aymeric Augustin Owner

Use assertNotIn.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
tests/generic_views/edit.py
@@ -315,3 +315,17 @@ def test_delete_without_redirect(self):
except ImproperlyConfigured:
pass
+class SuccessMessageMixinTests(TestCase):
+ urls = 'generic_views.urls'
+
+ def test_set_messages_success(self):
+ author = {'name': 'John Doe',
+ 'slug': 'success-msg'}
+ req = self.client.post('/edit/authors/create/msg/', author)
+ self.assertIn(views.AuthorCreateViewWithMsg.success_message % author,
Aymeric Augustin Owner

Minor: I tend to prefer testing with a literal: "John Doe was created successfully". That's slightly more reliable. But that's your call :)

Actually taking that I think from Carl's talk since I can update the test in one place rather than having to update the constant twice. I might be confusing it with another talk. The PyCon firehose of learning was a bit intense this time around.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
django/contrib/messages/views.py
@@ -0,0 +1,18 @@
+from django.views.generic.edit import FormMixin, ModelFormMixin
Preston Holmes Collaborator
ptone added a note

unsed ModelFormMixin

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
docs/ref/contrib/messages.txt
((11 lines not shown))
+:class:`~django.contrib.messages.views.SuccessMessageMixin`.
+For example::
+
+ # views.py
+ from django.contrib.messages.views import SuccessMessageMixin
+ from django.views.generic.edit import CreateView
+ from myapp.models import Author
+
+ class AuthorCreate(SuccessMessageMixin, CreateView):
+ model = Author
+ success_url = '/success/'
+ success_message = "%(name)s was created successfully"
+
+The cleaned data from the ``form`` is available for string interpolation using
+the ``%(field_name)s`` syntax. For ModelForms, if you need access to fields
+from the saved ``object`` override the ``get_success_message`` method. For
Preston Holmes Collaborator
ptone added a note

ideally get_success_message should be a link to that method's docs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Preston Holmes
Collaborator

Made a couple minor inline comments.

I think the tests should be moved to django/contrib/messages/tests, since contrib can't count on core in tests, but we shouldn't build dependencies for the other way around.

Otherwise, on first review, this looks like it delivers on the feature request

Preston Holmes
Collaborator

merged in 9a85ad8

Preston Holmes ptone closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Mar 18, 2013
  1. Paul Collins

    Fixes ticket 16319

    paulcollinsiii authored
    This is mainly a cleanup of the patch in 16319. I changed the formatting
    of the success_message to use the .format() system instead.
  2. Paul Collins
  3. Paul Collins
Commits on Mar 19, 2013
  1. Paul Collins
  2. Paul Collins
  3. Paul Collins
This page is out of date. Refresh to see the latest.
1  django/contrib/messages/tests/__init__.py
View
@@ -2,3 +2,4 @@
from django.contrib.messages.tests.fallback import FallbackTest
from django.contrib.messages.tests.middleware import MiddlewareTest
from django.contrib.messages.tests.session import SessionTest
+from django.contrib.messages.tests.mixins import SuccessMessageMixinTests
19 django/contrib/messages/tests/mixins.py
View
@@ -0,0 +1,19 @@
+from django.test.testcases import TestCase
+from django.contrib.messages.tests.urls import ContactFormViewWithMsg
+from django.core.urlresolvers import reverse
+
+class SuccessMessageMixinTests(TestCase):
+ urls = 'django.contrib.messages.tests.urls'
+
+ def test_set_messages_success(self):
+ author = {'name': 'John Doe',
+ 'slug': 'success-msg'}
+ add_url = reverse('add_success_msg')
+ req = self.client.post(add_url, author)
+ self.assertIn(ContactFormViewWithMsg.success_message % author,
+ req.cookies['messages'].value)
+
+ def test_set_message_false(self):
+ req = self.client.post('/edit/authors/create/msg/',
+ {'name': 'John Doe'})
+ self.assertNotIn('messages', req.cookies)
18 django/contrib/messages/tests/urls.py
View
@@ -1,10 +1,13 @@
-from django.conf.urls import patterns
+from django.conf.urls import patterns, url
from django.contrib import messages
from django.core.urlresolvers import reverse
+from django import forms
from django.http import HttpResponseRedirect, HttpResponse
from django.template import RequestContext, Template
from django.template.response import TemplateResponse
from django.views.decorators.cache import never_cache
+from django.contrib.messages.views import SuccessMessageMixin
+from django.views.generic.edit import FormView
TEMPLATE = """{% if messages %}
<ul class="messages">
@@ -49,8 +52,21 @@ def show(request):
def show_template_response(request):
return TemplateResponse(request, Template(TEMPLATE))
+
+class ContactForm(forms.Form):
+ name = forms.CharField(required=True)
+ slug = forms.SlugField(required=True)
+
+
+class ContactFormViewWithMsg(SuccessMessageMixin, FormView):
+ form_class = ContactForm
+ success_url = show
+ success_message = "%(name)s was created successfully"
+
+
urlpatterns = patterns('',
('^add/(debug|info|success|warning|error)/$', add),
+ url('^add/msg/$', ContactFormViewWithMsg.as_view(), name='add_success_msg'),
('^show/$', show),
('^template_response/add/(debug|info|success|warning|error)/$', add_template_response),
('^template_response/show/$', show_template_response),
18 django/contrib/messages/views.py
View
@@ -0,0 +1,18 @@
+from django.views.generic.edit import FormMixin
+from django.contrib import messages
+
+
+class SuccessMessageMixin(FormMixin):
+ """
+ Adds a success message on successful form submission.
+ """
+ success_message = None
+
+ def form_valid(self, form):
+ success_message = self.get_success_message(form.cleaned_data)
+ if success_message:
+ messages.success(self.request, success_message)
+ return super(SuccessMessageMixin, self).form_valid(form)
+
+ def get_success_message(self, cleaned_data):
+ return self.success_message % cleaned_data
50 docs/ref/contrib/messages.txt
View
@@ -286,6 +286,56 @@ example::
use one of the ``add_message`` family of methods. It does not hide failures
that may occur for other reasons.
+Adding messages in Class Based Views
+------------------------------------
+
+.. versionadded:: 1.6
+
+.. class:: django.contrib.messages.views.SuccessMessageMixin
+
+ Adds a success message attribute to
+ :class:`~django.views.generic.edit.FormView` based classes
+
+ .. method:: get_success_message(cleaned_data)
+
+ ``cleaned_data`` is the cleaned data from the form which is used for
+ string formatting
+
+**Example views.py**::
+
+ from django.contrib.messages.views import SuccessMessageMixin
+ from django.views.generic.edit import CreateView
+ from myapp.models import Author
+
+ class AuthorCreate(SuccessMessageMixin, CreateView):
Aymeric Augustin Owner

Extra line break -- keep the docs compact.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ model = Author
+ success_url = '/success/'
+ success_message = "%(name)s was created successfully"
+
+The cleaned data from the ``form`` is available for string interpolation using
+the ``%(field_name)s`` syntax. For ModelForms, if you need access to fields
+from the saved ``object`` override the
+:meth:`~django.contrib.messages.views.SuccessMessageMixin.get_success_message`
+method.
+
+**Example views.py for ModelForms**::
+
+ from django.contrib.messages.views import SuccessMessageMixin
+ from django.views.generic.edit import CreateView
+ from myapp.models import ComplicatedModel
+
+ class ComplicatedCreate(CreateView, SuccessMessageMixin):
+ model = ComplicatedModel
+ success_url = '/success/'
+ success_message = "%(calculated_field)s was created successfully"
+
+ def get_success_message(self, cleaned_data):
+ return self.success_message % dict(cleaned_data,
+ calculated_field=self.object.calculated_field)
+
+Note the change in the inheritance order here. The ``CreateView`` must be first
+so that ``object`` is created before ``get_success_message`` is called.
+
Expiration of messages
======================
4 docs/releases/1.6.txt
View
@@ -126,6 +126,10 @@ Minor features
* The ``MemcachedCache`` cache backend now uses the latest :mod:`pickle`
protocol available.
+* Added :class:`~django.contrib.messages.views.SuccessMessageMixin` which
+ provides a ``success_message`` attribute for
+ :class:`~django.view.generic.edit.FormView` based classes.
+
* Added the :attr:`django.db.models.ForeignKey.db_constraint` and
:attr:`django.db.models.ManyToManyField.db_constraint` options.
1  tests/generic_views/views.py
View
@@ -1,6 +1,7 @@
from __future__ import absolute_import
from django.contrib.auth.decorators import login_required
+from django.contrib.messages.views import SuccessMessageMixin
from django.core.paginator import Paginator
from django.core.urlresolvers import reverse, reverse_lazy
from django.utils.decorators import method_decorator
Something went wrong with that request. Please try again.