Validation in case of multiple objects serializer - django

My data input is in the form of list of 'n' number of dicts
"contact_person":[
{
"contactperson_salutation[0]":"sddd",
"contactperson_first_name[0]":"santoorr",
"contactperson_last_name[0]":"",
"contactperson_email[0]":"gfgh",
"contactperson_mobile_number[0]":"",
"contactperson_work_phone_number[0]":"jio"
},
{
"contactperson_salutation[1]":"dfsf",
"contactperson_first_name[1]":"lux",
"contactperson_last_name[1]":"",
"contactperson_email[1]":"",
"contactperson_mobile_number[1]":"",
"contactperson_work_phone_number[1]":"9048"
}, .............]
My model is like this:
class ContactPerson(models.Model):
client = models.ForeignKey(Client, on_delete=models.CASCADE)
contactperson_salutation = models.CharField(max_length=4, choices=SALUTATIONS)
contactperson_first_name = models.CharField(max_length=128)
contactperson_last_name = models.CharField(max_length=128, blank=True)
contactperson_email = models.EmailField(blank=True, null=True)
contactperson_mobile_number = models.CharField(max_length=20, blank=True)
contactperson_work_phone_number = models.CharField(max_length=20, blank=True)
How to write serializer when the fields names are changing for every dict in the input list..
And if errors occurs the Error Response should be in this format:
[
{
"contactperson_email[0]":"Invalid Email",
"contactperson_mobile_number[0]":"Invalid mobile phone",
"contactperson_work_phone_number[0]":"Invalid workphone number"
},
{
"contactperson_mobile_number[1]":"Invalid mobile phone",
"contactperson_work_phone_number[1]":"Invalid workphone number"
}
]

You would probably benefit from parsing the "contactperson_salutation[0]"-like strings to build a list contactperson_salutation with all the ocurrences.
Same thing for each of the other fields.

try overwriting the to_internal_value method of the Serializer to achieve this. But the error would still be not in the format you require.
The error message will contain your model keys, not the keys which are suffixed with [x].
class ContactPersonSerializer(serializers.ModelSerializer):
class Meta:
model = ContactPerson
fields = [
"contactperson_salutation",
"contactperson_first_name",
"contactperson_last_name",
"contactperson_email",
"contactperson_mobile_number",
"contactperson_work_phone_number",
]
def to_internal_value(self, data):
if hasattr(data, "_mutable"):
data._mutable = True
data = {key[:-3]: value for key, value in data.items()}
if hasattr(data, "_mutable"):
data._mutable = False
return super().to_internal_value(data)

Related

Django Rest Reverse Nested Relationships in Serializers

