Django REST framework: Join tables - django

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

Related

DRF: Serialization with cross reference table

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.

How to update a User and its Profile in nested serializer using generic ListCreateAPIView?

I am working on genericAPIViews in DRF. I am using a built in user model with UserProfile model having one to one relation with it. But I am unable to update user due to nested serializer. My question is that how I can update my built in User model and Profile User model at the same time as UserProfile model is nested in User model.Here is my code:
Models.py
USER_CHOICE = (
('SS', 'SS'),
('SP', 'SP')
)
LOGIN_TYPE = (
('Local', 'Local'),
('Facebook', 'Facebook'),
('Google', 'Google')
)
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile')
cell_phone = models.CharField(max_length=15, blank=True, default="", null=True)
country = models.CharField(max_length=50, blank=True, default="", null=True)
state = models.CharField(max_length=50, blank=True, default="", null=True)
profile_image = models.FileField(upload_to='user_images/', default='', blank=True)
postal_code = models.CharField(max_length=50, blank=True, default="", null=True)
registration_id = models.CharField(max_length=200, null=True, blank=True, default=None)
active = models.BooleanField(default=True)
# roles = models.ForeignKey(Role, null=True, on_delete=models.CASCADE, related_name='role', blank=True)
user_type = models.CharField(max_length=50, choices=USER_CHOICE, null=True, blank=True)
login_type = models.CharField(max_length=40, choices=LOGIN_TYPE, default='local')
reset_pass = models.BooleanField(default=False)
confirmed_email = models.BooleanField(default=False)
remember_me = models.BooleanField(default=False)
reset_code = models.CharField(max_length=200, null=True, blank=True, default="")
reset_code_time = models.DateTimeField(auto_now_add=True, blank=True)
longitude = models.DecimalField(max_digits=80, decimal_places=10, default=0.00)
latitude = models.DecimalField(max_digits=80, decimal_places=10, default=0.00)
r_code = models.CharField(max_length=15, null=True, blank=True)
refer_user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True, related_name="user_refer")
referred = models.ManyToManyField(User, related_name="user_referred", null=True, blank=True)
otp = models.CharField(max_length=6, blank=True, default="", null=True)
def __str__(self):
return self.user.username
Seralizer.py
from rest_framework import serializers
from django.contrib.auth.models import User
from .models import UserProfile
class UserProfileSerializer(serializers.ModelSerializer):
class Meta:
model = UserProfile
fields = '__all__'
class UserSerializer(serializers.ModelSerializer):
profile = UserProfileSerializer()
class Meta:
model = User
fields = ['id', 'username', 'email', 'first_name', 'last_name', 'profile']
def create(self, validated_data):
profile_data = validated_data.pop('profile')
user = User.objects.create(**validated_data)
Profile.objects.create(user=user, **profile_data)
return user
Views.py
class UserList(generics.ListCreateAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
class UserDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes = [IsAdminUser]
How can write my .update() methods which can be override according to DRF documentation.Thanks in advance for your addition to my knowledge.
There is update method in ModelSerializer which can be overriden the same way as you did with create.
def update(self, instance, validated_data):
profile_data = validated_data.pop('profile', {})
profile = instance.profile
for attr, value in profile_data.items():
setattr(profile, attr, value)
profile.save()
return super(UserSerializer, self).update(instance, validated_data)
You can also write nested serializer.

Django Rest Framework- foreign key throwing error

I am using django rest framework wherein the model has composite primary key, one of the them being a foreign key.
models/TestSuite.py
class TestSuite(models.Model):
team_name = models.ForeignKey('Team', on_delete=models.DO_NOTHING, db_column='team_name')
suite_name = models.CharField(max_length=100)
description = models.CharField(max_length=200, blank=True, null=True)
schedule = models.CharField(max_length=100, blank=True, null=True)
email_list_ok = models.CharField(max_length=200, blank=True, null=True)
email_list_fail = models.CharField(max_length=200, blank=True, null=True)
template_name = models.ForeignKey('EmailTemplates', on_delete=models.DO_NOTHING, db_column='template_name')
class Meta:
managed = False
db_table = 'test_suite'
unique_together = (('team_name', 'suite_name'),)
models/Team.py
class Team(models.Model):
team_name = models.CharField(primary_key=True, max_length=30)
description = models.CharField(max_length=100, blank=True, null=True)
class Meta:
managed = False
db_table = 'team'
TestSuiteSerializer.py
class Meta:
model = models.TestSuite
fields = '__all__'
TestSuiteViewSet.py
class TestSuiteViewSet(viewsets.ModelViewSet):
queryset = models.TestSuite.objects.all()
serializer_class = serializers.TestSuiteSerializer
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data,
many=isinstance(request.data, list))
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data,
status=status.HTTP_201_CREATED, headers=headers)
Now when I do a post request, it throws below errors
When the post() has team_name already existing in team table
{
"team_name": [
"test suite with this team name already exists."
]
}
When the post() has team_name not existing in team table
Exception Type: ValueError
Exception Value:
Cannot assign "'dummy'": "TestSuite.team_name" must be a "Team" instance.
Can someone please help me here. I am assuming I am missing something.
The first argument to your foreign key fields should be the model itself, not a string of the model (eg. not 'Team', but Team - likewise for EmailTemplate)
class TestSuite(models.Model):
# Change this field's first argument from a string to the Team class
team_name = models.ForeignKey(Team, on_delete=models.DO_NOTHING, db_column='team_name')
suite_name = models.CharField(max_length=100)
description = models.CharField(max_length=200, blank=True, null=True)
schedule = models.CharField(max_length=100, blank=True, null=True)
email_list_ok = models.CharField(max_length=200, blank=True, null=True)
email_list_fail = models.CharField(max_length=200, blank=True, null=True)
# Change this field's first argument from a string to the EmailTemplates class
template_name = models.ForeignKey(EmailTemplates, on_delete=models.DO_NOTHING, db_column='template_name')
class Meta:
managed = False
db_table = 'test_suite'
unique_together = (('team_name', 'suite_name'),)

