Skip to content
This repository has been archived by the owner on Mar 15, 2018. It is now read-only.

Commit

Permalink
Browse files Browse the repository at this point in the history
Merge pull request #1193 from diox/delete-translations
Allow deletion of Translations (bug 902435)
  • Loading branch information
diox committed Oct 16, 2013
2 parents 259400c + 708cb83 commit b5fd030
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 4 deletions.
3 changes: 2 additions & 1 deletion apps/translations/fields.py
Expand Up @@ -23,7 +23,8 @@ class TranslatedField(models.ForeignKey):
def __init__(self, **kwargs):
# to_field: The field on the related object that the relation is to.
# Django wants to default to translations.autoid, but we need id.
options = dict(null=True, to_field='id', unique=True, blank=True)
options = dict(null=True, to_field='id', unique=True, blank=True,
on_delete=models.SET_NULL)
kwargs.update(options)
self.short = kwargs.pop('short', True)
self.require_locale = kwargs.pop('require_locale', True)
Expand Down
37 changes: 35 additions & 2 deletions apps/translations/models.py
@@ -1,4 +1,5 @@
from django.db import models, connection
from django.db import connections, models, router
from django.db.models.deletion import Collector
from django.utils import encoding

import bleach
Expand Down Expand Up @@ -54,6 +55,38 @@ def save(self, **kwargs):
self.clean()
return super(Translation, self).save(**kwargs)

def delete(self, using=None):
# FIXME: if the Translation is the one used as default/fallback,
# then deleting it will mean the corresponding field on the related
# model will stay empty even if there are translations in other
# languages!
cls = self.__class__
using = using or router.db_for_write(cls, instance=self)
# Look for all translations for the same string (id=self.id) except the
# current one (autoid=self.autoid).
qs = cls.objects.filter(id=self.id).exclude(autoid=self.autoid)
if qs.using(using).exists():
# If other Translations for the same id exist, we just need to
# delete this one and *only* this one, without letting Django
# collect dependencies (it'd remove the others, which we want to
# keep).
assert self._get_pk_val() is not None
collector = Collector(using=using)
collector.collect([self], collect_related=False)
# In addition, because we have FK pointing to a non-unique column,
# we need to force MySQL to ignore constraints because it's dumb
# and would otherwise complain even if there are remaining rows
# that matches the FK.
with connections[using].constraint_checks_disabled():
collector.delete()
else:
# If no other Translations with that id exist, then we should let
# django behave normally. It should find the related model and set
# the FKs to NULL.
return super(Translation, self).delete(using=using)

delete.alters_data = True

@classmethod
def _cache_key(cls, pk, db):
# Hard-coding the class name here so that subclasses don't try to cache
Expand All @@ -75,7 +108,7 @@ def new(cls, string, locale, id=None):
"""
if id is None:
# Get a sequence key for the new translation.
cursor = connection.cursor()
cursor = connections['default'].cursor()
cursor.execute("""UPDATE translations_seq
SET id=LAST_INSERT_ID(id + @@global.auto_increment_increment)""")

Expand Down
42 changes: 41 additions & 1 deletion apps/translations/tests/test_models.py
Expand Up @@ -13,7 +13,7 @@
import multidb
from mock import patch
from nose import SkipTest
from nose.tools import eq_
from nose.tools import eq_, ok_
from test_utils import ExtraAppTestCase, trans_eq

from testapp.models import TranslatedModel, UntranslatedModel, FancyModel
Expand Down Expand Up @@ -400,6 +400,46 @@ def test_translations_reading_from_multiple_db_pinning(self):
eq_(len(connections['slave-1'].queries), 0)
eq_(len(connections['slave-2'].queries), 0)

def test_delete_set_null(self):
"""
Test that deleting a translation sets the corresponding FK to NULL,
if it was the only translation for this field.
"""
obj = TranslatedModel.objects.get(id=1)
trans_id = obj.description.id
eq_(Translation.objects.filter(id=trans_id).count(), 1)

obj.description.delete()

obj = TranslatedModel.objects.no_cache().get(id=1)
eq_(obj.description_id, None)
eq_(obj.description, None)
eq_(Translation.objects.no_cache().filter(id=trans_id).exists(), False)

@patch.object(TranslatedModel, 'get_fallback', create=True)
def test_delete_keep_other_translations(self, get_fallback):
# To make sure both translations for the name are used, set the
# fallback to the second locale, which is 'de'.
get_fallback.return_value = 'de'

obj = TranslatedModel.objects.get(id=1)

orig_name_id = obj.name.id
eq_(obj.name.locale.lower(), 'en-us')
eq_(Translation.objects.filter(id=orig_name_id).count(), 2)

obj.name.delete()

obj = TranslatedModel.objects.no_cache().get(id=1)
eq_(Translation.objects.no_cache().filter(id=orig_name_id).count(), 1)

# We shouldn't have set name_id to None.
eq_(obj.name_id, orig_name_id)

# We should find a Translation.
eq_(obj.name.id, orig_name_id)
eq_(obj.name.locale, 'de')


def test_translation_bool():
t = lambda s: Translation(localized_string=s)
Expand Down

0 comments on commit b5fd030

Please sign in to comment.