Search code examples
pythondjangodjango-contenttypes

Autopopulate a set of generic many to many fields in Django?


I'm trying to combine this answer and this one, with a bit of for looping.

On creating a character, I want to add all possible skills with a value of 0 but I'm getting confused on how to follow the above answers.

I have this mixin:

class CrossCharacterMixin(models.Model):
    cross_character_types = models.Q(app_label='mage', model='mage')
    content_type = models.ForeignKey(ContentType, limit_choices_to=cross_character_types,
                                     null=True, blank=True)
    object_id = models.PositiveIntegerField(null=True)
    content_object = GenericForeignKey('content_type', 'object_id')

    class Meta:
        abstract = True

(eventually, the cross_character_types will be expanded)

And this model:

class CharacterSkillLink(Trait, CrossCharacterMixin):
    PRIORITY_CHOICES = (
        (1, 'Primary'), (2, 'Secondary'), (3, 'Tertiary')
    )
    skill = models.ForeignKey('SkillAbility')
    priority = models.PositiveSmallIntegerField(
        choices=PRIORITY_CHOICES, default=None)
    speciality = models.CharField(max_length=200, null=True, blank=True)

    def __str__(self):
        spec_string = " (" + self.speciality + ")" if self.speciality else ""
        return self.skill.skill.label + spec_string

What I've started writing is this, on the NWODCharacter model:

def save(self, *args, **kwargs):
    if not self.pk:
        character_skills_through = CharacterSkillLink.content_object.model

        CharacterSkillLink.objects.bulk_create([
            [character_skills_through(skill=SkillAbility(
                skill), content_object=self) for skill in SkillAbility.Skills]
        ])

    super(NWODCharacter, self).save(*args, **kwargs)

This doesn't work as I don't think I'm passing in the right objects.

Based on this answer though:

from django.db import models

class Users(models.Model):
    pass

class Sample(models.Model):
    users = models.ManyToManyField(Users)

Users().save()
Users().save()

# Access the through model directly
ThroughModel = Sample.users.through

users = Users.objects.filter(pk__in=[1,2])

sample_object = Sample()
sample_object.save()

ThroughModel.objects.bulk_create([
    ThroughModel(users_id=users[0].pk, sample_id=sample_object.pk),
    ThroughModel(users_id=users[1].pk, sample_id=sample_object.pk)
])

In this situation, what is my ThroughModel? Is it CharacterSkillLink.content_object.model ?

How do I do this in my scenario? I'm sorry if this is trivial, but I'm struggling to get my head round it.


Solution

  • It looks to me like CharacterSkillLink itself is your through model in this case... it generically joins a content type to a SkillAbility

    If you think about it, it also makes sense that if you're doing a bulk_create the objects that you pass in must be of the same model you're doing a bulk_create on.

    So I think you want something like this:

    def save(self, *args, **kwargs):
        initialise_skill_links = not self.pk
        super(NWODCharacter, self).save(*args, **kwargs)
        if initialise_skill_links:
            CharacterSkillLink.objects.bulk_create([
                CharacterSkillLink(
                    skill=SkillAbility.objects.get_or_create(skill=skill)[0],
                    content_object=self
                )
                for skill in SkillAbility.Skills
            ])
    

    Note you had too many pairs of [] inside your bulk_create.

    Also I think you should use SkillAbility.objects.get_or_create()... for a foreign key you need the related object to exist. Just doing SkillAbility() won't fetch it from the db if it already exists and won't save it to the db if it doesn't.