How to use custom fields for complex django models?

I want to define a model where a field can be one of the other defined models.
I have three models like Plane, Train and Bus. Each model has its own fields.
e.g.
class Train(models.Model):
id = models.AutoField(primary_key=True)
train_name = models.CharField(max_length=200)
date_of_journey= models.DateField()
from_station = models.CharField(max_length=4)
to_station = models.CharField(max_length=4)
class_selection = models.CharField(max_length=6, choices=class_choices)
and
class Plane(models.Model):
id = models.AutoField(primary_key=True)
date_of_journey= models.DateField()
from_airport = models.CharField(max_length=4)
to_airport = models.CharField(max_length=4)
plane_model = models.CharField(max_length=10)
class Bus(models.Model):
id = models.AutoField(primary_key=True)
date_of_journey= models.DateField()
from_city = models.CharField(max_length=100)
to_city = models.CharField(max_length=100)
I want to make a model named Trip which will have following structure:
class Trip(models.Model):
id = models.AutoField(primary_key=True)
trip_name = models.CharField(max_length=250)
reason = models.CharField(max_length=30, null=True, blank=True)
individual_journey = JourneyType(oneToManyField)
A trip can have multiple individual journeys and each journey should be either a Bus journey, a Train journey or a plane journey.
from django.core.exceptions import ValidationError
class JourneyType(models.Model):
bus = models.ForeignKey('Bus', blank=True, null=True)
train = models.ForeignKey('Train', blank=True, null=True)
plain = models.ForeignKey('Plain', blank=True, null=True)
def clean(self):
if not (self.bus or self.train or self.plain) or \
(self.bus and self.train) or (self.bus and self.plain) or \
(self.train and self.plain):
raise ValidationError('You should specify (only) one journey type')
def __unicode__(self):
if self.bus:
return 'Bus id={0}'.format(self.bus)
elif self.train:
return 'Train id={0}'.format(self.train)
else:
return 'Plain id={0}'.format(self.plain)
class Trip(models.Model):
# ...
individual_journey = models.ForeignKey('JourneyType')

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',)