Skip to content

Commit

Permalink
Exclude m2m fields in TranslationsMixin.update_translations. Fixes Kr…
Browse files Browse the repository at this point in the history
  • Loading branch information
spectras committed Mar 21, 2016
1 parent bdb0836 commit 0b92183
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 7 deletions.
15 changes: 14 additions & 1 deletion docs/public/release_notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,23 @@
Release Notes
#############

.. release 1.5.1
*****************************
1.5.1 - current release
*****************************

Fixes:

- Filter out m2m and generic fields in
:meth:`~hvad.contrib.restframework.serializers.TranslationsMixin.update_translation`
so it does not bite when using (unsupported) m2m fields or generic relations in a
translation — :issue:`285`.

.. release 1.5.0
*****************************
1.5.0 - current release
1.5.0
*****************************

Released on February 2, 2016
Expand Down
17 changes: 12 additions & 5 deletions hvad/contrib/restframework/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,16 +134,23 @@ def update(self, instance, data):

def update_translation(self, instance, data):
if django.VERSION >= (1, 8):
fields = [field.name for field in self.Meta.model._meta.translations_model._meta.get_fields()
if field.name not in ('id', 'master', 'language_code')]
fields = set(field.name
for field in self.Meta.model._meta.translations_model._meta.get_fields()
if not field.is_relation or # regular fields are ok
field.one_to_one or # one to one is ok
field.many_to_one and field.related_model) # many_to_one only if not generic
else:
fields = [name for name in self.Meta.model._meta.translations_model._meta.get_all_field_names()
if name not in ('id', 'master', 'master_id', 'language_code')]
fields = set(name
for name in self.Meta.model._meta.translations_model._meta.get_all_field_names())
fields.intersection_update(data)
vetoed = fields.intersection('id', 'master', 'master_id', 'language_code')
if vetoed:
raise KeyError('These fields are not allowed in data: ' % ', '.join(vetoed))

for key, value in data.items():
setattr(instance, key, value)
instance.save(update_fields=fields)


#=============================================================================

class TranslatableModelMixin(object):
Expand Down
18 changes: 18 additions & 0 deletions hvad/test_utils/project/app/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from django.template.defaultfilters import slugify
from hvad.models import TranslatableModel, TranslatedFields
from hvad.manager import TranslationManager, TranslationQueryset
from hvad.utils import get_cached_translation
from django.utils.encoding import python_2_unicode_compatible

#===============================================================================
Expand Down Expand Up @@ -101,6 +102,23 @@ def __str__(self):
return self.name


@python_2_unicode_compatible
class TranslatedMany(TranslatableModel):
""" WARNING: this is not officially supported. This test model is more
of a monitor so we know what we break, than a promise not to break it.
"""
name = models.CharField(max_length=128)
translations = TranslatedFields(
translated_field = models.CharField(max_length=128),
many = models.ManyToManyField(Normal, related_name='translated_many'),
)

def __str__(self):
return ('%s, %s <%s>' % (self.name, self.translated_field, self.language_code)
if get_cached_translation(self) is not None
else '%s <none>' % self.name)


class Standard(models.Model):
""" Untranslatable Model with foreign key to Normal """
normal_field = models.CharField(max_length=255)
Expand Down
31 changes: 30 additions & 1 deletion hvad/tests/contrib/restframework.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from rest_framework.serializers import ModelSerializer, CharField
from hvad.test_utils.context_managers import LanguageOverride
from hvad.test_utils.testcase import HvadTestCase
from hvad.test_utils.project.app.models import Normal, Related
from hvad.test_utils.project.app.models import Normal, Related, TranslatedMany
from hvad.test_utils.data import NORMAL
from hvad.test_utils.fixtures import NormalFixture
from hvad.contrib.restframework import (TranslationsMixin,
Expand Down Expand Up @@ -420,6 +420,35 @@ def test_update_partial(self):
qs = Normal.objects.language('all').filter(pk=self.normal_id[1], shared_field='shared')
self.assertCountEqual([obj.language_code for obj in qs], self.translations)

def test_update_translated_many(self):
'Update an existing instance, with an excluded translated M2M field'
""" WARNING - this is unsupported, this test case is here so we know
what we break rather than to enforce that behavior
"""
obj = TranslatedMany.objects.language('en').create(name='shared', translated_field='English')
# reload with normal caching conditions so query counting works
obj = TranslatedMany.objects.untranslated().prefetch_related('translations').get(pk=obj.pk)
data = {
'name': 'shared-updated',
'translations': {
'en': {'translated_field': u'English-updated'},
'fr': {'translated_field': u'French-added'},
},
}
class TranslationSerializerClass(ModelSerializer):
class Meta:
exclude = ['many']
class SerializerClass(TranslationsMixin, ModelSerializer):
class Meta:
model = TranslatedMany
translations_serializer = TranslationSerializerClass

serializer = SerializerClass(instance=obj, data=data)
self.assertTrue(serializer.is_valid())
with self.assertNumQueries(4): # update shared, update en, insert fr
# select to delete (none so no actual delete after)
serializer.save()

#=============================================================================

class CombinedTests(HvadTestCase, NormalFixture):
Expand Down

0 comments on commit 0b92183

Please sign in to comment.