Skip to content

Commit a932168

Browse files
committed
Prepare version 1.3 for release: compile release notes, get rid of pending removals, switch deprecations to assertions.
1 parent c20a3f0 commit a932168

12 files changed

Lines changed: 91 additions & 209 deletions

File tree

docs/internal/manager.rst

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -501,11 +501,6 @@ TranslationManager
501501
will filter by language, returning only objects that have a translation
502502
in the specified language. Translated fields will be available on the
503503
objects, in the specified language.
504-
505-
.. method:: using_translations(self)
506-
507-
Functionally equivalent to calling :meth:`language` with no argument. This
508-
method is deprecated and will be removed in the future.
509504

510505
.. method:: untranslated(self)
511506

docs/internal/models.rst

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -57,20 +57,6 @@ BaseTranslationModel
5757
class is abstract.
5858

5959

60-
**********************
61-
TranslatableModelBase
62-
**********************
63-
64-
.. class:: TranslatableModelBase
65-
66-
.. deprecated:: 0.5
67-
68-
Metaclass of :class:`TranslatableModel`. It is no longer used and should be
69-
removed from metaclass inheritance tree in projects.
70-
71-
.. method:: __new__(cls, name, bases, attrs)
72-
73-
7460
******************
7561
TranslatableModel
7662
******************

docs/public/models.rst

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,11 @@ regular Django, with the following additional features:
2020
:term:`translations <Translations Model>` of your model directly. Behind the
2121
scenes, it will be a reversed ForeignKey from the
2222
:term:`Translations Model` to your :term:`Shared Model`.
23-
- Custom settings can be set on the :term:`Translations Model` by passing them
24-
as a ``meta`` dictionnary to :class:`~hvad.models.TranslatedFields`.
23+
- Translatable fields can be used in the model options. For options that take
24+
groupings of fields (``unique_together`` and ``index_together``), each grouping
25+
may have either translatable or non-translatable fields, but not both.
26+
- Special field ``language_code`` may be used for defining ``unique_together``
27+
constraints that are only unique per language.
2528

2629
A full example of a model with translations::
2730

@@ -35,8 +38,9 @@ A full example of a model with translations::
3538
title = models.CharField(max_length=100),
3639
subtitle = models.CharField(max_length=255),
3740
released = models.DateTimeField(),
38-
meta={'unique_together': [('title', 'subtitle')]},
3941
)
42+
class Meta:
43+
unique_together = [('title', 'subtitle')]
4044

4145
.. note:: The :djterm:`Meta <meta-options>` class of the model may not use the
4246
translatable fields in :attr:`~django.db.models.Options.order_with_respect_to`.

docs/public/release_notes.rst

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,51 @@
22
Release Notes
33
#############
44

5+
.. release 1.3.0
6+
7+
*****************************
8+
1.3.0 - upcoming release
9+
*****************************
10+
11+
This release is a collection of fixes and improvements, some of which may
12+
introduce minor compatibility issues. Please make sure you fix any deprecation
13+
warnings before upgrading to avoid those issues.
14+
15+
Python and Django versions supported:
16+
17+
- Django 1.5 is no longer officially supported.
18+
- Django 1.6 has reached its end of life, and support will be dropped in hvad 1.4.
19+
- As a reminder, Django 1.4 is still supported, so supported versions for this
20+
release are: 1.4, 1.6, 1.7, 1.8.
21+
22+
New Features:
23+
24+
- Russian and Latvian translations are now included, thanks to Juris Malinens — :issue:`248`.
25+
26+
Compatibility Warnings: deprecated features pending removal in 1.3 have been
27+
removed. Most notably:
28+
29+
- Calling ``save()`` on an invalid form now raises an assertion exception.
30+
- Classes ``TranslatableModelBase``, ``TranslationFallbackManager``,
31+
``TranslatableBaseView`` and method ``TranslationManager.using_translations()``
32+
no longer exist.
33+
- Deprecated view methods and context modifiers now raise an assertion exception.
34+
35+
Fixes:
36+
37+
- Lift Django restrictions on translated fields in ``Meta.unique_together`` and
38+
``Meta.index_together`` — :issue:`252`.
39+
- Properly forward model validation methods to translation validation methods, so
40+
that model validation detects constraint violations on the translation as well.
41+
Fixes duplicate detection in admin for unique constraints on translations — :issue:`251`.
42+
- Detect name clash between translated and non-translated fields — :issue:`240`.
43+
- Validate that at least one translation is provided when deserializing objects in
44+
:class:`~hvad.contrib.restframework.TranslationsMixin` — :issue:`256`.
45+
- Fix handling of model edition from an admin popup in Django 1.7 and newer — :issue:`253`.
46+
- Generate proper ORM structures for fallbacks. Avoids table relabeling breaking
47+
queries, for instance when using ``update()`` or feeding a queryset to another
48+
queryset — :issue:`250`.
49+
550
.. release 1.2.2
651
752
*****************************

