Using ManyToManyField, not save

Hi Community

I have 3 models: Doctor, Specialty and DoctorSpecialty.

When I try to register a doctor and mark 1 or more specialties, and I save, it tells me that everything is fine. When reviewing the DoctorSpecialty table, it is empty. If it saves the doctor but does not save any specialty.

These are the models

Model PteSpecialty

class PteSpecialty(models.Model):
    """Modelo de lista de especialidades"""

    code = models.CharField(
        max_length=10,
        verbose_name="Codigo",
        help_text="Indica codigo de la especialidad",
    )
    name = models.CharField(
        max_length=200,
        verbose_name="Especialidad",
        help_text="Indica nombre de la especialidad",
    )
    date_created = models.DateTimeField(blank=True, null=True)
    user_created = models.CharField(max_length=50, blank=True, null=True)
    date_modified = models.DateTimeField(blank=True, null=True)
    user_modified = models.CharField(max_length=50, blank=True, null=True)

    def __str__(self):
        """__str__ returns <type 'str'>"""
        return f"{self.name}-({self.code})"

    def get_absolute_url(self):
        """view detail Specialty doctor"""
        return reverse("specialty-detail", args=[self.pk])

    def get_edit_absolute_url(self):
        """Link Edit data"""
        return reverse("specialty-update", args=[self.pk])

    def get_delete_absolute_url(self):
        """delete Specialty"""
        return reverse("specialty-delete", args=[self.pk])
    
    def save(self, *args, **kwargs):
        usuario = get_current_authenticated_user()
        
        if self.date_created is None or not self.date_created:
            self.date_created = timezone.now()
            self.user_created = usuario.username
        else:
            self.date_modified = timezone.now()
            self.user_modified = usuario.username

        super(PteSpecialty, self).save(*args, **kwargs)

    class Meta:
        """ Class Meta de PteSpecialty"""
        managed = False
        db_table = "pte_specialty"
        db_table_comment = "Define la especialidad del doctor "

** Model PteDoctor**

class PteDoctor(models.Model):
    """Modelo Doctor"""

    code = models.CharField(
        max_length=20, verbose_name="Código", help_text="Código doctor"
    )
    names = models.CharField(
        max_length=100,
        verbose_name="Nombres",
        help_text="Indica los nombres del doctor",
    )
    lastnames = models.CharField(
        max_length=100,
        verbose_name="Apellidos",
        help_text="Indica los apellidos del doctor",
    )
    collegiate_code = models.CharField(
        max_length=100,
        verbose_name="No. Colegiado",
        help_text="Indica número colegaido",
    )
    estado = models.BooleanField(
        verbose_name="Estado", help_text="Indica si esta activo o inactivo"
    )
    
    specialists = models.ManyToManyField(PteSpecialty)
    date_created = models.DateTimeField(blank=True, null=True)
    user_created = models.CharField(max_length=50, blank=True, null=True)
    date_modified = models.DateTimeField(blank=True, null=True)
    user_modified = models.CharField(max_length=50, blank=True, null=True)

    def __str__(self):
        """__str__ returns <type 'str'>"""
        return f"{self.names} {self.lastnames}-({self.code})"

    def get_absolute_url(self):
        """view detail Doctor"""
        return reverse("doctor-detail", args=[self.pk])

    def get_edit_absolute_url(self):
        """Link Edit data"""
        return reverse("doctor-update", args=[self.pk])

    def get_delete_absolute_url(self):
        """delete LovH"""
        return reverse("doctor-delete", args=[self.pk])
    
    def save(self, *args, **kwargs):
        usuario = get_current_authenticated_user()
        
        if self.date_created is None or not self.date_created:
            self.date_created = timezone.now()
            self.user_created = usuario.username
        else:
            self.date_modified = timezone.now()
            self.user_modified = usuario.username

        super(PteDoctor, self).save(*args, **kwargs)

    class Meta:
        """ Class Meta de PteDoctor """
        managed = False
        db_table = "pte_doctor"
        db_table_comment = "Catalogo de doctores"

Table pte_doctor_specialists, Postgres

-- Table: public.pte_doctor_specialists

-- DROP TABLE IF EXISTS public.pte_doctor_specialists;

