Skip to content

Commit fcecd7c

Browse files
committed
Merge remote-tracking branch 'jazzband/master' into erikvw/master
2 parents e541d04 + fa53806 commit fcecd7c

39 files changed

+1282
-74
lines changed

.github/workflows/release.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,14 @@ jobs:
1212
runs-on: ubuntu-latest
1313

1414
steps:
15-
- uses: actions/checkout@v2
15+
- uses: actions/checkout@v3
1616
with:
1717
fetch-depth: 0
1818

1919
- name: Set up Python
20-
uses: actions/setup-python@v2
20+
uses: actions/setup-python@v4
2121
with:
22-
python-version: 3.8
22+
python-version: 3.x
2323

2424
- name: Install dependencies
2525
run: |

.github/workflows/test.yml

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@ on: [push, pull_request]
66
jobs:
77
build:
88
name: build (Python ${{ matrix.python-version }}, Django ${{ matrix.django-version }})
9-
runs-on: ubuntu-18.04
9+
runs-on: ubuntu-latest
1010

1111
strategy:
1212
fail-fast: false
1313
matrix:
14-
python-version: ['3.7', '3.8', '3.9', '3.10']
14+
python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12-dev']
1515
django-version: ['3.2', '4.0', '4.1', 'main']
1616

1717
exclude:
@@ -22,6 +22,22 @@ jobs:
2222
- python-version: '3.7'
2323
django-version: 'main'
2424

25+
- python-version: '3.8'
26+
django-version: 'main'
27+
28+
- python-version: '3.9'
29+
django-version: 'main'
30+
31+
- python-version: '3.11'
32+
django-version: '3.2'
33+
- python-version: '3.11'
34+
django-version: '4.0'
35+
36+
- python-version: '3.12-dev'
37+
django-version: '3.2'
38+
- python-version: '3.12-dev'
39+
django-version: '4.0'
40+
2541
services:
2642

2743
postgres:
@@ -54,20 +70,20 @@ jobs:
5470
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
5571

5672
steps:
57-
- uses: actions/checkout@v2
73+
- uses: actions/checkout@v3
5874

5975
- name: Set up Python ${{ matrix.python-version }}
60-
uses: actions/setup-python@v2
76+
uses: actions/setup-python@v4
6177
with:
6278
python-version: ${{ matrix.python-version }}
6379

6480
- name: Get pip cache dir
6581
id: pip-cache
6682
run: |
67-
echo "::set-output name=dir::$(pip cache dir)"
83+
echo "dir=$(pip cache dir)" >> $GITHUB_OUTPUT
6884
6985
- name: Cache
70-
uses: actions/cache@v2
86+
uses: actions/cache@v3
7187
with:
7288
path: ${{ steps.pip-cache.outputs.dir }}
7389
key:
@@ -87,6 +103,6 @@ jobs:
87103
DJANGO: ${{ matrix.django-version }}
88104

89105
- name: Upload coverage
90-
uses: codecov/codecov-action@v1
106+
uses: codecov/codecov-action@v3
91107
with:
92108
name: Python ${{ matrix.python-version }}

.pre-commit-config.yaml

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,32 @@
11
---
22
repos:
33
- repo: https://github.com/PyCQA/bandit
4-
rev: 1.7.4
4+
rev: 1.7.5
55
hooks:
66
- id: bandit
77
args:
88
- "-x *test*.py"
99

1010
- repo: https://github.com/psf/black
11-
rev: 22.6.0
11+
rev: 23.1.0
1212
hooks:
1313
- id: black
1414
language_version: python3.8
1515

1616
- repo: https://github.com/pycqa/flake8
17-
rev: 5.0.4
17+
rev: 6.0.0
1818
hooks:
1919
- id: flake8
2020
args:
2121
- "--config=tox.ini"
2222

2323
- repo: https://github.com/PyCQA/isort
24-
rev: 5.10.1
24+
rev: 5.12.0
2525
hooks:
2626
- id: isort
2727

2828
- repo: https://github.com/pre-commit/pre-commit-hooks
29-
rev: v4.3.0
29+
rev: v4.4.0
3030
hooks:
3131
- id: requirements-txt-fixer
3232
files: requirements/.*\.txt$
@@ -40,14 +40,14 @@ repos:
4040
- id: detect-private-key
4141

4242
- repo: https://github.com/adrienverge/yamllint
43-
rev: v1.27.1
43+
rev: v1.30.0
4444
hooks:
4545
- id: yamllint
4646
args:
4747
- "--strict"
4848

4949
- repo: https://github.com/asottile/pyupgrade
50-
rev: v2.37.3
50+
rev: v3.3.1
5151
hooks:
5252
- id: pyupgrade
5353
args: [--py37-plus]

