Django Serialization - Partial update on nested objects - python

Searched around for a few hours on this and I am surprised I couldn't find an answer but here it goes.
Let's say I have the following models:
class Mission(models.Model):
mission_name = models.CharField(max_length=150)
...
class Player(models.Model):
player_name = models.CharField(max_length=150, unique = True)
state = models.CharField(max_length=150)
currentMission = models.ForeignKey(Mission,on_delete=models.SET_NULL, blank=True, null=True))
Objectives:
When creating a mission, I would like to provide the players' names that are going to participate on this mission (Names are unique). That means, when mission is created, I have to update the currentMission field of each given player. (Players already exist when mission is created)
When I try to GET a mission, I would like to see the names of the players that participate
My attempt
Class MissionSerializer(serializers.ModelSerializer):
#This is to get a list of the players that participate in this mission
players = PlayerSerializer(many=True, read_only=True)
class Meta:
model= Mission
fields = ['mission_name','players']
def create(self,validated_data):
mission = Mission.objects.create(**validated_data)
# Notice that I get the "players" data from "self.initial_data" and not from validated_data
# This is because validated_data does not have the "players" field,
# (I guess because I didn't provide all the required fields on my request for the player. I just provided the players' names )
players_data = self.initial_data['players']
#Update each player's current mission
for player_data in players_data:
qs = Player.objects.filter(player_name=player_data['player_name'])
obj = get_object_or_404(qs)
serializer = PlayerSerializer(obj, data={'currentMission': mission.id}, partial=True)
if (serializer.is_valid()):
serializer.save()
class PlayerSerializer(serializers.ModelSerializer):
class Meta:
model = Player
fields = ('__all__')
def update(self, instance, validated_data):
instance.currentMission = validated_data['currentMission']
instance.save()
return instance
The above works for objective #1. However, it does not work for objective #2. That is, when I GET a mission, the list of players is not present on the result.
My question is, how could I also retrieve this list when performing GET requests?

I think that players field lacks a source. Adding a source to the field should solve your problem.
players = PlayerSerializer(many=True, read_only=True, source='player_set')
Also, I'd recommend to prefetch that players to optimize the database query.
In your view;
mission_queryset = Mission.objects.filter(...).prefetch_related('player_set')

Related

A Model is linked to two other models. How do I access the properties of that two other models in django

