Question

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.

Was it helpful?

Solution

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()

OTHER TIPS

Reversing the inheritance should get you up and running without changes.

class BaseModel(models.Model, DirtyFieldsMixin):
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top