DRF: Serialization with cross reference table - django

I have 3 models, the first one contains controller names and their IDs, the second measurements and their IDs and the third has foreign keys to both and is used as a cross-reference based on IDs.
With the following serializer and view I can have my API return the measurements each controller has.
Serializer:
class MeasurementsSerializer(serializers.ModelSerializer):
# used in stationsMeasurementsInfo view
def create(self, validated_data):
pass
def update(self, instance, validated_data):
pass
class Meta:
model = Measurements
fields = ['code', 'measurement', 'unit', 'type']
View:
class stationsMeasurementsInfo(generics.ListAPIView):
#returns the measurements a station tracks
#authentication_classes = (TokenAuthentication,)
#permission_classes = (IsAuthenticated,)
serializer_class = MeasurementsSerializer
def get_queryset(self):
source = self.kwargs['sourceName']
name = self.kwargs['stationName']
final = Measurements.objects.filter(microcontrollersmeasurements__microcontroller__name=name)
return final
def list(self, request, *args, **kwargs):
res = super(stationsMeasurementsInfo, self).list(request, *args, **kwargs)
res.data = {"source": self.kwargs['sourceName'],
"stations": [{
"station": self.kwargs['stationName'],
"measurements": res.data
}
]
}
return res
The problem is that I cannot actually retrieve the name of the controller, currently, I manually insert it into the list which means that I cannot retrieve the measurements of multiple controllers with a single API call. How would I go about fixing this issue?
EDIT:
Models:
class Microcontrollers(models.Model):
name = models.CharField(max_length=25)
serial_number = models.CharField(max_length=20, blank=True, null=True)
type = models.CharField(max_length=15, blank=True, null=True)
software = models.CharField(max_length=20, blank=True, null=True)
version = models.CharField(max_length=5, blank=True, null=True)
date_installed = models.DateField(blank=True, null=True)
date_battery_last_replaced = models.DateField(blank=True, null=True)
source = models.CharField(max_length=10, blank=True, null=True)
friendly_name = models.CharField(max_length=45, blank=True, null=True)
private = models.IntegerField()
datetime_updated = models.DateTimeField(db_column='DateTime_Updated') # Field name made lowercase.
class Meta:
managed = True
db_table = 'microcontrollers'
verbose_name_plural = "Microcontrollers"
def __str__(self):
return self.friendly_name
class Measurements(models.Model):
code = models.CharField(max_length=30)
measurement = models.CharField(max_length=30)
unit = models.CharField(max_length=25)
type = models.CharField(max_length=25)
datetime_updated = models.DateTimeField(db_column='DateTime_Updated') # Field name made lowercase.
class Meta:
managed = True
db_table = 'measurements'
datetime_updated = models.DateTimeField(db_column='DateTime_Updated') # Field name made lowercase.
class MicrocontrollersMeasurements(models.Model):
microcontroller = models.ForeignKey(Microcontrollers, models.DO_NOTHING, blank=True, null=True)
measurement = models.ForeignKey(Measurements, models.DO_NOTHING, blank=True, null=True)
datetime_updated = models.DateTimeField(db_column='DateTime_Updated') # Field name made lowercase.
class Meta:
managed = True
db_table = 'microcontrollers_measurements'
def __str__(self):
return self.measurement.measurement

First of all I think one-to-many relation between Microcontroller and Measurement would be better for this situation - by logic, you can't have one masurement for many microcontrollers.
Your models names should by singular like Microcontroller, Measurement.
If you still wants to stay with this relations, this solution should work:
Add many to many field to Measurements model.
microcontrollers = models.ManyToManyField(
Microcontrollers,
related_name="measurements",
through='yourapp.MicrocontrollersMeasurements',
through_fields=('measurement', 'microcontroller')
)
Update your MeasurementsSerializer
class MeasurementsSerializer(serializers.ModelSerializer):
...
microcontrollers = MicrocontrollerSerializer(read_only=True, many=True)
class Meta:
model = Measurements
fields = ['code', 'measurement', 'unit', 'type', 'microcontrollers']
Remember to prefetch_related('microcontrollers') for better database query performance.

