From 035d1e99a9cca495b110c4ed426cb877fabac5b1 Mon Sep 17 00:00:00 2001 From: Michal Danilak Date: Thu, 3 Jan 2013 01:42:04 +0100 Subject: [PATCH 1/2] Added model validation to DjangoItem. --- scrapy/contrib/djangoitem.py | 51 +++++++++++++++++++++--- scrapy/tests/test_djangoitem/__init__.py | 36 ++++++++++++++++- 2 files changed, 81 insertions(+), 6 deletions(-) diff --git a/scrapy/contrib/djangoitem.py b/scrapy/contrib/djangoitem.py index 571dace5c..0b9b3b6f6 100644 --- a/scrapy/contrib/djangoitem.py +++ b/scrapy/contrib/djangoitem.py @@ -1,3 +1,5 @@ +from django.core.exceptions import ValidationError + from scrapy.item import Field, Item, ItemMeta @@ -24,10 +26,49 @@ class DjangoItem(Item): django_model = None + def __init__(self, *args, **kwargs): + super(DjangoItem, self).__init__(*args, **kwargs) + self._instance = None + self._errors = None + def save(self, commit=True): - modelargs = dict((k, self.get(k)) for k in self._values - if k in self._model_fields) - model = self.django_model(**modelargs) if commit: - model.save() - return model + self.instance.save() + return self.instance + + def is_valid(self, exclude=None): + self._get_errors(exclude) + return not bool(self._errors) + + def _get_errors(self, exclude=None): + if self._errors is not None: + return self._errors + + self._errors = {} + if exclude is None: + exclude = [] + + try: + self.instance.clean_fields(exclude=exclude) + except ValidationError, e: + self._errors = e.update_error_dict(self._errors) + + try: + self.instance.clean() + except ValidationError, e: + self._errors = e.update_error_dict(self._errors) + + # uniqueness is not checked, because it is faster to check it when + # saving object to database. Just beware, that failed save() + # raises IntegrityError instead of ValidationError. + + return self._errors + errors = property(_get_errors) + + @property + def instance(self): + if self._instance is None: + modelargs = dict((k, self.get(k)) for k in self._values + if k in self._model_fields) + self._instance = self.django_model(**modelargs) + return self._instance diff --git a/scrapy/tests/test_djangoitem/__init__.py b/scrapy/tests/test_djangoitem/__init__.py index 7f413825d..9f1dc686a 100644 --- a/scrapy/tests/test_djangoitem/__init__.py +++ b/scrapy/tests/test_djangoitem/__init__.py @@ -34,7 +34,7 @@ class IdentifiedPersonItem(DjangoItem): class DjangoItemTest(unittest.TestCase): - + def setUp(self): if not django: raise unittest.SkipTest("Django is not available") @@ -74,9 +74,43 @@ class DjangoItemTest(unittest.TestCase): i = OverrideFieldPersonItem() i['name'] = 'John' + # it is not obvious that "age" should be saved also, since it was + # redefined in child class + i['age'] = '22' person = i.save(commit=False) self.assertEqual(person.name, 'John') + self.assertEqual(person.age, '22') + + def test_validation(self): + long_name = 'z' * 300 + i = BasePersonItem(name=long_name) + self.assertFalse(i.is_valid()) + self.assertDictEqual( + { + 'age': [u'This field cannot be null.'], + 'name': [u'Ensure this value has at most 255 characters (it has 300).'] + }, + i.errors) + + i = BasePersonItem(name='John', age='22') + self.assertDictEqual({}, i.errors) + self.assertTrue(i.is_valid()) + + # once the item is validated, it does not validate again + i['name'] = long_name + self.assertTrue(i.is_valid()) + + def test_override_validation(self): + i = OverrideFieldPersonItem() + i['name'] = 'John' + self.assertFalse(i.is_valid()) + + i = i = OverrideFieldPersonItem() + i['name'] = 'John' + i['age'] = '22' + self.assertTrue(i.is_valid()) + def test_default_field_values(self): i = BasePersonItem() From 2cfbd13c17085f282a74cc7ff2c898590e7fe4c0 Mon Sep 17 00:00:00 2001 From: Michal Danilak Date: Thu, 3 Jan 2013 02:01:04 +0100 Subject: [PATCH 2/2] Added "exclude" parameter testing to unittests. --- scrapy/tests/test_djangoitem/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scrapy/tests/test_djangoitem/__init__.py b/scrapy/tests/test_djangoitem/__init__.py index 9f1dc686a..8a8b341e6 100644 --- a/scrapy/tests/test_djangoitem/__init__.py +++ b/scrapy/tests/test_djangoitem/__init__.py @@ -93,9 +93,9 @@ class DjangoItemTest(unittest.TestCase): }, i.errors) - i = BasePersonItem(name='John', age='22') + i = BasePersonItem(name='John') + self.assertTrue(i.is_valid(exclude=['age'])) self.assertDictEqual({}, i.errors) - self.assertTrue(i.is_valid()) # once the item is validated, it does not validate again i['name'] = long_name