Autopopulate a set of generic many to many fields in Django?
Asked Answered
I

1

1

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.

Inheritable answered 10/2, 2015 at 16:49 Comment(1)
Could the downvoter let me know how to improve the question?Inheritable
N
1

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.

Norikonorina answered 10/2, 2015 at 17:38 Comment(10)
Literally just left work, and heading home. Will try to use this tomorrow. You're right btw, CharacterSkillLink is my through model...I don't know what I was thinking.Inheritable
This works fine on my admin page, but it wasn't displaying any skills in the drop down box (separate issue) for that inline. I removed the inlines to see if it'd create and save the skills regardless, but now it's giving me two errors. The first is at the top of the error page: ValueError at /admin/characters/mage/add/ and Cannot assign "(<SkillAbility: Academics>, False)": "CharacterSkillLink.skill" must be a "SkillAbility" instance. and then, Error in formatting: 'CharacterSkillLink' object has no attribute 'speciality' setting speciality = "" and specifying has no effect. Help?Inheritable
I found the issue get_or_create returns a tuple, and skill is assigned this tutple, not a SkillAbility object. This seems like it should work (it doesn't error) but I don't think it is working. I've added a GenericRelation to it and when I save, and try to access mage_instance.skills.all() it returns and empty list.Inheritable
I've updated the answer with the edit you wanted to make, also added [0] to deal with the tuple issueNorikonorina
I'm just testing this works now...I don't think it does but when I'm looking at the db in a db viewer the tables for CharacterSkillLink aren't being updated properly, the object_id is blank. Not sure if that is expected/normal?Inheritable
yes the problem is there is no object_id able to be set yet, because we are executing this code in a if not self.pk block... will update the answerNorikonorina
Well...yeah that seems obvious now you say it.Inheritable
Thanks that looks good, but what if I have a subclass that also calls save? Will that conflict? I'm begining to see how this is working now. If need be we could continue in chatInheritable
when you call super in the overridden save method of the subclass it will run the code above, so the mechanism will work fine still, you just have to decide in the subclass which things you want to do before the super call and which things afterNorikonorina
I have a similar 'initialise' specific to the subclass. I'm trying to figure out whether or not it would conflict. Thanks for all your efforts though :)Inheritable

© 2022 - 2024 — McMap. All rights reserved.