How to get a relation from a relation in a serializer? - django

I have 3 models:
class Artist(Timestamps):
name = models.CharField('Name', max_length=255, blank=True)
...
class Festival(Timestamps):
name = models.CharField(max_length=255, blank=True)
...
class Event(Timestamps):
artist = models.ForeignKey(Artist, on_delete=models.CASCADE, null=True)
festival = models.ForeignKey(Festival, on_delete=models.CASCADE, null=True)
Now I wan't all the id's from the festivals an artist is playing. I have a serializer like this:
class ArtistFestivalSerializer(serializers.ModelSerializer):
class Meta:
model = Artist
fields = ('id', 'name', 'event_set')
But this only gives me the id's of the event. Any ideas how to get trough the Event to the Festival?
Thanks in advance
EDIT - the view is:
class FestivalArtists(generics.ListAPIView):
serializer_class = ArtistFestivalSerializer
def get_queryset(self):
queryset = Artist.objects.prefetch_related('event_set').filter(event__isnull=False).distinct().order_by('name')
return queryset

I think you need to add the custom field for that.
class ArtistFestivalSerializer(serializers.ModelSerializer):
festival_ids = serializers.SerializerMethodField(read_only = True)
class Meta:
model = Artist
fields = ('id', 'name', 'event_set', 'festival_ids')
def get_festival_ids(self, obj):
return list(Event.objects.filter(artist = obj).values_list('festival_id').distinct())

Related

How do I pull in data from multiple models into a specific model serializer?

I have this model that represents a bookmark or favorite. It has multiple foreign keys to other models. In the api I would like to pull in the data from each of the models that is referenced in the particular bookmark.
The model:
class Bookmark(models.Model):
marktype = models.CharField(max_length=10)
post = models.OneToOneField(Post, on_delete=models.CASCADE, null=True, blank=True)
question = models.OneToOneField(Question, on_delete=models.CASCADE, null=True, blank=True)
owner = models.ForeignKey(User, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True, verbose_name="created at")
updated_at = models.DateTimeField(auto_now=True, verbose_name="updated at")
class Meta:
verbose_name = "bookmark"
verbose_name_plural = "bookmarks"
ordering = ["created_at"]
db_table = "bookmarks"
def __str__(self):
return "{}'s bookmark".format(self.owner.username)
I tried to use a SerializerMethodField but I get an error: 'NoneType' object has no attribute 'id'
Here is the serializer
class BookmarkSerializer(serializers.ModelSerializer):
post = serializers.SerializerMethodField()
question = serializers.SerializerMethodField()
class Meta:
model = Bookmark
fields = '__all__'
def get_post(self, obj):
obj = Post.objects.get(id=obj.post.id)
post = ShortPostSerializer(obj)
return post.data
def get_question(self, obj):
obj = Question.objects.get(id=obj.question.id)
question = ShortQuestionSerializer(obj)
return question.data
what am I doing wrong please?
You can update your serializer like the following (You can short it as you want or use your ShortQuestionSerializer as well instead of QuestionSerializer),
class QuestionSerializer(serializers.ModelSerializer):
class Meta:
model = Question
fields = '__all__'
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = '__all__'
class BookmarkSerializer(serializers.ModelSerializer):
post = PostSerializer()
question = QuestionSerializer()
class Meta:
model = Bookmark
fields = '__all__'

Django Rest Framework prefetching data

I would like to minimize number of queries to get data. I have 3 models:
class SubCategory(models.Model):
game = models.ForeignKey(Game, verbose_name=_("Gra"), on_delete=models.CASCADE)
name = models.CharField(verbose_name=_("Nazwa"), max_length=40)
...
class GameTask(CloneModel):
game = models.ForeignKey(Game, verbose_name='Gra', related_name='tasks', on_delete=models.CASCADE)
name = models.CharField(verbose_name='Nazwa', max_length=200)
subcategory = models.ForeignKey(SubCategory, verbose_name=_("Podkategoria"), on_delete=models.SET_NULL, blank=True, null=True)
class TaskLevel(CloneModel):
name = models.CharField(max_length=50)
master_task = models.ForeignKey(GameTask, related_name='sub_levels', on_delete=models.CASCADE)
My subcategory view looks like:
class SubCategoryList(ListAPIView, PermissionMixin):
permission_classes = (permissions.IsAuthenticated,)
serializer_class = SubCategorySerializer
def get_queryset(self):
return SubCategory.objects.filter(game=self.get_game()).order_by("slug")
and my SubCategorySerializer:
class SubCategorySerializer(serializers.ModelSerializer):
developments = SubCategoryDevelopmentSerializer(many=True, read_only=True)
in_progress = serializers.SerializerMethodField()
class Meta:
model = SubCategory
fields = ("id", "name", "slug", "description", "image", "developments", "in_progress")
def get_in_progress(self, obj: SubCategory):
user = self.context["request"].user
subcategory_tasks = obj.gametask_set.all()
// rest of the logic
All I want to achieve is to return only tasks, which are related to TaskLevel model as master_task. I was trying using
subcategory_tasks = obj.gametask_set.all().prefetch_related("sub_levels")
but the amount of queries was the same. Could somebody give my any hint how to solve this?

