djangomany-to-manycascading-deletes

Django ManyToMany creations and cascading deletions


Django ManyToMany creations and cascading deletions

Django newbie here. I have looked under several threads and I have not found anything related so I hope you can help me out on this.

I am working on a ManyToMany model involving Doctors and Patients, that is, both can have multiple relationships between them. I have the following model definition:

class Patient(models.Model):
    first_name = models.CharField(max_length=255)
    last_name = models.CharField(max_length=255)
    email = models.EmailField(unique=True)
    (other fields definitions)

class Doctor(models.Model):
    first_name = models.CharField(max_length=255)
    last_name = models.CharField(max_length=255)
    email = models.EmailField(unique=True)
    (other fields definitions)
…
    patients = models.ManyToManyField(Patient)

The doctor is the main user that should be able to visualize all related patients (no problem there). When adding a new patient though, I would like to know how to create the relationship in the app_doctor_patients join table. I’ve read the documentation regarding the Related objects reference (https://docs.djangoproject.com/en/4.2/ref/models/relations/#django.db.models.fields.related.RelatedManager) but I do not know which is the best way to apply these functions. For starters, I visualized the following:

def add_new_patient(request, doctor_id=None, curp=None):
    doctor = Doctor.objects.get(id=doctor_id)
    try:
        patient = Patient.objects.get(curp=curp)
        # Associates Patient 'patient' with Doctor 'doctor'.
        doctor.patients.add(patient)
    except Patient.DoesNotExist:
        # Creates a new patient object, saves it and puts it in the related object set. Returns the newly created object
        patient = d.patients.create(
            first_name="John", first_last_name="McCain")

Regarding deletions, I would like for Doctors to be kept while deleting related Patients. Only the relationships should be removed. For this I thought of using the following:

p = Patient.objects.get(id=345)
d = Doctor.objects.get(id=1)
p.doctors_set.remove(d)  # Disassociates Doctor d from Patient p.

For the opposite operation, when deleting a Doctor, besides deleting all related records in the app_doctor_patients table, I would need to also remove all related Patients ONLY IF this patient has only one relation left (with the current doctor being deleted). I was reading about the m2m_changed signal (https://docs.djangoproject.com/en/4.2/ref/signals/#django.db.models.signals.m2m_changed), but I do not understand how to apply it. I have the following partial code

@receiver(pre_delete, sender=Doctor)
def pre_delete_story(sender, instance, **kwargs):
    for patient in instance.patients.all():
        if patient.doctors.count() == 1:
            # instance is the only Doctor attending this Patient, so delete it
            patient.delete()

Where are these to be defined? What am I missing?

Your help would be greatly appreciated.


Adding to my original question, I am currently using Class-based views (using APIView from rest_framework.views) and the above code is preliminary. That is, I have not included it in my working code. What I have right now to display and save new patients is the following:

class PatientList(APIView):
    def get(self, request):
        doctor_id = 25  # currently hardcoded, eventually to be taken from singed-on user
        doctor = Doctor.objects.get(pk=doctor_id)
        patients = doctor.patients.all()
        serializer = PatientDisplaySerializer(patients, many=True)
        return Response(serializer.data)

    def post(self, request):
        doctor_id = 25  # currently hardcoded, eventually to be taken from singed-on user
        serializer = PatientSaveSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        serializer.save()
        print(serializer.validated_data)
        return Response(serializer.data)

Regarding the serializer.save() command, should I override the save() function in the serializers.py file, or where should I include my add_new_patient() function and the code to build the relationship to the doctor?

Where should the remove() function be called? I have the general idea of the functionality I want but I do not know where to include it. Thanks again.


Solution

  • for adding a new patient to a doctor you can use the add method as you have correctly done. the create method is used to create a new related object, in this case, a new patient, and associate it with the doctor.

    in your code after creating the new patient add it to doctor's patients:

    def add_new_patient(request, doctor_id=None, curp=None):
        doctor = Doctor.objects.get(id=doctor_id)
        try:
            patient = Patient.objects.get(curp=curp)
            # Associates Patient 'patient' with Doctor 'doctor'.
            doctor.patients.add(patient)
        except Patient.DoesNotExist:
            # Creates a new patient object, saves it and puts it in the related object set. Returns the newly created object
            patient = d.patients.create(
                first_name="John", first_last_name="McCain")
            doctor.patients.add(patient)
    

    about deletions, if you want to remove the relationship between a doctor and a patient without deleting the patient itself, you should use the remove method which only removes the relationship between the two entities, on the other hand if you use the clear method it will remove all relationships between the doctor and all patients which might not be the behavior you want.

    for the deletion of a doctor you can use the pre_delete signal to check if the patient only has one doctor related to it. if it does then you can delete the patient as you have done in your code, in django signals allow certain senders to notify a set of receivers when some action has taken place. they provide a way for decoupled applications to get notified when certain events occur elsewhere in the application.

    the pre_delete signal is sent before a model's delete() method is called. it allows you to perform certain actions or checks before the deletion of an object. this is very useful when you need to perform some operations or cleanups just before an object is deleted from the database.

    in your use case, you are using the pre_delete signal you want to check wether any patients associated with the doctor have only one doctor in their relationships before a Doctor instance is deleted if they do you want to delete those patients as well.

    to do that you need to import the necessary modules:

    from django.db.models.signals import pre_delete
    from django.dispatch import receiver
    

    define the pre_delete signal with the @receiver decorator , this decorator connects the function with the signal:

    @receiver(pre_delete, sender=Doctor)
    def pre_delete_story(sender, instance, **kwargs):
        # yor logic for pre_deletaion actions goes here
    

    the function should take certain arguments:sender which is the model class that sent the signal instance, in this case, the Doctor model, which is the instance of the model that is about to delete.

    I hope this helps you.