Related

Extra fields on many-to-many relationships: How to automatically create the "through" fields?

I'm trying to use a many to many field in order to add a set of facilities to a lead.
The trouble is that i need an extra field for the date and time of the tour scheduled for each of the facilities i add to the lead.
So when i create a lead and add facilities to the lead i have that extra field where i can enter date and time and access it afterwards as a list of facilities with their tour dates on the leads page.
I came across many to many fields using "through" but I'm not sure if that's even the right option to use in this case. ManyToMany Fields with extra fields
How can i use a many to many field using "through" and have the through field automatically be generated for each facility i add to my lead with the many to many field? Or is using through not a good option?
I'm using Django Rest Framework with a React Frontend:
models.py
class Facility(models.Model):
Name = models.CharField(max_length=150, null=True, blank=False)
mainimage = models.ImageField(null=True, blank=True)
Email = models.EmailField(max_length=150, null=True, blank=True)
TelephoneNumber = models.CharField(max_length=30, null=True, blank=True)
FacilityDescription = models.TextField(max_length=1000, null=True, blank=True)
def __str__(self):
return self.Name
class Lead(models.Model):
assigned_facilities = models.ManyToManyField(Facility, related_name='assigned_facilities', null=True, blank=True)
first_name = models.CharField(max_length=40, null=True, blank=True)
last_name = models.CharField(max_length=40, null=True, blank=True)
def __str__(self):
return f"{self.first_name} {self.last_name}"
serializers.py
class LeadUpdateSerializer(serializers.ModelSerializer):
is_owner = serializers.SerializerMethodField()
class Meta:
model = Lead
fields = (
"id",
"first_name",
"last_name",
"assigned_facilities",
)
read_only_fields = ("id")
def get_is_owner(self, obj):
user = self.context["request"].user
return obj.agent == user
Leads.js
const cpBoard = useSelector((state) => state.cpBoard);
const facilityIds = (cpBoard.cpBoardItems?.map(cpBoardItem => (cpBoardItem.id)));
function submitFacilities() {
axios.patch(API.leads.update(id), { "assigned_facilities": facilityIds}, {
headers: {
"Authorization": `Bearer ${accessToken}`,
'Accept' : 'application/json',
},
withCredentials: true,
})
.then(res => {
fetchLeads()
})
.finally(() => {
})
}
UPDATE:
I'm currently trying to use the solution suggested below but i'm getting an error when i try to update a lead:
AttributeError: 'NoneType' object has no attribute 'scheduled_datetime' File "/serializers.py", line 253, in to_representation ret["scheduled_datetime"] = str(instance.leadfacilityassociation.first().scheduled_datetime)
models.py
class Facility(models.Model):
name = models.CharField(max_length=150, null=True, blank=False)
main_image = models.ImageField(null=True, blank=True)
email = models.EmailField(max_length=150, null=True, blank=True)
telephone_number = models.CharField(max_length=30, null=True, blank=True)
facility_description = models.TextField(max_length=1000, null=True, blank=True)
def __str__(self):
return self.Name
class Lead(models.Model):
first_name = models.CharField(max_length=40, null=True, blank=True)
last_name = models.CharField(max_length=40, null=True, blank=True)
def __str__(self):
return f"{self.first_name} {self.last_name}"
class LeadFacilityAssociation(models.Model):
assigned_facilities = models.ForeignKey(Facility, related_name='leadfacilityassociation')
lead = models.ForeignKey(Lead, related_name='leadfacilityassociation')
scheduled_datetime = models.DateTimeField(null=True, blank=True)
serializers.py
class LeadUpdateSerializer(serializers.ModelSerializer):
is_owner = serializers.SerializerMethodField()
assigned_facilities = serializers.Integer(required=True)
scheduled_datetime = serializers.DateTimeField(required=True)
class Meta:
model = Lead
fields = (
"id",
"first_name",
"last_name",
"assigned_facilities",
"scheduled_datetime",
)
read_only_fields = ("id")
def get_is_owner(self, obj):
user = self.context["request"].user
return obj.agent == user
def create(self, validated_data):
assigned_facilities = validated_data.pop("assigned_facilities")
scheduled_datetime = validated_data.pop("scheduled_datetime")
instance = Lead.objects.create(**validated_data)
instance.leadfacilityassociation.create(assigned_facilities=assigned_facilities,scheduled_datetime=scheduled_datetime)
return instance
def to_representation(self, instance):
ret = super().to_representation(instance)
ret["scheduled_datetime"] = str(instance.leadfacilityassociation.first().scheduled_datetime)
ret["assigned_facilities"] = instance.leadfacilityassociation.first().assigned_facilities
return ret
ManyToManyField is basically create another association model on Database level to associate ModelA(id) with ModelB(id). We are unable to add additional fields in it.
Now in your case, let's restructure the schema.
class Facility(models.Model):
name = models.CharField(max_length=150, null=True, blank=False)
main_image = models.ImageField(null=True, blank=True)
email = models.EmailField(max_length=150, null=True, blank=True)
telephone_number = models.CharField(max_length=30, null=True, blank=True)
facility_description = models.TextField(max_length=1000, null=True, blank=True)
def __str__(self):
return self.Name
class Lead(models.Model):
first_name = models.CharField(max_length=40, null=True, blank=True)
last_name = models.CharField(max_length=40, null=True, blank=True)
def __str__(self):
return f"{self.first_name} {self.last_name}"
class LeadFacilityAssociation(models.Model):
assigned_facilities = models.ForeignKey(Facility, related_name='leadfacilityassociation')
lead = models.ForeignKey(Lead, related_name='leadfacilityassociation')
scheduled_datetime = models.DateTimeField(null=True, blank=True)
Class Facility: The fields remains same in this Model.
Class Model: Remove assigned_facilities field from because we no longer need it.
Class LeadFacilityAssociation: Adding this Model to associate the Lead and Facility with additional scheduled_datetime field. Now you are able to mapped multiple Leads with multiple Facility with separate scheduled_datetime.
Serializer
class LeadUpdateSerializer(serializers.ModelSerializer):
is_owner = serializers.SerializerMethodField()
assigned_facilities = serializers.Integer(required=True)
scheduled_datetime = serializers.DateTimeField(required=True)
class Meta:
model = Lead
fields = (
"id",
"first_name",
"last_name",
"assigned_facilities",
"scheduled_datetime",
)
read_only_fields = ("id")
def get_is_owner(self, obj):
user = self.context["request"].user
return obj.agent == user
def create(self, validated_data):
assigned_facilities = validated_data.pop("assigned_facilities")
scheduled_datetime = validated_data.pop("scheduled_datetime")
instance = Lead.objects.create(**validated_data)
instance.leadfacilityassociation.create(assigned_facilities=assigned_facilities,scheduled_datetime=scheduled_datetime)
return instance
def to_representation(self, instance):
ret = super().to_representation(instance)
ret["scheduled_datetime"] = str(instance.leadfacilityassociation.first().scheduled_datetime)
ret["assigned_facilities"] = instance.leadfacilityassociation.first().assigned_facilities
return ret
Note
This serializer is designed to create only one Lead with one Facility. For creating multiple facilities you can change the overrided method create. Use bulk_create for creating multiple facilities against Lead. Also change the to_representation method body for returning multiple facilities against Lead.
Using through= seems the way to go in your case. To achieve it you need to create a Model for your M2M table
LeadFacility(models.Model):
facility = models.ForeignKey(Facility, on_delete=models.CASCADE)
lead = models.ForeignKey(Lead, on_delete=models.CASCADE)
datetime = models.DateTimeField()
You can set the datetime value using the following syntax :
my_lead.assigned_facilities.add(my_facility, through_defautlts={"datetime": my_datetime})
Or simply creating the LeadFacilty explicitly :
LeadFacility.objects.create(lead=my_lead, facility=my_facility, datetime=my_datetime)
To access those fields, you'll to define related_names in the LeadFacility model

