Django Rest Framework: dynamic database on POST - RelatedField or PrimaryKeyRelatedField - python

I'm developing RESTFul services with DRF and I have multiple databases depending on the country (see my last question here)
I'm having a problem now with relationships, I have two models: Category and SubCategory:
class SubCategory(models.Model):
objects = CountryQuerySet.as_manager()
id = models.AutoField(primary_key=True,db_column='sub_category_id')
name = models.TextField()
female_items_in_category = models.BooleanField()
male_items_in_category = models.BooleanField()
kids_items_in_category = models.BooleanField()
category = models.ForeignKey('Category')
class Meta:
managed = True
db_table = Constants().SUBCATEGORY
And the serializer is:
class SubCategorySerializer(serializers.ModelSerializer):
category = PrimaryKeyRelatedField(queryset=Category.objects.using('es').all())
class Meta:
model = SubCategory
fields = ('id', 'name','female_items_in_category','male_items_in_category','kids_items_in_category','category')
If I don't set the queryset with the proper country it fails, because it doesn't know where to get the category.
Here the problem
I already set the country in the serializer context (in the ModelViewSet):
def get_serializer_context(self):
return {Constants().COUNTRY: self.kwargs.get(Constants().COUNTRY)}
But I can not find the proper way to get the self.context.get(Constants().COUNTRY) in the serializer.
Do you any have an idea to solve this? Thanks!

Well, I found a solution to my problem: I overwrite the method get_fields in the serializer:
def get_fields(self, *args, **kwargs):
fields = super(SubCategorySerializer, self).get_fields()
country = self.context.get(Constants().COUNTRY)
qs = Category.objects.using(country).all()
fields['category'].queryset = qs
return fields
And that works!

Related

DRF: Conditially change serializer

I'm using Django 3.0 and I have a serializer using django-rest-framework. Let's say that for example I have a Forum object. Each forum has an owner that is a user.
In my GET /forums/ endpoint, I'd like to just have the owner_id. However, in my GET /forums/<forum_id>/ endpoint I'd like to return the entire embedded object.
Is there any way to have one serializer support both of these scenarios? If not, I would hate to have to make two serializers just to support this.
class ForumSerializer(serializers.ModelSerializer, compact=True):
if self.compact is False:
owner = UserSerializer(source='owner', read_only=True)
else:
owner_id = serializers.UUIDField(source='owner_id')
...
How can I achieve this compact thing?
class Meta:
fields = [...]
read_only_fields = ['owner', 'owner_id']
You can add a SerializerMethodField like this:
class ForumSerializer(serializers.ModelSerializer):
owner = serializer.SerializerMethodField()
def get_owner(self, obj):
if self.context['is_compact'] == True:
return obj.owner.pk
else:
return UserSerializer(obj.owner).data
class Meta:
model = YourModel
fields = '__all__'
# Usage in view
serializer = ForumSerializer(context={'is_compact':True})
I am passing is_compact value through serializer's extra context.
create two serializer classes
class ForumSerializerId(ModelSerializer):
class Meta:
model = Forum
fields = ['forum_id']
class ForumSerializerDetail(ModelSerializer):
class Meta:
model = Forum
on your view.py
forums(request):
forum_list = Forum.objects.all()
forum_serializer = ForumSerializerId(forum_list,many=True)
return Response({"form":forum_serializer.data})
forum_detail(request,pk):
forum = get_object_or_404(Forum,pk)
forum_serializer = ForumSerializerDetail(forum)
return Response({"form":forum_serializer.data})

How do you add existing objects to a ManyToMany field using Serializer?

