Django nested serializer giving me None for all fields - django

So I'm working with Django and djangorestframework (versions 3.2.12 and 3.12.4, respectively) and I'm running into an issue with nested serializers.
I've got FantasyLeague and FantasyLeagueSettings models. FantasyLeagueSettings has a FK to FantasyLeague
models.py
class FantasyLeague(models.Model):
name = models.CharField(max_length=100)
managers = models.ManyToManyField(User)
yahoo_league_id = models.CharField(max_length=20, blank=True, null=True)
class FantasyLeagueSettings(models.Model):
league = models.ForeignKey(FantasyLeague, on_delete=models.CASCADE, related_name='settings')
max_teams = models.IntegerField(blank=True, null=True)
num_playoff_teams = models.IntegerField(blank=True, null=True)
playoff_start_week = models.IntegerField(blank=True, null=True)
I'm serializing them like so:
serializers.py
class FantasyLeagueSettingsSerializer(serializers.ModelSerializer):
class Meta:
model = FantasyLeagueSettings
fields = ['max_teams', 'num_playoff_teams', 'playoff_start_week']
class FantasyLeagueSerializer(serializers.ModelSerializer):
managers = UserSerializer(read_only=True, many=True)
settings = FantasyLeagueSettingsSerializer(many=False, read_only=True)
class Meta:
model = FantasyLeague
fields = ['id', 'name', 'managers', 'settings', 'yahoo_league_id' ]
But for some reason FantasyLeagueSerializer(league).data gives me a value of None for all of the settings fields (even though the settings data exists)
Testing code:
league = FantasyLeague.objects.get(pk=1)
print('🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥')
print(FantasyLeagueSerializer(league).data)
# FantasyLeagueSerializer(league).data: {'id': 1, 'name': 'PLAYING FOR KEEPS', 'managers': [OrderedDict([('id', 1), ('username', 'Smurflo'), ('settings', OrderedDict([('selected_theme', 'kanagawa')])), ('is_staff', True)])], 'settings': OrderedDict([('max_teams', None), ('num_playoff_teams', None), ('playoff_start_week', None)]), 'yahoo_league_id': '461051' }
# Note how all of the settings fields are None
# Even though league.settings clearly has data and FantasyLeagueSettingsSerializer works
print('🎆🎆🎆🎆🎆🎆🎆🎆🎆🎆🎆🎆🎆🎆🎆🎆🎆🎆🎆')
print(FantasyLeagueSettingsSerializer(league.settings.all()[0]).data)
# {'max_teams': 12, 'num_playoff_teams': 6, 'playoff_start_week': 15}
Anyone know what's going on here? What am I missing?

The reason for the problem is due to the uncoordinated relationship between FantasyLeague and FantasyLeagueSettings.
if instances of FantasyLeague can have more than one settings, then you should change many=False to many=True in settings field of FantasyLeagueSerializer serializer.
class FantasyLeagueSerializer(serializers.ModelSerializer):
...
settings = FantasyLeagueSettingsSerializer(many=True, read_only=True)
...
but if FantasyLeague's instances can have at most one settings, then you should change league's type to OneToOneField in FantasyLeagueSettings model.
class FantasyLeagueSettings(models.Model):
league = models.OneToOneField(FantasyLeague, on_delete=models.CASCADE, related_name='settings')
...

Related

Django Rest Framework read/write ModelSerializer with ChoiceField

I have a few field in my model "OrderItem" that are ChoiceFields or Enums. I want to represent the label of the choice and doing it with serializers.CharField(source='get_service_display'). See also Django Rest Framework with ChoiceField.
That works fine, but creating objects is not working anymore with following error message OrderItem() got an unexpected keyword argument 'get_service_display'
Here is my model
class OrderItem(TimeStampedModel):
class Categories(models.IntegerChoices):
GRASS = (1, _('grass'))
order = models.ForeignKey(Order, related_name='order_items', on_delete=models.CASCADE)
unit_price = models.DecimalField(max_digits=6, decimal_places=2)
category = models.IntegerField(choices=Categories.choices)
and here my serializer
class OrderItemSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(required=False)
category = serializers.CharField(source='get_category_display')
class Meta:
model = OrderItem
fields = ['id', 'unit_price', 'category']
read_only_fields = ['unit_price']
how can I create a model like this?
Try this:
from rest_framework import fields
from your_app_path.models import Categories
class OrderItemSerializer(serializers.ModelSerializer):
category = fields.ChoiceField(Categories.choices)
One other note. I'm not familiar with sub-classing models just for choices. I usually do it this way:
class OrderItem(TimeStampedModel):
GRASS = 'grass'
CATEGORY_CHOICES = (
(1, _(GRASS)),
)
order = models.ForeignKey(Order, related_name='order_items', on_delete=models.CASCADE)
unit_price = models.DecimalField(max_digits=6, decimal_places=2)
category = models.IntegerField(choices=CATEGORY_CHOICES)