Need Help in django form widgets

I am in a middle of a project. I need help in using widgets. I have a Model for which i want a model form :
My model is :
class Appointments(models.Model):
doctor = models.ForeignKey(Doctor, on_delete=models.CASCADE)
patient = models.ForeignKey(Patient, on_delete=models.CASCADE)
app_time = models.DateTimeField()
diognoses = models.TextField(max_length=1000, null=True, blank=True)
prescriptions = models.TextField(max_length=250, null=True, blank=True)
class Meta:
unique_together = ('doctor', 'patient', 'app_time')
def __str__(self):
st = str(self.patient.user.name)+str(self.doctor.user.name)
return st
The corresponding model form is :
class AppointmentBookForm(forms.ModelForm):
app_time = forms.DateTimeField(widget=forms.SplitDateTimeWidget())
class Meta:
model = Appointments
fields = ['doctor','app_time']
Now for app_time I have split the date and time field which is already working fine.
Now I want a dropdown for date and a suitable widget for time.
The time should contain only hours and minutes.
And Finally I also want to provide the options for timing depending on the date and doctor.
For plain html the easiest way might be to define two input fields, one for the time and one for the date.
Input type="date":
https://www.w3schools.com/tags/att_input_type_date.asp
Input type="time":
https://www.w3schools.com/tags/att_input_type_time.asp
There are multiple results to this, here is one proposal:
models.py
from datetime import datetime
class Appointments(models.Model):
doctor = models.ForeignKey(Doctor, on_delete=models.CASCADE)
patient = models.ForeignKey(Patient, on_delete=models.CASCADE)
time = models.TimeField()
date = models.DateField()
app_time = models.DateTimeField()
diognoses = models.TextField(max_length=1000, null=True, blank=True)
prescriptions = models.TextField(max_length=250, null=True, blank=True)
def save(self, *args, **kwargs):
self.app_time = datetime.combine(self.date, self.time)
super().save(*args, **kwargs)
class Meta:
unique_together = ('doctor', 'patient', 'app_time')
def __str__(self):
st = str(self.patient.user.name)+str(self.doctor.user.name)
return st
forms.py
class AppointmentBookForm(forms.ModelForm):
class Meta:
model = Appointments
fields = ['doctor', 'time', 'date']
"""
# Optional: Define dedicated types or classes for the single fields
widgets = {
'time': forms.DateField(attr={"class": "myclass", "type": "time"})
}
"""
The app_time is quite redundant in this case. You could also define a property to receive the datetime:
class Appointments(models.Model):
doctor = models.ForeignKey(Doctor, on_delete=models.CASCADE)
patient = models.ForeignKey(Patient, on_delete=models.CASCADE)
time = models.TimeField()
date = models.DateField()
diognoses = models.TextField(max_length=1000, null=True, blank=True)
prescriptions = models.TextField(max_length=250, null=True, blank=True)
#property
def app_time(self):
return datetime.combine(date, time)
class Meta:
unique_together = ('doctor', 'patient', 'time', 'date')
def __str__(self):
st = str(self.patient.user.name)+str(self.doctor.user.name)
return st
example:
appointment = Appointments.objects.first()
print(appointment.app_time)
Alternatively, use some nice front end framework if you want to keep a single datetimefield.

