schrodingerscatcuriosity
schrodingerscatcuriosity

Reputation: 1860

Django save related ManyToMany model

I have two models with ManyToMany relationship as this:

class Subject(models.Model):
    teachers = models.ManyToManyField(
        Teacher
    )
    subject = models.CharField(
       'Materia',
       max_length = 50,
       unique = True
   )
   level = models.CharField(
       'Nivel',
       max_length = 3,
       choices = LEVEL_CHOICES
   )

class Teacher(Person):
    # fields definition

Following the docs I know I can save Subject model without saving Teacher field. That's fine.

Now I want to add a Subject to Teacher, so I need a form with selected teacher, and a choice field with all the subjects.

I found this question that relates to what I want to accomplish, but I think it doesn't address what I'm looking for.

First I populate the subject choice filed, as shown in the ModelForm code below.

forms.py

class SubjectTeacherForm(ModelForm):
    subject = forms.ChoiceField(choices=[(m.id, m.subject) for m in Subject.objects.all()])

    class Meta:
        model = Subject
        fields = ['teachers', 'subject']

urls.py

#here I send the teacher id to the view
url(r'^materias/asignar/profesor/(?P<teacher_id>\d+)/$',MateriaTeacherCreateView.as_view(), name='materia-create-teacher'),

views.py

class SubjectTeacherCreateView(SuccessMessageMixin, CreateView):
    model = Subject
    template_name = 'edu/subject/subject_create.html'
    form_class = SubjectTeacherForm

    def form_valid(self, form):
        # here I want to add the relation
        teacher = Teacher.objects.get(pk=self.kwargs['teacher_id'])
        # I understad that object here is the ModelForm, not the Model, so this is wrong
        self.object.teachers.add(teacher)
        return redirect(self.get_success_url())

    def get_context_data(self, **kwargs):
        context = super(SubjectTeacherCreateView, self).get_context_data(**kwargs)
        # here I define the selected teacher to pass to the template
        context['teacher'] = Teacher.objects.get(pk=self.kwargs['teacher_id'])
        return context

the template

<form method="post" class="form-horizontal">
    {% csrf_token %}
    {{ form.teacher }}
    {{ form.subject }}

As expected, this is not working, the form doesn't save, it throws the error:

AttributeError at /edu/materias/asignar/profesor/11/
'NoneType' object has no attribute 'teachers'

So, obviously I'm doing something wrong.

My question is if I can use class based views to accomplish this, or I should write a method to save the relationship, and what should I change in my code.

I expect I made myself clear enough, thanks.

Upvotes: 1

Views: 1248

Answers (2)

Ykh
Ykh

Reputation: 7717

you need call super(SubjectTeacherCreateView, self).form_valid(form) in your form_valid then you can get self.object,this is source form_vaild method before you override:

def form_valid(self, form):
    """
    If the form is valid, save the associated model.
    """
    self.object = form.save()
    return super(ModelFormMixin, self).form_valid(form)

but as your condition ,this is the right way:

def form_valid(self, form):
    try:
        subject = Subject.object.get(id=form.cleaned_data['subject'])
        teacher = Teacher.objects.get(pk=self.kwargs['teacher_id'])
        subject.teachers.add(teacher)
    except Subject.DoesNotExist:
        # handle it youself
    return redirect(self.get_success_url()

Upvotes: 3

binpy
binpy

Reputation: 4194

I think you need a specific get_object() to find the object of subject from the teacher_id.

def get_object(self):
    teacher_id = self.kwargs['teacher_id']
    subject = get_object_or_404(self.model, teachers__id=teacher_id)
    return subject

and also, if self.object.teachers.add(teacher) doesn't work, try to assign as a list self.object.teachers.add([teacher]).

Upvotes: 0

Related Questions