I am working on creating a GET only endpoint that shows the standings of a given season for a sports league. Honestly, at this point I have tried nearly everything with no luck. All other Serializers work fine so far and there is no issue with the way my database is setup. I have omitted many fields and tables due to the complexity, but anything relevant to the question is included below.
First, let me show you the rough format of how I want to JSON to be returned.
Sample Response (The end goal)
{
divisions: [
{
"divisionName": "WEEKNIGHT B",
"divisionId": "ee68d8ab-2752-4df6-b11d-d289573c66df",
teams: [
{
"teamId": "b07560bc-aac2-4c6c-bbfe-11a368137712",
"statsId": "53852698-9b78-4f36-9a2e-4751b21972f9",
"teamName": "FNA",
},
{
"teamId": "406eb5aa-6004-4220-b219-59476a3136d1",
"statsId": "a96ebf10-87c5-4f19-99c3-867253f4a502",
"teamName": "COPENHAGEN ROAD SODAS",
},
]
},
{
"divisionName": "WEEKNIGHT C",
"divisionId": "4e1469ae-2435-4a3d-a621-19a979ede7c1",
teams: [
{
"teamId": "ebc7e632-073e-4484-85f9-29c0997bec25",
"statsId": "cd6373a7-4f53-4286-80f2-eb3a8a49ee3a",
"teamName": "HAWKS",
"gamesPlayed": 29,
},
{
"teamId": "d8cda7a6-15f4-4e8f-8c65-ef14485957e4",
"statsId": "4492a128-763a-44ad-9ffa-abae2c39b425",
"teamName": "DUISLANDERS",
},
]
}
]
}
Through the URL I am passing in the Season ID, so everything is based off the season id. Below is an example of how I can replicate the type of response I want, by iterating through some queries which I know is rather messy, but I am using it just for example purposes.
Sample Query to show how it can be done using queries
standingsDict = {
"standings": []
}
divisionList = []
season = Season.objects.get(name='Fall 2021')
divisions = Division.objects.filter(seasondivision__season__id=season.id)
for div in divisions:
teamstats = TeamStats.objects.filter(
season_division_team__season_division__season_id=season.id,
season_division_team__season_division__division_id=div)
divDict = {
"divisionName": div.name,
"divisionId": div.id,
"teams": []
}
teamsList = []
for stats in teamstats:
teamDict = {
"teamId": stats.season_division_team.team.id,
"statsId": stats.id,
"seasonDivisionTeamId": stats.season_division_team_id,
"teamName": stats.season_division_team.team.name,
}
teamsList.append(teamDict)
divDict['teams'] = teamsList
divisionList.append(divDict)
standingsDict['standings'] = divisionList
Output Snippet of that loop
{
"standings":[
{
"divisionName":"WEEKNIGHT B",
"divisionId":"UUID(""ee68d8ab-2752-4df6-b11d-d289573c66df"")",
"teams":[
{
"teamId":"UUID(""756ea44f-885f-4ea0-ae4c-8a72c894067f"")",
"statsId":"UUID(""8ef1f683-ad5b-40d8-9277-0c2689cff771"")",
"seasonDivisionTeamId":"UUID(""19e29f9a-af04-4a4f-9e38-bad0cbf8817d"")",
"teamName":"GABAGOOLS"
},
{
"teamId":"UUID(""aefdc0dc-e2aa-4ce5-ad28-47f0724b57ab"")",
"statsId":"UUID(""05e0012c-0adc-4d15-bbc5-901e49489667"")",
"seasonDivisionTeamId":"UUID(""8adab3d1-7614-4ece-92f5-f0ab34b23c4c"")",
"teamName":"LONG BEACH THUNDER MAJOR"
},
Currently with the way everything is setup, I can traverse the FK Relationships to get them in a forward manner, for example, I can go from SeasonDivisionTeam, get the TeamStatsSerializer, and for each team, I can display the division. But that displays the same division multiple times for all the Team Stats, which is not what I am looking for.
Models
class BaseModel(LifecycleModelMixin, models.Model):
date_modified = models.DateTimeField(auto_now=True)
date_created = models.DateTimeField(auto_now_add=True)
id = models.UUIDField(default=uuid.uuid4, unique=True, primary_key=True, editable=False)
class Meta:
abstract = True
class Season(BaseModel):
name = models.CharField(max_length=50)
startDate = models.DateField(null=True, blank=True)
endDate = models.DateField(null=True, blank=True)
class Division(BaseModel):
name = models.CharField(max_length=50)
seasons = models.ManyToManyField(Season, through='SeasonDivision')
class SeasonDivision(LifecycleModelMixin, models.Model):
id = models.UUIDField(default=uuid.uuid4, unique=True, primary_key=True, editable=False)
division = models.ForeignKey(Division, on_delete=models.CASCADE)
season = models.ForeignKey(Season, on_delete=models.CASCADE)
class Team(BaseModel):
name = models.CharField(max_length=50)
season_divisions = models.ManyToManyField(SeasonDivision, through='SeasonDivisionTeam')
class SeasonDivisionTeam(LifecycleModelMixin, models.Model):
id = models.UUIDField(default=uuid.uuid4, unique=True, primary_key=True, editable=False)
team = models.ForeignKey(Team, on_delete=models.CASCADE)
season_division = models.ForeignKey(SeasonDivision, on_delete=models.CASCADE)
class Player(BaseModel):
season_division_teams = models.ManyToManyField(SeasonDivisionTeam, through='SeasonDivisionTeamPlayer')
firstName = models.CharField(max_length=80)
class SeasonDivisionTeamPlayer(LifecycleModelMixin, models.Model):
id = models.UUIDField(default=uuid.uuid4, unique=True, primary_key=True, editable=False)
player = models.ForeignKey(Player, on_delete=models.CASCADE)
season_division_team = models.ForeignKey(SeasonDivisionTeam, on_delete=models.CASCADE)
class PlayerStats(BaseModel):
season_division_team_player = models.ForeignKey(SeasonDivisionTeamPlayer, on_delete=models.CASCADE)
goals = models.IntegerField(default=0)
class TeamStats(BaseModel):
season_division_team = models.ForeignKey(SeasonDivisionTeam, on_delete=models.CASCADE)
regWins = models.IntegerField(default=0)
Serializers (Many of these have been changed multiple times, so what I have below is just the last thing I was testing that didn't work. )
class StandingsSerializer(serializers.ModelSerializer):
teamstats = TeamStatsSerializer(many=True, source='teamstats_set')
class Meta:
model = SeasonDivisionTeam
fields = [
"teamstats",
]
depth = 1
Views (Same as above, have done alot of testing with these, so what I have below is just the most recent test)
class StandingsList(generics.ListCreateAPIView):
queryset = Season.objects.order_by('-endDate').all()
serializer_class = StandingsSerializer
def get_queryset(self):
season_id = self.request.query_params.get('season_id')
queryset = SeasonDivisionTeam.objects.filter(season_division__season_id=season_id)
return queryset

How to push flat data with POST in nested serializer DRF

I have an API that can create testruns, but I need an instrument serial number to create it.
I would like to be able to have this POST request :
{
"serial_number":"4331214L"
"operator": "John Doe"
}
But, right now I have to do :
{
"instrument": {
"serial_number":"4331214L"
},
"operator": "John Doe"
}
current models:
class InstrumentModel(models.Model):
class Meta:
db_table = "instruments"
verbose_name = "Instrument"
serial_number = models.CharField(max_length=10, unique=True, db_index=True)
def __str__(self):
return self.serial_number
class TestRun(models.Model):
class Meta:
db_table = "test_runs"
verbose_name = "Test run"
operator = models.CharField(max_length=70)
instrument = models.ForeignKey(InstrumentModel, related_name="instruments", db_column="instrument", on_delete=models.CASCADE)
created_at = models.DateTimeField(db_index=True, default=timezone.now)
I tried with the depth meta field. That doesn't work. Maybe it's not at the serializer level?
class TestRunSerializer(serializers.ModelSerializer):
instrument = InstrumentSerializer()
class Meta:
model = TestRun
fields = ('operator', 'instrument')
depth = 1
def create(self, validated_data):
serial_number = validated_data.pop('serial_number')
instrument, _ = InstrumentModel.objects.get_or_create(serial_number=serial_number)
return TestRun.objects.create(**validated_data, instrument=instrument)
I think it can be right like that if you used to_internal_value
class TestRunSerializer(serializers.ModelSerializer):
instrument = InstrumentSerializer()
class Meta:
model = TestRun
fields = ('operator', 'instrument')
depth = 1
def to_internal_value(self, data):
serial_number = obj.get('serial_number')
if not serial_number:
raise serializers.ValidationError({
'serial_number': 'This field is required.'
})
data['insturment'] = {'serial_number': serial_number}
return data

Django Rest framework- getting total number of rows

I would like to output a field that counts the number of Candidat in Candidat Model. I am currently using the following serializer:
class CountCSerializer(serializers.ModelSerializer):
user_count = serializers.SerializerMethodField()
class Meta:
model = Candidat
fields = ( 'user_count',)
def get_user_count(self, obj):
return Candidat.objects.count()
and the following api:
class CountCViewSet(ModelViewSet):
queryset = Candidat.objects.all()
serializer_class = CountCSerializer
urls.py:
router.register(r'CountC', CountCViewSet, base_name='users-count')
models.py:
class Candidat(models.Model):
name = models.CharField(max_length=50)
lastName = models.CharField(max_length=50)
email = models.CharField(max_length=50)
tel = models.CharField(max_length=50, default=0)
password = models.CharField(max_length=50)
civility = models.CharField(max_length=50)
birthDate = models.DateField(auto_now=False, auto_now_add=False)
gouvernorate = models.CharField(max_length=50)
def __str__(self):
return "Candidat: {}".format(self.name)
But im getting nothing!
Any help in the matter would be much appreciated.
I was looking for the same and noticed that ModelViewSet generates a count by default. You can see it by navigating to the endpoint in a browser or checking the response body in Postman.
Example:
{
"count": 10,
"next": null,
"previous": null,
"results": [
{
"id": 62,
"entity_name": "The company name",
"entity_website_url": "thecompany.com",
"entity_city": "Los Angeles",
"entity_state": "CA"
}
...
]
}

"This field is required." when sending JSON using POST with Django REST Framework

I have the following JSON I'm trying to post:
[
{
"action_alert_details": [],
"action_email": [
{
"tskmail_attach": null,
"tskmail_id": 4444,
"tskmail_memo": "TEST!",
"tskmail_priority": 1,
"tskmail_subject": "TEST!",
"tskmail_to_ext": "blah#blah.com",
"tskmail_to_int": null
}
],
"action_job_details": [],
"action_log_details": [],
"action_snmp_details": [],
"action_variable_details": [],
"nodmst_name": null,
"owner_name": "Operations ",
"servicemst_name": null,
"tskmst_desc": null,
"tskmst_id": 4444,
"tskmst_lstchgtm": "2014-09-17T16:02:29",
"tskmst_name": "act_test ",
"tskmst_public": "Y",
"tskmst_type": 1
}
]
When sending it through the following view, it's complaining:
[
{
"action_email": [
{
"tskmail_id": "This field is required."
}
]
}
]
But as you can see in my JSON at the top, it's there. So, why does my view not seem to recognize that it's there during serialization?
def put(self, request, format=None):
data = request.DATA
owner = data[0]['owner_name']
ownerid = Owner.objects.filter(owner_name=owner).values_list('owner_id', flat=True)[0]
data[0].update({'owner_id': ownerid})
actid = data[0]['tskmst_id']
actname = data[0]['tskmst_name']
if data[0]['nodmst_name'] == None:
data[0].update({'nodmst_id': None})
else:
nodname = data[0]['nodmst_name']
nodid = Nodmst.objects.filter(nodmst_name=nodname).values_list('nodmst_id', flat=True)[0]
data[0].update({'nodmst_id': nodid})
if data[0]['servicemst_name'] == None:
data[0].update({'servicemst_id': None})
else:
servicename = data[0]['servicemst_name']
serviceid = Servicemst.objects.filter(servicemst_name=servicename).values_list('servicemst_id', flat=True)[0]
data[0].update({'servicemst_id': serviceid})
if Tskmst.objects.filter(tskmst_name=actname).exists():
data[0]['tskmst_id'] = Tskmst.objects.filter(tskmst_name=actname).values_list('tskmst_id', flat=True)[0]
data[0]['action_email'][0]['tskmail_id'] = Tskmst.objects.filter(tskmst_name=actname).values_list('tskmst_id', flat=True)[0]
else:
maxtskid = Tskmst.objects.latest('tskmst_id').tskmst_id
data[0]['tskmst_id'] = maxtskid + 1
data[0]['action_email'][0]['tskmail_id'] = maxtskid + 1
Tblcnt.objects.filter(tblcnt_tblname='tskmst').update(tblcnt_lstid=(maxtskid +1))
Tblcnt.objects.filter(tblcnt_tblname='tskmail').update(tblcnt_lstid=(maxtskid +1))
serializer = self.get_serializer_class()(data=request.DATA, many=True)
if serializer.is_valid():
# serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Here is my serializers that are pulling the data together:
class ActionMailSerializer(serializers.ModelSerializer):
class Meta:
model = Tskmail
resource_name = 'tskmail'
class ActionPUTSerializer(serializers.ModelSerializer):
owner_id = serializers.Field(source='owner_id')
action_email = ActionMailSerializer()
class Meta:
model = Tskmst
resource_name = 'tskmst'
depth = 1
fields = ('tskmst_id', 'tskmst_name', 'tskmst_desc', 'tskmst_type', 'owner_id', 'tskmst_public',
'tskmst_lstchgtm', 'nodmst_id', 'servicemst_id', 'action_email', 'action_alert_details',
'action_snmp_details', 'action_job_details', 'action_log_details', 'action_variable_details')
Posting this for the potential that other people might experience this issue which seems to be pertaining to it being a legacy DB. The problem lies in the fact that the PK field in "tskmail" table is also a FK to "tskmst".
See Multi-Column Primary Key support for more info.
What I ended up doing is making another set of models strictly for PUT that has no FK relationship but is strictly an Integer Field.
class TskmailPOST(models.Model):
tskmail_id = models.IntegerField(primary_key=True)
tskmail_to_int = models.TextField(blank=True)
tskmail_to_ext = models.TextField(blank=True)
tskmail_subject = models.TextField(blank=True)
tskmail_memo = models.TextField(blank=True) # This field type is a guess.
tskmail_priority = models.SmallIntegerField(blank=True, null=True)
tskmail_attach = models.TextField(blank=True)
class Meta:
managed = False
db_table = 'tskmail'
Instead of -
class TskmailPOST(models.Model):
tskmail_id = models.ForeignKey(Tskmst, db_column='tskmail_id', primary_key=True)
tskmail_to_int = models.TextField(blank=True)
tskmail_to_ext = models.TextField(blank=True)
tskmail_subject = models.TextField(blank=True)
tskmail_memo = models.TextField(blank=True) # This field type is a guess.
tskmail_priority = models.SmallIntegerField(blank=True, null=True)
tskmail_attach = models.TextField(blank=True)
class Meta:
managed = False
db_table = 'tskmail'
Then I had to call 2 separate serializers and using the POST data, break it into 2 separate dict files and validate the first, load it then validate the second and load it.

Django REST Framework: define fields in nested object?

I got events that happen at locations:
class Event(models.Model):
title = models.CharField(max_length=200)
date_published = models.DateTimeField('published date',default=datetime.now, blank=True)
date_start = models.DateTimeField('start date')
date_end = models.DateTimeField('end date')
def __unicode__(self):
return self.title
description = models.TextField()
price = models.IntegerField(null=True, blank=True)
tags = TaggableManager()
location = models.ForeignKey(Location, blank=False)
class Location(models.Model):
location_title = models.CharField(max_length=200)
location_date_published = models.DateTimeField('published date',default=datetime.now, blank=True)
location_latitude = models.CharField(max_length=200)
location_longitude = models.CharField(max_length=200)
location_address = models.CharField(max_length=200)
location_city = models.CharField(max_length=200)
location_zipcode = models.CharField(max_length=200)
location_state = models.CharField(max_length=200)
location_country = models.CharField(max_length=200)
location_description = models.TextField()
def __unicode__(self):
return u'%s' % (self.location_title)
I can get the results of all via:
class EventSerializer(serializers.HyperlinkedModelSerializer):
id = serializers.Field()
class Meta:
model = Event
depth = 2
fields = ('url','id','title','date_start','date_end','description', 'price', 'location')
Which outputs:
{
"url": "http://localhost:8000/api/event/3/",
"id": 3,
"title": "Testing",
"date_start": "2013-03-10T20:19:00Z",
"date_end": "2013-03-10T20:19:00Z",
"description": "fgdgdfg",
"price": 10,
"location": {
"id": 2,
"location_title": "Mighty",
"location_date_published": "2013-03-10T20:16:00Z",
"location_latitude": "37.767475",
"location_longitude": "-122.406878",
"location_address": "119 Utah St, San Francisco, CA 94103, USA",
"location_city": "San Francisco",
"location_zipcode": "94103",
"location_state": "California",
"location_country": "United States",
"location_description": "Some place"
}
},
However, I don't want it to grab all fields, as I don't need all of them. How can I define what fields should be retrieved from my nested object? Thanks!
Serializers can be nested, so do something like this...
class LocationSerializer(serializers.ModelSerializer):
class Meta:
model = Location
fields = (...)
class EventSerializer(serializers.HyperlinkedModelSerializer):
id = serializers.Field()
location = LocationSerializer()
class Meta:
model = Event
fields = ('url','id','title','date_start','date_end','description', 'price', 'location')
I have been to this and did not get a perfect solution, But I did something you may check for it.
This method will not create nested serializers
**class LocationSerializer(serializers.ModelSerializer):**
class Meta:
model = Location
fields = (...) #does not matter
exclude = (...) #does not matter
class EventSerializer(serializers.ModelSerializer):**
loc_field_1 = serializers.CharField(required=False,*source='location.loc_field_1'*)
loc_field_2 = serializers.CharField(required=False,*source='location.loc_field_2'*)
***#ADD YOUR DESIRE FIELD YOU WANT TO ACCESS FROM OTHER SERIALIZERS***
class Meta:
model = Event
fields =('url','id','title','date_start','date_end','description', 'price', 'location')
I found this question when I was trying to figure out how to exclude certain fields from a serializer only when it was being nested. Looks like Tasawer Nawaz had that question as well. You can do that by overriding get_field_names. Here's an example based on Tom Christie's answer:
class LocationSerializer(serializers.ModelSerializer):
class Meta:
model = Location
fields = (...)
exclude_when_nested = {'location_title', 'location_date_published'} # not an official DRF meta attribute ...
def get_field_names(self, *args, **kwargs):
field_names = super(LinkUserSerializer, self).get_field_names(*args, **kwargs)
if self.parent:
field_names = [i for i in field_names if i not in self.Meta.exclude_when_nested]
return field_names
class EventSerializer(serializers.HyperlinkedModelSerializer):
id = serializers.Field()
location = LocationSerializer()
class Meta:
model = Event
fields = ('url','id','title','date_start','date_end','description', 'price', 'location')