Skip to content

Commit

Permalink
Merge branch 'release-1.6.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
LincolnPuzey committed Apr 7, 2021
2 parents 04ac7fd + c853ff1 commit 36617e0
Show file tree
Hide file tree
Showing 18 changed files with 161 additions and 67 deletions.
32 changes: 24 additions & 8 deletions .travis.yml
@@ -1,5 +1,5 @@
os: linux
dist: xenial
dist: bionic

language: python

Expand All @@ -8,7 +8,7 @@ jobs:
# Python version is just for the look on travis.
# Test the full python/django matrix with postgresql
- python: 3.6
env: TOXENV=py36-django111-postgresql
env: TOXENV=py36-django111-postgresql COLLECT_COVERAGE=true
- python: 3.7
env: TOXENV=py37-django111-postgresql
- python: 3.6
Expand Down Expand Up @@ -43,25 +43,41 @@ jobs:
env: TOXENV=py38-django31-postgresql
- python: 3.9
env: TOXENV=py39-django31-postgresql
- python: 3.6
env: TOXENV=py36-django32-postgresql
- python: 3.7
env: TOXENV=py37-django32-postgresql
- python: 3.8
env: TOXENV=py38-django32-postgresql
- python: 3.9
env: TOXENV=py39-django32-postgresql
# Test against sqlite once for each major django version.
- python: 3.6
env: TOXENV=py36-django111-sqlite
- python: 3.7
env: TOXENV=py37-django22-sqlite
- python: 3.8
env: TOXENV=py38-django31-sqlite
env: TOXENV=py38-django32-sqlite
# Test on ppc64le once for each major django version.
- python: 3.6
env: TOXENV=py36-django111-sqlite
arch: ppc64le
- python: 3.7
env: TOXENV=py37-django22-sqlite
arch: ppc64le
- python: 3.8
env: TOXENV=py38-django32-sqlite
arch: ppc64le
# Check flake8 once.
- python: 3.6
env: TOXENV=py36-flake8

services:
- postgresql

addons:
postgresql: "9.6"

before_script:
- psql -c 'create database dirtyfields_test;' -U postgres
# only create postgres database if needed.
- if [[ $TOXENV =~ "postgresql" ]]; then psql -c 'create database dirtyfields_test;' -U postgres; fi

script:
- tox
Expand All @@ -72,7 +88,7 @@ install:

after_success:
# only upload coverage report to coveralls from one job.
- if test "$TOXENV" = "py36-django111-postgresql"; then coveralls; fi
- if test "$COLLECT_COVERAGE" = "true"; then coveralls; fi

deploy:
edge: true # opt in to dpl v2
Expand Down
1 change: 1 addition & 0 deletions CLASSIFIERS.txt
Expand Up @@ -16,4 +16,5 @@ Framework :: Django :: 2.1
Framework :: Django :: 2.2
Framework :: Django :: 3.0
Framework :: Django :: 3.1
Framework :: Django :: 3.2
Topic :: Software Development :: Libraries :: Python Modules
11 changes: 11 additions & 0 deletions ChangeLog.rst
Expand Up @@ -8,6 +8,17 @@ master

No changes yet

.. _v1.6.0:

1.6.0 (07/04/2021)
------------------

*New:*
- Remove pytz as a dependency.
- Confirm support of Django 3.2

.. _v1.5.0:

1.5.0 (15/01/2021)
------------------

Expand Down
31 changes: 14 additions & 17 deletions README.rst
Expand Up @@ -6,12 +6,16 @@ Django Dirty Fields
:alt: Join the chat at https://gitter.im/romgar/django-dirtyfields
:target: https://gitter.im/romgar/django-dirtyfields?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge
.. image:: https://img.shields.io/pypi/v/django-dirtyfields.svg
:alt: Published PyPI version
:target: https://pypi.org/project/django-dirtyfields/
.. image:: https://travis-ci.org/romgar/django-dirtyfields.svg?branch=develop
:target: https://travis-ci.org/romgar/django-dirtyfields?branch=develop
.. image:: https://coveralls.io/repos/romgar/django-dirtyfields/badge.svg?branch=develop
:target: https://coveralls.io/r/romgar/django-dirtyfields?branch=develop
:alt: Travis CI status
:target: https://travis-ci.org/romgar/django-dirtyfields
.. image:: https://coveralls.io/repos/github/romgar/django-dirtyfields/badge.svg?branch=develop
:alt: Coveralls code coverage status
:target: https://coveralls.io/github/romgar/django-dirtyfields?branch=develop
.. image:: https://readthedocs.org/projects/django-dirtyfields/badge/?version=develop
:alt: Read the Docs documentation status
:target: https://django-dirtyfields.readthedocs.org/en/develop/?badge=develop

