I have a problem with nested serializers.
In Shell everything works, but when I make an HTTP request i'ts always an error like "field required".
Models:
class Product(models.Model):
index = models.CharField(max_length=12, unique=True, db_index=True)
quantity = models.PositiveIntegerField(default=0)
def __str__(self):
return self.index
class Name(models.Model):
product = models.ForeignKey(Product, related_name='names', on_delete=models.CASCADE, null=True)
language = models.CharField(max_length=50, default="name_pl")
title = models.CharField(max_length=300, blank=True, null=True)
def __str__(self):
return self.language
Serializers:
class ProductSerializer(serializers.ModelSerializer):
names = NameSerializer(many=True)
class Meta:
model = Product
fields = ["index", "quantity", "names"]
def create(self, validated_data):
names = validated_data.pop('names')
product = Product.objects.create(**validated_data)
for name in names:
Name.objects.create(product=product, **name)
return product
views:
class NameView(viewsets.ModelViewSet):
queryset = Name.objects.all()
serializer_class = NameSerializer
filter_backends = [DjangoFilterBackend, filters.SearchFilter]
filterset_fields = ('id',)
search_fields = ['id']
class ProductView(viewsets.ModelViewSet):
queryset = Product.objects.all()
serializer_class = ProductSerializer
filter_backends = [DjangoFilterBackend, filters.SearchFilter]
filterset_fields = ('id',)
search_fields = ['id']
permission_classes = (CustomDjangoModelPermissions,)
I'm trying to POST data:
data = {
"index": "11111",
"quantity": 1213,
"names": [
{"language": "DE","title": "GER"},
{"language": "CZ","title": "CZZ"}
]
}
RESPOND:
names field required
I've tried to override create in view, to "look" whats wrong:
def create(self, request):
data = request.data
serializer = ProductSerializer(data=data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=201)
return Response(request.data, status=400)
Then I get:
{'index': '11111', 'quantity': '1213', 'names': 'title'}
So it can't send all of data, so it can't validate.
Need help, pls ;)
Problem solved:
Just forget to add in request:
data = json.dumps(data)
Related
I have two models, I have to make an endpoint where the results of two tables should appear in the json, which have a fongeringkey that joins them.
My code is the following:
models.py
class Property(models.Model):
address = models.CharField(max_length=120)
city = models.CharField(max_length=32)
price = models.BigIntegerField()
description = models.TextField(blank=True, null=True)
year = models.IntegerField(blank=True, null=True)
class Meta:
managed = False
db_table = 'property'
class StatusHistory(models.Model):
property = models.ForeignKey(Property, on_delete=models.CASCADE)
status = models.ForeignKey(Status, on_delete=models.CASCADE)
update_date = models.DateTimeField()
class Meta:
managed = False
db_table = 'status_history'
views.py
class StandardResultsSetPagination(PageNumberPagination):
page_size = 10
page_size_query_param = "page_size"
max_page_size = 1000
class PropertyListView(viewsets.ModelViewSet):
http_method_names = ['get', 'head']
serializer_class = PropertyListSerializer
queryset = Property.objects.all()
pagination_class = StandardResultsSetPagination
def get_serializer_class(self):
if self.action == 'list':
return PropertyListSerializer
return PropertyListSerializer
def get_queryset(self):
queryset = Property.objects.all()
if self.request.GET.get('year'):
queryset = queryset.filter(year=self.request.GET.get('year'))
if self.request.GET.get('city'):
queryset = queryset.filter(city=self.request.GET.get('city'))
if self.request.GET.get('status'):
#what would I do here?
pass
else:
queryset = queryset.order_by('-year')
return queryset
def list(self, request):
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
serializers.py
class PropertyListSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Property
fields = ('id', 'address', 'city', 'price', 'description', 'year')
class StatusHistoryListSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = StatusHistory
fields = ('property', 'status', 'update_date')
I don't know how to filter by get with the parameters, city, year(model Property) and status(model StatusHistory).
You only need to filter based in the status of the history you can do the following:
if self.request.GET.get('status'):
queryset = queryset.filter(statushistory__status=self.request.GET.get('status'))
i have the below config and i would like to map the url field in UserProfileView to the related user's username instead of the default pk field
currently the url looks likes below, appreciate any help
{
"user": 23,
"bio": "My bio",
"created_on": "2020-06-12T21:24:52.746329Z",
"url": "http://localhost:8000/bookshop/bio/8/?format=api"
},
what i am looking for is
{
"user": 23, <--- this is the user <pk>
"bio": "My bio",
"created_on": "2020-06-12T21:24:52.746329Z",
"url": "http://localhost:8000/bookshop/bio/username/?format=api"
},
models.py
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile')
bio = models.CharField(max_length=255)
created_on = models.DateTimeField(auto_now_add=True)
last_updated = models.DateTimeField(auto_now=True)
def __str__(self):
return self.user.username
views.py
class UserProfileViewSets(viewsets.ModelViewSet):
authentication_classes = [TokenAuthentication, ]
permission_classes = [rest_permissions.IsAuthenticated, permissions.UserProfileOwnerUpdate, ]
queryset = models.UserProfile.objects.all()
serializer_class = serializers.UserProfileSerializer
renderer_classes = [renderers.AdminRenderer, renderers.JSONRenderer, renderers.BrowsableAPIRenderer, ]
# lookup_field = 'user.username'
def perform_create(self, serializer):
serializer.save(user=self.request.user)
serializer.py
class UserProfileSerializer(serializers.ModelSerializer):
class Meta:
model = models.UserProfile
fields = ['user', 'bio', 'created_on', 'url']
extra_kwargs = {
'last_updated': {
'read_only': True
},
'user': {
'read_only': True
},
}
after struggling and reading many articles, I did it and posting down the solution if anybody was looking for the same use case.
the fields are being related to each other by OneToOne relationship
models.py
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile')
bio = models.CharField(max_length=255)
created_on = models.DateTimeField(auto_now_add=True)
last_updated = models.DateTimeField(auto_now=True)
def __str__(self):
return self.user.username
class User(AbstractBaseUser, PermissionsMixin):
""""
Customizes the default user account
"""
email = models.EmailField(unique=True, help_text='username is the email address')
first_name = models.CharField(max_length=40, blank=False)
last_name = models.CharField(max_length=40, blank=False)
date_joined = models.DateTimeField(auto_now_add=True)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
username = models.CharField(max_length=15, unique=True, null=True, blank=False,
validators=(validators.UnicodeUsernameValidator, ))
is_borrower = models.BooleanField(default=False)
The serializer is a HyperlinkedModelSerializer, as shown below the user SerializerField is PrimaryKeyRelatedField and it is being related to another column/field in the User model user.username - i made this as the default PrimaryKeyRelatedField is the pk and i dont want to expose that on the API
the url key is customized to be HyperlinkedRelatedField to point to the above field - the user with a viewname user-related
serializer.py
class UserProfileSerializer(serializers.HyperlinkedModelSerializer):
user = serializers.PrimaryKeyRelatedField(source='user.username', read_only=True)
url = serializers.HyperlinkedRelatedField(read_only=True, view_name='user-detail', )
class Meta:
model = models.UserProfile
fields = ['user', 'bio', 'created_on', 'url']
extra_kwargs = {
'last_updated': {
'read_only': True
},
'user': {
'read_only': True
},
}
on the views, i defined the lookup_field to be user and override the get_object method as now the queryset should be filtered by the username
views.py
class UserProfileViewSets(viewsets.ModelViewSet):
authentication_classes = [TokenAuthentication, ]
permission_classes = [rest_permissions.IsAuthenticated, permissions.UserProfileOwnerUpdate, ]
queryset = models.UserProfile.objects.all()
serializer_class = serializers.UserProfileSerializer
renderer_classes = [renderers.AdminRenderer, renderers.JSONRenderer, renderers.BrowsableAPIRenderer, ]
lookup_field = 'user'
def perform_create(self, serializer):
serializer.save(user=self.request.user)
def get_object(self):
queryset = self.filter_queryset(models.UserProfile.objects.get(user__username=self.kwargs.get('user')))
return queryset
EDIT:
I did the requirements in another approach and think this one is more neat way , so below the modifications.
You need to create anew customized HyperLinkedIdentityField where you over right the kwargs, check the below kwargs, the value is mapped to the related model where a OneToOneForgienKey deifined
class AuthorHyperLinkedIdentityField(serializers.HyperlinkedIdentityField):
def get_url(self, obj, view_name, request, format):
if hasattr(obj, 'pk') and obj.pk is None:
return None
return self.reverse(view_name, kwargs={
'obj_username': obj.author.username
}, format=format, request=request)
on the view you overright the lookup_field with the kwargs defined in the CustomizedField
class AuthorViewSet(viewsets.ModelViewSet):
serializer_class = serializers.AuthorSerializer
queryset = models.Author.objects.all()
renderer_classes = [renderers.JSONRenderer, renderers.BrowsableAPIRenderer, renderers.AdminRenderer]
# the below is not used but i keep it for reference
# lookup_field = 'author__username'
# the below should match the kwargs in the customized HyperLinkedIdentityField
lookup_field = 'obj_username'
The final serializer would look like
class AuthorSerializer(serializers.HyperlinkedModelSerializer):
"""
Serializers Author Model
"""
# first_name = serializers.SlugRelatedField(source='author', slug_field='first_name',
# read_only=True)
# last_name = serializers.SlugRelatedField(source='author', slug_field='last_name',
# read_only=True)
author = serializers.PrimaryKeyRelatedField(queryset=models.User.objects.filter(groups__name='Authors'),
write_only=True)
name = serializers.SerializerMethodField()
username = serializers.PrimaryKeyRelatedField(source='author.username', read_only=True)
# the below commented line is building the URL field based on the lookup_field = username
# which takes its value from the username PrimaryKeyRelatedField above
# url = serializers.HyperlinkedRelatedField(view_name='user-detail', read_only=True)
url = AuthorHyperLinkedIdentityField(view_name='author-detail', read_only=True)
class Meta:
model = models.Author
fields = ['author', 'name', 'username', 'url', ]
def get_name(self, author):
return '%s %s' % (author.author.first_name, author.author.last_name)
below the Author Model for your reference
class Author(models.Model):
"""
A Model to store the Authors info
"""
author = models.OneToOneField(User, on_delete=models.CASCADE, related_name='authors')
is_author = models.BooleanField(default=True, editable=True, )
class Meta:
constraints = [
models.UniqueConstraint(fields=['author'], name='check_unique_author')
]
def __str__(self):
return '%s %s' % (self.author.first_name, self.author.last_name)
def author_full_name(self):
return '%s %s' % (self.author.first_name, self.author.last_name)
I have been trying to use the Django_filter on an APIView, but it just does not work. I am trying to implement a filter search, on some fields on a model.
below is how the model is set up
class User(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(max_length=254, unique=True)
name = models.CharField(max_length=250)
picture = models.TextField(null=True, blank=True)
is_staff = models.BooleanField(default=False)
is_superuser = models.BooleanField(default=False)
is_active = models.BooleanField(default=True)
last_login = models.DateTimeField(null=True, blank=True)
date_joined = models.DateTimeField(auto_now_add=True)
slug = models.SlugField(max_length=255, unique=True, blank=True)
class Skill(models.Model):
name = models.CharField(max_length=60)
subcategory = models.CharField(max_length=60, blank=True, null=True)
created_on = models.DateTimeField(auto_now=True)
updated_on = models.DateTimeField(auto_now_add=True)
updated_by = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, on_delete=models.DO_NOTHING)
the views.py set up is also as shown below
from django_filters import rest_framework as filters
class UserFilter(filters.FilterSet):
email = filters.CharFilter(lookup_expr='icontains')
name = filters.CharFilter(lookup_expr='icontains')
profiles__skills = filters.CharFilter(lookup_expr='icontains')
class Meta:
model = User
fields = ('email', 'name', 'profiles__skills')
class ListUsersView(APIView, MyPaginationMixin):
'''
Gets all the users in the database
'''
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes = [AllowAny]
pagination_class = api_settings.DEFAULT_PAGINATION_CLASS
filterset_class = UserFilter
def get(self, request):
page = self.paginate_queryset(self.queryset)
if page is not None:
serializer_context = {"request": request}
serializer = self.serializer_class(page, context=serializer_context, many=True)
return self.get_paginated_response(serializer.data)
and finally my serializer.py
class UserSerializer(serializers.ModelSerializer):
slug = serializers.SlugField(read_only=True)
class Meta:
model = User
fields = ('email', 'name', 'slug', 'picture')
read_only_fields = ('email', 'name', 'slug',)
my urls.py
path('users/', qv.ListUsersView.as_view(), name='list-users'),
this is how my result looks like
please, how can I get the Django filter to work on the APIView
It seems you are trying to get the similar or exact behavior of DRF ListAPIView by using APIView. I would suggest using ListAPIView over APIView in your case.
from rest_framework import generics
from django_filters import rest_framework as filters
class ListUsersView(generics.ListAPIView):
'''
Gets all the users in the database
'''
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes = [AllowAny]
filterset_class = UserFilter
filter_backends = (filters.backends.DjangoFilterBackend,)
To add filtering capability in APIView,
class MyAPIViewKlass(APIView):
filter_backends = (filters.DjangoFilterBackend,)
def filter_queryset(self, queryset):
for backend in list(self.filter_backends):
queryset = backend().filter_queryset(self.request, queryset, self)
return queryset
def get(self, request, *args, **kwargs):
base_qs = MyModel.objects.all()
filtered_qs = self.filter_queryset(base_qs)
serializer = MySerializer(filtered_qs, many=True)
return Response(serializer.data)
I have an object I would like to be able to make via django-rest-api UI.
This object has a manytomany field that holds other objects on it.
Even though that field is blank param is set to True, I get a response that "this field is requiered".
class Post(models.Model):
title = models.CharField(max_length=100)
slug = models.CharField(max_length=200, null=True)
description = models.CharField(max_length=200, null=True)
content = HTMLField('Content', null=True)
black_listed = models.ManyToManyField('profile_app.Profile', related_name='black_listed_posts', blank=True)
score = models.PositiveIntegerField(null=True, default=0, validators=[MaxValueValidator(100)])
serializers.py:
class PostSerializer(serializers.HyperlinkedModelSerializer):
black_listed = ProfileSerializer(many=True)
read_only = ('id',)
def create(self, validated_data):
self.black_listed = []
class Meta:
model = Post
fields = ('id', 'title', 'slug', 'description',
'content',
'black_listed', 'score')
views.py:
class PostViewSet(ModelViewSet):
serializer_class = PostSerializer
queryset = Post.objects.all()
lookup_field = "slug"
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.black_listed = []
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
As you can see, i tried overriding the create() method on both serializer and viewset, but that didnt work and still gave me that the black_list field is requiered.
What i expected that if the field is not required in the db, then the serializer can set it to None on the creation
what am i missing here?
EDIT:
ProfileSerializer:
class ProfileSerializer(serializers.ModelSerializer):
interests = InterestSerializer(read_only=True, many=True)
class Meta:
model = Profile
fields = ('slug', 'user_id', 'image', 'role', 'work_at', 'interests')
You should provide the required=False argument in your serializer declaration:
class PostSerializer(...):
black_listed = ProfileSerializer(many=True, required=False)
# __________________________________________^
If you want to be able to post null values for this field, you may also add allow_null=True.
I have an "Event" model and it has a many2many field with default user model.
class Event(models.Model):
name = models.CharField(max_length=50)
type = models.CharField(max_length=50)
location = models.CharField(max_length=255)
start_hour = models.CharField(max_length=50)
end_hour = models.CharField(max_length=50)
creator = models.CharField(max_length=50)
info = models.CharField(max_length=255, default='')
users = models.ManyToManyField(User)
def __str__(self):
return self.name
Now, I am trying to update this many2many field like following;
//my serializers
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id',)
class EventSerializer(serializers.ModelSerializer):
users = UserSerializer(many=True)
class Meta:
model = Event
fields = ('id', 'users')
def update(self, instance, validated_data):
submitted_users = validated_data.get('users')
if submitted_users:
for user in submitted_users:
user_instance = User.objects.get(id=user.id)
instance.users.add(user_instance)
instance.save()
return instance
//views.py
class UpdateParticipants(generics.RetrieveUpdateDestroyAPIView):
queryset = Event.objects.all()
serializer_class = EventSerializer
However, I am getting an error like in the belowe image
// this is the APIView that I used
Can you try with this code below?
Models
Example
class UserModel(User):
pass
class Event(models.Model):
name = models.CharField(max_length=50)
type = models.CharField(max_length=50)
location = models.CharField(max_length=255)
start_hour = models.CharField(max_length=50)
end_hour = models.CharField(max_length=50)
creator = models.CharField(max_length=50)
info = models.CharField(max_length=255, default='')
users = models.ManyToManyField(User, related_name='event_user') # Set related name to User object for Rest Framework
def __str__(self):
return self.name
Serializer
class EventSerializer(serializers.ModelSerializer):
users = serializers.PrimaryKeyRelatedField(queryset=User.objects.all(), required=False)
class Meta:
model = Event
class Userserializer(serializers.ModelSerializer):
event_user = EventSerializer(many=True)
class Meta:
model = UserModel
def update(self, instance, validated_data):
event_user = validated_data.pop('event_user', None)
print (event_user)
if validated_data:
for attr, value in validated_data.items():
if attr == 'password':
instance.set_password(value)
else:
setattr(instance, attr, value)
instance.save()
return instance
Views
class EventView(APIView):
permission_classes = (AllowAny,)
def get(self):
qv = User.objects.all()
serializer = Userserializer(qv, many=True)
return Response(data={'users': serializer.data}, status=status.HTTP_200_OK)
def put(self, request, user_id):
instance = Event.objects.get(user=user_id)
serializer = Userserializer(instance=instance,data=request.data, partial=True)
if serializer.is_valid(raise_exception=True):
serializer.save()
return Response(data={'users': serializer.data}, status=status.HTTP_200_OK)
return Response(data={'users': serializer.errors}, status=status.HTTP_403_FORBIDDEN)
URL-s
urlpatterns = [
url(r'^users/(?P<user_id>\d+)',EventView.as_view())
]
JOSN exaple for put
JSON
{
"id":1,
"event_user":[{
"name":"Changed Name"
}]
}