Django Rest Framework - how to get only one field from related models set

I have following models:
from django.db import models
class City(models.Model):
name = models.CharField(max_length=30)
last_update = models.DateTimeField(null=True)
class BusStop(models.Model):
city = models.ForeignKey(City, on_delete=models.CASCADE)
name = models.CharField(max_length=200, blank=True, default='')
Now using Django Rest Framework, I would like to create serializer that will return City details along with the list of all BusStops in the city - but I want the list to be only strings with BusStop names, like this:
{
"id": 1
"name": "City"
"last_update": "2019-09-19T22:13:54.851363Z"
"bus_stops": [
"stop1",
"stop2",
"stop3"
]
}
What I've tried so far is following serializers:
from rest_framework import serializers
class BusStopSerializer(serializers.ModelSerializer):
class Meta:
model = BusStop
fields = ('name', )
class CityDetailsSerializer(serializers.ModelSerializer):
busstop_set = BusStopSerializer(many=True)
class Meta:
model = City
fields = ('id', 'name', 'last_update', 'busstop_set')
But this creates list of objects with 'name' in them. So, how can I create a list with only BusStop names (as strings) in it?
Instead of the extra BusStopSerializer you could use a StringRelatedField:
# models.py
class BusStop(models.Model):
city = models.ForeignKey(City, on_delete=models.CASCADE)
name = models.CharField(max_length=200, blank=True, default='')
def __str__(self):
return self.name
# serializers.py
class CityDetailsSerializer(serializers.ModelSerializer):
bus_stops = StringRelatedField(many=True)
class Meta:
model = City
fields = ('id', 'name', 'last_update', 'bus_stops')
StringRelatedField, as recommended by wfehr, will only work as long as the __str__ method of BusStop only returns the name. An alternative is to use SlugRelatedField which allows you to specify a particular field from the related model, and has no dependency on __str__.
bus_stops = SlugRelatedField(many=True, slug_field='name')

Import column from another table

I made API with Django Restframework.
[models.py]
from django.db import models
class Article(models.Model):
article_no = models.AutoField(primary_key=True)
content = models.CharField(max_length=500, null=False)
password = models.CharField(max_length=20, null=False, default='1234')
date = models.DateTimeField(auto_now_add=True)
class Comment(models.Model):
article_no = models.ForeignKey('Article', on_delete=models.CASCADE)
content = models.CharField(max_length=50, null=False, default='')
password = models.CharField(max_length=20, null=False, default='1234')
date = models.DateTimeField(auto_now_add=True)
[views.py]
class ArticleDetail(APIView):
def get(self, request, article_no, format=None):
try:
article = models.Article.objects.get(article_no=article_no)
serializer = serializers.ArticleDetailSerializer(article)
return Response(status=status.HTTP_200_OK, data=serializer.data)
except models.Article.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
[urls.py]
urlpatterns = [
path('article/<int:article_no>', views.ArticleDetail.as_view(), name='article_detail'),
]
[serializers.py]
class ArticleDetailSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True, required=True)
comment = CommentSerializer(many=True, read_only=True)
class Meta:
model = models.Article
fields = (
'article_no',
'content',
'password',
'date',
'comment',
)
In serializers.py, I defined comment = CommentSerializer(many=True, read_only=True) and add it to fields.
And to test it, I add comment for article_no=1
But When I connect to /article/1, comment doesn't show anything.
I want to show all comments related it's article_no.
How can I fixed it?
Thanks.
Fixed source is here.
[serializers.py]
class ArticleDetailSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True, required=True)
class Meta:
model = models.Article
fields = (
'article_no',
'content',
'password',
'date',
'comments',
)
[models.py]
class Comment(models.Model):
article_no = models.ForeignKey(Article, on_delete=models.CASCADE, related_name='comments')
content = models.CharField(max_length=50, null=False, default='')
password = models.CharField(max_length=20, null=False, default='1234')
date = models.DateTimeField(auto_now_add=True)
When I connect to my server,
It only shows related comment's article_no.
But I want to show content and date also.
According to the Django documentation at https://docs.djangoproject.com/en/2.0/topics/db/queries/#backwards-related-objects you can access the list of objects by calling article_instance.comment_set.all() or you could set the related_name argument on the model on initialization
article_no = models.ForeignKey('Article', on_delete=models.CASCADE, related_name="comments")
and access is like article_instance.comments.all() or filter() or exclude()
There are quite a few options actually, and it depends on the use case, but for simplicity, in this case you may be able to just change the comment variable to comment_set, or you could change the related_name to comments and refer to it as such in your serializer.
required changes to ArticleDetailSerializer...
comment = CommentSerializer(many=True, read_only=True)
to
comments = CommentSerializer(many=True, read_only=True)
You also haven't created a CommentSerializer class, or you haven't posted it to the question.
example CommentSerializer....
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model=Comment
exclude=('article_no',)
I exclude the article_no and the remaining fields should be handled due to the assigned defaults on the django models.
The related object manager in Django returns a queryset that can be acted on like any other queryset. So you will want to consider whether an article might have an absurd amount of comments and limit the returned amount.
you can also use a SerializerMethodField and have more control over the returned queryset
comments = serializers.SerializerMethodField()
def get_comments(self, obj):
comments = obj.comments/comment_set.all()[:20] #return the first 20 comments
return CommentSerializer(comments/comment_set, many=True, read_only=True).data
now add comments/comment_set to the class Meta/fields tuple

