1
0
mirror of https://github.com/django/django.git synced 2025-10-26 07:06:08 +00:00

Fixed #21113 -- Made LogEntry.change_message language independent

Thanks Tim Graham for the review.
This commit is contained in:
Claude Paroz
2015-12-26 19:51:22 +01:00
parent 56aaae58a7
commit cf7894be88
11 changed files with 216 additions and 35 deletions

View File

@@ -1,5 +1,7 @@
from __future__ import unicode_literals
import json
from django.conf import settings
from django.contrib.admin.utils import quote
from django.contrib.contenttypes.models import ContentType
@@ -7,6 +9,7 @@ from django.db import models
from django.urls import NoReverseMatch, reverse
from django.utils import timezone
from django.utils.encoding import python_2_unicode_compatible, smart_text
from django.utils.text import get_text_list
from django.utils.translation import ugettext, ugettext_lazy as _
ADDITION = 1
@@ -18,6 +21,8 @@ class LogEntryManager(models.Manager):
use_in_migrations = True
def log_action(self, user_id, content_type_id, object_id, object_repr, action_flag, change_message=''):
if isinstance(change_message, list):
change_message = json.dumps(change_message)
self.model.objects.create(
user_id=user_id,
content_type_id=content_type_id,
@@ -50,6 +55,7 @@ class LogEntry(models.Model):
# Translators: 'repr' means representation (https://docs.python.org/3/library/functions.html#repr)
object_repr = models.CharField(_('object repr'), max_length=200)
action_flag = models.PositiveSmallIntegerField(_('action flag'))
# change_message is either a string or a JSON structure
change_message = models.TextField(_('change message'), blank=True)
objects = LogEntryManager()
@@ -69,7 +75,7 @@ class LogEntry(models.Model):
elif self.is_change():
return ugettext('Changed "%(object)s" - %(changes)s') % {
'object': self.object_repr,
'changes': self.change_message,
'changes': self.get_change_message(),
}
elif self.is_deletion():
return ugettext('Deleted "%(object)s."') % {'object': self.object_repr}
@@ -85,6 +91,46 @@ class LogEntry(models.Model):
def is_deletion(self):
return self.action_flag == DELETION
def get_change_message(self):
"""
If self.change_message is a JSON structure, interpret it as a change
string, properly translated.
"""
if self.change_message and self.change_message[0] == '[':
try:
change_message = json.loads(self.change_message)
except ValueError:
return self.change_message
messages = []
for sub_message in change_message:
if 'added' in sub_message:
if sub_message['added']:
sub_message['added']['name'] = ugettext(sub_message['added']['name'])
messages.append(ugettext('Added {name} "{object}".').format(**sub_message['added']))
else:
messages.append(ugettext('Added.'))
elif 'changed' in sub_message:
sub_message['changed']['fields'] = get_text_list(
sub_message['changed']['fields'], ugettext('and')
)
if 'name' in sub_message['changed']:
sub_message['changed']['name'] = ugettext(sub_message['changed']['name'])
messages.append(ugettext('Changed {fields} for {name} "{object}".').format(
**sub_message['changed']
))
else:
messages.append(ugettext('Changed {fields}.').format(**sub_message['changed']))
elif 'deleted' in sub_message:
sub_message['deleted']['name'] = ugettext(sub_message['deleted']['name'])
messages.append(ugettext('Deleted {name} "{object}".').format(**sub_message['deleted']))
change_message = ' '.join(msg[0].upper() + msg[1:] for msg in messages)
return change_message or ugettext('No fields changed.')
else:
return self.change_message
def get_edited_object(self):
"Returns the edited object represented by this log entry"
return self.content_type.get_object_for_this_type(pk=self.object_id)

View File

@@ -44,7 +44,9 @@ from django.utils.html import escape, format_html
from django.utils.http import urlencode, urlquote
from django.utils.safestring import mark_safe
from django.utils.text import capfirst, get_text_list
from django.utils.translation import string_concat, ugettext as _, ungettext
from django.utils.translation import (
override as translation_override, string_concat, ugettext as _, ungettext,
)
from django.views.decorators.csrf import csrf_protect
from django.views.generic import RedirectView
@@ -924,33 +926,44 @@ class ModelAdmin(BaseModelAdmin):
return urlencode({'_changelist_filters': preserved_filters})
return ''
@translation_override(None)
def construct_change_message(self, request, form, formsets, add=False):
"""
Construct a change message from a changed object.
Construct a JSON structure describing changes from a changed object.
Translations are deactivated so that strings are stored untranslated.
Translation happens later on LogEntry access.
"""
change_message = []
if add:
change_message.append(_('Added.'))
change_message.append({'added': {}})
elif form.changed_data:
change_message.append(_('Changed %s.') % get_text_list(form.changed_data, _('and')))
change_message.append({'changed': {'fields': form.changed_data}})
if formsets:
for formset in formsets:
for added_object in formset.new_objects:
change_message.append(_('Added %(name)s "%(object)s".')
% {'name': force_text(added_object._meta.verbose_name),
'object': force_text(added_object)})
change_message.append({
'added': {
'name': force_text(added_object._meta.verbose_name),
'object': force_text(added_object),
}
})
for changed_object, changed_fields in formset.changed_objects:
change_message.append(_('Changed %(list)s for %(name)s "%(object)s".')
% {'list': get_text_list(changed_fields, _('and')),
'name': force_text(changed_object._meta.verbose_name),
'object': force_text(changed_object)})
change_message.append({
'changed': {
'name': force_text(changed_object._meta.verbose_name),
'object': force_text(changed_object),
'fields': changed_fields,
}
})
for deleted_object in formset.deleted_objects:
change_message.append(_('Deleted %(name)s "%(object)s".')
% {'name': force_text(deleted_object._meta.verbose_name),
'object': force_text(deleted_object)})
change_message = ' '.join(change_message)
return change_message or _('No fields changed.')
change_message.append({
'deleted': {
'name': force_text(deleted_object._meta.verbose_name),
'object': force_text(deleted_object),
}
})
return change_message
def message_user(self, request, message, level=messages.INFO, extra_tags='',
fail_silently=False):

View File

@@ -29,7 +29,7 @@
<tr>
<th scope="row">{{ action.action_time|date:"DATETIME_FORMAT" }}</th>
<td>{{ action.user.get_username }}{% if action.user.get_full_name %} ({{ action.user.get_full_name }}){% endif %}</td>
<td>{{ action.change_message }}</td>
<td>{{ action.get_change_message }}</td>
</tr>
{% endfor %}
</tbody>