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")
create()
is the function I should call instead of the save()
function. Is this correct?save
function?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.
clear()
instead of remove()
? What is the difference?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.
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.