Skip to content

Commit b1d1e07

Browse files
committed
fixes saxix#15
- send proper messages to user when concurrency avoid updates - do not creaet LogEntry in the admin when concurrency avoid updates
1 parent b16f47b commit b1d1e07

File tree

2 files changed

+101
-3
lines changed

2 files changed

+101
-3
lines changed

concurrency/admin.py

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
## -*- coding: utf-8 -*-
22
from __future__ import absolute_import, unicode_literals
3-
from django.utils.encoding import force_text
3+
import re
4+
from django.utils.encoding import force_text, force_unicode
45
from django.contrib import admin, messages
56
from django.core.exceptions import ImproperlyConfigured, ValidationError
67
from django.db.models import Q
@@ -10,7 +11,7 @@
1011
from django.utils.safestring import mark_safe
1112
from django.contrib.admin import helpers
1213
from django.http import HttpResponse, HttpResponseRedirect
13-
from django.utils.translation import ugettext as _
14+
from django.utils.translation import ungettext, ugettext as _
1415
from concurrency import forms
1516
from concurrency.api import get_revision_of_object, get_version_fieldname
1617
from concurrency.config import conf, CONCURRENCY_LIST_EDITABLE_POLICY_SILENT
@@ -162,8 +163,9 @@ def save_model(self, request, obj, form, change):
162163
setattr(obj, get_version_fieldname(obj), int(version))
163164
super(ConcurrencyListEditableMixin, self).save_model(request, obj, form, change)
164165
except RecordModifiedError:
166+
self._concurrency_list_editable_errors.append(obj.pk)
165167
if self.list_editable_policy == CONCURRENCY_LIST_EDITABLE_POLICY_SILENT:
166-
messages.error(request, _("Record with pk `{0.pk}` has been modified and was not updated").format(obj))
168+
pass
167169
else:
168170
raise
169171

@@ -174,5 +176,49 @@ class ConcurrentModelAdmin(ConcurrencyActionMixin,
174176
form = ConcurrentForm
175177
formfield_overrides = {forms.VersionField: {'widget': VersionWidget}}
176178

179+
def changelist_view(self, request, extra_context=None):
180+
self._concurrency_list_editable_errors = []
181+
return super(ConcurrentModelAdmin, self).changelist_view(request, extra_context)
182+
177183
def save_model(self, request, obj, form, change):
178184
return super(ConcurrentModelAdmin, self).save_model(request, obj, form, change)
185+
186+
def log_change(self, request, object, message):
187+
if object.pk in self._concurrency_list_editable_errors:
188+
return
189+
super(ConcurrentModelAdmin, self).log_change(request, object, message)
190+
191+
def log_deletion(self, request, object, object_repr):
192+
if object.pk in self._concurrency_list_editable_errors:
193+
return
194+
super(ConcurrentModelAdmin, self).log_deletion(request, object, object_repr)
195+
196+
def message_user(self, request, message, **kwargs):
197+
# This is ugly but we do not want to touch the changelist_view() code.
198+
opts = self.model._meta
199+
if self._concurrency_list_editable_errors:
200+
names = force_unicode(opts.verbose_name), force_unicode(opts.verbose_name_plural)
201+
pattern = ur"(?P<num>\d+) ({0}|{1})".format(*names)
202+
rex = re.compile(pattern)
203+
m = rex.match(message)
204+
concurrency_errros = len(self._concurrency_list_editable_errors)
205+
if m:
206+
updated_record = int(m.group('num')) - len(self._concurrency_list_editable_errors)
207+
if updated_record == 0:
208+
message = _("No %(name)s were changed due conflict errors" % {'name': names[0]})
209+
else:
210+
ids = ",".join(map(str, self._concurrency_list_editable_errors))
211+
messages.error(request,
212+
ungettext("Record with pk `{0}` has been modified and was not updated",
213+
"Records `{0}` have been modified and were not updated",
214+
concurrency_errros).format(ids))
215+
if updated_record == 1:
216+
name = force_unicode(opts.verbose_name)
217+
else:
218+
name = force_unicode(opts.verbose_name_plural)
219+
message = ungettext("%(count)s %(name)s was changed successfully.",
220+
"%(count)s %(name)s were changed successfully.",
221+
updated_record) % {'count': updated_record,
222+
'name': name}
223+
224+
return super(ConcurrentModelAdmin, self).message_user(request, message, **kwargs)

concurrency/tests/admin_list_editable.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# -*- coding: utf-8 -*-
22
from concurrency.tests.base import AdminTestCase, SENTINEL
33
from concurrency.tests.models import ListEditableConcurrentModel, NoActionsConcurrentModel
4+
from django.contrib.admin.models import LogEntry
5+
from django.contrib.contenttypes.models import ContentType
46

57

68
class TestListEditable(AdminTestCase):
@@ -39,6 +41,56 @@ def test_concurrency(self):
3941
self.assertTrue(self.TARGET.objects.filter(dummy_char=SENTINEL).exists())
4042
self.assertFalse(self.TARGET.objects.filter(dummy_char='CHAR').exists())
4143

44+
def test_message_user(self):
45+
res = self.app.get('/admin/', user='sax')
46+
res = res.click(self.TARGET._meta.verbose_name_plural)
47+
48+
self._create_conflict(1)
49+
50+
form = res.forms['changelist-form']
51+
form['form-0-dummy_char'] = 'CHAR1'
52+
form['form-1-dummy_char'] = 'CHAR2'
53+
res = form.submit('_save').follow()
54+
55+
messages = map(str, list(res.context['messages']))
56+
57+
self.assertIn('Record with pk `1` has been modified and was not updated',
58+
messages)
59+
self.assertIn('1 ListEditable-ConcurrentModel was changed successfully.',
60+
messages)
61+
62+
def test_message_user_no_changes(self):
63+
res = self.app.get('/admin/', user='sax')
64+
res = res.click(self.TARGET._meta.verbose_name_plural)
65+
66+
self._create_conflict(1)
67+
68+
form = res.forms['changelist-form']
69+
form['form-0-dummy_char'] = 'CHAR1'
70+
res = form.submit('_save').follow()
71+
72+
messages = map(str, list(res.context['messages']))
73+
74+
self.assertIn('No ListEditable-ConcurrentModel were changed due conflict errors',
75+
messages)
76+
self.assertEqual(len(messages), 1)
77+
78+
def test_log_change(self):
79+
res = self.app.get('/admin/', user='sax')
80+
res = res.click(self.TARGET._meta.verbose_name_plural)
81+
log_filter = dict(user__username='sax',
82+
content_type=ContentType.objects.get_for_model(self.TARGET))
83+
84+
logs = list(LogEntry.objects.filter(**log_filter).values_list('pk', flat=True))
85+
86+
self._create_conflict(1)
87+
88+
form = res.forms['changelist-form']
89+
form['form-0-dummy_char'] = 'CHAR1'
90+
res = form.submit('_save').follow()
91+
new_logs = LogEntry.objects.filter(**log_filter).exclude(id__in=logs).exists()
92+
self.assertFalse(new_logs, "LogEntry created even if conflict error")
93+
4294

4395
class TestListEditableWithNoActions(TestListEditable):
4496
TARGET = NoActionsConcurrentModel

0 commit comments

Comments
 (0)