I am trying to create an API with Artists and Songs, with a ManyToMany relationship between the two. Using the API to create a Song with an Artist that is not in the database works fine. The problem arises when I attempt to use the POST method to create a new Song with an Artist that already exists in the database. I tried overwriting the SongSerializer create() method using get_or_create() based on another post here, but I kept getting Bad Request errors when the Artist already exists in the database. The relevant code snippets:
models.py
class Artist(models.Model):
artist_name = models.CharField(max_length=200, unique=True)
class Meta:
ordering = ['artist_name']
def __str__(self):
return self.artist_name
class Song(models.Model):
song_title = models.CharField(max_length=200)
artists = models.ManyToManyField(Artist, related_name='songs')
class Meta:
ordering = ['song_title']
def __str__(self):
return self.song_title
serializers.py
class ArtistNameSerializer(serializers.ModelSerializer):
class Meta:
model = Artist
fields = ('artist_name',)
def to_representation(self, value):
return value.artist_name
class SongTitleSerializer(serializers.ModelSerializer):
songs = serializers.PrimaryKeyRelatedField(read_only=True, many=True)
def to_representation(self, value):
return value.song_title
class Meta:
model = Song
fields = ('songs',)
class ArtistSerializer(serializers.HyperlinkedModelSerializer):
songs = SongTitleSerializer(read_only=True, many=True)
class Meta:
model = Artist
fields = ('id', 'artist_name', 'songs')
class SongSerializer(serializers.HyperlinkedModelSerializer):
artists = ArtistNameSerializer(many=True)
class Meta:
model = Song
fields = ('id', 'song_title', 'artists',)
def create(self, validated_data):
artist_data = validated_data.pop('artists')
song = Song.objects.create(**validated_data)
song.save()
for artist_item in artist_data:
a, created = Artist.objects.get_or_create(artist_name=artist_item['artist_name'])
song.artists.add(a)
return song
I've done some tests and it looks like the program doesn't even go into the create() method I'm using, going straight to showing me the Bad Request error. What am I missing? Thanks in advance!
On you Artist model you have a constrain on the artist_model field (unique=True)
if you print the serializer in question with:
print(SongSerializer())
you get something like this:
SongSerializer():
id = IntegerField(label='ID', read_only=True)
song_title = CharField(max_length=200)
artists = ArtistNameSerializer(many=True):
artist_name = CharField(max_length=200, validators=[<UniqueValidator(queryset=Artist.objects.all())>])
under the artist_name field is a Validator "UniqueValidator"
so in case of a write operation you can disable the validator in the serializer with:
class ArtistNameSerializer(serializers.ModelSerializer):
class Meta:
model = models.Artist
fields = ('artist_name',)
extra_kwargs = {
'artist_name': {
'validators': [],
}
}
hope this help..

How to get model data to appear as a field in another model's response

These are simplified versions of my models (the user model is just an id and name)
class Convo(models.Model):
owner = models.ForeignKey(User, on_delete=models.CASCADE, related_name='convo_owner')
users = models.ManyToManyField(User, through='Convo_user')
class Convo_user (models.Model):
user = models.ForeignKey(UserProfile, on_delete=models.CASCADE)
convo = models.ForeignKey(Convo, on_delete=models.CASCADE)
class Comments(models.Model):
name = models.CharField(max_length=255)
content = models.TextField(max_length=1024)
convo = models.ForeignKey(Convo, on_delete=models.CASCADE)
This is my view
class ConvoViewSet(viewsets.ModelViewSet):
serializer_class = serializers.ConvoSerializer
def get_queryset(self):
return None
def list(self, request):
curr_user = request.user.id
# Collecting the list of conversations
conversations = models.Conversation.object.filter(ConvoUser__user_id=request.user.id)
#Getting list of conversation id's
conv_ids = list(conversations.values_list('id', flat=True).order_by('id'))
#Getting list of relevant comments
comments = models.Comments.objects.filter(conversation_id__in=conv_ids)
return Response(self.get_serializer(conversations, many=True).data)
And my current serializer
class ConvoSerializer(serializers.ModelSerializer):
"""A serializer for messaging objects"""
# access = AccessSerializer(many=True)
# model = models.Comments
# fields = ('id', 'name', 'content', 'convo_id')
class Meta:
model = models.Convo
fields = ('id', 'owner_id')
The current response I get is of the form
[
{
"id": 1,
"owner_id": 32
}, ...
]
But I would like to add a comments field that shows all the properties of comments into the response, so basically everything in the second queryset (called comments) and I'm not sure how to go about this at all. (I retrieve the comments in the way I do because I'm trying to minimize the calls to the database). Would I need to create a new view for comments, make its own serializer and then somehow combine them into the serializer for the convo?
The way you've set up your models, you can access the comments of each Convo through Django's ORM by using convo_object.comments_set.all(), so you could set up your ConvoSerializer to access that instance's comments, like this:
class ConvoSerializer(serializers.ModelSerializer):
"""A serializer for messaging objects"""
comments_set = CommentSerializer(many=True)
class Meta:
model = models.Convo
fields = ('id', 'owner_id', 'comments_set')
and then you define your CommentSerializer like:
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = models.Comments
fields = ('id', 'name', 'content')
No data appears because my serializers are using the default database, not sure why but a step forward
EDIT:
Django: Database used for prefetch_related is not the same that the parent query Provided me the correct answer, I was able to choose the database with this method because for some reason inner queries use the default DB

Django REST: Serializer lookup by UUID

