[Python-modules-commits] [django-dirtyfields] 02/06: Import django-dirtyfields_1.3.orig.tar.gz
Christopher Stuart Hoskin
mans0954 at moszumanska.debian.org
Mon Oct 30 23:25:10 UTC 2017
This is an automated email from the git hooks/post-receive script.
mans0954 pushed a commit to branch master
in repository django-dirtyfields.
commit c1d695ba5f9b425468b1ecb942feac275e50f690
Author: Christopher Hoskin <mans0954 at debian.org>
Date: Mon Oct 30 23:17:16 2017 +0000
Import django-dirtyfields_1.3.orig.tar.gz
---
.travis.yml | 11 +--------
ChangeLog.rst | 33 +++++++++++++++++++++++++--
README.rst | 2 +-
docs/index.rst | 24 ++++++++++++++++++++
requirements.txt | 2 +-
setup.py | 2 +-
src/dirtyfields/compat.py | 45 -------------------------------------
src/dirtyfields/dirtyfields.py | 51 ++++++++++++++++++++++++++++--------------
tests/models.py | 14 ++++++++++++
tests/test_core.py | 10 +++++++++
tests/test_memory_leak.py | 16 +++++++++++++
tests/test_save_fields.py | 40 +++++++++++++++++++++++++++++++--
tests/test_specified_fields.py | 28 +++++++++++++++++++++++
tox.ini | 33 ++++++++-------------------
14 files changed, 208 insertions(+), 103 deletions(-)
diff --git a/.travis.yml b/.travis.yml
index bc8e8ca..70c732d 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,24 +2,15 @@ language: python
python:
- 2.7
- - 3.4
+ - 3.5
env:
- - TOXENV=django14
- - TOXENV=django15
- - TOXENV=django16
- - TOXENV=django17
- TOXENV=django18
- TOXENV=django19
- TOXENV=django110
- TOXENV=coverage
- TOXENV=postgresql
-matrix:
- exclude:
- - python: 3.4
- env: TOXENV=django14
-
services:
- postgresql
diff --git a/ChangeLog.rst b/ChangeLog.rst
index 00f2afb..3b02ffc 100644
--- a/ChangeLog.rst
+++ b/ChangeLog.rst
@@ -1,6 +1,32 @@
ChangeLog
=========
+.. _master:
+
+master (unreleased)
+-------------------
+
+Up-to-date with 1.3
+
+
+.. _v1.3:
+
+1.3 (23/08/2017)
+----------------
+
+*New:*
+
+ - Drop support for unsupported Django versions: 1.4, 1.5, 1.6 and 1.7 series.
+ - Fixes issue with verbose mode when the object has not been yet saved in the database (MR #99). Thanks vapkarian.
+ - Add test coverage for Django 1.11.
+ - A new attribute :code:`FIELDS_TO_CHECK` has been added to :code:`DirtyFieldsMixin` to specify a limited set of fields to check.
+
+*Bugfix:*
+
+ - Correctly handle :code:`ForeignKey.db_column` :code:`{}_id` in :code:`update_fields`. Thanks Hugo Smett.
+ - Fixes #111: Eliminate a memory leak.
+ - Handle deferred fields in :code:`update_fields`
+
.. _v1.2.1:
@@ -10,6 +36,9 @@ ChangeLog
*New:*
- :code:`django-dirtyfields` is now tested with PostgreSQL, especially with specific fields
+
+*Bugfix:*
+
- Fixes #80: Use of :code:`Field.rel` raises warnings from Django 1.9+
- Fixes #84: Use :code:`only()` in conjunction with 2 foreign keys triggers a recursion error
- Fixes #77: Shallow copy does not work with Django 1.9's JSONField
@@ -46,7 +75,7 @@ ChangeLog
1.0.1 (2016-07-25)
------------------
-*bugfix:*
+*Bugfix:*
- Fixing a bug preventing :code:`django-dirtyfields` to work properly on models with custom primary keys.
@@ -97,7 +126,7 @@ There is a backward-incompatibility on this version. Please read careful below.
0.8.1 (2015-12-08)
------------------
-*bugfix:*
+*Bugfix:*
- Not comparing fields that are deferred (:code:`only` method on :code:`QuerySet`).
- Being more tolerant when comparing values that can be on another type than expected.
diff --git a/README.rst b/README.rst
index 35b656b..21226a5 100644
--- a/README.rst
+++ b/README.rst
@@ -15,7 +15,7 @@ Django Dirty Fields
Tracking dirty fields on a Django model instance.
Dirty means that field in-memory and database values are different.
-This package is compatible and tested with Django 1.4 to 1.10.
+This package is compatible and tested with latest versions of Django (1.8, 1.9, 1.10, 1.11 series).
`Full documentation <http://django-dirtyfields.readthedocs.org/en/develop/>`_
diff --git a/docs/index.rst b/docs/index.rst
index 6697fe0..5982673 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -118,6 +118,30 @@ in the database needs to be updated with these form values.
It can have serious performance issues depending on your project.
+Checking a limited set of model fields.
+-------------------------------------
+If you want to check a limited set of model fields, you should set ``FIELDS_TO_CHECK`` in your model inheriting from ``DirtyFieldsMixin``:
+
+::
+
+ class TestModelWithSpecifiedFields(DirtyFieldsMixin, models.Model):
+ boolean1 = models.BooleanField(default=True)
+ boolean2 = models.BooleanField(default=True)
+ FIELDS_TO_CHECK = ['boolean1']
+
+ >>> from tests.models import TestModelWithSpecifiedFields
+ >>> tm = TestModelWithSpecifiedFields.objects.create()
+
+ >>> tm.boolean1 = False
+ >>> tm.boolean2 = False
+
+ >>> tm.get_dirty_fields()
+ {'boolean1': True}
+
+
+This can be used in order to increase performance.
+
+
Saving dirty fields.
----------------------------
If you want to only save dirty fields from an instance in the database (only these fields will be involved in SQL query), you can use ``save_dirty_fields`` method.
diff --git a/requirements.txt b/requirements.txt
index f11637f..a42745b 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,2 +1,2 @@
-Django>=1.4
+Django>=1.8
pytz>=2015.7
\ No newline at end of file
diff --git a/setup.py b/setup.py
index c55eabe..0f36875 100644
--- a/setup.py
+++ b/setup.py
@@ -7,7 +7,7 @@ def listify(filename):
setup(
name="django-dirtyfields",
- version="1.2.1",
+ version="1.3",
url='http://github.com/romgar/django-dirtyfields',
license='BSD',
description=("Tracking dirty fields on a Django model instance "
diff --git a/src/dirtyfields/compat.py b/src/dirtyfields/compat.py
index c4e6da9..4c951e5 100644
--- a/src/dirtyfields/compat.py
+++ b/src/dirtyfields/compat.py
@@ -1,7 +1,5 @@
import sys
import django
-from django.db.models import signals
-from django.db.models.query_utils import DeferredAttribute
def get_m2m_with_model(given_model):
@@ -15,49 +13,6 @@ def get_m2m_with_model(given_model):
]
-def is_db_expression(value):
- try:
- # django < 1.8
- from django.db.models.expressions import ExpressionNode
- return isinstance(value, ExpressionNode)
- except ImportError:
- # django >= 1.8 (big refactoring in Lookup/Expressions/Transforms)
- from django.db.models.expressions import BaseExpression, Combinable
- return isinstance(value, (BaseExpression, Combinable))
-
-
-def is_deferred(instance, field):
- if django.VERSION < (1, 8):
- attr = instance.__class__.__dict__.get(field.attname)
- return isinstance(attr, DeferredAttribute)
- else:
- return field.get_attname() in instance.get_deferred_fields()
-
-
-def save_specific_fields(instance, fields_list):
-
- update_fields = fields_list.keys()
- if django.VERSION >= (1, 5):
- instance.save(update_fields=update_fields)
- else:
- # dirtyfields is by default returning dirty fields with their old value
- # We should pass the new value(s) to update the database
- new_fields_list = {field_name: getattr(instance, field_name)
- for field_name, field_value in fields_list.items()}
-
- # dirtyfield is based on post_save signal to save last database value in memory.
- # As we need to manually launch post_save signal, we also launch pre_save
- # to be coherent with django 'classic' save signals.
- signals.pre_save.send(sender=instance.__class__, instance=instance, update_fields=update_fields)
-
- # django < 1.5 does not support update_fields option on save method
- instance.__class__.objects.filter(pk=instance.pk).update(**new_fields_list)
-
- # dirtyfield is based on post_save signal to save last database value in memory.
- # As update() method does not trigger this signal, we launch it explicitly.
- signals.post_save.send(sender=instance.__class__, instance=instance, update_fields=update_fields)
-
-
def is_buffer(value):
if sys.version_info < (3, 0, 0):
return isinstance(value, buffer)
diff --git a/src/dirtyfields/dirtyfields.py b/src/dirtyfields/dirtyfields.py
index 985f2d5..4f7b025 100644
--- a/src/dirtyfields/dirtyfields.py
+++ b/src/dirtyfields/dirtyfields.py
@@ -2,11 +2,12 @@
from copy import deepcopy
from django.core.exceptions import ValidationError
+from django.db.models.expressions import BaseExpression
+from django.db.models.expressions import Combinable
from django.db.models.signals import post_save, m2m_changed
from .compare import raw_compare, compare_states
-from .compat import (is_db_expression, save_specific_fields,
- is_deferred, is_buffer, get_m2m_with_model, remote_field)
+from .compat import is_buffer, get_m2m_with_model, remote_field
class DirtyFieldsMixin(object):
@@ -16,10 +17,12 @@ class DirtyFieldsMixin(object):
# https://github.com/romgar/django-dirtyfields/issues/73
ENABLE_M2M_CHECK = False
+ FIELDS_TO_CHECK = None
+
def __init__(self, *args, **kwargs):
super(DirtyFieldsMixin, self).__init__(*args, **kwargs)
post_save.connect(
- reset_state, sender=self.__class__,
+ reset_state, sender=self.__class__, weak=False,
dispatch_uid='{name}-DirtyFieldsMixin-sweeper'.format(
name=self.__class__.__name__))
if self.ENABLE_M2M_CHECK:
@@ -29,7 +32,7 @@ class DirtyFieldsMixin(object):
def _connect_m2m_relations(self):
for m2m_field, model in get_m2m_with_model(self.__class__):
m2m_changed.connect(
- reset_state, sender=remote_field(m2m_field).through,
+ reset_state, sender=remote_field(m2m_field).through, weak=False,
dispatch_uid='{name}-DirtyFieldsMixin-sweeper-m2m'.format(
name=self.__class__.__name__))
@@ -37,6 +40,9 @@ class DirtyFieldsMixin(object):
all_field = {}
for field in self._meta.fields:
+ if self.FIELDS_TO_CHECK and (field.get_attname() not in self.FIELDS_TO_CHECK):
+ continue
+
if field.primary_key and not include_primary_key:
continue
@@ -44,13 +50,13 @@ class DirtyFieldsMixin(object):
if not check_relationship:
continue
- if is_deferred(self, field):
+ if field.get_attname() in self.get_deferred_fields():
continue
field_value = getattr(self, field.attname)
# If current field value is an expression, we are not evaluating it
- if is_db_expression(field_value):
+ if isinstance(field_value, (BaseExpression, Combinable)):
continue
try:
@@ -71,15 +77,17 @@ class DirtyFieldsMixin(object):
return all_field
def _as_dict_m2m(self):
+ m2m_fields = {}
+
if self.pk:
- m2m_fields = dict([
- (f.attname, set([
- obj.pk for obj in getattr(self, f.attname).all()
- ]))
- for f, model in get_m2m_with_model(self.__class__)
- ])
- return m2m_fields
- return {}
+ for f, model in get_m2m_with_model(self.__class__):
+ if self.FIELDS_TO_CHECK and (f.attname not in self.FIELDS_TO_CHECK):
+ continue
+
+ m2m_fields[f.attname] = set([obj.pk for obj in getattr(self, f.attname).all()])
+
+ return m2m_fields
+
def get_dirty_fields(self, check_relationship=False, check_m2m=None, verbose=False):
if self._state.adding:
@@ -87,6 +95,9 @@ class DirtyFieldsMixin(object):
# for consistency (see https://github.com/romgar/django-dirtyfields/issues/65 for more details)
pk_specified = self.pk is not None
initial_dict = self._as_dict(check_relationship, include_primary_key=pk_specified)
+ if verbose:
+ initial_dict = {key: {'saved': None, 'current': value}
+ for key, value in initial_dict.items()}
return initial_dict
if check_m2m is not None and not self.ENABLE_M2M_CHECK:
@@ -114,7 +125,7 @@ class DirtyFieldsMixin(object):
def save_dirty_fields(self):
dirty_fields = self.get_dirty_fields(check_relationship=True)
- save_specific_fields(self, dirty_fields)
+ self.save(update_fields=dirty_fields.keys())
def reset_state(sender, instance, **kwargs):
@@ -123,8 +134,14 @@ def reset_state(sender, instance, **kwargs):
update_fields = kwargs.pop('update_fields', {})
new_state = instance._as_dict(check_relationship=True)
if update_fields:
- for field in update_fields:
- instance._original_state[field] = new_state[field]
+ for field_name in update_fields:
+ field = sender._meta.get_field(field_name)
+
+ if field.get_attname() in instance.get_deferred_fields():
+ continue
+
+ instance._original_state[field.name] = new_state[field.name]
+
else:
instance._original_state = new_state
if instance.ENABLE_M2M_CHECK:
diff --git a/tests/models.py b/tests/models.py
index ba6a7d2..70a8001 100644
--- a/tests/models.py
+++ b/tests/models.py
@@ -117,3 +117,17 @@ if is_postgresql_env_with_json_field():
class TestModelWithJSONField(DirtyFieldsMixin, models.Model):
json_field = JSONField()
+
+
+class TestModelWithSpecifiedFields(DirtyFieldsMixin, models.Model):
+ boolean1 = models.BooleanField(default=True)
+ boolean2 = models.BooleanField(default=True)
+ FIELDS_TO_CHECK = ['boolean1']
+
+
+class TestModelWithM2MAndSpecifiedFields(DirtyFieldsMixin, models.Model):
+ m2m1 = models.ManyToManyField(TestModel)
+ m2m2 = models.ManyToManyField(TestModel)
+ ENABLE_M2M_CHECK = True
+ FIELDS_TO_CHECK = ['m2m1']
+
diff --git a/tests/test_core.py b/tests/test_core.py
index 7a1bb3b..30ba2a8 100644
--- a/tests/test_core.py
+++ b/tests/test_core.py
@@ -153,3 +153,13 @@ def test_verbose_mode():
assert tm.get_dirty_fields(verbose=True) == {
'boolean': {'saved': True, 'current': False}
}
+
+
+ at pytest.mark.django_db
+def test_verbose_mode_on_adding():
+ tm = TestModel()
+
+ assert tm.get_dirty_fields(verbose=True) == {
+ 'boolean': {'saved': None, 'current': True},
+ 'characters': {'saved': None, 'current': u''}
+ }
diff --git a/tests/test_memory_leak.py b/tests/test_memory_leak.py
new file mode 100644
index 0000000..56d32a7
--- /dev/null
+++ b/tests/test_memory_leak.py
@@ -0,0 +1,16 @@
+import resource
+
+import pytest
+
+from .models import TestModel as DirtyMixinModel
+
+pytestmark = pytest.mark.django_db
+
+
+def test_rss_usage():
+ DirtyMixinModel()
+ rss_1 = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
+ for _ in range(1000):
+ DirtyMixinModel()
+ rss_2 = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
+ assert rss_2 == rss_1, 'There is a memory leak!'
diff --git a/tests/test_save_fields.py b/tests/test_save_fields.py
index 7eaf372..fdde255 100644
--- a/tests/test_save_fields.py
+++ b/tests/test_save_fields.py
@@ -3,7 +3,7 @@ import unittest
import django
import pytest
-from .models import TestModel, TestMixedFieldsModel
+from .models import TestModel, TestMixedFieldsModel, TestModelWithForeignKey
from .utils import assert_number_of_queries_on_regex
@@ -55,7 +55,6 @@ def test_save_dirty_related_field():
assert TestMixedFieldsModel.objects.get(pk=tmfm.pk).fkey_id == tm1.id
- at unittest.skipIf(django.VERSION < (1, 5), "Django 1.4 doesn't support update_fields param on save()")
@pytest.mark.django_db
def test_save_only_specific_fields_should_let_other_fields_dirty():
tm = TestModel.objects.create(boolean=True, characters='dummy')
@@ -67,3 +66,40 @@ def test_save_only_specific_fields_should_let_other_fields_dirty():
# 'characters' field should still be dirty, update_fields was only saving the 'boolean' field in the db
assert tm.get_dirty_fields() == {'characters': 'dummy'}
+
+
+ at pytest.mark.django_db
+def test_handle_foreignkeys_id_field_in_update_fields():
+ tm1 = TestModel.objects.create(boolean=True, characters='dummy')
+ tm2 = TestModel.objects.create(boolean=True, characters='dummy')
+ tmwfk = TestModelWithForeignKey.objects.create(fkey=tm1)
+
+ tmwfk.fkey = tm2
+ assert tmwfk.get_dirty_fields(check_relationship=True) == {'fkey': tm1.pk}
+
+ tmwfk.save(update_fields=['fkey_id'])
+ assert tmwfk.get_dirty_fields(check_relationship=True) == {}
+
+
+ at pytest.mark.django_db
+def test_correctly_handle_foreignkeys_id_field_in_update_fields():
+ tm1 = TestModel.objects.create(boolean=True, characters='dummy')
+ tm2 = TestModel.objects.create(boolean=True, characters='dummy')
+ tmwfk = TestModelWithForeignKey.objects.create(fkey=tm1)
+
+ tmwfk.fkey_id = tm2.pk
+ assert tmwfk.get_dirty_fields(check_relationship=True) == {'fkey': tm1.pk}
+
+ tmwfk.save(update_fields=['fkey'])
+ assert tmwfk.get_dirty_fields(check_relationship=True) == {}
+
+
+ at pytest.mark.django_db
+def test_save_deferred_field_with_update_fields():
+ TestModel.objects.create()
+
+ tm = TestModel.objects.defer('boolean').first()
+ tm.boolean = False
+ # Test that providing a deferred field to the update_fields
+ # save parameter doesn't raise a KeyError anymore.
+ tm.save(update_fields=['boolean'])
diff --git a/tests/test_specified_fields.py b/tests/test_specified_fields.py
new file mode 100644
index 0000000..9d68d0b
--- /dev/null
+++ b/tests/test_specified_fields.py
@@ -0,0 +1,28 @@
+import pytest
+
+from .models import TestModel, TestModelWithSpecifiedFields, TestModelWithM2MAndSpecifiedFields
+
+
+ at pytest.mark.django_db
+def test_dirty_fields_on_model_with_specified_fields():
+ tm = TestModelWithSpecifiedFields.objects.create()
+
+ tm.boolean1 = False
+ tm.boolean2 = False
+
+ # boolean1 is tracked, boolean2 isn`t tracked
+ assert tm.get_dirty_fields() == {'boolean1': True}
+
+
+ at pytest.mark.django_db
+def test_dirty_fields_on_model_with_m2m_and_specified_fields():
+ tm = TestModelWithM2MAndSpecifiedFields.objects.create()
+ tm2 = TestModel.objects.create()
+
+ tm.m2m1.add(tm2)
+ tm.m2m2.add(tm2)
+
+ # m2m1 is tracked, m2m2 isn`t tracked
+ assert tm.get_dirty_fields(check_m2m={'m2m1': set([])}) == {'m2m1': set([tm2.id])}
+ assert tm.get_dirty_fields(check_m2m={'m2m2': set([])}) == {}
+
diff --git a/tox.ini b/tox.ini
index e4d3fc9..07cb563 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
[tox]
-envlist = django14,django15,django16,django17,django18,django19,django110,coverage,postgresql
+envlist = django18,django19,django110,django111,coverage,postgresql
[testenv]
setenv =
@@ -7,31 +7,11 @@ setenv =
commands =
py.test --ds=tests.django_settings -v
deps =
- pytest==2.7.1
- pytest-django==2.8.0
+ pytest==3.0.6
+ pytest-django==3.1.2
jsonfield==1.0.3
pytz
-[testenv:django14]
-deps =
- django>=1.4,<1.4.99
- {[testenv]deps}
-
-[testenv:django15]
-deps =
- django>=1.5,<1.5.99
- {[testenv]deps}
-
-[testenv:django16]
-deps =
- django>=1.6,<1.6.99
- {[testenv]deps}
-
-[testenv:django17]
-deps =
- django>=1.7,<1.7.99
- {[testenv]deps}
-
[testenv:django18]
deps =
django>=1.8,<1.8.99
@@ -47,13 +27,18 @@ deps =
django>=1.10,<1.10.99
{[testenv]deps}
+[testenv:django111]
+deps =
+ django>=1.11,<1.11.99
+ {[testenv]deps}
+
[testenv:postgresql]
setenv =
PYTHONPATH = {toxinidir}
commands =
py.test --ds=tests.postgresql_django_settings -v
deps =
- django>=1.10,<1.10.99
+ django>=1.11,<1.11.99
psycopg2
{[testenv]deps}
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/django-dirtyfields.git
More information about the Python-modules-commits
mailing list