retrieving the last instance of a model in another model's modelSerializer in django rest framework

I am creating rest APIs for a website in which users can purchase one of the provided subscriptions.
In this website there is a user-info API which returns the information about the logged in user which can be used to show their info on the website.
The problem is that, the mentioned API's serializer is a modelSerializer on the "User" model and the information that I want to return is the instance of "Subscription" model which the latest instance of "SubPurchase" model refers to.
These are my serializers, models and views.And I need to somehow return the user's current subscription's ID and name along with the user's information. If you have any further questions, ask me in the comments and I'll answer them.
# models.py
class User(AbstractBaseUser, PermissionsMixin):
userID = models.AutoField(primary_key=True)
username = models.CharField(max_length=100, unique=True, validators=[RegexValidator(regex="^(?=[a-z0-9._]{5,20}$)(?!.*[_.]{2})[^_.].*[^_.]$")])
email= models.EmailField(max_length=100, unique=True, validators=[EmailValidator()])
name = models.CharField(max_length=100)
isSuspended = models.BooleanField(default=False)
isAdmin = models.BooleanField(default=False)
emailActivation = models.BooleanField(default=False)
balance = models.IntegerField(default=0)
objects = UserManager()
USERNAME_FIELD = 'username'
class Subscription(models.Model):
subID = models.AutoField(primary_key=True)
nameOf = models.CharField(max_length=50)
price = models.PositiveIntegerField()
salePercentage = models.PositiveIntegerField(default=0)
saleExpiration = models.DateTimeField(default=datetime.datetime.now, blank=True)
def __str__(self):
return f"{self.nameOf}"
class SubPurchase(models.Model):
price = models.PositiveIntegerField()
dateOf = models.DateTimeField(auto_now_add=True)
user = models.ForeignKey(User, null=True, on_delete=models.SET_NULL)
subscription = models.ForeignKey(Subscription, null=True, on_delete=models.SET_NULL)
def __str__(self):
return self.subscription
# serializers.py
class UserInfoSerializer(serializers.ModelSerializer):
class Meta:
model = get_user_model()
fields = ('userID', 'username','email', 'name', 'balance', 'emailActivation', 'isSuspended')
read_only_fields = ('userID', 'username','email', 'name', 'balance', 'emailActivation', 'isSuspended')
# views.py
class UserInfoViewSet(viewsets.ModelViewSet):
queryset = get_user_model().objects.all()
serializer_class = UserInfoSerializer
def get_queryset(self):
uID = getattr(self.request.user,'userID')
return get_user_model().objects.filter(userID=uID)
def get_object(self):
uID = getattr(self.request.user,'userID')
return self.queryset.filter(userID=uID)
Again, I need to change the UserInfoSerializer in a way that would give me the user's current subscription's name, ID and expiration date which would be 30 days after the purchase date
If you are only interested in the returned data, you can override the function to_representation of your serializer and create a serializer for your related model. If I understood correctly, the current subscription of your user is the last one (if sorted by "dateOf"). So something like that could do the trick
class SubscriptionSerializer(serializers.ModelSerializer):
class Meta:
model = Subscription
fields = ('nameOf', 'id', 'saleExpiration ')
class UserInfoSerializer(serializers.ModelSerializer):
class Meta:
model = get_user_model()
fields = ('userID', 'username','email', 'name', 'balance', 'emailActivation', 'isSuspended')
read_only_fields = ('userID', 'username','email', 'name', 'balance', 'emailActivation', 'isSuspended')
def to_representation(self, instance):
data = super().to_representation(instance)
current_subs = instance.subpurchase_set.order_by('dateOf').last().subscription
data['current_subscription'] = SubscriptionSerializer(instance=current_subs).data
return data
you can use NestedSerializers to achieve what you are looking for
basically, nested serialization is a method in which you can return, create, put..., into a model from another model, it goes like this..
models.py
class User(AbstractBaseUser, PermissionsMixin):
....
#user model data
class SubPurchase(models.Model):
...
user = models.ForeignKey(User, null=True, blank=True, on_delete=models.CASCADE)
serializers.py
class SubscriptionSerializer(serializers.ModelSerializer):
class Meta:
model = Subscription
fields =["anyfield you wanna include"]
class SubPurchaseSerializer(serializers.ModelSerializer):
class Meta:
model = SubPurchase
fields =["anyfield you wanna include"]
class UserInfoSerializer(serializers.ModelSerializer):
subpurchace = SubPurchaseSerializer()
subscription= SubscriptionSerializer() #later included in the fields of this serializer
class Meta:
model = get_user_model()
fields = ('userID','subpurchace', 'subscription', 'username','email', 'name', 'balance', 'emailActivation', 'isSuspended')
read_only_fields = ('userID', 'username','email', 'name', 'balance', 'emailActivation', 'isSuspended')