Properly using Foreign Key references in search_fields, Django admin

I've got a weird conundrum that I need some help with in Django 1.8.4 using python 3.4 in a virtual-env.
I've got 2 models in 2 different apps... as follows with multiple Foreign Key references.
Inventory App
class InventoryItem(models.Model):
item_unique_code = models.CharField(max_length=256, blank=False, null=False)
category = models.CharField(max_length=256, blank=False, null=False,choices=[('RAW','Raw Material'),('FG','Finished Good'),('PKG','Packaging')])
name = models.CharField(max_length=64, blank=False, null=False)
supplier = models.CharField(max_length=96, blank=False,null=False)
approved_by = models.CharField(max_length=64, editable=False)
date_approved = models.DateTimeField(auto_now_add=True, editable=False)
comments = models.TextField(blank=True, null=True)
def __str__(self):
return "%s | %s | %s" % (self.item_unique_code,self.name,self.supplier)
class Meta:
managed = True
unique_together = (('item_unique_code', 'category', 'name', 'supplier'),)
Recipe App
class RecipeControl(models.Model):
#recipe_name choice field needs to be a query set of all records containing "FG-Finished Goods"
recipe_name = models.ForeignKey(items.InventoryItem, related_name='recipe_name', limit_choices_to={'category': 'FG'})
customer = models.ForeignKey(customers.CustomerProfile, related_name='customer')
ingredient = models.ForeignKey(items.InventoryItem, related_name='ingredient')
min_weight = models.DecimalField(max_digits=16, decimal_places=2, blank=True, null=True)
max_weight = models.DecimalField(max_digits=16, decimal_places=2, blank=True, null=True)
active_recipe = models.BooleanField(default=False)
active_by = models.CharField(max_length=64, editable=False)
revision = models.IntegerField(default=0)
last_updated = models.DateTimeField(auto_now_add=True, editable=False)
def __str__(self):
return "%s" % (self.recipe_name)
class Meta:
managed = True
unique_together = (('recipe_name', 'customer', 'ingredient'),)
I've been getting some weird results in my Recipe's Admin class...
from django.contrib import admin
from django.contrib.auth.models import User
from .models import RecipeControl
from Inventory import models
class RecipeView(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
obj.active_by = request.user.username
obj.save()
fieldsets = [
('Recipe Information', {'fields': ['recipe_name', 'customer']}),
('Ingredients', {'fields': ['ingredient','min_weight','max_weight','active_recipe']}),
('Audit Trail', {'fields': ['active_by','revision','last_updated'],'classes':['collaspe']}),
]
list_select_related = ['recipe_name','customer','ingredient']
search_fields = ['recipe_name','customer','ingredient','active_by']
readonly_fields = ('last_updated','active_by')
list_display = ['recipe_name','customer','ingredient','min_weight','max_weight','last_updated','active_by', 'active_recipe']
admin.site.register(RecipeControl, RecipeView)
The issue I've come across is if I try to do a search on any ForeignKey field, Django throws this error...
Exception Type: TypeError at /admin/Recipe/recipecontrol/
Exception Value: Related Field got invalid lookup: icontains
According to the Django Admin Doc's and other older questions on stackoverflow on the subject it says I should be doing something along the lines of search_fields = ['inventoryitem__name'] but I think this is in reference to FK's in the same app model.py.
Is there a more correct way of referencing/importing other models from other apps that I'm missing or do I have to use some kind of callable method magic to get the search function to look up correctly? I've tried a multitude of different combinations and nothing seems to work. I'm relatively new to Django so I'm confident it's something simple.
You should use the double underscore notation to search a field on a related object. However, you should use the name of the foreign key field (e.g. recipe_name), not the name of the model (e.g. InventoryItem). It doesn't matter whether or not the foreign key's model is in the same app. For example:
search_fields = ['recipe_name__name']
Note that if you want to search the recipe_name and ingredient fields, you need to include both fields, even though they are foreign keys to the same model.
search_fields = ['recipe_name__name', 'ingredient__name']

Filter on nested serializer in django rest framework

I'm building a serializer in django using the django rest framework. I need to filter the query set for a nested model.
I found How do you filter a nested serializer in Django Rest Framework?, which seemed to have the answer, but when I implemented it there was no change in my data. The only difference I can see is that the serializer referencing the filtered list serializer has other fields as well.
The models (abbreviated for clarity):
class GCUser(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
email = models.EmailField()
is_member = models.BooleanField(default=False)
age = models.SmallIntegerField(blank=True, null=True)
session_key = models.CharField(max_length=100, db_index=True, blank=True, null=True)
class Connection(models.Model):
creation_date = models.DateTimeField(auto_now_add=True)
user = models.ForeignKey(GCUser, related_name='user_connection')
event = models.ForeignKey(Event, related_name='event_connection')
role = models.CharField(max_length=8, choices=constants.Roles.ROLE_CHOICES,)
class Event(Game):
creation_date = models.DateTimeField(auto_now_add=True)
public = models.BooleanField(default=False)
start_time = models.DateTimeField(null=True, blank=True)
end_time = models.DateTimeField(null=True, blank=True)
gm_is_player = models.BooleanField(default=False,
help_text='Check if GM will be playing the game',
verbose_name='GM is a player')
gm_is_designer = models.BooleanField(default=False, help_text='Check if GM designed the game')
user_notes = models.TextField(blank=True, default='', verbose_name='Note to Scheduler')
scheduler_notes = models.TextField(blank=True, default='')
experience = models.CharField(max_length=3, choices=constants.ExpLevels.EXPERIENCE_CHOICES,
default=constants.ExpLevels.NOVICE,)
status = models.CharField(max_length=4, db_index=True,
choices=constants.Status.STATUS_CHOICES,)
Here's my code:
class FilteredListSerializer(serializers.ListSerializer):
def to_representation(self, data):
data = data.filter(status=constants.Status.ASSIGNED).order_by('start_time')
return super(FilteredListSerializer, self).to_representation(data)
class UserEventSerializer(serializers.ModelSerializer):
class Meta:
list_serializer_class = FilteredListSerializer
model = models.Event
fields = ('id', 'event_name', 'conflict_type', 'start_time', 'end_time')
class UserConnectionSerializer(serializers.ModelSerializer):
event = UserEventSerializer()
class Meta:
model = models.Connection
fields = ('get_role_display', 'conflict_type', 'event')
class GCUserSerializer(serializers.ModelSerializer):
user_connection = UserConnectionSerializer(many=True)
class Meta:
model = models.GCUser
fields = ('pk', 'first_name', 'last_name', 'email',
'is_member', 'age', 'user_connection')
PyCharm tells me that "class FilteredListSerializer must implement all abstract methods" but it doesn't actually throw an error. I put a breakpoint at the first line of the list serializer, but it doesn't get tripped.
I'm using Python 3.4 with django 1.7.
Thanks in advance for your help.
Edited to add: Looking into the serializer code, I realized what may be the key difference: my call has many=True, whereas the one from the previous post didn't. I tried taking out the model=, but as expected that threw an error, so apparently the "working" code in the earlier post didn't actually run as written.
So I am not sure how to use the method you are using, but, if I understand your question correctly, I believe you could do something like this:
class UserEventSerializer(serializers.ModelSerializer):
class Meta:
model = models.Event
fields = ('id', 'event_name', 'conflict_type', 'start_time', 'end_time')
class UserConnectionSerializer(serializers.ModelSerializer):
event = serializers.SerializerMethodField()
class Meta:
model = models.Connection
fields = ('get_role_display', 'conflict_type', 'event')
def get_event(self, obj):
if obj.event.status == constants.Status.ASSIGNED:
serializer = UserEventSerializer(obj.event)
return serializer.data
else:
serializer = UserEventSerializer(None)
return serializer.data
N.B. This assumes that you are trying to exclude Events from being serialized if their status is not assigned.
I hope this helps. If I didn't understand the problem, let me know.
it's not a bug or error. ModelSerializer has got already implemented all needed methods (most inhereted from Serializer class), but ListSerializer inherits from BaseSerializer and got implemented e.g.: .create() or .to_representation(), but not .update(). I've got some similar problems in PyCharm, when subclassing a Serializer. After implementing create, update and to_representation methods this issue was gone