From 339db1d0f1ae02b35724b43a1fdc1bbc6231271e Mon Sep 17 00:00:00 2001 From: Peter Bex Date: Mon, 31 Aug 2020 14:34:27 +0200 Subject: [PATCH] Call full_clean() in save(), so we don't need django-fullclean (#128) At least, for Binder models this is the case. All models managed by Binder views should inherit from BinderModel, and if not, the user should code validation error checking in _store(). In all our projects we use django-fullclean because being able to accidentally save invalid models is a huge mind fuck. Thus, it is pointless to call full_clean() in our views, as it means we'll be running potentially expensive validations twice. For example, relation fields check that the foreign relation exists, which requires a query per relation field. It is technically a breaking change, but for most projects this will be a non-issue. The test suite of Boekestijn passed except for one test which was specifically for its User view, which uses a non-Binder model. This was relatively easy to convert to use a full_clean() in its _store() method. --- CHANGELOG.md | 10 ++ binder/models.py | 52 ++++++++++ binder/views.py | 113 ++++++--------------- tests/filters/test_uuid_field_filtering.py | 4 - tests/plugins/test_csvexport.py | 2 +- tests/plugins/test_imageview.py | 2 +- tests/test_history.py | 3 - tests/test_model_view_basics.py | 58 ----------- tests/test_multi_put.py | 3 - tests/test_pagination.py | 3 - tests/test_postgres_fields.py | 4 - tests/test_soft_and_hard_deletes.py | 6 -- tests/test_validation_errors.py | 1 - tests/test_websocket.py | 4 - tests/testapp/models/zoo.py | 6 -- 15 files changed, 96 insertions(+), 175 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f74b27b..0839f07f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,16 @@ Using `Q()` objects instead of querysets allows making queries in `with_ids` without requiring a subquery on filtered relations, which can be a big performance win on large tables. +Now, `full_clean` is automatically called upon `save()` of a +`BinderModel`. This should not be a huge breaking change because most +projects "in the wild" are already using the +[django-fullclean](https://github.com/fish-ball/django-fullclean) +plugin. This should reduce the number of checks done while saving +objects. However, it also means that if a non-binder model is saved +it will no longer be validated. If you want this to happen you need +to override `_store` to do some checking. + + ## Version 1.4.0 ### Features diff --git a/binder/models.py b/binder/models.py index 1d31119d..15bcf09d 100644 --- a/binder/models.py +++ b/binder/models.py @@ -1,5 +1,6 @@ import re import warnings +from collections import defaultdict from datetime import date, datetime, time from contextlib import suppress @@ -470,6 +471,57 @@ def annotations(cls): return getattr(cls, ann_name) + def save(self, *args, **kwargs): + self.full_clean() # Never allow saving invalid models! + return super().save(*args, **kwargs) + + + def full_clean(self, *args, **kwargs): + # Determine if the field needs an extra nullability check. + # Expects the field object (not the field name) + def field_needs_nullability_check(field): + if isinstance(field, (models.CharField, models.TextField, models.BooleanField)): + if field.blank and not field.null: + return True + + return False + + + validation_errors = defaultdict(list) + + try: + res = super().full_clean(*args, **kwargs) + except ValidationError as ve: + if hasattr(ve, 'error_dict'): + for key, value in ve.error_dict.items(): + validation_errors[key] += value + elif hasattr(ve, 'error_list'): + for e in ve.error_list: + validation_errors['null'].append(e) # XXX + + # Django's standard full_clean() doesn't complain about some + # not-NULL fields being None. This causes save() to explode + # with a django.db.IntegrityError because the column is NOT + # NULL. Tyvm, Django. So we perform an extra NULL check for + # some cases. See #66, T2989, T9646. + for f in self._meta.fields: + if field_needs_nullability_check(f): + # gettattr on a foreignkey foo gets the related model, while foo_id just gets the id. + # We don't need or want the model (nor the DB query), we'll take the id thankyouverymuch. + name = f.name + ('_id' if isinstance(f, models.ForeignKey) else '') + + if getattr(self, name) is None and getattr(self, f.name) is None: + validation_errors[f.name].append(ValidationError( + 'This field cannot be null.', + code='null', + )) + + if validation_errors: + raise ValidationError(validation_errors) + else: + return res + + def history_obj_post_init(sender, instance, **kwargs): instance._history = instance.binder_concrete_fields_as_dict(skip_deferred_fields=True) diff --git a/binder/views.py b/binder/views.py index 1fa8d996..314b5b9a 100644 --- a/binder/views.py +++ b/binder/views.py @@ -1076,37 +1076,20 @@ def get(self, request, pk=None, withs=None): - # Determine if the field needs an extra nullability check. - # Expects the field object (not the field name) - def field_needs_nullability_check(self, field): - if isinstance(field, (models.CharField, models.TextField, models.BooleanField)): - if field.blank and not field.null: - return True - - return False - - - - def binder_clean(self, obj, pk=None): - try: - res = obj.full_clean() - except ValidationError as ve: - model_name = self.get_model_view(obj.__class__)._model_name() - - raise BinderValidationError({ - model_name: { - obj.pk if pk is None else pk: { - f: [ - {'code': e.code, 'message': e.messages[0]} - for e in el - ] - for f, el in ve.error_dict.items() - } + def binder_validation_error(self, obj, validation_error, pk=None): + model_name = self.get_model_view(obj.__class__)._model_name() + + return BinderValidationError({ + model_name: { + obj.pk if pk is None else pk: { + f: [ + {'code': e.code, 'message': e.messages[0]} + for e in el + ] + for f, el in validation_error.error_dict.items() } - }) - else: - return res - + } + }) # Deserialize JSON to Django Model objects. @@ -1150,39 +1133,15 @@ def store_m2m_field(obj, field, value, request): except BinderValidationError as e: validation_errors.append(e) - try: - self.binder_clean(obj, pk=pk) - except BinderValidationError as bve: - validation_errors.append(bve) - - - # full_clean() doesn't complain about some not-NULL fields being None. - # This causes save() to explode with a django.db.IntegrityError because the - # column is NOT NULL. Tyvm, Django. - # So we perform an extra NULL check for some cases. See #66, T2989, T9646. - for f in obj._meta.fields: - if self.field_needs_nullability_check(f): - # gettattr on a foreignkey foo gets the related model, while foo_id just gets the id. - # We don't need or want the model (nor the DB query), we'll take the id thankyouverymuch. - name = f.name + ('_id' if isinstance(f, models.ForeignKey) else '') - - if getattr(obj, name) is None: - e = BinderValidationError({ - self._model_name(): { - obj.pk if pk is None else pk: { - f.name: [{ - 'code': 'null', - 'message': 'This field cannot be null.' - }] - } - } - }) - validation_errors.append(e) if validation_errors: raise sum(validation_errors, None) - obj.save() + try: + obj.save() + except ValidationError as ve: + validation_errors.append(self.binder_validation_error(obj, ve, pk=pk)) + for field, value in deferred_m2ms.items(): try: @@ -1253,20 +1212,18 @@ def _store_m2m_field(self, obj, field, value, request): for addobj in obj_field.model.objects.filter(id__in=new_ids - old_ids): setattr(addobj, obj_field.field.name, obj) try: - self.binder_clean(addobj) - except BinderValidationError as bve: - validation_errors.append(bve) + addobj.save() + except ValidationError as ve: + validation_errors.append(self.binder_validation_error(addobj, ve)) else: addobj.save() elif getattr(obj._meta.model, field).__class__ == models.fields.related.ReverseOneToOneDescriptor: remote_obj = obj._meta.get_field(field).related_model.objects.get(pk=value[0]) setattr(obj, field, remote_obj) try: - self.binder_clean(remote_obj) - except BinderValidationError as bve: - validation_errors.append(bve) - else: remote_obj.save() + except ValidationError as ve: + validation_errors.append(self.binder_validation_error(remote_obj, ve)) elif any(f.name == field for f in self._get_reverse_relations()): #### XXX FIXME XXX ugly quick fix for reverse relation + multiput issue if any(v for v in value if v < 0): @@ -1370,6 +1327,10 @@ def _store_field(self, obj, field, value, request, pk=None): except TypeError: raise BinderFieldTypeError(self.model.__name__, field) except ValidationError as ve: + # This would be nice, but this particular validation error + # has no error dict... (TODO FIXME) + # raise self.binder_validation_error(obj, ve, pk=pk) + model_name = self.get_model_view(obj.__class__)._model_name() raise BinderValidationError({ model_name: { @@ -1961,8 +1922,10 @@ def soft_delete(self, obj, undelete, request): return obj.deleted = not undelete - self.binder_clean(obj, pk=obj.pk) - obj.save() + try: + obj.save() + except ValidationError as ve: + raise self.binder_validation_error(obj, ve) @@ -2112,19 +2075,7 @@ def dispatch_file_field(self, request, pk=None, file_field=None): return JsonResponse( {"data": {file_field_name: path}} ) except ValidationError as ve: - model_name = self.get_model_view(obj.__class__)._model_name() - - raise BinderValidationError({ - model_name: { - obj.pk if pk is None else pk: { - f: [ - {'code': e.code, 'message': e.messages[0]} - for e in el - ] - for f, el in ve.error_dict.items() - } - } - }) + raise self.binder_validation_error(obj, ve, pk=pk) if request.method == 'DELETE': self._require_model_perm('change', request) diff --git a/tests/filters/test_uuid_field_filtering.py b/tests/filters/test_uuid_field_filtering.py index 928a14e1..fb22ddf9 100644 --- a/tests/filters/test_uuid_field_filtering.py +++ b/tests/filters/test_uuid_field_filtering.py @@ -25,19 +25,15 @@ def setUp(self): self.artis.save() fabbby = Caretaker(name='fabbby') - fabbby.full_clean() fabbby.save() door1 = Gate(zoo=self.gaia, keeper=fabbby, serial_number=None) - door1.full_clean() door1.save() door2 = Gate(zoo=self.emmen, keeper=fabbby, serial_number='{2e93ec15-2d68-477d-960f-52779ef6198b}') - door2.full_clean() door2.save() door3 = Gate(zoo=self.artis, keeper=fabbby, serial_number='3e93ec15-2d68-477d-960f-52779ef6198b') - door3.full_clean() door3.save() diff --git a/tests/plugins/test_csvexport.py b/tests/plugins/test_csvexport.py index 97c25ebd..bc4d88ee 100644 --- a/tests/plugins/test_csvexport.py +++ b/tests/plugins/test_csvexport.py @@ -25,7 +25,7 @@ def temp_imagefile(width, height, format): return f def setUp(self): - animal = Animal() + animal = Animal(name='test') animal.save() self.pictures = [] diff --git a/tests/plugins/test_imageview.py b/tests/plugins/test_imageview.py index 5471c318..cabc9398 100644 --- a/tests/plugins/test_imageview.py +++ b/tests/plugins/test_imageview.py @@ -37,7 +37,7 @@ def temp_imagefile(width, height, format): return f def _get_picture(self, width, height): - animal = Animal() + animal = Animal(name='test') animal.save() file = ImageTest.temp_imagefile(width, height, 'jpeg') diff --git a/tests/test_history.py b/tests/test_history.py index 80feed0c..fe900300 100644 --- a/tests/test_history.py +++ b/tests/test_history.py @@ -59,7 +59,6 @@ def test_model_with_history_creates_changes_on_creation(self): def test_model_with_history_creates_changes_on_update_but_only_for_changed_fields(self): daffy = Animal(name='Daffy Duck') - daffy.full_clean() daffy.save() # Model changes outside the HTTP API aren't recorded (should they be?) @@ -94,10 +93,8 @@ def test_model_with_history_creates_changes_on_update_but_only_for_changed_field def test_model_with_related_history_model_creates_changes_on_the_same_changeset(self): mickey = Caretaker(name='Mickey') - mickey.full_clean() mickey.save() pluto = Animal(name='Pluto') - pluto.full_clean() pluto.save() # Model changes outside the HTTP API aren't recorded (should they be?) diff --git a/tests/test_model_view_basics.py b/tests/test_model_view_basics.py index 25b3f87c..8a047a00 100644 --- a/tests/test_model_view_basics.py +++ b/tests/test_model_view_basics.py @@ -33,7 +33,6 @@ def test_post_new_model(self): def test_get_model_with_valid_id(self): daffy = Animal(name='Daffy Duck') - daffy.full_clean() daffy.save() response = self.client.get('/animal/%d/' % (daffy.pk,)) @@ -59,7 +58,6 @@ def test_get_model_shown_properties(self): Test that the properties under shown_properties are added to the result of a get model request """ gaia = Zoo(name='GaiaZOO') - gaia.full_clean() gaia.save() response = self.client.get('/zoo/{}/'.format(gaia.id)) @@ -71,7 +69,6 @@ def test_get_model_shown_properties(self): self.assertEqual(0, result['data']['animal_count']) coyote = Animal(name='Wile E. Coyote', zoo=gaia) - coyote.full_clean() coyote.save() response = self.client.get('/zoo/{}/'.format(gaia.id)) @@ -93,23 +90,18 @@ def test_get_collection_with_no_models_returns_empty_array(self): def test_get_collection_sorting(self): gaia = Zoo(name='GaiaZOO') - gaia.full_clean() gaia.save() emmen = Zoo(name='Wildlands Adventure Zoo Emmen') - emmen.full_clean() emmen.save() coyote = Animal(name='Wile E. Coyote', zoo=gaia) - coyote.full_clean() coyote.save() roadrunner = Animal(name='Roadrunner', zoo=gaia) - roadrunner.full_clean() roadrunner.save() woody = Animal(name='Woody Woodpecker', zoo=emmen) - woody.full_clean() woody.save() response = self.client.get('/animal/', data={'order_by': 'name'}) @@ -166,31 +158,24 @@ def test_get_collection_sorting(self): def test_get_collection_filtering(self): gaia = Zoo(name='GaiaZOO') - gaia.full_clean() gaia.save() emmen = Zoo(name='Wildlands Adventure Zoo Emmen') - emmen.full_clean() emmen.save() artis = Zoo(name='Artis') - artis.full_clean() artis.save() coyote = Animal(name='Wile E. Coyote', zoo=gaia) - coyote.full_clean() coyote.save() roadrunner = Animal(name='Roadrunner', zoo=gaia) - roadrunner.full_clean() roadrunner.save() woody = Animal(name='Woody Woodpecker', zoo=emmen) - woody.full_clean() woody.save() donald = Animal(name='Donald Duck', zoo=artis) - donald.full_clean() donald.save() response = self.client.get('/animal/', data={'.name': 'Wile E. Coyote'}) @@ -226,31 +211,24 @@ def test_get_collection_filtering(self): def test_get_collection_with_foreignkey(self): gaia = Zoo(name='GaiaZOO') - gaia.full_clean() gaia.save() emmen = Zoo(name='Wildlands Adventure Zoo Emmen') - emmen.full_clean() emmen.save() artis = Zoo(name='Artis') - artis.full_clean() artis.save() harderwijk = Zoo(name='Dolfinarium Harderwijk') # Should not be in result set - harderwijk.full_clean() harderwijk.save() coyote = Animal(name='Wile E. Coyote', zoo=gaia) - coyote.full_clean() coyote.save() roadrunner = Animal(name='Roadrunner', zoo=gaia) - roadrunner.full_clean() roadrunner.save() woody = Animal(name='Woody Woodpecker', zoo=emmen, zoo_of_birth=artis) - woody.full_clean() woody.save() # Quick check that foreign key relations are excluded unless we ask for them @@ -290,29 +268,23 @@ def test_get_collection_with_foreignkey(self): def test_get_collection_with_reverse_foreignkey(self): gaia = Zoo(name='GaiaZOO') - gaia.full_clean() gaia.save() emmen = Zoo(name='Wildlands Adventure Zoo Emmen') - emmen.full_clean() emmen.save() artis = Zoo(name='Artis') - artis.full_clean() artis.save() coyote = Animal(name='Wile E. Coyote', zoo=gaia) - coyote.full_clean() coyote.save() roadrunner = Animal(name='Roadrunner', zoo=gaia) - roadrunner.full_clean() roadrunner.save() gaia.most_popular_animals.add(coyote) woody = Animal(name='Woody Woodpecker', zoo=emmen, zoo_of_birth=artis) - woody.full_clean() woody.save() # Quick check that foreign key relations are excluded unless we ask for them @@ -386,19 +358,15 @@ def test_get_collection_with_reverse_foreignkey(self): def test_get_collection_with_reverse_foreignkey_through_other_relation(self): gaia = Zoo(name='GaiaZOO') - gaia.full_clean() gaia.save() coyote = Animal(name='Wile E. Coyote', zoo=gaia) - coyote.full_clean() coyote.save() roadrunner = Animal(name='Roadrunner', zoo=gaia) - roadrunner.full_clean() roadrunner.save() henk = ZooEmployee(zoo=gaia, name='Henk') - henk.full_clean() henk.save() response = self.client.get('/zoo_employee/%s/' % henk.id, data={'with': 'zoo.animals'}) @@ -423,29 +391,23 @@ def test_get_collection_with_reverse_foreignkey_through_other_relation(self): def test_get_collection_with_disabled_reverse_foreignkey(self): gaia = Zoo(name='GaiaZOO') - gaia.full_clean() gaia.save() emmen = Zoo(name='Wildlands Adventure Zoo Emmen') - emmen.full_clean() emmen.save() artis = Zoo(name='Artis') - artis.full_clean() artis.save() coyote = Animal(name='Wile E. Coyote', zoo=gaia) - coyote.full_clean() coyote.save() gaia.most_popular_animals.add(coyote) roadrunner = Animal(name='Roadrunner', zoo=gaia) - roadrunner.full_clean() roadrunner.save() woody = Animal(name='Woody Woodpecker', zoo=emmen, zoo_of_birth=artis) - woody.full_clean() woody.save() # Quick check that foreign key relations are excluded unless we ask for them @@ -491,24 +453,19 @@ def test_get_collection_with_disabled_reverse_foreignkey(self): def test_get_collection_with_one_to_one(self): scrooge = Animal(name='Scrooge McDuck') - scrooge.full_clean() scrooge.save() frock = Costume(description="Gentleman's frock coat", animal=scrooge) - frock.full_clean() frock.save() donald = Animal(name='Donald Duck') - donald.full_clean() donald.save() sailor = Costume(description='Weird sailor costume', animal=donald) - sailor.full_clean() sailor.save() # This animal goes naked pluto = Animal(name='Pluto') - pluto.full_clean() pluto.save() @@ -541,15 +498,12 @@ def test_get_collection_with_one_to_one(self): def test_get_model_with_relation_without_id(self): gaia = Zoo(name='GaiaZOO') - gaia.full_clean() gaia.save() fabbby = Caretaker(name='fabbby') - fabbby.full_clean() fabbby.save() door = Gate(zoo=gaia, keeper=fabbby) - door.full_clean() door.save() response = self.client.get('/zoo/{}/'.format(gaia.id), data={'with': 'gate.keeper'}) @@ -569,27 +523,21 @@ def test_get_model_with_relation_without_id(self): def test_get_collection_filtering_following_nested_references(self): emmen = Zoo(name='Wildlands Adventure Zoo Emmen') - emmen.full_clean() emmen.save() gaia = Zoo(name='GaiaZOO') - gaia.full_clean() gaia.save() scrooge = Animal(name='Scrooge McDuck', zoo=gaia) - scrooge.full_clean() scrooge.save() frock = Costume(description="Gentleman's frock coat", animal=scrooge) - frock.full_clean() frock.save() donald = Animal(name='Donald Duck', zoo=emmen) - donald.full_clean() donald.save() sailor = Costume(description='Weird sailor costume', animal=donald) - sailor.full_clean() sailor.save() @@ -627,7 +575,6 @@ def test_get_collection_filtering_following_nested_references(self): def test_post_new_model_with_foreign_key_value(self): artis = Zoo(name='Artis') - artis.full_clean() artis.save() model_data = { @@ -650,7 +597,6 @@ def test_post_new_model_with_foreign_key_value(self): def test_post_new_model_with_one_to_one_value(self): donald = Animal(name='Donald Duck') - donald.full_clean() donald.save() model_data = { @@ -672,15 +618,12 @@ def test_post_new_model_with_one_to_one_value(self): def test_post_new_model_with_reverse_foreign_key_multi_value(self): scooby = Animal(name='Scooby Doo') - scooby.full_clean() scooby.save() scrappy = Animal(name='Scrappy Doo') - scrappy.full_clean() scrappy.save() woody = Animal(name='Woody Woodpecker') - woody.full_clean() woody.save() model_data = { @@ -704,7 +647,6 @@ def test_post_new_model_with_reverse_foreign_key_multi_value(self): def test_post_put_respect_with_clause(self): emmen = Zoo(name='Wildlands Adventure Zoo Emmen') - emmen.full_clean() emmen.save() model_data = { diff --git a/tests/test_multi_put.py b/tests/test_multi_put.py index cd2b4b07..8b19f07a 100644 --- a/tests/test_multi_put.py +++ b/tests/test_multi_put.py @@ -419,16 +419,13 @@ def test_remove_relation_through_backref_with_custom_unsetter(self): # This apparently only happened in the multi-put, but still.... def test_update_model_with_m2m_field_causes_no_error(self): artis = Zoo(name='Artis') - artis.full_clean() artis.save() contact1 = ContactPerson(name='cp1') - contact1.full_clean() contact1.save() contact1.zoos.add(artis) contact2 = ContactPerson(name='cp2') - contact2.full_clean() contact2.save() contact2.zoos.add(artis) diff --git a/tests/test_pagination.py b/tests/test_pagination.py index 01ead0d5..e1702fb3 100644 --- a/tests/test_pagination.py +++ b/tests/test_pagination.py @@ -45,15 +45,12 @@ def setUp(self): self.gaia.save() self.wildlands = Zoo(name='Wildlands Adventure Zoo Emmen') # 4 - self.wildlands.full_clean() self.wildlands.save() self.artis = Zoo(name='Artis') # 1 - self.artis.full_clean() self.artis.save() self.harderwijk = Zoo(name='Dolfinarium Harderwijk') # 2 - self.harderwijk.full_clean() self.harderwijk.save() self.donald = Animal(name='Donald Duck', zoo=self.wildlands) # 1 diff --git a/tests/test_postgres_fields.py b/tests/test_postgres_fields.py index faf833c2..4713d218 100644 --- a/tests/test_postgres_fields.py +++ b/tests/test_postgres_fields.py @@ -29,19 +29,15 @@ def setUp(self): gaia.save() coyote = Animal(name='Wile E. Coyote', zoo=gaia) - coyote.full_clean() coyote.save() roadrunner = Animal(name='Roadrunner', zoo=gaia) - roadrunner.full_clean() roadrunner.save() self.coyote_feeding = FeedingSchedule(animal=coyote, foods=['meat'], schedule_details={'10:30': ['meat'], '16:00': ['meat']}) - self.coyote_feeding.full_clean() self.coyote_feeding.save() self.rr_feeding = FeedingSchedule(animal=roadrunner, foods=['corn', 'bugs'], schedule_details={'10:30': ['corn'], '16:00': ['corn', 'bugs']}) - self.rr_feeding.full_clean() self.rr_feeding.save() diff --git a/tests/test_soft_and_hard_deletes.py b/tests/test_soft_and_hard_deletes.py index 8b4f2d72..1526e8b1 100644 --- a/tests/test_soft_and_hard_deletes.py +++ b/tests/test_soft_and_hard_deletes.py @@ -18,11 +18,9 @@ def setUp(self): def test_non_soft_deletable_model_is_hard_deleted_on_delete_verb(self): donald = Animal(name='Donald Duck') - donald.full_clean() donald.save() sailor = Costume(description='Weird sailor costume', animal=donald) - sailor.full_clean() sailor.save() response = self.client.delete('/costume/%d/' % sailor.pk) @@ -34,7 +32,6 @@ def test_non_soft_deletable_model_is_hard_deleted_on_delete_verb(self): def test_soft_deletable_model_is_softdeleted_on_delete_verb(self): donald = Animal(name='Donald Duck') - donald.full_clean() donald.save() self.assertFalse(donald.deleted) @@ -49,7 +46,6 @@ def test_soft_deletable_model_is_softdeleted_on_delete_verb(self): def test_soft_deletable_model_is_undeleted_on_post(self): donald = Animal(name='Donald Duck', deleted=True) - donald.full_clean() donald.save() self.assertTrue(donald.deleted) @@ -68,11 +64,9 @@ def test_soft_deletable_model_is_undeleted_on_post(self): def test_hard_deletable_model_raises_validation_error_on_cascaded_delete_failure(self): walt = Caretaker(name='Walt Disney') - walt.full_clean() walt.save() donald = Animal(name='Donald Duck', caretaker=walt) - donald.full_clean() donald.save() # Body must be empty, otherwise we get another error diff --git a/tests/test_validation_errors.py b/tests/test_validation_errors.py index 623549d5..45e41ea6 100644 --- a/tests/test_validation_errors.py +++ b/tests/test_validation_errors.py @@ -160,7 +160,6 @@ def test_post_validate_field_gets_invalid_string_representation_for_data_type(se def test_put_validate_max_length(self): model = Animal(name='Harambe') - model.full_clean() model.save() model_data = { 'name': 'HarambeHarambeHarambeHarambeHarambeHarambeHarambeHarambeHarambeHarambe' diff --git a/tests/test_websocket.py b/tests/test_websocket.py index 8b2ae6bb..e3ddc627 100644 --- a/tests/test_websocket.py +++ b/tests/test_websocket.py @@ -50,11 +50,9 @@ def test_room_controller_list_rooms_for_user(self): @override_settings(HIGH_TEMPLAR_URL="http://localhost:8002") def test_post_save_trigger(self, mock): doggo = Animal(name='Woofer') - doggo.full_clean() doggo.save() costume = Costume(nickname='Gnarls Barker', description='Foo Bark', animal=doggo) - costume.full_clean() costume.save() self.assertEqual(1, requests.post.call_count) mock.assert_called_with('http://localhost:8002/trigger/', data=json.dumps({ @@ -64,11 +62,9 @@ def test_post_save_trigger(self, mock): def test_post_succeeds_when_trigger_fails(self): doggo = Animal(name='Woofer') - doggo.full_clean() doggo.save() costume = Costume(nickname='Gnarls Barker', description='Foo Bark', animal=doggo) - costume.full_clean() costume.save() self.assertIsNotNone(costume.pk) diff --git a/tests/testapp/models/zoo.py b/tests/testapp/models/zoo.py index e423b0ac..22c62bd9 100644 --- a/tests/testapp/models/zoo.py +++ b/tests/testapp/models/zoo.py @@ -32,12 +32,6 @@ def animal_count(self): return self.animals.count() - # A poor man's version of the django-fullclean package. - def save(self, *args, **kwargs): - self.full_clean() - return super().save(*args, **kwargs) - - def clean(self): errors = {}