I am in a process of creating a Room Booking Management System using Django. I have faced an issue in accessing models.
Here is my Room Model
class Room(models.Model):
name = models.CharField(max_length=30)
date = models.DateField()
defined_check_in_time = models.IntegerField()
defined_check_out_time = models.IntegerField()
booked = models.BooleanField(default = False)
USERNAME_FIELD = 'name'
class Meta:
ordering = ['date', 'defined_check_in_time']
def __str__(self):
return self.name
def is_booked(self):
return self.booked
def set_booked(self):
self.booked = True
Here is my Booking Model
class Booking(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
room = models.ForeignKey(Room, on_delete=models.CASCADE)
def __str__(self):
return f'{self.user} has booked {self.room} from {self.room.defined_check_in_time} to {self.room.defined_check_out_time} on {self.room.date}'
I have linked the User model and Room model in the Booking model using a foreign key when a user books a room. User model is defined in another file. I have not included that model code here.
When a user books a room, the corresponding room object and user object is linked to the booking object. To display the bookings of the user, I need to query the Booking model using a User object.
Here, my question is how i can access user and room object attributes inside the booking object?
To access information within your booking object, you must first select a booking object, whichever way you choose to identify a booking.. maybe by an id. Let's say booking id=100
booking = Booking.objects.get(id=100)
Now to find foreign key info you can just . lookup.
booking_user = booking.user.username
booking_room = booking.room.name
or to find any bookings on rooms booked:
booked_rooms = Booking.objects.filter(room.booked == True)

how to populate a CreateView field based on count of model instances in Django

I have a model for players on a ladder.
class Player(models.Model):
# Availability choice list
available = 'AVL'
injured = 'INJ'
away = 'AWY'
retired = 'RET'
AVAILABILITY_CHOICES = [
(available, 'available'),
(injured, 'injured'),
(away, 'out of town'),
(retired, 'retired')
]
# Player fields
first = models.CharField('First Name', max_length=30)
last = models.CharField('Last Name', max_length=30)
cell = models.CharField('Cell Phone', max_length=12)
email = models.EmailField('Email') # changed field type
availability = models.CharField('Availability', choices = AVAILABILITY_CHOICES, \
max_length = 15, default='AVL')
ranking = models.IntegerField(default = 99)
I am also using a CreateView to create a new player.
class PlayerCreateView(CreateView):
model =Player
num_players = Player.objects.all().count()
print('There are {} players'.format(num_players))
context_object_name = 'player'
fields = ['ranking', 'first', 'last', 'cell', 'email', 'availability']
template_name = 'player_create.html'
success_url = reverse_lazy('players')
I can't believe I'm asking this (still new to Django) but I haven't found a way to pre-populate the player's ranking field. So basically I need to count the number of existing players and populate the ranking field. It can't be based on ID because some players may have been deleted.
In the model I tried using a variable for the default, but Django threw a fit. (NameError: name 'Player' is not defined) ... evidently because you can't reference Player within the class definition of Player.
There must be a "djangonista" way of doing this simple thing.
Well, you can prepopulate the form in the create view by overriding get_initial:
class PlayerCreateView(CreateView):
model = Player
# ...
def get_initial(self):
initial = super().get_initial()
initial["ranking"] = Player.objects.count() # whatever formula
return initial
If you want a dynamic default value in the model you can specify a callable as default:
def default_ranking():
return Player.objects.count()
class Player(models.Model):
ranking = models.IntegerField(default=default_ranking) # no parentheses!
This will not fill the form field, however, but only come into effect if an instance is saved without a ranking value.

What is the "instance" being passed to the to_representation function of my ListSerializer?

The goal of this project is to create an API that refreshes hourly with the most up to date betting odds for a list of games that I'll be scraping hourly from the internet. The goal structure for the JSON returned will be each game as the parent object and the nested children will be the top 1 record for each of linesmakers being scraped by updated date. My understanding is that the best way to accomplish this is to modify the to_representation function within the ListSerializer to return the appropriate queryset.
Because I need the game_id of the parent element to grab the children of the appropriate game, I've attempted to pull the game_id out of the data that gets passed. The issue is that this line looks to be populated correctly when I see what it contains through an exception, but when I let the full code run, I get a list index is out of range exception.
For ex.
class OddsMakerListSerializer(serializers.ListSerializer):
def to_representation(self, data):
game = data.all()[0].game_id
#if I put this here it evaluates to 1 which should run the raw sql below correctly
raise Exception(game)
data = OddsMaker.objects.filter(odds_id__in = RawSQL(''' SELECT o.odds_id
FROM gamesbackend_oddsmaker o
INNER JOIN (
SELECT game_id
, oddsmaker
, max(updated_datetime) as last_updated
FROM gamesbackend_oddsmaker
WHERE game_id = %s
GROUP BY game_id
, oddsmaker
) l on o.game_id = l.game_id
and o.oddsmaker = l.oddsmaker
and o.updated_datetime = l.last_updated
''', [game]))
#if I put this here the data appears to be populated correctly and contain the right data
raise Exception(data)
data = [game for game in data]
return data
Now, if I remove these raise Exceptions, I get the list index is out of range. My initial thought was that there's something else that depends on "data" being returned as a list, so I created the list comprehension snippet, but that doesn't resolve the issue.
So, my question is 1) Is there an easier way to accomplish what I'm going for? I'm not using a postgres backend so distinct on isn't available to me. and 2) If not, its not clear to me what instance is that's being passed in or what is expected to be returned. I've consulted the documentation and it looks as though it expects a dictionary and that might be part of the issue, but again the error message references a list. https://www.django-rest-framework.org/api-guide/serializers/#overriding-serialization-and-deserialization-behavior
I appreciate any help in understanding what is going on here in advance.
Edit:
The rest of the serializers:
class OddsMakerSerializer(serializers.ModelSerializer):
class Meta:
list_serializer_class = OddsMakerListSerializer
model = OddsMaker
fields = ('odds_id','game_id','oddsmaker','home_ml',
'away_ml','home_spread','home_spread_odds',
'away_spread_odds','total','total_over_odds',
'total_under_odds','updated_datetime')
class GameSerializer(serializers.ModelSerializer):
oddsmaker_set = OddsMakerSerializer(many=True, read_only=True)
class Meta:
model = Game
fields = ('game_id','date','sport', 'home_team',
'away_team','home_score', 'away_score',
'home_win','away_win', 'game_completed',
'oddsmaker_set')
models.py:
class Game(models.Model):
game_id = models.AutoField(primary_key=True)
date = models.DateTimeField(null=True)
sport=models.CharField(max_length=256, null=True)
home_team = models.CharField(max_length=256, null=True)
away_team = models.CharField(max_length=256, null=True)
home_score = models.IntegerField(default=0, null=True)
away_score = models.IntegerField(default=0, null=True)
home_win = models.BooleanField(default=0, null=True)
away_win = models.BooleanField(default=0, null=True)
game_completed = models.BooleanField(default=0, null=True)
class OddsMaker(models.Model):
odds_id = models.AutoField(primary_key=True)
game = models.ForeignKey('Game', on_delete = models.CASCADE)
oddsmaker = models.CharField(max_length=256)
home_ml = models.IntegerField(default=999999)
away_ml = models.IntegerField(default=999999)
home_spread = models.FloatField(default=999)
home_spread_odds = models.IntegerField(default=9999)
away_spread_odds = models.IntegerField(default=9999)
total = models.FloatField(default=999)
total_over_odds = models.IntegerField(default=999)
total_under_odds = models.IntegerField(default=999)
updated_datetime = models.DateTimeField(auto_now=True)
views.py:
class GameView(viewsets.ModelViewSet):
queryset = Game.objects.all()
serializer_class = GameSerializer
Thanks
To answer the question in the title:
The instance being passed to the Serializer.to_representation() is the instance you pass when initializing the serializer
queryset = MyModel.objects.all()
Serializer(queryset, many=True)
instance = MyModel.objects.all().first()
Serializer(data)
Usually you don't have to inherit from ListSerializer per se. You can inherit from BaseSerializer and whenever you pass many=True during initialization, it will automatically 'becomeaListSerializer`. You can see this in action here
To answer your problem
from django.db.models import Max
class OddsMakerListSerializer(serializers.ListSerializer):
def to_representation(self, data): # data passed is a queryset of oddsmaker
# Do your filtering here
latest_date = data.aggregate(
latest_date=Max('updated_datetime')
).get('latest_date').date()
latest_records = data.filter(
updated_date_time__year=latest_date.year,
updated_date_time__month=latest_date.month,
updated_date_time__day=latest_date.day
)
return super().to_representation(latest_records)

How to add another field in connection with a ManyToManyField?

class Team(models.Model):
college = models.CharField(max_length=20)
image = models.FileField(upload_to='documents/',null=True)
def __str__(self):
return self.college
class Athletics(Match):
time = ?
player = models.ManyToManyField(Player, related_name='player')
game_level = models.CharField(max_length=256, null=True, choices=LEVEL_CHOICES) # like semi-final, final etc
game_specific = models.CharField(max_length=256,null=True, choices=EVENT_CHOICES) #like Men's Shot Put or Men's Triple Jump etc
def __str__(self):
return str(self.game_level)
I am making an Athletics Sports Model for a sports tournament. I have an existing Player Model where I have listed players names and their teams. Now as you can see, I have added player field as ManyToManyField to choose for say 4-5 players match. Now I need finishing time for each player for judging who qualifies for the next round. Can it be possible to do this in this model only? Or I have to add another model? Help me!
You need an intermediate model between Player and Athletics to record information specific to the many-to-many relationship of the two models. Please read Extra fields on many-to-many relationships.
class PlayeAthletics(models.Model):
player = models.ForeignKey(Player, on_delete=models.CASCADE)
athletics = models.ForeignKey(Athletics, on_delete=models.CASCADE)
time = models.IntegerField()
class Athletics(Match):
player = models.ManyToManyField(Player, through='PlayerAthletics')

display foreign key in parent class

I could not show reverse relation in Django Rest Framework. Here in my case, a rent can have multiple images and i want to display all the images of that rent(living room image, kitchen image, bathroom image etc) in /api/v1/rent, i mean in rent resource.
So, how do I fetch these Galleries to display in the Rental resource?
class Rental(models.Model):
user = models.ForeignKey(User)
name = models.CharField(max_length=300, blank=False, null=False)
phone_number = models.PositiveIntegerField(null=False, blank=False)
renter = models.CharField(choices=RENTER_CHOICES, max_length=1, default=RENTER_CHOICES[0])
def __str__(self):
return self.name
def user_directory_path(instance, filename):
return 'rent_{0}/{1}'.format(instance.rent.id, filename)
class Gallery(models.Model):
rent = models.ForeignKey(Rental, related_name="rent")
image = models.FileField(upload_to=user_directory_path)
tag = models.CharField(max_length=1, choices=TAGS, null=True, blank=True)
class GallerySerializer(serializers.ModelSerializer):
rent = serializers.ReadOnlyField()
class Meta:
model = Gallery
fields = ('rent', 'image', 'tag',)
class RentalSerializer(serializers.ModelSerializer):
user = serializers.ReadOnlyField(source='user.username')
# gallery = serializers.SerializerMethodField('get_children')
#
# def get_children(self, obj):
# print ('obj', obj.gallery_set)
# serializer = GallerySerializer(obj.gallery_set.all(), many=True)
# return serializer.data
gallery = GallerySerializer(source='gallery_set',many=True, read_only=True)
class Meta:
model = Rental
fields = ('user', 'name', 'phone_number','gallery',)
Right now i dont get the list of gallery, using both way, one commented way and another source='gallery_set' way.
UPDATE
if i do rent = GallerySerializer(many=True) i get the list of galleries but does it make any sense?
[
{
"user": "admin",
"name": "Sunrise Home",
"phone_number": 9842333833,
"rent": [
{
"image": "http://localhost:8000/media/rent_1/sittingRoom.jpg",
"tag": "L"
}
]
}
]
in above api you see the galleries are shown but the name shows it as a list of rent inside rental resource. Can anyone help me to design an api better way?
When you specify the foreign key relationship for Rental in your Gallery model the related_name you specify automatically defines a field on the Rental object. The name of that field is whatever you set related_name to, in this case rent.
The important thing to understand here is that the related_name will be attached to the opposite side of the relationship from where the key is declared in your models. Because the related name is going to be a property of a Rental object, a better name for it might be galleries in your example.
Assuming you change the related_name in your models from rental to galleries, you can define your Rental serializer to output a list of associated galleries:
class RentalSerializer(serializers.ModelSerializer):
user = serializers.ReadOnlyField(source='user.username')
galleries = GallerySerializer(many=True, read_only=True)
class Meta:
model = Rental
fields = ('user', 'name', 'phone_number','galleries')
By declaring a GallerySerializer with the same name as the related_name defined in your foreign key relationship, it will automatically find only galleries which are associated with the Rental being serialized.
In another example:
Let's say you have several Eggs in a Basket. We would want to create a many-to-one relationship between Eggs and a Basket and would accomplish this by creating a foreign-key relationship between the two. That would be in the form of storing the ID of the associated Basket for each Egg.
In Django, we would declare that foreign key on the Egg model. This allows us to access the basket that an egg is in via egg.basket. We also want to determine what eggs are in a particular basket. We do that by defining a related_name on the foreign-key field. This tells Django what the field linking the basket to all its contained eggs should be called. Since this field refers to all eggs in a basket, we would call it eggs.
In code:
class Basket(models.Model):
color = models.CharField()
class Egg(models.Model):
color = models.CharField()
basket = models.ForeignKey(Basket, related_name="eggs")

Categories