1

I'm using a Django database model to store objects corresponding to a remote ticket service. In testing this model, I'm running several tests to make sure that log messages work correctly for the database model - I'm using Django-nose to run these tests.

The database model looks something like this, using the JSONField from this StackOverflow answer with a small modification to support lists as well as dicts:

class TicketEntity(django.db.models.Model)
    tickets = JSONField(null=True, blank=True, default=[], serialize=True)
    queued_ticket_data = JSONField(null=True, blank=True, default=[], serialize=True)
    ...
    @classmethod
    def create(cls, *args, **kwargs):
        instance = cls(*args, **kwargs)
        instance.save()
        return instance

    def queue_output_for_ticket(self, log_data):
        self.queued_ticket_data += [log_data]
        self.save()

    def push_ticket(self):
        self.tickets += [self.queued_ticket_data]
        self.queued_ticket_data = []
        self.save()
        return True

The tests (in the order they appear to get run) look like this:

def test_ticket_entity_push_ticket(self):
    entity = TicketEntity.create()
    entity.queue_output_for_ticket("log output")
    entity.queue_output_for_ticket("more log output")
    entity.push_ticket()
    self.assertEquals(entity.tickets, [[log_data, log_data_1]])
    self.assertFalse(entity.queued_ticket_data)

 def test_ticket_entity_queue_output_for_ticket(self):
    entity = TicketEntity.create()
    log_data = "Here's some output to log.\nHere's another line of output.\nAnd another."
    entity.queue_output_for_ticket(log_data)
    self.assertEquals(entity.queued_ticket_data, [log_data])

The first test passes, perfectly fine. The second test fails on that assert statement, because the entity.queued_ticket_data looks like this:

["log output", 
 "more log output",
 "Here's some output to log.\nHere's another line of output.\nAnd another."]

Those first two elements are there at the very start of the test, immediately after we call TicketEntity.create(). They shouldn't be there - the new instance should be clean because we're creating it from scratch.

Similarly, tickets is pre-filled as well. The TicketEntity has other fields that are not JSONFields, and they do not seem to exhibit this problem.

Can someone shed some light on why this problem might be happening, and what kind of modification we'd need for a fix?

Green Cloak Guy
  • 23,793
  • 4
  • 33
  • 53
  • 1
    This is a variant of the [mutable default argument](https://stackoverflow.com/questions/1132941/least-astonishment-and-the-mutable-default-argument) gotcha, and is specifically mentioned in [the docs for JSONField](https://docs.djangoproject.com/en/2.1/ref/contrib/postgres/fields/#django.contrib.postgres.fields.JSONField). – Daniel Roseman Nov 15 '18 at 16:58
  • That seems to have been the problem, thanks! We didn't know that JSONField was already a thing, so we never thought to look in the documentation. – Green Cloak Guy Nov 15 '18 at 18:26
  • 1
    Possible duplicate of ["Least Astonishment" and the Mutable Default Argument](https://stackoverflow.com/questions/1132941/least-astonishment-and-the-mutable-default-argument) – Green Cloak Guy Nov 15 '18 at 18:27

0 Answers0