Django REST framework: Join tables

I am trying to make an API view where the user inputs the name of a controller and receives the measurements it can return as a JSON.
models.py
class Microcontrollers(models.Model):
name = models.CharField(max_length=25)
serial_number = models.CharField(max_length=20, blank=True, null=True)
type = models.CharField(max_length=15, blank=True, null=True)
software = models.CharField(max_length=20, blank=True, null=True)
version = models.CharField(max_length=5, blank=True, null=True)
date_installed = models.DateField(blank=True, null=True)
date_battery_last_replaced = models.DateField(blank=True, null=True)
source = models.CharField(max_length=10, blank=True, null=True)
friendly_name = models.CharField(max_length=45, blank=True, null=True)
class MicrocontrollersMeasurements(models.Model):
microcontroller = models.ForeignKey(Microcontrollers, models.DO_NOTHING, blank=True, null=True)
measurement = models.ForeignKey(Measurements, models.DO_NOTHING, blank=True, null=True)
class Measurements(models.Model):
code = models.CharField(max_length=25)
measurement = models.CharField(max_length=25)
unit = models.CharField(max_length=25)
type = models.CharField(max_length=25)
serializers.py
class SourceStationsSerializer(serializers.ModelSerializer):
def create(self, validated_data):
pass
def update(self, instance, validated_data):
pass
class Meta:
model = Microcontrollers
fields = ['id', 'name']
class StationMeasurementsSerializer(serializers.ModelSerializer):
def create(self, validated_data):
pass
def update(self, instance, validated_data):
pass
class Meta:
model = MicrocontrollersMeasurements
fields = '__all__'
class MeasurementsSerializer(serializers.ModelSerializer):
station = SourceStationsSerializer()
stationMeasurements = StationMeasurementsSerializer()
def create(self, validated_data):
pass
def update(self, instance, validated_data):
pass
class Meta:
model = Measurements
fields = '__all__'
The database creates IDs for each controller and measurement. My question is how can i join the 3 tables so i can find the measurements that belong to each controller.
well you dont actually need the intermediate models in django..
class Measurements(models.Model):
microcontroller = models.ForeignKey(Microcontrollers,related_name="measurements",on_delete=models.DO_NOTHING, blank=True, null=True)
code = models.CharField(max_length=25)
measurement = models.CharField(max_length=25)
unit = models.CharField(max_length=25)
type = models.CharField(max_length=25)
note the related_name='measurements"
Now Say,
pic = Microcontrollers.objects.get(id=1) #or any instance of MMicrocontrollers()
measurements_pic = pic.measurements.all() # will get a list of all measurements associated with pic
this "measurements" in pic.measurements.all() here is the related name. Django created the intemediate table and all that stuff.. and simplifies it.
For the output purposes
out = MeasurementsSerializer(measurements_pic,many=True)
will do the job.
Note: 'type' is a keyword in python

