1

What I am trying to do?

I have created a fixture for a through model and now I want to load it in my database.

What is the problem?

While loading the fixture using Django loaddata command for through model I get this error:

django.core.serializers.base.DeserializationError: Problem installing fixture 
'm.json': ['“Dave Johnson” value must be an integer.']:(room.membership:pk=None) 
field_value was 'Dave Johnson'

models.py

class Person(models.Model):
    name = models.CharField(max_length=100, unique=True)

    def natural_key(self):
       return self.name

class Group(models.Model):
    name = models.CharField(max_length=100, unique=True)
    members = models.ManyToManyField(Person, through='Membership')

    def natural_key(self):
       return self.name

class Membership(models.Model):
    person = models.ForeignKey(Person, on_delete=models.CASCADE)
    group = models.ForeignKey(Group, on_delete=models.CASCADE)
    joined_on = models.DateTimeField(auto_now_add=True)

    objects = MembershipManager()

    def natural_key(self):
       return self.person.name, self.group.name

I am creating fixture like this:

python manage.py dumpdata room.Membership 
--natural-foreign --natural-primary > m.json

which creates the following json:

[
{
    "model": "room.membership",
    "fields": {
        "person": "Dave Johnson",
        "group": "Django Learning",
        "joined_on": "2020-12-03T13:14:28.572Z"
    }
}
]

I have also added get_by_natural_key method in the manager for through model like this:

class MembershipManager(models.Manager):
   def get_by_natural_key(self, person_name, group_name):
      return self.get(person__name=person_name, group__name=group_name)

And loading the fixture

python manage.py loaddata m.json

Other models are working fine. I can load them without any issue it is only the through model which is not working.

Ahtisham
  • 9,170
  • 4
  • 43
  • 57

2 Answers2

0

You are creating a model, not a manager. You should also ensure that the combination of fields is unique:

class MembershipManager(models.Manager):
    def get_by_natural_key(self, person_name, group_name):
        return self.get(person__name=person_name, group__name=group_name)


class Membership(models.Model):
    person = models.ForeignKey(Person, on_delete=models.CASCADE)
    group = models.ForeignKey(Group, on_delete=models.CASCADE)
    joined_on = models.DateTimeField(auto_now_add=True)
    objects = MembershipManager()

    class Meta:
        constraints = [
            models.UniqueConstraint(
                fields=['person', 'contact'], name='unique_person_group'
            )
        ]

    def natural_key(self):
        return self.person.name, self.group.name

For the Person and Group models, you will need managers as well:

class NameManager(models.Manager):
    def get_by_natural_key(self, name):
        return self.get(name=name)


class Person(models.Model):
    name = models.CharField(max_length=100, unique=True)
    objects = NameManager()

    def natural_key(self):
        return self.name


class Group(models.Model):
    name = models.CharField(max_length=100, unique=True)
    members = models.ManyToManyField(Person, through='Membership')
    objects = NameManager()

    def natural_key(self):
        return self.name
Willem Van Onsem
  • 443,496
  • 30
  • 428
  • 555
  • I have already added the manager in the model maybe I forgot to add it in the question let me add it – Ahtisham Feb 04 '23 at 19:43
  • @Ahtisham: but you inherited from `models.Model`, not `models.Manager` in your code, hence `objects` is *not* a manager... – Willem Van Onsem Feb 04 '23 at 19:44
  • No it is Manager not Model again a typo here. – Ahtisham Feb 04 '23 at 19:50
  • @Ahtisham: you did specify managers for the `Person` and `Group` ? – Willem Van Onsem Feb 04 '23 at 19:59
  • No they worked without manager. I just included the `natural_key` method – Ahtisham Feb 04 '23 at 20:05
  • @Ahtisham: no, they did not: the manager is used to *fetch* an item referred to, so in this case, in order to construct a `Membership`, it will need to fetch a `Person`/`Group` by name (its natural key), so it will try to use the `get_by_natural_key` from the manager of the `Person`/`Group` and that is what fails, the manager is thus not used to *construct* a person/group/manager, but to fetch the "ingredients" to make such record. – Willem Van Onsem Feb 04 '23 at 20:06
  • Okay I tried adding managers for both `Person` as well as `Group`. Also I added the grouped unique constriant on `Membership` but I still get same error. – Ahtisham Feb 04 '23 at 21:14
0

After drilling down more on django source code about loaddata command I found out that it uses django deserialization internally. So I thought to read about serialization and deserialization which has this section in the docs that talks about dependencies during serialization.

So based on that following changes fixed the issue:

Solution:

Update the through model natural_key method and add natural_key dependencies:

def natural_key(self):
    return self.person.natural_key(), self.group.natural_key()

natural_key.dependencies = ['room.Person', 'room.Group']

I also updated the natural_key of both Person and Group models to return tuple rather than single element i.e return self.name, . Moreover added managers for both the models.

Ahtisham
  • 9,170
  • 4
  • 43
  • 57