Django REST foreign key issue - django-rest-framework

Trying to implement and test my own serializer, I have the following issue:
Searched for many question including this error message, I didn't manage to find any solution to my problem.
DRF Foreign key misusage?
ValueError: Cannot assign "'effcad53-bc45-41fa-be43-4f22c0376eb5'": "Product.related_workspace" must be a "Workspace" instance.
The Workspace class:
class Workspace(models.Model):
id = models.UUIDField(
primary_key=True,
default=uuid.uuid4,
)
related_login = models.ForeignKey(
Login,
on_delete=models.PROTECT,
)
description = models.CharField(
max_length=150,
)
def __str__(self):
login = Login.objects.get(pk=self.related_login_id)
return f'{login.username} ({self.description})'
class Meta:
db_table = 'workspaces'
The Product class:
class Product(models.Model):
id = models.UUIDField(
primary_key=True,
default=uuid.uuid4,
)
related_workspace = models.ForeignKey(
Workspace,
on_delete=models.PROTECT,
)
code = models.CharField(
max_length=50,
)
description = models.CharField(
max_length=150,
)
class Meta:
db_table = 'products'
unique_together = ('related_workspace', 'code',)
The ProductSerializer class:
class ProductSerializer(serializers.Serializer):
id = serializers.UUIDField(read_only=True)
related_workspace = serializers.UUIDField()
code = serializers.CharField()
description = serializers.CharField()
def create(self, validated_data):
return Product.objects.create(**validated_data)
def update(self, instance, validated_data):
instance.related_workspace = validated_data.get('related_workspace', instance.related_workspace)
instance.code = validated_data.get('code', instance.code)
instance.description = validated_data.get('description', instance.description)
instance.save()
return instance
The script I'm using to test my serializer :
PROJECT_NAME = 'api'
def main():
#
from api.applications.product.models import Product
from api.applications.product.serializers import ProductSerializer
#
# Create a product
#
code = 'P001'
description = f'Product {code}'
#
# This is a valid workspace id!
#
related_workspace = 'effcad53-bc45-41fa-be43-4f22c0376eb5'
#
product = Product(
code=code,
description=description,
related_workspace=related_workspace,
)
product.save()
#
serializer = ProductSerializer(product)
print('serializer.data:', serializer.data)
if __name__ == '__main__':
import os
os.environ.setdefault('DJANGO_SETTINGS_MODULE', '%s.settings' % PROJECT_NAME)
import django
django.setup()
main()
Any advice of what I'm missing ?

First thing, when you are testing anything in Django its easiest to write test classes in a test module and use manage.py to run them. All that setup logic you are doing is already in manage.py.
python manage.py test my_app.tests.my_test_class
In your serializer you are setting a foreign key to related_workspace = serializers.UUIDField(), instead you want to nest your serializers (docs) so it pulls out the Workspace data, serializes, saves, and returns a Workspace object. You should have two serializers and they should look something like this, I am going to use ModelSerializer.
from rest_framework import serializers
# Workspace Serializer
class WorkspaceSerializer(serializers.ModelSerializer):
class Meta:
model = Workspace
fields = "__all__"
class ProductSerializer(serializers.ModelSerializer):
related_workspace = WorkspaceSerializer()
class Meta:
model = Product
fields = ["id", "related_workspace", "code", "description"]
def create(self, validated_data):
workspace_data = validated_data.pop('related_workspace')
workspace_serializer = WorkspaceSerializer(data=workspace_data)
if workspace_serializer.is_valid(reaise_exception=True):
workspace = workspace_serializer.save()
product = Product.objects.create(related_workspace = workspace, **validated_data)
return product
We want to remove the workspace data, then feed it to its own serializer, if that data is valid, we save the serializer and we get a Workspace object in return. Then we can create our Product object with that returned workspace object.

Searching deeper, my mistake was in the way I was creating my Product instance...
In my test script, I just updated:
product = Product(
code=code,
description=description,
related_workspace=related_workspace,
)
To:
product = Product(
code=code,
description=description,
related_workspace=Workspace.objects.get(pk=related_workspace)
)

Related

TypeError: Field 'id' expected a number but got <class 'rest_framework.fields.CurrentUserDefault'>