CREATE TABLE IF NOT EXISTS public.pte_doctor_specialists
(
    id bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY ( INCREMENT 1 START 1 MINVALUE 1 MAXVALUE 9223372036854775807 CACHE 1 ),
    ptedoctor_id bigint NOT NULL,
    ptespecialty_id bigint NOT NULL,
    CONSTRAINT pte_doctor_specialists_pkey PRIMARY KEY (id)
        USING INDEX TABLESPACE dbs_pacientes,
    CONSTRAINT pte_doctor_specialists_ptedoctor_id_ptespecialt_6710246f_uniq UNIQUE (ptedoctor_id, ptespecialty_id)
        USING INDEX TABLESPACE dbs_pacientes,
    CONSTRAINT fk_pte_doctor_id FOREIGN KEY (ptedoctor_id)
        REFERENCES public.pte_doctor (id) MATCH SIMPLE
        ON UPDATE NO ACTION
        ON DELETE NO ACTION
        DEFERRABLE INITIALLY DEFERRED,
    CONSTRAINT fk_pte_specialty_id FOREIGN KEY (ptespecialty_id)
        REFERENCES public.pte_specialty (id) MATCH SIMPLE
        ON UPDATE NO ACTION
        ON DELETE NO ACTION
        DEFERRABLE INITIALLY DEFERRED
)

TABLESPACE dbs_pacientes;

ALTER TABLE IF EXISTS public.pte_doctor_specialists
    OWNER to paciente;
-- Index: pte_doctor_specialists_ptedoctor_id_791d891d

-- DROP INDEX IF EXISTS public.pte_doctor_specialists_ptedoctor_id_791d891d;

CREATE INDEX IF NOT EXISTS pte_doctor_specialists_ptedoctor_id_791d891d
    ON public.pte_doctor_specialists USING btree
    (ptedoctor_id ASC NULLS LAST)
    TABLESPACE dbs_pacientes;
-- Index: pte_doctor_specialists_ptespecialty_id_f2f4a7ec

-- DROP INDEX IF EXISTS public.pte_doctor_specialists_ptespecialty_id_f2f4a7ec;

CREATE INDEX IF NOT EXISTS pte_doctor_specialists_ptespecialty_id_f2f4a7ec
    ON public.pte_doctor_specialists USING btree
    (ptespecialty_id ASC NULLS LAST)
    TABLESPACE dbs_pacientes;

This is the form code

class FormDoctor(forms.ModelForm):
    """Detail Lov"""

    code = myFunctions.UpperField(
        label="Codigo",
        required=True,
        max_length=10,
        widget=forms.TextInput(
            attrs={
                "placeholder": "Código doctor",
                "class": "text-uppercase",
                "maxlength": "10",
            }
        ),
        error_messages={"required": "Introduzca Código"},
    )

    names = myFunctions.UpperField(
        label="Nombres",
        required=True,
        max_length=100,
        widget=forms.TextInput(
            attrs={
                "placeholder": "Nombres doctor",
                "class": "text-uppercase",
                "maxlength": "100",
            }
        ),
        error_messages={"required": "Introduzca nombres"},
    )
    lastnames = myFunctions.UpperField(
        label="Apellidos",
        required=True,
        max_length=100,
        widget=forms.TextInput(
            attrs={"placeholder": "Apellidos doctor", "class": "text-uppercase"}
        ),
        error_messages={"required": "Introduzca apellidos"},
    )

    collegiate_code = myFunctions.UpperField(
        label="No. Colegiado",
        required=True,
        max_length=5,
        widget=forms.TextInput(
            attrs={
                "placeholder": "Número colegiado",
                "class": "text-uppercase",
                "maxlength": "5",
            }
        ),
        error_messages={"required": "Introduzca número colegiado"},
    )

    estado = forms.BooleanField(initial=True, required=False, label="Activar?")

    specialty = forms.ModelMultipleChoiceField(
        label = "Especialidades",
        queryset=PteSpecialty.objects.all(), 
        widget=forms.CheckboxSelectMultiple
    )
    class Meta:
        """Meta"""

        model = PteDoctor
        # fields = "__all__"
        fields = [
            "code",
            "names",
            "lastnames",
            "collegiate_code",
            "estado",
            "specialty",
        ]


This is the create/edit screen


The specialties come out ok, you can choose and when you save it says that everything is ok.

When reviewing the table it does not save any specialties, what could be the fault in my code?

We need to see the view being used for this.

Hi,
I share the View Add and Update code.

@method_decorator(login_required, name="dispatch")
class DoctorAdd(SuccessMessageMixin, CreateView):
    """Agrega registro de Doctores"""

    model = PteDoctor
    form_class = FormDoctor
    template_name = "doctor/add.html"
    success_message = "Doctor, creado correctamente !"

    def get_success_url(self):
        # Redireccionamos a la vista principal de LOVH
        return reverse_lazy("doctor-listing")

View Update

@method_decorator(login_required, name="dispatch")
class DoctorUpdate(SuccessMessageMixin, UpdateView):
    """Actualizar Doctor"""

    model = PteDoctor
    form_class = FormDoctor
    success_message = "Doctor actualizado correctamente !"

    # Redireccionamos a la página principal luego de actualizar un registro o postre
    def get_success_url(self):
        return reverse_lazy(
            "doctor-listing"
        )  # Redireccionamos a la vista principal 'leer'