Django design model - when a data of status field changes, remaining models status change with it

Its my first time trying django as my first programming project.
I have a hierarchy structure of Company -> Business -> Outlets using foreign key.
I would like to know is there anyway to structured it in a way where the Company status is saved as inactive status, the remaining business, outlets models that will be triggered as inactive status.
from django.db import models
from django.contrib.auth.models import User
# Create your models here.
class Common_Info(models.Model):
"""(Common description)"""
name = models.CharField(blank=True, max_length=100)
slug = models.SlugField(unique=True, max_length=120)
address_1 = models.CharField(max_length=50, null=True)
address_2 = models.CharField(max_length=50, null=True)
address_3 = models.CharField(max_length=50, null=True)
post_code = models.CharField(max_length=6, null=False)
registration_no. = models.CharField(max_length=15,null=False)
gst_no. = models.CharField(max_length=15,null=True)
telphone_no. = models.CharField(max_legth=15, null=False)
fax_no. = models.CharField(max_legth=15, null=True)
email_address = models.EmailField(max_length=254,null=False)
"""(Status choice)"""
Active_Status = 1
Inactive_Status = 0
STATUS_CHOICES = (
(Active_Status, 'Active'),
(Inactive_Status, 'Inactive'),
)
status = models.IntegerField(choices=STATUS_CHOICES, default=Active_Status)
create_date = models.DateTimeField(auto_now_add=True)
create_user = models.ForeignKey(settings.AUTH_USER_MODEL)
modified_date = models.DateTimeField(auto_now_add=True)
modified_user = models.ForeignKey(settings.AUTH_USER_MODEL)
class Meta:
abstract = True
class Company(Common_Info):
"""(Company additional description)"""
gst_no. = models.CharField(max_length=15,null=True)
class Meta:
verbose_name ='Company'
verbose_name_plural = "Companies"
def __unicode__(self):
return u"Company"
class Business(Common_Info):
"""(Business description)"""
parent=models.ForeignKey(Company, on_delete=models.CASCADE)
gst_no. = models.CharField(max_length=15,null=True)
class Meta:
verbose_name ='Business'
verbose_name_plural = "Businesses"
def __unicode__(self):
return u"Business"
class Outlet(Common_Info):
outlet_code = models.CharField(max_length=3, unique=True)
business_name = models.ForeignKey(Business, on_delete=models.CASCADE)
def __unicode__(self):
return u"Outlet"
Is there something similar to cascade on delete or other more elegant way of extending such a function across other apps in the project.
I don't think there is any direct support in ORM. But you can override the save() method to update the related the outlets and business. You can use related objects to fetch business and outlets. Here is an example:
class Company(Common_Info):
"""(Company additional description)"""
gst_no. = models.CharField(max_length=15,null=True)
class Meta:
verbose_name ='Company'
verbose_name_plural = "Companies"
def __unicode__(self):
return u"Company"
def save(self, *args, **kwargs):
super(Company, self).save(*args, **kwargs)
if self.status == 0:
self.business_set.update(status=self.status)
self.outlet_set.update(status=self.status)

