Skip to content

Commit f9ebbb4

Browse files
committed
Merge Add Mixin that allows refresh_from_db. Clse #246
1 parent 7a4cbf9 commit f9ebbb4

File tree

3 files changed

+72
-2
lines changed

3 files changed

+72
-2
lines changed

django_fsm/__init__.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,32 @@ def set_state(self, instance, state):
471471
instance.__dict__[self.attname] = self.to_python(state)
472472

473473

474+
class FSMModelMixin(object):
475+
"""
476+
Mixin that allows refresh_from_db for models with fsm protected fields
477+
"""
478+
479+
def _get_protected_fsm_fields(self):
480+
def is_fsm_and_protected(f):
481+
return isinstance(f, FSMFieldMixin) and f.protected
482+
protected_fields = filter(is_fsm_and_protected, self._meta.concrete_fields)
483+
return {f.attname for f in protected_fields}
484+
485+
def refresh_from_db(self, *args, **kwargs):
486+
fields = kwargs.pop("fields", None)
487+
488+
# Use provided fields, if not set then reload all non-deferred fields.0
489+
if not fields:
490+
deferred_fields = self.get_deferred_fields()
491+
protected_fields = self._get_protected_fsm_fields()
492+
skipped_fields = deferred_fields.union(protected_fields)
493+
494+
fields = [f.attname for f in self._meta.concrete_fields
495+
if f.attname not in skipped_fields]
496+
497+
kwargs['fields'] = fields
498+
super(FSMModelMixin, self).refresh_from_db(*args, **kwargs)
499+
474500
class ConcurrentTransitionMixin(object):
475501
"""
476502
Protects a Model from undesirable effects caused by concurrently executed transitions,
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import unittest
2+
3+
import django
4+
from django.db import models
5+
from django.test import TestCase
6+
7+
from django_fsm import FSMField, FSMModelMixin, transition
8+
9+
10+
class RefreshableProtectedAccessModel(models.Model):
11+
status = FSMField(default='new', protected=True)
12+
13+
@transition(field=status, source='new', target='published')
14+
def publish(self):
15+
pass
16+
17+
class Meta:
18+
app_label = 'django_fsm'
19+
20+
21+
class RefreshableModel(FSMModelMixin, RefreshableProtectedAccessModel):
22+
pass
23+
24+
25+
class TestDirectAccessModels(TestCase):
26+
def test_no_direct_access(self):
27+
instance = RefreshableProtectedAccessModel()
28+
self.assertEqual(instance.status, 'new')
29+
30+
def try_change():
31+
instance.status = 'change'
32+
33+
self.assertRaises(AttributeError, try_change)
34+
35+
instance.publish()
36+
instance.save()
37+
self.assertEqual(instance.status, 'published')
38+
39+
@unittest.skipIf(django.VERSION < (1, 8), "Django introduced refresh_from_db in 1.8")
40+
def test_refresh_from_db(self):
41+
instance = RefreshableModel()
42+
instance.save()
43+
44+
instance.refresh_from_db()

tox.ini

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ envlist =
55
# py33-dj{16,18}
66
py{34,35,36}-dj{18,19,110,111}
77
py{36,37}-dj{20,21}
8-
py{37,38,39}-dj{22,30,31,32}
9-
py{38,39,310}-dj{40,41}
8+
py{37,38}-dj{22,30,31,32}
9+
py{38,310}-dj{40,41}
1010
skipsdist = True
1111

1212
[testenv]

0 commit comments

Comments
 (0)