I wonder if the Save method influences the PteDoctor model. Although I did a test, without the save method (Comment the code)


def save(self, *args, **kwargs):
        usuario = get_current_authenticated_user()
        
        if self.date_created is None or not self.date_created:
            self.date_created = timezone.now()
            self.user_created = usuario.username
        else:
            self.date_modified = timezone.now()
            self.user_modified = usuario.username

        super(PteDoctor, self).save(*args, **kwargs)

Thanks Luis

I think it’s a problem that requires separate save_m2m()…
Would you like to override the form_valid method and use it?

from django.views.generic.edit import FormMixin
... 
class DoctorUpdate:
    ...
    def form_valid(self, form):
        self.object = form.save(commit=False)
        self.object.save()
        form.save_m2m()
        return FormMixin.form_valid(self, form)
1 Like

Hi @white-seolpyo
The code didn’t help me, but I think you’re right, I’m going to investigate this, see what I can adjust when it’s Many-to-many…
If you see anything else I can correct, thank you.

I created the problem. I had an inconsistency between the name of the specialists model and the name of the form

So:
Model → PteDoctor → specialists

Form → FormDoctor → specialty

Solution, in Form correct the name

Form → FormDoctor → specialists

After so much review, I detected the error.
Always thanks for helping you!!

good. with form.save()? or form.save(commit=False)?

Neither of the two, I put the code view, forms and model the save method

solved code

View

@method_decorator(login_required, name="dispatch")
class DoctorUpdate(SuccessMessageMixin, UpdateView):
    """Actualizar Doctor"""

    model = PteDoctor
    form_class = FormDoctor
    success_message = "Doctor actualizado correctamente !"
    
    # Redireccionamos a la página principal luego de actualizar un registro o postre
    def get_success_url(self):
        self.success_message = "Doctor actualizado correctamente !" 
        return reverse_lazy(
            "doctor-listing"
        )  # Redireccionamos a la vista principal 'leer'

Forms

class FormDoctor(forms.ModelForm):
    """Detail Lov"""

    code = myFunctions.UpperField(
        label="Codigo",
        required=True,
        max_length=10,
        widget=forms.TextInput(
            attrs={
                "placeholder": "Código doctor",
                "class": "text-uppercase",
                "maxlength": "10",
            }
        ),
        error_messages={"required": "Introduzca Código"},
    )

    names = myFunctions.UpperField(
        label="Nombres",
        required=True,
        max_length=100,
        widget=forms.TextInput(
            attrs={
                "placeholder": "Nombres doctor",
                "class": "text-uppercase",
                "maxlength": "100",
            }
        ),
        error_messages={"required": "Introduzca nombres"},
    )
    lastnames = myFunctions.UpperField(
        label="Apellidos",
        required=True,
        max_length=100,
        widget=forms.TextInput(
            attrs={"placeholder": "Apellidos doctor", "class": "text-uppercase"}
        ),
        error_messages={"required": "Introduzca apellidos"},
    )

    collegiate_code = myFunctions.UpperField(
        label="No. Colegiado",
        required=True,
        max_length=5,
        widget=forms.TextInput(
            attrs={
                "placeholder": "Número colegiado",
                "class": "text-uppercase",
                "maxlength": "5",
            }
        ),
        error_messages={"required": "Introduzca número colegiado"},
    )

    estado = forms.BooleanField(initial=True, required=False, label="Activar?")

    specialists = forms.ModelMultipleChoiceField(
        label = "Especialidades",
        queryset=PteSpecialty.objects.all(),
        widget=forms.CheckboxSelectMultiple
    )
    # CustomSpecialtyChoice(
    #     label="Especialidades",
    #     queryset=PteSpecialty.objects.all(),
    #     widget=forms.CheckboxSelectMultiple,
    # )

    class Meta:
        """Meta"""

        model = PteDoctor
        # fields = "__all__"
        fields = [
            "code",
            "names",
            "lastnames",
            "collegiate_code",
            "estado",
            "specialists",
        ]

Model → class PteDoctor(models.Model): method save

def save(self, *args, **kwargs):
        usuario = get_current_authenticated_user()

        if self.date_created is None or not self.date_created:
            self.date_created = timezone.now()
            self.user_created = usuario.username
        else:
            self.date_modified = timezone.now()
            self.user_modified = usuario.username

        super().save(*args, **kwargs)
1 Like

I was wondering if form.save(commit=False) is required when there is a manytomany field.

It seems like it’s not required.

Thanks for letting me know.

1 Like