Tracking dirty fields on a Django model instance.
Expand All @@ -20,20 +24,13 @@ Dirty means that field in-memory and database values are different.
This package is compatible and tested with the following Python & Django versions:



+---------------+------------------------------------------------------+
| Django | Python |
+===============+======================================================+
| 1.11 | 3.6, 3.7 (as of 1.11.17) |
+---------------+------------------------------------------------------+
| 2.0, 2.1 | 3.6, 3.7 |
+---------------+------------------------------------------------------+
| 2.2 | 3.6, 3.7, 3.8 (as of 2.2.8), 3.9 (as of 2.2.17) |
+---------------+------------------------------------------------------+
| 3.0 | 3.6, 3.7, 3.8, 3.9 (as of 3.0.11) |
+---------------+------------------------------------------------------+
| 3.1 | 3.6, 3.7, 3.8, 3.9 (as of 3.1.3) |
+---------------+------------------------------------------------------+
+------------------------+------------------------+
| Django | Python |
+========================+========================+
| 1.11, 2.0, 2.1 | 3.6, 3.7 |
+------------------------+------------------------+
| 2.2, 3.0, 3.1, 3.2 | 3.6, 3.7, 3.8 ,3.9 |
+------------------------+------------------------+



Expand Down
8 changes: 8 additions & 0 deletions pyproject.toml
@@ -1,3 +1,11 @@
[tool.pytest.ini_options]
django_find_project = false
DJANGO_SETTINGS_MODULE = 'tests.django_settings'

[tool.coverage.run]
branch = true
source = ['dirtyfields']

[tool.coverage.report]
show_missing = true
precision = 2
3 changes: 0 additions & 3 deletions pytest.ini

This file was deleted.

1 change: 0 additions & 1 deletion requirements.txt
@@ -1,2 +1 @@
Django>=1.11
pytz>=2015.7
2 changes: 1 addition & 1 deletion setup.py
Expand Up @@ -12,7 +12,7 @@ def listify(filename):