I'm creating this simple shopping API in Django REST.
Internally I'm using IDs for foreign key constraints, while guuids are brought to the outside world.
For the checkout procedure, the user provides a list of article IDs he is willing to purchase. The object in the POST data thus looks as follows:
{
assets: [
{
'product': 'd9d5044d-2284-4d15-aa76-2eee3675035b',
'amount': 4
},
....
]
}
I'm using the following ticket/asset models:
# Ticket
class Ticket(models.Model):
uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
owner = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='tickets', on_delete=models.CASCADE)
# Assets
class Asset(models.Model):
ticket = models.ForeignKey(Ticket, related_name='assets', on_delete=models.CASCADE)
stock_item = models.ForeignKey(Stock, related_name='stock_item', on_delete=models.SET_NULL, null=True)
amount = models.IntegerField(validators=[MinValueValidator(0)])
And the serializers look as follows:
# Asset serializer
class AssetSerializer(serializers.ModelSerializer):
class Meta:
model = Asset
fields = ('stock_item', 'amount')
# Ticket serializer
class TicketSerializer(WritableNestedModelSerializer):
owner = serializers.ReadOnlyField(source='owner.username')
assets = AssetSerializer(many=True)
class Meta:
model = Ticket
fields = ('uuid', 'owner', 'assets', )
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
When posting an object of the type specified above, the following error is presented:
{"assets":[{"stock_item": ["Invalid type. Expected PK, received string"]}]}
Which I can't seem to solve, how do I instruct the serializer to use the uuid as the lookup value? I solved a similar problem on view-level earlier by using the lookup_field member, but that doesn't seem to solve it. Any suggestions?
Enter code here
If I have understood you correctly, a SlugRelatedField should be able to find the correct related object.
class AssetSerializer(serializers.ModelSerializer):
ticket = serializers.SlugRelatedField(
read_only=True,
slug_field='uuid',
queryset=Ticket.objects.all() # Might be redundant with read_only=True
)
class Meta:
model = Asset
fields = ('ticket', 'stock_item', 'amount')
Elaborating on #BjornW's comment:
class UUIDRelatedField(serializers.SlugRelatedField):
slug_field = 'uuid'
def __init__(self, **kwargs):
super().__init__(slug_field=self.slug_field, **kwargs)
def to_representation(self, obj):
return getattr(obj, self.slug_field).hex

Django rest framework foreign key constraint fails creating model

I have the following in a Django Rest Framework setup:
models.py:
class Sku(BaseModel):
sku_code = models.CharField(max_length=18, primary_key=True)
supplier_id = models.PositiveIntegerField(db_index=True)
soh = models.PositiveIntegerField(default=0)
reserved = models.PositiveIntegerField(default=0)
broken = models.PositiveIntegerField(default=0)
unallocated = models.PositiveIntegerField(default=0)
reorder = models.PositiveIntegerField(default=0)
class Reservation(BaseModel):
sku = models.ForeignKey(Sku, db_column='sku_code')
order_id = models.PositiveIntegerField(db_index=True)
serializers.py:
class SkuSerializer(serializers.ModelSerializer):
class Meta:
model = Sku
fields = (
'sku_code',
'supplier_id',
'soh',
'reserved',
'broken',
'unallocated',
'reorder',
'created_at',
'modified_at',
)
class ReservationSerializer(serializers.ModelSerializer):
sku = SkuSerializer(read_only=True)
class Meta:
model = Reservation
fields = (
'id',
'order_id',
'sku',
'created_at',
'modified_at'
)
views.py:
class ReservationList(mixins.CreateModelMixin,
generics.GenericAPIView):
queryset = Reservation.objects.all()
serializer_class = ReservationSerializer
def post(self, request, *args, **kwargs):
sku = get_object_or_404(Sku, sku_code=request.data['sku_code'])
request.data['sku'] = sku
return self.create(request, *args, **kwargs)
Now when I post to the url linked to ReservationList.post view above I get the error: IntegrityError: (1048, "Column 'sku_code' cannot be null").
It seems to be bypassing the serializers validation and failing on the database layer. For some reason it doesn't accept the SKU being passed in.
What am I doing wrong here? I have tried to follow the example at http://www.django-rest-framework.org/api-guide/relations/#nested-relationships but this seems to break down with the CreateModelMixin. I can't tell if there is something wrong with how my models or serializers are set up.
You've set the sku field to read only, that's why the serializer is ignoring it when you post.
From the relevant documentation
Read-only fields are included in the API output, but should not be
included in the input during create or update operations. Any
'read_only' fields that are incorrectly included in the serializer
input will be ignored.

Categories

Resources