Seems I need to apply a dot notation to CurrentUserDefault() object, tried .id but failed
class DotPrivateSerializer(serializers.ModelSerializer):
tag = serializers.SerializerMethodField()
def get_tag(self,obj):
queryset=TagPrivate.objects.filter(user=serializers.CurrentUserDefault) # <--TypeError
return TagPrivateSerializer(queryset).data
models.py
class DotPrivate(models.Model):
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE
)
name = models.CharField(max_length=255)
description = models.TextField(max_length=350, blank=True)
lon = models.CharField(max_length=20)
lat = models.CharField(max_length=20)
rating = models.FloatField(validators=[MinValueValidator(0.0), MaxValueValidator(5.0)])
tag = models.ForeignKey('TagPrivate', on_delete=models.PROTECT)
in the following link in the first answer I found some solution but I do not completly understand it:
'CurrentUserDefault' object has no attribute 'user'
class TagPrivateSerializer(serializers.ModelSerializer):
class Meta:
model = TagPrivate
fields = ('id', 'name')
read_only_fields = ('id',)
You can not use CurrentUserDefault, this is just a value that the Django serializer will use for a default=… parameter, and then later swap for the request user.
You can fetch this from the request in the context, so:
class DotPrivateSerializer(serializers.ModelSerializer):
tag = serializers.SerializerMethodField()
def get_tag(self, obj):
queryset = TagPrivate.objects.filter(user=self.context['request'].user)
return TagPrivateSerializer(queryset).data
In the ModelViewSet, you will need to pass the user, so:
class DotPrivateViewSet(ModelViewSet):
queryset = # …
permission_classes = # …
serializer_class = DotPrivateSerializer
def get_serializer_context(self):
context = super().get_serializer_context()
context.update(request=self.request)
return context

DRF - Add User to a model with ManyToManyField

