Skip to content

Commit

Permalink
Implement support for translated fields in save's update_fields argum…
Browse files Browse the repository at this point in the history
…ent.
  • Loading branch information
spectras committed Dec 5, 2015
1 parent 4daaaed commit 35c37a5
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 11 deletions.
8 changes: 7 additions & 1 deletion docs/public/models.rst
Original file line number Diff line number Diff line change
Expand Up @@ -117,13 +117,19 @@ get_available_languages
save
====

.. method:: save(force_insert=False, force_update=False, using=None)
.. method:: save(force_insert=False, force_update=False, using=None, update_fields=None)

Overrides :meth:`~django.db.models.Model.save`.

This method runs an extra query to save the translation cached on
this instance, if any translation was cached.

It accepts both translated and untranslated fields in ``update_fields``.

- If only untranslated fields are specified, the extra query will be skipped.
- If only translated fields are specified, the shared model update will be skipped.
Note that this means signals will not be triggered.


**********************
Working with relations
Expand Down
8 changes: 8 additions & 0 deletions docs/public/release_notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@ New features:
- It is now possible to specify a :ref:`custom translation base <custom-translation-models>`
model, allowing advanced translation manipulation, such as controlling their loading
with :meth:`~django.db.models.Model.from_db`.
- Translated model's :meth:`~django.db.models.Model.save` method now accepts translated field
names in ``update_fields``. Also, if only translated fields, or only untranslated fields
are specified in ``update_fields``, the extra query will be skipped.

Compatibility Warnings:

- Saving of translations now happens in the model's :meth:`~django.db.models.Model.save` method.
It used to happen in the ``post_save`` signal.

.. release 1.4.0
Expand Down
34 changes: 24 additions & 10 deletions hvad/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,13 +252,29 @@ def __init__(self, *args, **kwargs):
tkwargs['language_code'] = tkwargs.get('language_code') or get_language()
set_cached_translation(self, self._meta.translations_model(**tkwargs))

@classmethod
def save_translations(cls, instance, **kwargs):
'Signal handler for post_save'
translation = get_cached_translation(instance)
if translation is not None:
translation.master = instance
translation.save()
def save(self, *args, **skwargs):
translation_model = self._meta.translations_model
translation = get_cached_translation(self)
tkwargs = skwargs.copy()

# split update_fields in shared/translated fields
update_fields = skwargs.get('update_fields')
if update_fields is not None:
supdate, tupdate = [], []
for name in update_fields:
if name in self._translated_field_names and not name in ('id', 'master_id', 'master'):
tupdate.append(name)
else:
supdate.append(name)
skwargs['update_fields'], tkwargs['update_fields'] = supdate, tupdate

# save share and translated model in a single transaction
if update_fields is None or skwargs['update_fields']:
super(TranslatableModel, self).save(*args, **skwargs)
if (update_fields is None or tkwargs['update_fields']) and translation is not None:
translation.master = self
translation.save(*args, **tkwargs)
save.alters_data = True

def translate(self, language_code):
''' Create a new translation for current instance.
Expand All @@ -269,6 +285,7 @@ def translate(self, language_code):
self._meta.translations_model(language_code=language_code)
)
return self
translate.alters_data = True

def safe_translation_getter(self, name, default=None):
cache = get_cached_translation(self)
Expand Down Expand Up @@ -469,7 +486,4 @@ def prepare_translatable_model(sender, **kwargs):
model._meta
)

# Attach save_translations
post_save.connect(model.save_translations, sender=model, weak=False)

class_prepared.connect(prepare_translatable_model)
44 changes: 44 additions & 0 deletions hvad/tests/basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,50 @@ def test_create_lang_deprecation(self):
)


class UpdateTest(HvadTestCase, NormalFixture):
normal_count = 2

def test_basic_update(self):
obj = Normal.objects.language('en').get(pk=self.normal_id[1])
obj.shared_field = 'update_shared'
obj.translated_field = 'update_translated'
with self.assertNumQueries(2):
obj.save()
obj = Normal.objects.language().get(pk=self.normal_id[1])
self.assertEqual(obj.shared_field, 'update_shared')
self.assertEqual(obj.translated_field, 'update_translated')

def test_force_update(self):
obj = Normal.objects.language('en').get(pk=self.normal_id[1])
obj.shared_field = 'update_shared'
obj.translated_field = 'update_translated'
with self.assertNumQueries(2):
obj.save(force_update=True)
obj = Normal.objects.language().get(pk=self.normal_id[1])
self.assertEqual(obj.shared_field, 'update_shared')
self.assertEqual(obj.translated_field, 'update_translated')

def test_update_fields_shared(self):
obj = Normal.objects.language('en').get(pk=self.normal_id[1])
obj.shared_field = 'update_shared'
obj.translated_field = 'update_translated'
with self.assertNumQueries(1):
obj.save(update_fields=['shared_field'])
obj = Normal.objects.language().get(pk=self.normal_id[1])
self.assertEqual(obj.shared_field, 'update_shared')
self.assertEqual(obj.translated_field, NORMAL[1].translated_field['en'])

def test_update_fields_translated(self):
obj = Normal.objects.language('en').get(pk=self.normal_id[1])
obj.shared_field = 'update_shared'
obj.translated_field = 'update_translated'
with self.assertNumQueries(1):
obj.save(update_fields=['translated_field'])
obj = Normal.objects.language().get(pk=self.normal_id[1])
self.assertEqual(obj.shared_field, NORMAL[1].shared_field)
self.assertEqual(obj.translated_field, 'update_translated')


class DeleteTest(HvadTestCase, NormalFixture):
normal_count = 2

Expand Down

0 comments on commit 35c37a5

Please sign in to comment.