1

Having a bit of trouble trying to bulk add a list of items to a many to many field and though having tried various things have no clue on how to approach this. I've looked at the Django documentation and cant seem to find what I'm looking for.

Here is the code for my models:

class Subject(models.Model):
    noun = models.CharField(max_length=30, null=True, blank=True)

class Knowledge(models.Model):
    item_text = models.TextField()
    item_subjects =  models.ManyToManyField(Subject, null=True, blank=True)

def add_subjects(sender, instance, *args, **kwargs):

    if instance.item_info:
         item_subjects = classifier.predict_subjects(instance.item_info)

         if item_subjects:
             ....

post_save.connect(add_subjects, sender=Knowledge)

The list is being generated by the classifer.predict_subjects function. I have tried using the m2m_changed connector and the pre_save and post_save connect. I'm not even sure the many to many field is the right option would it be better to do make a foreign key relationship. in place of the '...' I have tried this but it doesn't create the relationship between and only saves the last one.

for sub in item_subjects:
    subject = Subject(id=instance.id, noun=sub)
    subject.save()

I've also tried

instance.item_subjects = item_subjects

and a load more things that I can't really remember, I don't really think I'm in the right ballpark to be honest. Any suggestions?

edit:

ok, so I have got it adding all of the list items but still haven't managed to link these items to the many to many field.

        for sub in item_subjects:
            subject = Subject.objects.get_or_create(noun=sub)

edit 2:

So doing pretty much exactly the same thing outside of the loop in the Django shell seems to be working and saves the entry but it doesn't inside the function.

>>> k[0].item_subjects.all()
<QuerySet []>
>>> d, b = Subject.objects.get_or_create(noun="cats")
<Subject: cats>
>>> k[0].item_subjects.add(d)
>>> k[0].item_subjects.all()
<QuerySet [<Subject: cats>]>

edit 3

So I took what Robert suggested and it works in the shell just like above just not when using it in the admin interface. The print statements in my code show the array item being updated but it just dosen't persist. I read around and this seems to be a problem to do with the admin form clearing items before saving.

def sub_related_changed(sender, instance, *args, **kwargs):

    print instance.item_subjects.all()

    if instance.item_info:
        item_subjects = classifier.predict_subjects(instance.item_info)

        if item_subjects:
            for sub in item_subjects:
                subject, created = Subject.objects.get_or_create(noun=sub)
                instance.item_subjects.add(subject)

        print instance.item_subjects.all()


post_save.connect(sub_related_changed, sender=Knowledge)

I have tried using the function as m2m_changed signal as follows:

m2m_changed.connect(model_saved, sender=Knowledge.item_subjects.through)

But this either generates a recursive loop or doesn't fire.

simsosims
  • 285
  • 5
  • 13

1 Answers1

0

Once you have the subject objects (as you have in your edit), you can add them with

for sub in item_subjects:
    subject, created = Subject.objects.get_or_create(noun=sub)
    instance.item_subjects.add(subject)

The "item_subjects" attribute is a way of managing the related items. The through relationships are created via the "add" method.

Once you've done this, you can do things like instance.item_subjects.filter(noun='foo') or instance.item_subjects.all().delete() and so on

Documentation Reference: https://docs.djangoproject.com/en/1.11/topics/db/examples/many_to_many/

EDIT Ahh I didn't realize that this was taking place in the Django Admin. I think you're right that that's the issue. Upon save, the admin calls two methods: The first is model_save() which calls the model's save() method (where I assume this code lives). The second method it calls is "save_related" which first clears out ManyToMany relationships and then saves them based on the submitted form data. In your case, there is no valid form data because you're creating the objeccts on save.

If you put the relevant parts of this code into the save_related() method of the admin, the changes should persist.

I can be more specific about where it should go if you'll post both your < app >/models.py and your < app >/admin.py files.

Reference from another SO question: Issue with ManyToMany Relationships not updating inmediatly after save

Community
  • 1
  • 1
Robert Townley
  • 3,414
  • 3
  • 28
  • 54
  • thanks for your help, its still not working for some reason. trying pretty much the same thing in the django shell works though. eg `>>> k[0].item_subjects.all() >>> d, b = Subject.objects.get_or_create(noun="cats") >>> d >>> k[0].item_subjects.add(d) >>> k[0].item_subjects.all() ]>` is there something else i have to do here. – simsosims Apr 19 '17 at 21:44
  • Glad it's working in the shell. Mind editing to show the new code that you're using outside of the shell (the code that still isn't working?) now that you've added the .add() method in? – Robert Townley Apr 20 '17 at 13:03
  • Ahh I didn't realize this was taking place in the admin. I think you could be right about the admin overwrite issue. Just did another update to my answer. – Robert Townley Apr 23 '17 at 08:07