hvad/forms.py

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -176,17 +176,9 @@ def save(self, commit=True):
176176
If no language is specified in self.cleaned_data, assume the instance
177177
is preloaded with correct language.
178178
'''
179-
if not self.is_valid():
180-
# raise in 1.3, remove in 1.5
181-
warnings.warn('Calling save() on an invalid form is deprecated and '
182-
'will fail in the future. Check the result of .is_valid() '
183-
'before calling save().', DeprecationWarning, stacklevel=2)
184-
raise ValueError((
185-
_("The %s could not be created because the data didn't validate.")
186-
if self.instance.pk is None else
187-
_("The %s could not be changed because the data didn't validate.")
188-
) % self.instance._meta.object_name
189-
)
179+
assert self.is_valid(), ('Method save() must not be called on an invalid '
180+
'form. Check the result of .is_valid() before '
181+
'calling save().')
190182

191183
# Get the right translation for object and language
192184
# It should have been done in _post_clean, but instance may have been

hvad/manager.py

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -976,17 +976,6 @@ def iterator(self):
976976
FallbackQueryset = LegacyFallbackQueryset if LEGACY_FALLBACKS else SelfJoinFallbackQueryset
977977

978978

979-
class TranslationFallbackManager(models.Manager): # pragma: no cover
980-
"""
981-
Manager class for the shared model, without specific translations. Allows
982-
using `use_fallbacks()` to enable per object language fallback.
983-
"""
984-
def __init__(self, *args, **kwargs):
985-
# remove in 1.3
986-
raise RuntimeError('TranslationFallbackManager is no longer used. Please use '
987-
'TranslationManager\'s untranslated() method.')
988-
989-
990979
#===============================================================================
991980
# TranslationManager
992981
#===============================================================================
@@ -1011,10 +1000,6 @@ def __init__(self, *args, **kwargs):
10111000
self.default_class = kwargs.pop('default_class', self.default_class)
10121001
super(TranslationManager, self).__init__(*args, **kwargs)
10131002

1014-
def using_translations(self): #pragma: no cover
1015-
# remove in 1.3
1016-
raise RuntimeError('using_translations() has been removed, use language() instead')
1017-
10181003
def _make_queryset(self, klass, core_filters):
10191004
''' Builds a queryset of given class.
10201005
core_filters tells whether the queryset will bypass RelatedManager

hvad/models.py

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -27,19 +27,12 @@ def update_settings(*args, **kwargs):
2727
FALLBACK_LANGUAGES = tuple( code for code, name in settings.LANGUAGES )
2828
TABLE_NAME_SEPARATOR = getattr(settings, 'HVAD_TABLE_NAME_SEPARATOR', '_')
2929

30-
if hasattr(settings, 'NANI_TABLE_NAME_SEPARATOR'):
31-
# remove in 1.3
32-
raise ImproperlyConfigured(
33-
'NANI_TABLE_NAME_SEPARATOR setting is obsolete and has been '
34-
'removed. Please rename it to HVAD_TABLE_NAME_SEPARATOR.')
35-
36-
3730
#===============================================================================
3831

3932
def _split_together(constraints, fields, meta, name):
4033
sconst, tconst = [], []
4134
if name in meta:
42-
# raise in 1.3, remove in 1.5
35+
# raise in 1.4, remove in 1.6
4336
warnings.warn('Passing \'%s\' to TranslatedFields is deprecated. Please use '
4437
'Please Meta.%s instead.' % (name, name), DeprecationWarning)
4538
tconst.extend(meta[name])
@@ -205,16 +198,6 @@ class Meta:
205198
abstract = True
206199

207200

208-
class TranslatableModelBase(ModelBase):
209-
def __new__(cls, *args, **kwargs):
210-
# remove in 1.3
211-
raise RuntimeError(
212-
'TranslatableModelBase metaclass is no longer used and has been '
213-
'removed. Hvad no longer uses a custom metaclass so conflict '
214-
'resolution is no longer required, TranslatableModelBase can be '
215-
'dropped.')
216-
217-
218201
class NoTranslation(object):
219202
pass
220203

hvad/tests/basic.py

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from django.utils import translation
99
from hvad.compat import with_metaclass
1010
from hvad.manager import TranslationQueryset, TranslationManager
11-
from hvad.models import TranslatableModel, TranslatableModelBase, TranslatedFields
11+
from hvad.models import TranslatableModel, TranslatedFields
1212
from hvad.test_utils.data import NORMAL
1313
from hvad.test_utils.fixtures import NormalFixture
1414
from hvad.test_utils.testcase import HvadTestCase, minimumDjangoVersion
@@ -174,15 +174,6 @@ class Meta:
174174
model = type('MyBaseModel', (TranslatableModel,), attrs)
175175
self.assertTrue(model._meta.abstract)
176176

177-
def test_metaclass_override(self):
178-
class CustomMetaclass(TranslatableModelBase):
179-
def __new__(*args, **kwargs):
180-
return TranslatableModelBase.__new__(*args, **kwargs)
181-
182-
with self.assertRaises(RuntimeError):
183-
class CustomMetaclassModel(with_metaclass(CustomMetaclass, TranslatableModel)):
184-
translations = TranslatedFields()
185-
186177
def test_internal_properties(self):
187178
self.assertCountEqual(Normal()._translated_field_names,
188179
['id', 'master', 'master_id', 'language_code', 'translated_field'])
@@ -586,12 +577,6 @@ class MyOtherTableNameTestModel(TranslatableModel):
586577
)
587578
self.assertTrue(MyOtherTableNameTestModel._meta.translations_model._meta.db_table.endswith('_myothertablenametestmodelO_Otranslation'))
588579