setup(
name="django-dirtyfields",
version="1.5.0",
version="1.6.0",
url='https://github.com/romgar/django-dirtyfields',
project_urls={
"Documentation": "https://django-dirtyfields.readthedocs.org/en/develop/",
Expand Down
17 changes: 8 additions & 9 deletions src/dirtyfields/compare.py
@@ -1,8 +1,7 @@
import datetime
import pytz
import warnings
from datetime import datetime, timezone

from django.utils import timezone
from django.utils import timezone as django_timezone


def compare_states(new_state, original_state, compare_function, normalise_function):
Expand Down Expand Up @@ -33,13 +32,13 @@ def raw_compare(new_value, old_value):
return new_value == old_value


def timezone_support_compare(new_value, old_value, timezone_to_set=pytz.UTC):
def timezone_support_compare(new_value, old_value, timezone_to_set=timezone.utc):

if not (isinstance(new_value, datetime.datetime) and isinstance(old_value, datetime.datetime)):
if not (isinstance(new_value, datetime) and isinstance(old_value, datetime)):
return raw_compare(new_value, old_value)

db_value_is_aware = timezone.is_aware(old_value)
in_memory_value_is_aware = timezone.is_aware(new_value)
db_value_is_aware = django_timezone.is_aware(old_value)
in_memory_value_is_aware = django_timezone.is_aware(new_value)

if db_value_is_aware == in_memory_value_is_aware:
return raw_compare(new_value, old_value)
Expand All @@ -49,14 +48,14 @@ def timezone_support_compare(new_value, old_value, timezone_to_set=pytz.UTC):
warnings.warn(u"DateTimeField received a naive datetime (%s)"
u" while time zone support is active." % new_value,
RuntimeWarning)
new_value = timezone.make_aware(new_value, timezone_to_set).astimezone(pytz.utc)
new_value = django_timezone.make_aware(new_value, timezone_to_set).astimezone(timezone.utc)
else:
# The db is not timezone aware, but the value we are passing for comparison is aware.
warnings.warn(u"Time zone support is not active (settings.USE_TZ=False), "
u"and you pass a time zone aware value (%s)"
u" Converting database value before comparison." % new_value,
RuntimeWarning)
old_value = timezone.make_aware(old_value, pytz.utc).astimezone(timezone_to_set)
old_value = django_timezone.make_aware(old_value, timezone.utc).astimezone(timezone_to_set)

return raw_compare(new_value, old_value)

Expand Down
3 changes: 1 addition & 2 deletions tests-requirements.txt
@@ -1,5 +1,4 @@
pytest==6.2.1
pytz
pytest==6.2.3
# Because we test a wide range of Django versions, leave versions of these unspecified.
pytest-django
jsonfield
3 changes: 3 additions & 0 deletions tests/django_settings.py
@@ -1,5 +1,6 @@
# Minimum settings that are needed to run django test suite
import os
import tempfile

SECRET_KEY = 'WE DONT CARE ABOUT IT'

Expand All @@ -26,3 +27,5 @@
}

INSTALLED_APPS = ('tests', )

MEDIA_ROOT = tempfile.mkdtemp(prefix="django-dirtyfields-test-media-root-")
1 change: 1 addition & 0 deletions tests/files/bar.txt
@@ -0,0 +1 @@
bar-content
1 change: 1 addition & 0 deletions tests/files/foo.txt
@@ -0,0 +1 @@
foo-content
15 changes: 11 additions & 4 deletions tests/models.py
@@ -1,6 +1,6 @@
from django.db import models
from django.db.models.signals import pre_save
from django.utils import timezone
from django.utils import timezone as django_timezone
from jsonfield import JSONField as JSONFieldThirdParty

from dirtyfields import DirtyFieldsMixin
Expand Down Expand Up @@ -61,12 +61,15 @@ class ExpressionModelTest(DirtyFieldsMixin, models.Model):

class DatetimeModelTest(DirtyFieldsMixin, models.Model):
compare_function = (timezone_support_compare, {})
datetime_field = models.DateTimeField(default=timezone.now)
datetime_field = models.DateTimeField(default=django_timezone.now)


class CurrentDatetimeModelTest(DirtyFieldsMixin, models.Model):
compare_function = (timezone_support_compare, {'timezone_to_set': timezone.get_current_timezone()})
datetime_field = models.DateTimeField(default=timezone.now)
compare_function = (
timezone_support_compare,
{'timezone_to_set': django_timezone.get_current_timezone()},
)
datetime_field = models.DateTimeField(default=django_timezone.now)


class Many2ManyModelTest(DirtyFieldsMixin, models.Model):
Expand Down Expand Up @@ -155,3 +158,7 @@ class ModelWithM2MAndSpecifiedFieldsTest(DirtyFieldsMixin, models.Model):

class BinaryModelTest(DirtyFieldsMixin, models.Model):
bytea = models.BinaryField()


class FileFieldModel(DirtyFieldsMixin, models.Model):
file1 = models.FileField(upload_to="file1/")
54 changes: 52 additions & 2 deletions tests/test_core.py
@@ -1,8 +1,14 @@
from decimal import Decimal
from os.path import dirname, join

import pytest
from django.core.files.base import ContentFile, File

from .models import (ModelTest, ModelWithForeignKeyTest, ModelWithOneToOneFieldTest,
SubclassModelTest, ModelWithDecimalFieldTest)
from .models import (ModelTest, ModelWithForeignKeyTest,
ModelWithOneToOneFieldTest,
SubclassModelTest, ModelWithDecimalFieldTest,
FileFieldModel)
from .utils import FakeFieldFile


@pytest.mark.django_db
Expand Down Expand Up @@ -199,3 +205,47 @@ def test_refresh_from_db_no_fields():
assert tm.boolean is False
assert tm.characters == "new value"
assert tm.get_dirty_fields() == {"boolean": True, "characters": "old value"}


@pytest.mark.django_db
def test_file_fields_content_file():
tm = FileFieldModel()
# field is dirty because model is unsaved
assert tm.get_dirty_fields() == {"file1": FakeFieldFile("")}
tm.save()
assert tm.get_dirty_fields() == {}

# set file makes field dirty
tm.file1.save("test-file-1.txt", ContentFile(b"Test file content 1"), save=False)
assert tm.get_dirty_fields() == {"file1": FakeFieldFile("")}
tm.save()
assert tm.get_dirty_fields() == {}

# change file makes field dirty
tm.file1.save("test-file-2.txt", ContentFile(b"Test file content 2"), save=False)
assert tm.get_dirty_fields() == {"file1": FakeFieldFile("file1/test-file-1.txt")}
tm.save()
assert tm.get_dirty_fields() == {}


@pytest.mark.django_db
def test_file_fields_real_file():
tm = FileFieldModel()
# field is dirty because model is unsaved
assert tm.get_dirty_fields() == {"file1": FakeFieldFile("")}
tm.save()
assert tm.get_dirty_fields() == {}

# set file makes field dirty
with open(join(dirname(__file__), "files", "foo.txt"), "rb") as f:
tm.file1.save("test-file-3.txt", File(f), save=False)
assert tm.get_dirty_fields() == {"file1": FakeFieldFile("")}
tm.save()
assert tm.get_dirty_fields() == {}

# change file makes field dirty
with open(join(dirname(__file__), "files", "bar.txt"), "rb") as f:
tm.file1.save("test-file-4.txt", File(f), save=False)
assert tm.get_dirty_fields() == {"file1": FakeFieldFile("file1/test-file-3.txt")}
tm.save()
assert tm.get_dirty_fields() == {}

0 comments on commit 36617e0

Please sign in to comment.