AUTHORS.rst

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,9 @@ Authors
2121
- Bheesham Persaud (`bheesham <https://github.com/bheesham>`_)
2222
- `bradford281 <https://github.com/bradford281>`_
2323
- Brian Armstrong (`barm <https://github.com/barm>`_)
24-
- Buddy Lindsey, Jr.
2524
- Brian Dixon
25+
- Brian Mesick (`bmedx <https://github.com/bmedx>`_)
26+
- Buddy Lindsey, Jr.
2627
- Carlos San Emeterio (`Carlos-San-Emeterio <https://github.com/Carlos-San-Emeterio>`_)
2728
- Christopher Broderick (`uhurusurfa <https://github.com/uhurusurfa>`_)
2829
- Christopher Johns (`tyrantwave <https://github.com/tyrantwave>`_)
@@ -37,6 +38,7 @@ Authors
3738
- David Grochowski (`ThePumpingLemma <https://github.com/ThePumpingLemma>`_)
3839
- David Hite
3940
- David Smith
41+
- `ddabble <https://github.com/ddabble>`_
4042
- Dmytro Shyshov (`xahgmah <https://github.com/xahgmah>`_)
4143
- Edouard Richard (`vied12 <https://github.com/vied12>` _)
4244
- Eduardo Cuducos
@@ -56,6 +58,7 @@ Authors
5658
- Hanyin Zhang
5759
- Hernan Esteves (`sevetseh28 <https://github.com/sevetseh28>`_)
5860
- Hielke Walinga (`hwalinga <https://github.com/hwalinga>`_)
61+
- Hugo van Kemenade (`hugovk <https://github.com/hugovk>`_)
5962
- Jack Cushman (`jcushman <https://github.com/jcushman>`_)
6063
- Jake Howard (`RealOrangeOne <https://github.com/realorangeone>`_)
6164
- James Muranga (`jamesmura <https://github.com/jamesmura>`_)
@@ -74,11 +77,13 @@ Authors
7477
- Jonathan Zvesper (`zvesp <https://github.com/zvesp>`_)
7578
- Jordon Wing (`jordonwii <https://github.com/jordonwii`_)
7679
- Josh Fyne
80+
- Josh Thomas (`joshuadavidthomas <https://github.com/joshuadavidthomas>`_)
7781
- Keith Hackbarth
7882
- Kevin Foster
7983
- Klaas van Schelven
8084
- Kris Neuharth
8185
- Kyle Seever (`kseever <https://github.com/kseever>`_)
86+
- Léni Gauffier (`legau <https://github.com/legau>`_)
8287
- Leticia Portella
8388
- Lucas Wiman
8489
- Maciej "RooTer" Urbański
@@ -114,6 +119,7 @@ Authors
114119
- Stefan Borer (`sbor23 <https://github.com/sbor23>`_)
115120
- Steven Buss (`sbuss <https://github.com/sbuss>`_)
116121
- Steven Klass
122+
- Thijs Kramer (`thijskramer <https://github.com/thijskramer>`_)
117123
- Tim Schilling (`tim-schilling <https://github.com/tim-schilling>`_)
118124
- Todd Wolfson (`twolfson <https://github.com/twolfson>`_)
119125
- Tommy Beadle (`tbeadle <https://github.com/tbeadle>`_)

CHANGES.rst

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,26 @@ Changes
44
Unreleased
55
----------
66

7+
8+
3.3.0 (2023-03-08)
9+
------------------
10+
11+
- Made it possible to use the new ``m2m_fields`` with model inheritance (gh-1042)
12+
- Added two signals: ``pre_create_historical_m2m_records`` and ``post_create_historical_m2m_records`` (gh-1042)
13+
- Added ``tracked_fields`` attribute to historical models (gh-1038)
14+
- Fixed ``KeyError`` when running ``clean_duplicate_history`` on models with ``excluded_fields`` (gh-1038)
15+
- Added support for Python 3.11 (gh-1053)
16+
- Added Arabic translations (gh-1056)
17+
- Fixed a code example under "Tracking many to many relationships" (gh-1069)
18+
- Added a ``--base-manager`` option to the ``clean_duplicate_history`` management command (gh-1115)
19+
20+
3.2.0 (2022-09-28)
21+
------------------
22+
723
- Fixed typos in the docs
824
- Removed n+1 query from ``bulk_create_with_history`` utility (gh-975)
925
- Started using ``exists`` query instead of ``count`` in ``populate_history`` command (gh-982)
26+
- Add basic support for many-to-many fields (gh-399)
1027
- Added support for Django 4.1 (gh-1021)
1128
- Added feature to evaluate ``history`` model permisions explicitly when
1229
``SIMPLE_HISTORY_ENFORCE_HISTORY_MODEL_PERMISSIONS`` is set to ``True``

README.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
django-simple-history
22
=====================
33

4-
.. image:: https://github.com/jazzband/django-simple-history/workflows/build/badge.svg?branch=master
5-
:target: https://github.com/jazzband/django-simple-history/actions?workflow=build
4+
.. image:: https://github.com/jazzband/django-simple-history/actions/workflows/test.yml/badge.svg
5+
:target: https://github.com/jazzband/django-simple-history/actions/workflows/test.yml
66
:alt: Build Status
77

