UpdateView for ManyToMany relation with through model

Hi
I have a many to many relation between two models (Reference and Author) that works through a third model (WrittenBy) because I need to store the order of authors in the publication.
I managed to create a form for new references where users can select the name of the author or create a new one using Select2. This is the code for the view and forms:

class AuthorWidget(s2forms.ModelSelect2TagWidget):
    search_fields = [
        'name__icontains'
    ]

class AuthorForm(forms.ModelForm):
    author = forms.ModelChoiceField(queryset=Author.objects.all(),widget=AuthorWidget(attrs={'class':'select-widget','data-placeholder':'Search author',"data-token-separators": '[";"]','data-placeholder':'To create new author, just type name and finish with semicolon ;'}),label='Author')
    class Meta:
        model = WrittenBy
        fields = ['author']

AuthorsFormSet = formset_factory(AuthorForm, extra=1)

class ReferenceCreateView(CreateView):
    model = Reference
    form_class = ReferenceForm
    template_name = 'form_reference.html'

    def form_valid(self, form):
        ref = form.instance
        ref.save()
        # Habrá un subformulario por cada autor:
        num_authors = form.data['form-TOTAL_FORMS']
        i = 0
        # Iteramos por los subformularios para insertar los autores en la clase intermedia WrittenBy:
        while i < int(num_authors):
            # Si existe la clave de autor en el dict (si he han dado al + pero no han metido datos está contabilizado el formulario pero no hay clave parar el registro vacío)
            if 'form-'+str(i)+'-author' in form.data:
                auth_pk = form.data['form-'+str(i)+'-author']
                written = WrittenBy()
                written.reference = ref
                try:
                    # Si el autor existe lo cogemos
                    ref_author = Author.objects.get(pk=auth_pk)
                except:
                    # Si no existe lo creamos ahora:
                    ref_author = Author()
                    ref_author.name = form.data['form-'+str(i)+'-author']
                    ref_author.save()
                written.author = ref_author
                # Como orden de autor insertamos el número de iteración (obviando el 0)
                written.order = i+1
                written.save()
            i = i+1
        return super().form_valid(form)

    # Send parent template to context
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        # include authors formset
        context['formset'] = AuthorsFormSet()
        return context

The problem is that when I try to create an UpdateView I do not see how to render an AuthorForm for each existing author. I suppose there is a ‘standard’ way to do this but I can’t find it in the documentation.

What are you creating an UpdateView for? (What model) Just for an Author?

You kind of imply that you might want a page with multiple forms on it - perhaps all the Author for a particular work. Django provides a facility for creating multiple forms for a model called a Formset.

Also, keep in mind that the system-provided generic Class-Based Views (GCBV) are designed around the principle of working with single instances of a single model. If you’re trying to build a view that is going to display multiple forms for different models, you’re going to be better off creating your own view. (You could build it inheriting from View if you want to go that route.)

I want to create an UpdateView for Reference, just like the CreateView I posted, but I want it to display one form for each author. I managed to display one form for each author in the template but they are not populated (and, for some reason, select2 does not work).
I am using formset_factory to provide the reference form with multiple authors.
This is what I’m trying:

class ReferenceUpdateView(UpdateView):
    model = Reference
    form_class = ReferenceForm
    template_name = 'form_reference.html'

        def get_initial(self):
        initial = super().get_initial()
        ref = self.get_object()
        i = 0
        for author in ref.autores.all():
            initial['form-'+str(i)+'-author'] = author
            i = i+1
        return initial

    def form_valid(self, form):
        ref = form.instance
        # First remove all authors, we will add them later
        ref.autores.set([])
        ref.save()
        # Habrá un subformulario por cada autor:
        num_authors = form.data['form-TOTAL_FORMS']
        i = 0
        # Iteramos por los subformularios para insertar los autores en la clase intermedia WrittenBy:
        while i < int(num_authors):
            # Si existe la clave de autor en el dict (si he han dado al + pero no han metido datos está contabilizado el formulario pero no hay clave parar el registro vacío)
            if 'form-'+str(i)+'-author' in form.data:
                auth_pk = form.data['form-'+str(i)+'-author']
                written = WrittenBy()
                written.reference = ref
                try:
                    # Si el autor existe lo cogemos
                    ref_author = Author.objects.get(pk=auth_pk)
                except:
                    # Si no existe lo creamos ahora:
                    ref_author = Author()
                    ref_author.name = form.data['form-'+str(i)+'-author']
                    ref_author.save()
                written.author = ref_author
                # Como orden de autor insertamos el número de iteración (obviando el 0)
                written.order = i+1
                written.save()
            i = i+1
        return super().form_valid(form)


    # Send parent template to context
    def get_context_data(self, **kwargs):
        # include authors formset
        context['formset'] = AuthorsFormSet()
        return context

And in the template:

{% for form in formset %}
  {% for author in reference.autores.all %}
          {% for field in form.visible_fields %}
          <tr>
            <th>{{ field.label_tag }}</th>
            <td>
              {{ field }}
            </td>
          </tr>
          {% endfor %}
        {% endfor %}
{% endfor %}

This way I end up with a form for each author but I don’t know how to populate it with the existing author. Another problem is that each form has index 0 (id=“id_form-0-author”)…

Again:

UpdateView is the wrong tool for this. You want to use a formset, and build this from a View - not UpdateView.
Or - even easier - just make this a function-based view.

OK, thank you. I read it too fast the first time, sorry. I will avoid Class-Based Views.

For clarity - building this from View would be fine. Where you are going to find it awkward would be in trying to do this in any of CreateView, UpdateView, or DetailView. (Yes, it can be done using them, but my experience has been that it’s more hassle than it’s worth.)

I just managed to get it going. The use of UpdateView was OK, because it is an UpdateView for Reference, not for Author. I just needed to provide the initial values for the formset:

def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        # include authors formset
        ref = self.get_object()
        authors_list = []
        for author in ref.autores.all():
            aut = {'author':author}
            authors_list.append(aut)
        context['formset'] = AuthorsFormSet(
            initial= authors_list
        )
        return context

Thanks for your quick support!!