how to use RelatedField in Django rest framework?

I have problem with Django restframe work i have 2 table that one of them is a foreign key to another i have used RelatedField in serializer but i get an error:'Relational field must provide a queryset argument,
can someone help me in this case
my code is as below:
class DocTable(models.Model):
project = models.CharField(max_length=1000, null=True, blank=True)
document_no = models.CharField(max_length=1000, null=True, blank=True)
document_title = models.TextField(null=True, default='', blank=True)
class PlanTable(models.Model):
document = models.ForeignKey(DocTable, on_delete=models.CASCADE, related_name='doctable')
work_type = models.CharField(max_length=1000, null=True, blank=True)
description_work = models.TextField(null=True, default='', blank=True)
serializers.py
class DocTableSerializer(serializers.ModelSerializer):
doctable = serializers.RelatedField(many=True)
class Meta:
model = DocTable
fields = ['pk', 'project', 'document_no', 'doctable']
read_only_fields = ['pk']
class PlanTableSerializer(serializers.ModelSerializer):
class Meta:
model = PlanTable
fields = ['pk', 'document', 'work_type', 'description_work']
read_only_fields = ['pk']
views.py
class DocTableListView(generics.ListAPIView):
lookup_field = 'pk'
serializer_class = DocTableSerializer
def get_queryset(self):
return PlanTable.objects.all()
def get_object(self):
pk = self.kwargs.get('pk')
return PlanTable.objects.get(pk=pk)
You have to provide queryset in RelatedField like this.
class DocTableSerializer(serializers.ModelSerializer):
doctable = serializers.RelatedField(many=True, queryset=DocTable.objects.all())
Or if you only want to use this related field for retrieving data, you can mark it as read only
doctable = serializers.RelatedField(many=True, read_only=True)

Django REST Framework join table results in attribute exception

I am building an API in Django using REST Framework but am running into an issue.
Serializers:
class SquadSerializer(serializers.Serializer):
class Meta:
model = Squad
fields = ('name')
id = serializers.IntegerField(read_only=True)
name = serializers.CharField(style={'base_template': 'textarea.html'})
class MembershipSerializer(serializers.Serializer):
class Meta:
model = Membership
fields = ('employee_id', 'squad_id')
squad = SquadSerializer()
employee = EmployeeSerializer()
class EmployeeSerializer(serializers.HyperlinkedModelSerializer):
habitat = HabitatSerializer()
class Meta:
model = Employee
fields = ('id', 'first_name', 'last_name', 'function',
'start_date', 'end_date', 'visible_site', 'habitat')
Models:
class Employee(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=40)
function = models.CharField(max_length=50)
start_date = models.DateField()
end_date = models.DateField(null=True, blank=True)
visible_site = models.BooleanField()
habitat = models.ForeignKey(Habitat, on_delete=models.SET_NULL, null=True, blank=True)
class Squad(models.Model):
name = models.TextField(max_length=40)
class Membership(models.Model):
class Meta:
unique_together = (('employee', 'squad'))
employee = models.ForeignKey(Employee, on_delete=models.CASCADE, null=False, blank=True, default=1)
squad = models.ForeignKey(Squad, on_delete=models.CASCADE, null=False, blank=True, default=1)
The problem is that I keep running into this error:
AttributeError: Got AttributeError when attempting to get a value for field `name` on serializer `SquadSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `Membership` instance.
Original exception text was: 'Membership' object has no attribute 'name'.
When executing this test (and a couple others)
def test_membership_serializer_id_name_field_content(self):
"""
The name field of a squad should contain an id
"""
serializer = create_membership_serializer(self.membership, '')
self.assertEqual(serializer.data['id'], self.membership.id)
I've seen multipe people with the same issues here on Stack Overflow but the often suggest solution (to add many=True to SquadSerializer() and EmployeeSerializer()) doesn't work. I hope anyone here has any knowledge on why this happens.
If you want to map your seriailizer to your model, you should use ModelSerializer. In tupple, if it has only one value, you should write it as (1,) not (1). Your SquadSerializer should be like
class SquadSerializer(serializers.ModelSerializer):
class Meta:
model = Squad
fields = ('name',) # or ('id', 'name')
id = serializers.IntegerField(read_only=True)
name = serializers.CharField(style={'base_template': 'textarea.html'})
Your MembershipSerializer should be like
class MembershipSerializer(serializers.ModelSerializer):
class Meta:
model = Membership
fields = ('employee', 'squad')
squad = SquadSerializer()
employee = EmployeeSerializer()