Tricky issue passing data from form to form in django

I am a django newbie and have one more big struggle for longer time... :/
User can choose a 'main language' which is set as ForeignKey. User can choose 'further languages' as ManyToMany (Checkbox). Assuming, user selects english as 'main' language, so english has to be filterd out from the 'further languages'... have been searching so much and have no idea how to do it. Is this even possible without JavaScript?
Of course, I could set the 'queryset' in the second form but it would filter the objects after the submit... The similar problem is, when a selected country has to be connected to the proper zipcodes...
I am very thankful for any hints.
Best regards.
class Country(models.Model):
enter code here
country = models.CharField(max_length=40)
active = models.BooleanField(default=True)
class Meta:
verbose_name_plural = 'Länder'
def __str__(self):
return self.country
class ZipCode(models.Model):
zipcode = models.CharField(max_length=5)
city = models.CharField(max_length=255)
active = models.BooleanField(default=False)
class Meta:
verbose_name_plural = 'Postleitzahlen'
def __str__(self):
return '{0} {1}'.format(self.zipcode, self.city)
class MainLanguage(models.Model):
language = models.CharField(verbose_name='Hauptsprache', max_length=40)
active = models.BooleanField(default=True)
class Meta:
verbose_name_plural = 'Hauptsprachen'
ordering = ['language']
def __str__(self):
return self.language
class SecondLanguage(models.Model):
language = models.CharField(verbose_name='weitere Sprachen', max_length=40)
active = models.BooleanField(default=False)
class Meta:
verbose_name_plural = 'weitere Sprachen'
ordering = ['language']
def __str__(self):
return self.language
class CustomUserprofile(models.Model):
user = models.OneToOneField(User)
name = models.CharField(verbose_name='Vorname', max_length=40,
null=True, blank=True)
country = models.ForeignKey(Country, verbose_name='Land',
null=True, blank=True)
zipcode = models.ForeignKey(ZipCode, blank=True, null=True)
main_language = models.ForeignKey(
MainLanguage, verbose_name='Hauptsprache',
null=True, blank=True)
second_language = models.ManyToManyField(
SecondLanguage, verbose_name='weitere Sprachen',
null=True, blank=True)
class UserProfileForm(forms.ModelForm):
second_language = forms.ModelMultipleChoiceField(
queryset=SecondLanguage.objects.all(),
required=False,
widget=forms.CheckboxSelectMultiple)
class Meta:
model = CustomUserprofile
exclude = ('user',)