Is there a known issue when using fixtures in tests if you are implementing Django DirtyFields?
We use django_nose
's NoseTestSuiteRunner
with fixtures and coverage. I was going to try implementing django-dirtyfields
to do some custom post_save()
activities. The only change I made was the following:
from dirtyfields import DirtyFieldsMixin
class BaseModel(DirtyFieldsMixin, models.Model):
"""
Base model
"""
class Meta:
abstract = True
We use this BaseModel
to extend most of our other models. Adding the DirtyFieldsMixin
causes our tests to die with the following repeated error:
Problem installing fixture '/home/ricomoss/workspace/project/settings/../apps/contracts/fixtures/test_contracts_data.json': Traceback (most recent call last):
File "/home/ricomoss/.virtualenvs/project/local/lib/python2.7/site-packages/django/core/management/commands/loaddata.py", line 193, in handle
for obj in objects:
File "/home/ricomoss/.virtualenvs/project/local/lib/python2.7/site-packages/django/core/serializers/json.py", line 47, in Deserializer
raise DeserializationError(e)
DeserializationError: Field matching query does not exist.
From what I can tell this error occurs whenever a fixture is trying to load. Any thoughts? DirtyFieldsMixin
does not include any new fields (let alone required ones) that would cause my fixtures to need to be updated.
EDIT 1:
It appears my problem is directly related to the fixtures/signal problem in Django. If we look at the DirtyFieldsMixin
definition we can see immediately where the problem lies (I've verified this):
class DirtyFieldsMixin(object):
def __init__(self, *args, **kwargs):
super(DirtyFieldsMixin, self).__init__(*args, **kwargs)
post_save.connect(
self._reset_state, sender=self.__class__,
dispatch_uid='{}-DirtyFieldsMixin-sweeper'.format(
self.__class__.__name__))
self._reset_state()
def _reset_state(self, *args, **kwargs):
self._original_state = self._as_dict()
def _as_dict(self):
return dict([(f.name, getattr(self, f.name)) for f in
self._meta.local_fields if not f.rel])
def get_dirty_fields(self):
new_state = self._as_dict()
return dict([(key, value) for key, value in
self._original_state.iteritems()
if value != new_state[key]])
def is_dirty(self):
# in order to be dirty we need to have been saved at least once, so we
# check for a primary key and we need our dirty fields to not be empty
if not self.pk:
return True
return {} != self.get_dirty_fields()
Specifically,
post_save.connect(self._reset_state, sender=self.__class__,
dispatch_uid='{}-DirtyFieldsMixin-sweeper'.format(
self.__class__.__name__))
I tried creating a decorator as per a solution given. I am either implementing the decorator wrong or it doesn't work for this situation. I'm a little confused about it's use in this situation.
Would I put the decorator on the __init__
:
@utils.disable_for_loaddata
def __init__(self, *args, **kwargs):
super(DirtyFieldsMixin, self).__init__(*args, **kwargs)
post_save.connect(
self._reset_state, sender=self.__class__,
dispatch_uid='{}-DirtyFieldsMixin-sweeper'.format(
self.__class__.__name__))
self._reset_state()
I still get a failure in this case. Any suggestions?
EDIT 2:
I've also tried adding the decorator to the _reset_state
method. This does not work either.
@utils.disable_for_loaddata
def _reset_state(self, *args, **kwargs):
self._original_state = self._as_dict()
EDIT 3:
I think I'm making progress. I moved the inspect.stack()
portion of the decorator into the __init__
directly (just above the post_save
call).
I'm getting other activity, although my test suite still hasn't run correctly yet. I'm no longer getting the loaddata error.
def __init__(self, *args, **kwargs):
super(DirtyFieldsMixin, self).__init__(*args, **kwargs)
import inspect
for fr in inspect.stack():
if inspect.getmodulename(fr[1]) == 'loaddata':
return
post_save.connect(
self._reset_state, sender=self.__class__,
dispatch_uid='{}-DirtyFieldsMixin-sweeper'.format(
self.__class__.__name__))
self._reset_state()
EDIT 4:
I've decided that I'm going to write my own mixin for this process. I don't believe I need to use a signal for my purposes. As a result I will be overriding the save()
method and handling this in a special way.
I was able to implement a hack that I don't like. If someone comes up with a better solution I'd love to see it.
Basically I did what the suggestion solution's decorator does but look on the stack for any testcases.
class DirtyFieldsMixin(object):
def __init__(self, *args, **kwargs):
super(DirtyFieldsMixin, self).__init__(*args, **kwargs)
# Hack code used to avoid unittest failures due to the following
# post_save.connect() call.
import inspect
for fr in inspect.stack():
if inspect.getmodulename(fr[1]) == 'testcases':
return
post_save.connect(
self._reset_state, sender=self.__class__,
dispatch_uid='{}-DirtyFieldsMixin-sweeper'.format(
self.__class__.__name__))
self._reset_state()