Skip to content

Commit 91a4adc

Browse files
committed
update django support
1 parent 1a78446 commit 91a4adc

9 files changed

Lines changed: 177 additions & 17 deletions

File tree

.github/workflows/ci.yml

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,25 @@ jobs:
88
strategy:
99
matrix:
1010
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
11+
django-version: ["3.2", "4.2", "5.1"]
12+
exclude:
13+
# Django 3.2 requires Python 3.6-3.10.
14+
- python-version: "3.11"
15+
django-version: "3.2"
16+
- python-version: "3.12"
17+
django-version: "3.2"
18+
- python-version: "3.13"
19+
django-version: "3.2"
20+
21+
# Django 4.2 requires Python 3.8-3.12.
22+
- python-version: "3.13"
23+
django-version: "4.2"
24+
25+
# Django 5.1 requires Python 3.10+.
26+
- python-version: "3.8"
27+
django-version: "5.1"
28+
- python-version: "3.9"
29+
django-version: "5.1"
1130

1231
steps:
1332
- uses: actions/checkout@v3
@@ -20,6 +39,7 @@ jobs:
2039
python -m pip install --upgrade pip
2140
pip install -r requirements.txt
2241
pip install -r requirements-dev.txt
42+
pip install "Django~=${{ matrix.django-version }}.0"
2343
- name: Test with pytest
2444
run: |
25-
pytest
45+
pytest --no-migrations

README.md

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -37,27 +37,29 @@ declaring a `StateMachineField`.
3737
```python
3838

3939
from django.db import models
40+
from django.utils.translation import gettext_lazy as _
4041
from dsm.fields import StateMachineField
4142

4243

4344
class Order(models.Model):
45+
class Status(models.TextChoices):
46+
NEW = 'new', _('New')
47+
PROCESSING = 'processing', _('Processing')
48+
SENDING = 'sending', _('Sending')
49+
FINISHED = 'finished', _('Finished')
50+
CANCELLED = 'cancelled', _('Cancelled')
51+
4452
status = StateMachineField(
4553
transitions=(
46-
('new', ['confirm'], 'processing'),
47-
('processing', ['cancel'], 'cancelled'),
48-
('processing', ['send'], 'sending'),
49-
('sending', ['deliver'], 'finished'),
54+
(Status.NEW, ['confirm'], Status.PROCESSING),
55+
(Status.PROCESSING, ['cancel'], Status.CANCELLED),
56+
(Status.PROCESSING, ['send'], Status.SENDING),
57+
(Status.SENDING, ['deliver'], Status.FINISHED),
5058
),
59+
choices=Status.choices,
5160
max_length=16,
52-
choices=(
53-
('new', _('New')),
54-
('processing', _('Processing')),
55-
('sending', _('Sending')),
56-
('finished', _('Finished')),
57-
('canceled', _('Cancelled')),
58-
),
5961
db_index=True,
60-
default='new'
62+
default=Status.NEW,
6163
)
6264
```
6365

dsm/fields.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from django.db import models
2-
from . import StateMachine, Transitions
32

3+
from . import StateMachine, Transitions
44

55
__all__ = ['StateMachineField']
66

pyproject.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ classifiers = [
4444
]
4545
dependencies = [
4646
"observable",
47-
"six",
4847
]
4948

5049
[project.urls]

requirements-dev.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ ipython
33
ipdb
44
wheel
55
pytest
6+
pytest-django

requirements.txt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
1-
six
2-
observable
1+
observable

tests/models.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
from django.db import models
2+
from django.utils.translation import gettext_lazy as _
3+
from dsm.fields import StateMachineField
4+
5+
class Order(models.Model):
6+
class Status(models.TextChoices):
7+
NEW = 'new', _('New')
8+
PROCESSING = 'processing', _('Processing')
9+
SENDING = 'sending', _('Sending')
10+
FINISHED = 'finished', _('Finished')
11+
CANCELLED = 'cancelled', _('Cancelled')
12+
13+
status = StateMachineField(
14+
transitions=(
15+
(Status.NEW, ['confirm'], Status.PROCESSING),
16+
(Status.PROCESSING, ['cancel'], Status.CANCELLED),
17+
(Status.PROCESSING, ['send'], Status.SENDING),
18+
(Status.SENDING, ['deliver'], Status.FINISHED),
19+
),
20+
choices=Status.choices,
21+
max_length=16,
22+
db_index=True,
23+
default=Status.NEW,
24+
)
25+
26+
class Meta:
27+
app_label = 'tests'

