The issue is with the definition of the intermediary model.
Because altforms is a self-referencing relationship:
altforms = models.ManyToManyField("self", through="MonFormData")
Django expects MonFormData to contain two foreign keys to MonSpecies: one for the source species and one for the alternate species.
FormChangeItem can remain as an additional field on the relationship:
class MonSpecies(models.Model):
altforms = models.ManyToManyField(
"self",
through="MonFormData",
through_fields=("base_species", "alt_species"),
symmetrical=False,
related_name="base_forms",
)
class MonFormData(models.Model):
base_species = models.ForeignKey(
MonSpecies,
on_delete=models.CASCADE,
related_name="form_data_as_base",
)
alt_species = models.ForeignKey(
MonSpecies,
on_delete=models.CASCADE,
related_name="form_data_as_alt",
)
form_change_item = models.ForeignKey(
"OtherMonItemTags",
on_delete=models.CASCADE,
)
class OtherMonItemTags(models.Model):
name = models.CharField(max_length=50)
The through_fields argument explicitly tells Django which foreign key is the source and which is the target. Without it, Django uses the first foreign key to MonSpecies as the source and the second as the target.
Then the relationship can be created like this:
target = MonSpecies.objects.get(
name=da,
basepack=newpack,
)
currentmon.altforms.add(
target,
through_defaults={
"form_change_item": change_item,
},
)
Or directly through the intermediary model:
MonFormData.objects.create(
base_species=currentmon,
alt_species=target,
form_change_item=change_item,
)
change_item must be an instance of OtherMonItemTags.
If FormChangeItem is not actually needed as extra data, remove the through model and use a normal self-referencing many-to-many field:
altforms = models.ManyToManyField("self", symmetrical=False)
Then currentmon.altforms.add(target) will work directly.