88
.. image:: https://readthedocs.org/projects/django-simple-history/badge/?version=latest
@@ -43,7 +43,7 @@ This app supports the following combinations of Django and Python:
4343
========== ========================
4444
3.2 3.7, 3.8, 3.9, 3.10
4545
4.0 3.8, 3.9, 3.10
46-
4.1 3.8, 3.9, 3.10
46+
4.1 3.8, 3.9, 3.10, 3.11
4747
========== ========================
4848

4949
Getting Help

docs/common_issues.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@ Using custom OneToOneFields
260260
---------------------------
261261

262262
If you are using a custom OneToOneField that has additional arguments and receiving
263-
the the following ``TypeError``::
263+
the following ``TypeError``::
264264

265265
TypeError: __init__() got an unexpected keyword argument
266266

docs/historical_model.rst

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -447,3 +447,46 @@ And you don't want to create database index for ``question``, it is necessary to
447447
448448
By default, django-simple-history keeps all indices. and even forces them on unique fields and relations.
449449
WARNING: This will drop performance on historical lookups
450+
451+
Tracking many to many relationships
452+
-----------------------------------
453+
By default, many to many fields are ignored when tracking changes.
454+
If you want to track many to many relationships, you need to define them explicitly:
455+
456+
.. code-block:: python
457+
458+
class Category(models.Model):
459+
name = models.CharField(max_length=200)
460+
461+
class Poll(models.Model):
462+
question = models.CharField(max_length=200)
463+
categories = models.ManyToManyField(Category)
464+
history = HistoricalRecords(m2m_fields=[categories])
465+
466+
This will create a historical intermediate model that tracks each relational change
467+
between `Poll` and `Category`.
468+
469+
You may also define these fields in a model attribute (by default on `_history_m2m_fields`).
470+
This is mainly used for inherited models. You can override the attribute name by setting
471+
your own `m2m_fields_model_field_name` argument on the `HistoricalRecord` instance.
472+
473+
You will see the many to many changes when diffing between two historical records:
474+
475+
.. code-block:: python
476+
477+
informal = Category.objects.create(name="informal questions")
478+
official = Category.objects.create(name="official questions")
479+
p = Poll.objects.create(question="what's up?")
480+
p.save()
481+
p.categories.add(informal, official)
482+
p.categories.remove(informal)
483+
484+
last_record = p.history.latest()
485+
previous_record = last_record.prev_record
486+
delta = last_record.diff_against(previous_record)
487+
488+
for change in delta.changes:
489+
print("{} changed from {} to {}".format(change.field, change.old, change.new))
490+
491+
# Output:
492+
# categories changed from [{'poll': 1, 'category': 1}, { 'poll': 1, 'category': 2}] to [{'poll': 1, 'category': 2}]

docs/index.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,9 @@ This app supports the following combinations of Django and Python:
4141
========== =======================
4242
Django Python
4343
========== =======================
44-
3.2 3.7, 3.8, 3.9
44+
3.2 3.7, 3.8, 3.9, 3.10
4545
4.0 3.8, 3.9, 3.10
46-
4.1 3.8, 3.9, 3.10
46+
4.1 3.8, 3.9, 3.10, 3.11
4747
========== =======================
4848

4949
Contribute

docs/signals.rst

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,24 @@ saving a historical record. Arguments passed to the signals include the followin
2222
using
2323
The database alias being used
2424

25+
For Many To Many signals you've got the following :
26+
27+
.. glossary::
28+
instance
29+
The source model instance being saved
30+
31+
history_instance
32+
The corresponding history record
33+
34+
rows (for pre_create)
35+
The elements to be bulk inserted into the m2m table
36+
37+
created_rows (for post_create)
38+
The created elements into the m2m table
39+
40+
field
41+
The recorded field object
42+
2543
To connect the signals to your callbacks, you can use the ``@receiver`` decorator:
2644

2745
.. code-block:: python
@@ -30,6 +48,8 @@ To connect the signals to your callbacks, you can use the ``@receiver`` decorato
3048
from simple_history.signals import (
3149
pre_create_historical_record,
3250
post_create_historical_record
51+
pre_create_historical_m2m_records,
52+
post_create_historical_m2m_records,
3353
)
3454
3555
@receiver(pre_create_historical_record)
@@ -39,3 +59,11 @@ To connect the signals to your callbacks, you can use the ``@receiver`` decorato
3959
@receiver(post_create_historical_record)
4060
def post_create_historical_record_callback(sender, **kwargs):
4161
print("Sent after saving historical record")
62+
63+
@receiver(pre_create_historical_m2m_records)
64+
def pre_create_historical_m2m_records_callback(sender, **kwargs):
65+
print("Sent before saving many to many field on historical record")
66+
67+
@receiver(post_create_historical_m2m_records)
68+
def post_create_historical_m2m_records_callback(sender, **kwargs):
69+
print("Sent after saving many to many field on historical record")

0 commit comments

Comments
 (0)