589-
def test_table_name_override_rename(self):
590-
override = self.settings(NANI_TABLE_NAME_SEPARATOR='O_O')
591-
with self.assertRaises(ImproperlyConfigured):
592-
override.enable()
593-
override.disable()
594-
595580
def test_table_name_from_meta(self):
596581
from hvad.models import TranslatedFields
597582
from django.db import models

hvad/tests/forms.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,8 +154,7 @@ def test_empty(self):
154154
form = NormalForm()
155155
self.assertCountEqual(form.fields, ['shared_field', 'translated_field'])
156156
self.assertFalse(form.is_valid())
157-
with self.assertThrowsWarning(DeprecationWarning):
158-
self.assertRaises(ValueError, form.save)
157+
self.assertRaises(AssertionError, form.save)
159158

160159
form = NormalMediaForm()
161160
self.assertCountEqual(form.fields, ['shared_field', 'translated_field'])

hvad/tests/views.py

Lines changed: 6 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -339,69 +339,37 @@ def test__get_object_deprecation(self):
339339
with translation.override('en'):
340340
request = self.request_factory.get('/url/')
341341
request.user = self.user
342-
343-
with self.assertThrowsWarning(DeprecationWarning):
342+
with self.assertRaises(AssertionError):
344343
response = DeprecatedObjectUpdateView.as_view()(request, pk=self.normal_id[1])
345344

346-
self.assertEqual(response.status_code, 200)
347-
data = response.context_data['form'].initial
348-
self.assertEqual(data['id'], self.normal_id[1])
349-
self.assertEqual(data['shared_field'], NORMAL[1].shared_field)
350-
self.assertEqual(data['translated_field'], NORMAL[1].translated_field['en'])
351-
352345
def test_filter_kwargs_deprecation(self):
353346
with translation.override('en'):
354347
request = self.request_factory.get('/url/')
355348
request.user = self.user
356-
357-
with self.assertThrowsWarning(DeprecationWarning):
349+
with self.assertRaises(AssertionError):
358350
response = DeprecatedFilterUpdateView.as_view()(request, custom=NORMAL[1].shared_field)
359351

360-
self.assertEqual(response.status_code, 200)
361-
data = response.context_data['form'].initial
362-
self.assertEqual(data['id'], self.normal_id[1])
363-
self.assertEqual(data['shared_field'], NORMAL[1].shared_field)
364-
self.assertEqual(data['translated_field'], NORMAL[1].translated_field['en'])
365-
366352
def test_object_id_deprecation(self):
367353
with translation.override('en'):
368354
request = self.request_factory.get('/url/')
369355
request.user = self.user
370-
371-
with self.assertThrowsWarning(DeprecationWarning):
356+
with self.assertRaises(AssertionError):
372357
response = TestUpdateView.as_view()(request, object_id=self.normal_id[1])
373358

374-
self.assertEqual(response.status_code, 200)
375-
data = response.context_data['form'].initial
376-
self.assertEqual(data['id'], self.normal_id[1])
377-
self.assertEqual(data['shared_field'], NORMAL[1].shared_field)
378-
self.assertEqual(data['translated_field'], NORMAL[1].translated_field['en'])
379-
380359
def test__language_deprecation(self):
381360
request = self.request_factory.get('/url/?lang=ja')
382361
request.user = self.user
383-
384-
with self.assertThrowsWarning(DeprecationWarning):
362+
with self.assertRaises(AssertionError):
385363
response = DeprecatedLanguageUpdateView.as_view()(request, pk=self.normal_id[1])
386364

387-
self.assertEqual(response.status_code, 200)
388-
data = response.context_data['form'].initial
389-
self.assertEqual(data['id'], self.normal_id[1])
390-
self.assertEqual(data['shared_field'], NORMAL[1].shared_field)
391-
self.assertEqual(data['translated_field'], NORMAL[1].translated_field['ja'])
392-
393365
def test_context_modifiers_deprecation(self):
394366
with translation.override('en'):
395367
request = self.request_factory.get('/url/')
396368
request.user = self.user
397-
398-
with self.assertThrowsWarning(DeprecationWarning):
369+
with self.assertRaises(AssertionError):
399370
response = DeprecatedContextUpdateView.as_view()(request, pk=self.normal_id[1])
400371

401-
self.assertEqual(response.status_code, 200)
402-
self.assertEqual(response.context_data['modifier'], 'foo')
403-
404372
def test_translatable_base_deprecation(self):
405373
from hvad.views import TranslatableBaseView
406-
with self.assertThrowsWarning(DeprecationWarning):
374+
with self.assertRaises(AssertionError):
407375
TranslatableBaseView()

0 commit comments

Comments
 (0)