├── smart_save ├── __init__.py └── models.py ├── setup.cfg ├── .gitignore ├── setup.py ├── LICENSE.txt └── README.rst /smart_save/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.str 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | build/ 3 | dist/ 4 | *.egg-info 5 | docs/_build 6 | *.sqlite 7 | .tox 8 | .env 9 | 10 | .coverage 11 | htmlcov/ 12 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | from setuptools import find_packages, setup 4 | 5 | setup( 6 | name='django-smart-save', 7 | version='0.0.11', 8 | description='Automatically validates when you call your model’s save()', 9 | author='Daniel Gatis Carrazzoni', 10 | author_email='danielgatis@gmail.com', 11 | url='https://github.com/danielgatis/django-smart-save', 12 | license='BSD License', 13 | platforms=['OS Independent'], 14 | packages=find_packages(), 15 | classifiers=[ 16 | 'Development Status :: 4 - Beta', 17 | 'Environment :: Web Environment', 18 | 'Intended Audience :: Developers', 19 | 'License :: OSI Approved :: BSD License', 20 | 'Operating System :: OS Independent', 21 | 'Programming Language :: Python', 22 | 'Programming Language :: Python :: 2.6', 23 | 'Programming Language :: Python :: 2.7', 24 | 'Programming Language :: Python :: 3.3', 25 | 'Programming Language :: Python :: 3.4', 26 | 'Framework :: Django', 27 | ], 28 | include_package_data=True, 29 | zip_safe=False, 30 | ) 31 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2014, Daniel Gatis and contributors 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. Redistributions in binary 9 | form must reproduce the above copyright notice, this list of conditions and the 10 | following disclaimer in the documentation and/or other materials provided with 11 | the distribution 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 17 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 18 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 19 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 20 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 21 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 22 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | -------------------------------------------------------------------------------- /smart_save/models.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from django.conf import settings 4 | from django.db.models import Model 5 | from django.core.exceptions import ValidationError, ImproperlyConfigured 6 | 7 | 8 | SMART_SAVE_METHOD = getattr(settings, 'SMART_SAVE_METHOD', 'save_if_valid') 9 | 10 | 11 | if SMART_SAVE_METHOD == 'save': 12 | raise ImproperlyConfigured("SMART_SAVE_METHOD can't be 'save'") 13 | 14 | 15 | def save_if_valid(self, throw_exception=False, *args, **kwargs): 16 | """Make :meth:`save` call :meth:`full_clean`. 17 | 18 | Do you think Django models ``save`` method will validate all fields 19 | (i.e. call ``full_clean``) before saving or any time at all? Wrong! 20 | 21 | More info: 22 | 23 | * "Why doesn't django's model.save() call full clean?" 24 | http://stackoverflow.com/questions/4441539/ 25 | * "Model docs imply that ModelForm will call Model.full_clean(), 26 | but it won't." 27 | https://code.djangoproject.com/ticket/13100 28 | 29 | """ 30 | try: 31 | self.full_clean() 32 | self.save(*args, **kwargs) 33 | 34 | return True 35 | 36 | except ValidationError: 37 | e = sys.exc_info()[1] 38 | 39 | self._errors = {} 40 | for k, v in e.message_dict.items(): 41 | self._errors[k] = v 42 | 43 | if throw_exception: 44 | raise 45 | 46 | return False 47 | 48 | 49 | Model.add_to_class('_errors', {}) 50 | Model.add_to_class(SMART_SAVE_METHOD, save_if_valid) 51 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ==================== 2 | django-smart-save 3 | ==================== 4 | 5 | |License| 6 | 7 | .. |License| image:: https://img.shields.io/badge/License-BSD%202--Clause-blue.svg 8 | :target: https://opensource.org/licenses/BSD-2-Clause 9 | 10 | Adds the method ``save_if_valid`` to ``Model``, which calls both 11 | ``full_clean`` and ``save``. 12 | 13 | Motivation 14 | ========== 15 | 16 | Do you think Django models ``save`` method will validate all fields 17 | (i.e. call ``full_clean``) before saving or any time at all? Wrong! 18 | 19 | I discovered this awful truth when I couldn't understand why 20 | a model object with an email field (without `blank=True`) could be 21 | saved with an empty string as email address. 22 | 23 | More info: 24 | 25 | * "Why doesn't django's model.save() call full clean?" 26 | http://stackoverflow.com/questions/4441539/ 27 | * "Model docs imply that ModelForm will call Model.full_clean(), but it won't." 28 | https://code.djangoproject.com/ticket/13100 29 | 30 | 31 | Installing 32 | ========== 33 | 34 | First add the application to your Python path. The easiest way is to use 35 | `pip`:: 36 | 37 | pip install django-smart-save 38 | 39 | You should install by downloading the source and running:: 40 | 41 | $ python setup.py install 42 | 43 | Configuring 44 | ----------- 45 | 46 | Make sure you have `django.contrib.auth` installed, and add the `smart_save` 47 | application to your `INSTALLED_APPS` list:: 48 | 49 | INSTALLED_APPS = ( 50 | ... 51 | 'django.contrib.auth', 52 | 'smart_save', 53 | ) 54 | 55 | You can specify a different method name in your project settings (default: save_if_valid): 56 | 57 | SMART_SAVE_METHOD = 'my_save' 58 | 59 | 60 | Usage Overview 61 | ============== 62 | 63 | It is simple:: 64 | 65 | >>> user = User(username="chris") 66 | >>> user.save_if_valid() 67 | True 68 | >>> user = User(username="") 69 | >>> user.save_if_valid() 70 | False 71 | >>> user._errors 72 | {'username': ['This field cannot be blank.']} 73 | 74 | License 75 | ======= 76 | 77 | Anyone is free to use or modify this software under the terms of the BSD 78 | license. 79 | 80 | Buy me a coffee 81 | =============== 82 | 83 | Liked some of my work? Buy me a coffee (or more likely a beer) 84 | 85 | |BuyMeACoffee| 86 | 87 | .. |BuyMeACoffee| image:: https://bmc-cdn.nyc3.digitaloceanspaces.com/BMC-button-images/custom_images/orange_img.png 88 | :target: https://www.buymeacoffee.com/danielgatis 89 | --------------------------------------------------------------------------------