tests/test_django_example.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import os
2+
import unittest
3+
4+
from django.conf import settings
5+
from django.test import TestCase
6+
from django.utils.translation import gettext_lazy as _
7+
8+
# Configure Django settings for the test
9+
if not settings.configured:
10+
settings.configure(
11+
DATABASES={
12+
'default': {
13+
'ENGINE': 'django.db.backends.sqlite3',
14+
'NAME': ':memory:',
15+
}
16+
},
17+
INSTALLED_APPS=[
18+
'dsm',
19+
'tests',
20+
],
21+
# Add this to avoid AppRegistryNotReady error in some cases
22+
DEFAULT_AUTO_FIELD='django.db.models.AutoField',
23+
)
24+
import django
25+
django.setup()
26+
27+
28+
from dsm.fields import StateMachineField, MachineState
29+
from tests.models import Order
30+
31+
32+
class DjangoExampleTest(TestCase):
33+
def test_django_example(self):
34+
# Create an Order and check its status
35+
order = Order.objects.create()
36+
self.assertEqual(order.status, 'new')
37+
self.assertIsInstance(order.status, MachineState)
38+
39+
# Test transitions
40+
order.status.process('confirm')
41+
self.assertEqual(order.status, 'processing')
42+
43+
order.status.process('send')
44+
self.assertEqual(order.status, 'sending')
45+
46+
order.status.process('deliver')
47+
self.assertEqual(order.status, 'finished')
48+
49+
# Test another transition path
50+
order_to_cancel = Order.objects.create()
51+
self.assertEqual(order_to_cancel.status, 'new')
52+
order_to_cancel.status.process('confirm')
53+
self.assertEqual(order_to_cancel.status, 'processing')
54+
order_to_cancel.status.process('cancel')
55+
self.assertEqual(order_to_cancel.status, 'cancelled')

tests/test_fields.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import os
2+
import unittest
3+
4+
from django.conf import settings
5+
from django.core.management import call_command
6+
from django.db import models
7+
from django.test import TestCase
8+
from dsm.fields import StateMachineField
9+
10+
if not settings.configured:
11+
settings.configure(
12+
DATABASES={
13+
'default': {
14+
'ENGINE': 'django.db.backends.sqlite3',
15+
'NAME': ':memory:',
16+
}
17+
},
18+
INSTALLED_APPS=[
19+
'dsm',
20+
'tests',
21+
],
22+
DEFAULT_AUTO_FIELD='django.db.models.AutoField',
23+
)
24+
import django
25+
django.setup()
26+
27+
from tests.models import Order
28+
29+
30+
class StateMachineFieldTest(TestCase):
31+
def test_create_order(self):
32+
order = Order.objects.create()
33+
self.assertEqual(order.status, Order.Status.NEW)
34+
order.status.process('confirm')
35+
self.assertEqual(order.status, Order.Status.PROCESSING)
36+
order.save()
37+
order.refresh_from_db()
38+
self.assertEqual(order.status, Order.Status.PROCESSING)
39+
40+
def test_deconstruct(self):
41+
# Order needs to be created for the field to be deconstructed
42+
Order.objects.create()
43+
field = Order._meta.get_field('status')
44+
name, path, args, kwargs = field.deconstruct()
45+
self.assertEqual(name, 'status')
46+
self.assertEqual(path, 'dsm.fields.StateMachineField')
47+
self.assertEqual(len(args), 1)
48+
self.assertEqual(kwargs['max_length'], 16)
49+
self.assertEqual(kwargs['default'], 'new')
50+
self.assertEqual(kwargs['choices'], Order.Status.choices)
51+
52+
def test_choices(self):
53+
# Order needs to be created for the field to have choices
54+
Order.objects.create()
55+
field = Order._meta.get_field('status')
56+
self.assertEqual(field.choices, Order.Status.choices)
57+

0 commit comments

Comments
 (0)