I am trying to implement a feature to my backend and allow the owner of private "Group" to add other users by their usernames instead of ID's and allow them to add their images to FileField only once after they were added to the model. The code I have so far:
models.py
class Group(models.Model):
group_name = models.CharField(max_length=255)
group_text = models.TextField(max_length=360, blank=True)
owner = models.ForeignKey(
settings.AUTH_USER_MODEL, related_name='owner_user', on_delete=models.SET(get_deleted_user), default=1)
created_on = models.DateTimeField(auto_now_add=True, null=True)
shared_to = models.ManyToManyField(UserProfile, blank=True, related_name='shared_to_user', null=True)
def __str__(self):
return self.group_name
def save(self, *args, **kwargs):
super(Group, self).save(*args, **kwargs)
class GroupImage(models.Model):
group_file = models.FileField(blank=True, null=True,
upload_to='media/covers/%Y/%m/%D/')
gallery_group = models.ForeignKey(Group, related_name='images', on_delete=models.CASCADE)
serializers.py
class GroupImageSerializer(serializers.ModelSerializer):
class Meta:
model = models.GroupImage
fields = ('group_file', )
class SharedToSerializer(serializers.ModelSerializer):
class Meta:
model = models.Group
fields = ('shared_to', )
class GroupSerializer(serializers.ModelSerializer):
images = GroupImageSerializer(many=True, read_only=True)
person = SharedToSerializer(many=True, read_only=True)
class Meta:
model = models.Group
fields = ('id', 'group_name', 'group_text', 'person', 'images')
def create(self, validated_data):
images_data = self.context.get('view').request.FILES
owner_id = self.context['request'].user.id
gallery_group = models.Group.objects.create(group_name=validated_data.get('group_name', 'no-
group_name'), group_text=validated_data.get('group_text'), owner_id=1)
for image_data in images_data.values():
models.GroupImage.objects.create(gallery_group=gallery_group,
group_file=image_data)
return gallery_group
views.py
class GroupCreateAPIView(generics.CreateAPIView):
queryset = models.Group.objects.all()
serializer_class = serializers.GroupSerializer
permission_classes = [AllowAny]
So if your only requirement is how to add users by their username and not their id. You should use SlugRelatedField. I also feel your serializer naming convention is quite confusing. Below is the serializer for Group model that can add users to a group.
class GroupSerializer(Serializer):
... other fields here
shared_to = models.SlugRelatedField(queryset = UserProfile.objects.all(), many=True, slug_field="username", allow_empty=True)
So first checkout SlugRelatedField. This basically is used to map to objects using a specific field of that object(username in this case). You will then get all the UserProfile instances in the shared_to field of the validated_data
property of the serializer which you can fetch in create method and add to you group. And then in the file upload api for your group you can check whether this user belongs to the group or not for permission checking.

How to restrict fields when creating post request in DRF?

I am making a POST api using DRF. In that api, I need only few fields(name, size, customer_name, customer_address), but don't require this fields(status, ordered_time) because these fields I want to save these fields in run time as status='open' and ordered_time=DateTimeField.now()
views.py
class PizzaOrderCustomerView(APIView):
def post(self, request):
orders = request.data.get('orders')
# Create an article from the above data
serializer = ArticleSerializer(data=orders)
if serializer.is_valid(raise_exception=True):
article_saved = serializer.save()
return Response({"success": "Article '{}' created successfully".format(article_saved.name)})
models.py
class PizzaOrder(models.Model):
name = models.CharField(max_length=120)
size = models.CharField(max_length=10, choices=SIZE_CHOICE, default='MEDIUM')
customer_name = models.CharField(max_length=120)
customer_address = models.TextField()
ordered_time = models.DateTimeField(default=timezone.now, editable=False)
status = models.CharField(max_length=20, default='open', editable=False)
serializers.py
class OrderSerializer(serializers.ModelSerializer):
class Meta:
model = PizzaOrder
# fields = '__all__'
read_only_fields = ('status',)
But when I try to create an order, it needed status and ordered_time also. But it should save at the time of creating order automatically.
Suggest a good way to do it.
from rest_framework import viewsets, mixins
class PizzaViewsets(viewsets.ViewSet, mixins.CreateModelMixin):
model = PizzaOrder
serializer_class = OrderSerializer
queryset = model.objects.all(
serializer, it is always good practise to mention all fields instead of
all
class OrderSerializer(serializers.ModelSerializer):
class Meta:
model = PizzaOrder
fields = ('status','ordered_time','name', 'size', 'customer_name', 'customer_address',)
read_only_fields = ('status','ordered_time',)

How to return a queryset as a JSON response in Django RESTFramework?

I am trying to return a queryset as a JSON response in DRF but I always get a TypeError Object Not JSON serializable.
I have tried different methods but none of them worked. I have tried to use the JSONRenderer class and I have also tried to serialize a single object using SentSerializer. But nothing seems to solve the issue. I have just started learning DRF so it is a little confusing to me and really don't understand how serializers work and I am not sure if I have written them correctly or using them correctly.
# models
...
from django.contrib.auth.models import User
class Sentence(models.Model):
sent = models.CharField(max_length=255)
sent_correct = models.CharField(max_length=255, null=True)
pub_date = models.DateTimeField(default=timezone.now)
author = models.ForeignKey(User, on_delete=models.CASCADE, null=True, related_name='sentences')
# serializers
...
from django.contrib.auth.models import User
class UserSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True)
username = serializers.CharField(max_length=50)
class Meta:
model = User
# Tuple of serialized model fields (see link [2])
fields = ( "id", "username", "password", )
class SentSerializer(serializers.ModelSerializer):
sent = serializers.CharField(max_length=255)
sent_correct = serializers.CharField(max_length=255, required=False)
author = UserSerializer(read_only=True, required=False)
class Meta:
model = Sentence
fields = (
'sent', 'sent_correct', 'author'
)
# views
...
class SentCreateAPIView(APIView):
serializer_class = SentSerializer
permission_classes = (IsAuthenticated,)
def post(self, request):
ss = Sentence.objects.filter(author=request.user)[0:1]
ss = list(ss)
print("sentences " + str(ss))
serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)
vd = serializer.validated_data
sent_str = vd['sent']
s = Sentence(sent=sent_str, sent_correct=sent_str)
s.author = request.user
print(vd)
print(request.user)
s.save()
sent = nlp(sent_str)
tokens = [t.text for t in sent] # this list returns successfully
return Response(
{ 'sent': sent_str,
'sent_correct': sent_str,
'tokens': tokens,
'ss': ss, # this list throws TypeError},
status=status.HTTP_201_CREATED
)
Jakub Maślanka answered my question on Facebook in Django Python Web Framework group. I had to serialize the queryset like this:
# views.py
...
return Response(
{ 'sent': sent_str,
'sent_correct': sent_str,
'tokens': tokens,
'ss': SentSerializer(ss, many=True).data},
status=status.HTTP_201_CREATED
)
ss -> dict
Why dont you use generics.ListAPIView.
It has get_query_set method
You just need to override that method by your own query set
Less code is better -> class based and framework

Passing argument from view to Custom RelatedField serializer

How can I pass an argument to a serializers.RelatedField class from views.py. I need to pass language_id to query Language.objects model within that RelatedField.
I am not sure if I took a right approach to this issue. What I want to achieve is to present information about genres associated to a movie from database model about depending on the language. The MovieGenre model has genre ID field which I want to replace with actual Genre name.
My serialiser.py
class GenreField(serializers.RelatedField):
def to_representation(self, value, language_id=1):
genre_name = GenresVideo.objects.get(genre_id=value, language_id=language_id)
return genre_name.name
class MovieGenresSerializer(serializers.ModelSerializer):
genre_id = GenreField(read_only=True)
class Meta:
model = MoviesGenres
As you see, here I query Language.objects with default value but I would like to pass it from views (language_id).
My views.py:
class MovieGenresTestViewSet(viewsets.ModelViewSet):
lookup_field = 'movie'
queryset = MoviesGenres.objects.all()
serializer_class = MovieGenresSerializer
def list(self, request, language_pk):
queryset = MoviesGenres.objects.all()
serializer = MovieGenresSerializer(queryset, many=True)
return Response(serializer.data)
def retrieve(self, request, movie, language_pk):
queryset = MoviesGenres.objects.filter(movie=movie)
serializer = MovieGenresSerializer(queryset, many=True)
return Response(serializer.data)
And my urls.py:
router.register(r'lang', LanguagesViewSet, base_name='lang')
mov_gen = routers.NestedSimpleRouter(router, r'lang', lookup='language')
mov_gen.register(r'mg', MovieGenresTestViewSet, base_name='mg')
url(r'^api/', include(genre_spec.urls))
My models.py
class Languages(models.Model):
name = models.CharField(unique=True, max_length=255)
short_name = models.CharField(unique=True, max_length=4, blank=True, null=True)
active = models.BooleanField(default="")
class Meta:
managed = False
db_table = 'languages'
ordering = ('id',)
class GenresVideo(models.Model):
genre_id = models.IntegerField()
language = models.ForeignKey('Languages')
name = models.CharField(max_length=255, blank=True, null=True)
class Meta:
managed = False
db_table = 'genres_video'
unique_together = (('genre_id', 'language'),)
ordering = ('genre_id',)
class MoviesGenres(models.Model):
movie = models.ForeignKey(Movies)
genre_id = models.IntegerField()
class Meta:
managed = False
db_table = 'movies_genres'
unique_together = (('movie', 'genre_id'),)
Through the urls routes, I can get a correct response from API including the language_id. I just need to pass it to the view somehow.
Thanks a lot for help!
I'll try to answer to your first question, with the easiest implementation possible: SerializerMethodField. Because we will get the language id via the context passed to the serializer, we should either generate the context for the serializer, or let the framework do that for us.
Now to the problem at hand: you aren't filtering the queryset (MoviesGenres) by language per se. Thus, we can avoid overwriting the list and retrieve methods. Nevertheless, the router mechanism will inject in kwargs for the view method the language_pk parameter - that's the parameter that we will retrieve from within the serializer context:
class MovieGenresSerializer(serializers.ModelSerializer):
genre = searializers.SerializerMethodField()
class Meta:
model = MoviesGenres
def get_genre(self, instance):
# get the language id from the view kwargs
language_id = self.context['view'].kwargs['language_pk']
# get the genre
try:
genre_name = GenresVideo.objects.get(genre_id=instance.genre_id, language_id=language_id).name
except GenresVideo.DoesNotExist:
genre_name = None
# return the formatted output
return genre_name
class MovieGenresTestViewSet(viewsets.ModelViewSet):
lookup_field = 'movie'
queryset = MoviesGenres.objects.all()
serializer_class = MovieGenresSerializer

Resources