I have two tables in my database to insert a product record. I am storing the product info in the Product table and the rest of the info like Pirce, Quantity etc storing to another table which is ProductStock.
I am planning to send the data to a server something like this.
{
"name":'product name',
"brand":"socialcodia"
"product_stock":{
"price":"100",
"quantity":"50"
}
}
I am easily able to validate the product info from ProductSerializer. But I don't have any perfect idea to validate the ProductStockSerializer data.
ProductSerializer
from rest_framework import serializers
from .models import Product
from .models import Medical
class ProductSerializer(serializers.ModelSerializer):
medical = serializers.CharField(read_only=True)
id = serializers.CharField(read_only=True)
is_visible = serializers.CharField(read_only=True,default=True)
class Meta:
model = Product
fields = ['id','medical','category','item','brand','is_visible']
def validate(self, attrs):
request = self.context.get('request')
attrs['medical'] = Medical.objects.get(pk=request.info.get('medical'))
return attrs
Here I want to validate the product_stock info as well. cuz it's coming with a single request. So is there any way that i can import the ProductStockSerializer into ProductSerializer and pass the data to that serializer. then validate it.
ProductStockSerializer
from rest_framework import serializers
from .models import ProductStock
from medical.models import Medical
class ProductStockSerializer(serializers.ModelSerializer):
medical = serializers.CharField(read_only=True)
class Meta:
model = ProductStock
fields = ['medical','distributer','product','variant','batch','purchase_price','price','quantity','location','low_stock','expire_date']
def validate(self, attrs):
attrs['medical'] = Medical.objects.get(self.context.get('request').info.get('medical'))
batch = attrs.get('batch')
purchase_price = attrs.get('purchase_price')
price = attrs.get('price'),
if len(batch) < 3 or len(batch) > 30:
raise serializers.ValidationError("Invalid Batch Number")
if type(purchase_price) != int or type(purchase_price) != float:
raise serializers.ValidationError("Invalid Purchase Price")
if type(price) != int or type(price) != float:
raise serializers.ValidationError("Invalid Price")
return attrs;
ProductViewSet
class ProductViewSet(ModelViewSet):
queryset = Product.objects.all()
serializer_class = ProductSerializer
authentication_classes = [JWTAuthentication]
permission_classes = [IsAuthenticated]
def get_queryset(self):
if self.request.user.is_superuser:
return Product.objects.all()
return Product.objects.filter(medical=self.request.info['medical'])
I literally don't have any idea how to do this.
Thanks
How about adding your ProductStockSerializer data as a field to your ProductSerializer
class ProductSerializer(serializers.ModelSerializer):
...
product_stock = ProductStockSerializer()
class Meta:
fields = [ ... , product_stock ]
You should be able to use product_stock in your validation. If you plan to create ProductStock objects using this nested serializer please make sure to read on writable nested serializers.
The #coderiot answer is absolutely right.
Here's How I Solved, But it has a lot more code than the above answer.
class ProductSerializer(serializers.ModelSerializer):
medical = serializers.CharField(read_only=True)
id = serializers.CharField(read_only=True)
is_visible = serializers.CharField(read_only=True,default=True)
stock = serializers.SerializerMethodField(read_only=True)
class Meta:
model = Product
fields = ['stock','id','medical','category','item','brand','is_visible']
def get_stock(self,instance):
return ProductStockSerializer(instance=instance.stock,context=self.context).data
def __init__(self,*args,**kwargs):
super().__init__(*args,**kwargs)
self.fields['stock'] = ProductStockSerializer(context=self.context)
def validate(self, attrs):
request = self.context.get('request')
attrs['medical'] = Medical.objects.get(pk=request.info.get('medical'))
return attrs
Related
I am new to DRF. I want to get saved the model.
In models.py, PackageDetails and PhysicalDetail have foreignkey relationship to Member
My serializers.py is as follows:
from rest_framework import serializers
from .models import Member, PackageDetails, PhysicalDetail
class PackageDetailsSerializer(serializers.ModelSerializer):
is_expired = serializers.SerializerMethodField()
members_expiry_date = serializers.SerializerMethodField()
class Meta:
model = PackageDetails
exclude = ['id']
extra_fields = ['is_expired', 'members_expiry_date']
def get_is_expired(self, instance):
return instance.is_expired
def get_members_expiry_date(self, instance):
return instance.members_expiry_date
class PhysicalDetailSerializer(serializers.ModelSerializer):
class Meta:
model = PhysicalDetail
exclude = ['id']
class MemberSerializer(serializers.ModelSerializer):
physical_details = PhysicalDetailSerializer(many=True)
package_details = PackageDetailsSerializer(many=True)
class Meta:
model = Member
fields = '__all__'
extra_fields = ['physical_details', 'package_details']
def create(self, validated_data):
physical_detail_data = validated_data.pop("physical_details")
package_detail_data = validated_data.pop("package_details")
member = Member.objects.create(**validated_data)
PhysicalDetail.objects.create(member=member, **physical_detail_data)
PackageDetails.objects.create(member=member, **package_detail_data)
return member
views.py :
class MemberViewset(viewsets.ModelViewSet):
queryset = Member.objects.all()
serializer_class = MemberSerializer
class PackageDetailViewset(viewsets.ModelViewSet):
queryset = PackageDetails.objects.all()
serializer_class = PackageDetailsSerializer
class PhysicalDetailViewset(viewsets.ModelViewSet):
queryset = PhysicalDetail.objects.all()
serializer_class = PhysicalDetailSerializer
In GET request it worked well.. but in POST request with the same json format it responses the following:
{
"physical_details": [
"This field is required."
],
"package_details": [
"This field is required."
]
}
I've provided the fields.. so why this happening..
You removed those from dict using pop()
The pop() method removes and returns an element from a dictionary having the given key.
Try using get() instead
The get() method returns the value for the specified key if the key is in the dictionary.
I want to build a REST API where user can do operations on objects, based on their permissions. Consider a record that represents a car - it contains the license number, the type of the car and extra information. Also consider the following user system:
Owners - Who own the car object. Can modify it and delete it.
Editors - Who can only modify the object properties.
Viewers - Can only view the object properties.
Each record can contain multi owners/editors/viewers (The user who created the object should be automatically the owner). Also, owners can add or remove editors/viewers. In my head, I see it as a list of owners/editors/viewers.
So in case of a GET request, I want to be able to return all objects that the user has permissions for, separated into those three categories.
So under my api app, I have the following code:
The models.py file contains:
class CarRecord(models.Model):
type = models.CharField(max_length=50)
license = models.CharField(max_length=50)
The serializers.py file contains:
class CarRecordSerializer(serializers.ModelSerializer):
type = models.CharField(max_length=50)
license = models.CharField(max_length=50)
class Meta:
model = CarRecord
fields = ('__all__')
In view.py I have:
class CarRecordViews(APIView):
def get(self, request):
if not request.user.is_authenticated:
user = authenticate(username=request.data.username, password=request.data.password)
if user is not None:
return Response(data={"error": "invalid username/password"}, status=status.HTTP_401_UNAUTHORIZED)
# return all records of cars that user some type of permission for
Now, I want to get all the records of user that he has permissions to query (along with their permission type). I thought of adding a three extra fields under CarRecord - each one is a list of users that contains that permission type. But I'm not sure if it's the "Django way". So wanted to consult first with SO.
EDIT: I tried to add the following field to my CarRecord class:
owners = models.ManyToManyField(User, related_name='car_owners', verbose_name=('owners'), default=[])
Also I added:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['username']
lass CarRecordSerializer(serializers.ModelSerializer):
type = models.CharField(max_length=50)
license = models.CharField(max_length=50)
owners = UserSerializer(many=True)
class Meta:
model = CarRecord
fields = ('__all__')
And the way I create the CarRecordSerializer instance is:
serializer = CarRecordSerializer(data=request.data)
But I get:
{
"error": {
"owners": [
"This field is required."
]
}
}
How to make it work? I guess is my problem is how to serialize a ManyToMany object?
EDIT2: My second attempt is:
class CarRecord(models.Model):
date_created = models.DateTimeField()
type = models.CharField(max_length=50)
license = models.CharField(max_length=50)
owners = models.ManyToManyField(User, related_name='car_owners', verbose_name=('owners'), default=[]))
creator = models.ForeignKey(User,on_delete=models.DO_NOTHING)
# ...
class CarRecordSerializer(serializers.ModelSerializer):
date_created = serializers.DateTimeField(default=datetime.now(timezone.utc))
type = models.CharField(max_length=50)
license = models.CharField(max_length=50)
creator = serializers.StringRelatedField()
owners = serializers.PrimaryKeyRelatedField(many=True,read_only=True)
class Meta:
model = CarRecord
fields = '__all__'
def create(self, validated_data):
self.owners = [self.context['creator']]
record = CarRecord(**validated_data, creator=self.context['creator'])
record.save()
return record
# ...
# In post method:
serializer = CarRecordSerializer(data=request.data, context={ 'creator': user })
But now, in GET method, I filter the owners list with the user and it can't find the objects:
> CarRecord.objects.filter(owners=user)
<QuerySet []>
Also, in the Admin section I see that all of the objects automatically have all the users in the owners/editors/viewers lists. Why is that? Owners should contain only the user that created the record and editors and viewers should be empty lists. In another query, owner can add additional owners/editors/viewers.
Here is the solution I might think is the right one
class CarRecord(models.Model):
date_created = models.DateTimeField(auto_now_add=True)
type = models.CharField(max_length=50)
license = models.CharField(max_length=50)
owners = models.ManyToManyField(User, related_name='car_owners')
creator = models.ForeignKey(User,on_delete=models.DO_NOTHING)
class CarRecordSerializer(serializers.ModelSerializer):
creator = serializers.PrimaryKeyRelatedField(queryset=User.objects.all(), required=False)
owners_details = UserSerializer(source='owners', many=True, read_only=True)
class Meta:
model = CarRecord
fields = '__all__'
def create(self, validated_data):
try:
new_owners = validated_data.pop('owners')
except:
new_owners = None
car_record = super().create(validated_data)
if new_owners:
for new_owner in new_owners:
car_record.owners.add(new_owner)
return car_record
In views.py
from rest_frameword import generics
from rest_framework import permissions
class CustomCarRecordPermissions(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if request.method == 'GET':
return True
elif request.method == 'PUT' or request.method == 'PATCH':
return request.user == obj.creator or request.user in obj.owners.all()
elif request.method == 'DELETE':
return request.user == obj.creator
return False
class CarRecordListCreate(generics.ListCreateAPIView):
permission_classes = (IsAuthenticated, )
serializer_class = CarRecordSerializer
queryset = CarRecord.objects.all()
def post(self, request, *args, **kwargs):
request.data['creator'] = request.user.id
return super().create(request, *args, **kwargs)
class CarRecordDetailView(generics.RetrieveUpdateDestroyAPIView):
permission_classes = (CustomCarRecordPermissions, )
serializer_class = CarRecordSerializer
lookup_field = 'pk'
queryset = CarRecord.objects.all()
models is self explanatory;
In CarRecord serializers we set creator as required False and primary key related field so that we can supply request user id before create as shown in views.py post method.
In Detail view we set our custom permission; If the request is GET we allow permissions. But if the request is PUT or PATCH the owners and the creator are allowed. But if it is a delete request only creator is allowed.
I think the django-rest-framework-guardian package fits here. This package is based on django-guardian.
django-guardian is an implementation of object permissions for Django providing an extra authentication backend.
There is no change on your models.py
You should change serializers.py and views.py.
For example, your serializer should look like this
from rest_framework_guardian.serializers import ObjectPermissionsAssignmentMixin
class CarRecordSerializer(ObjectPermissionsAssignmentMixin, serializers.ModelSerializer):
type = models.CharField(max_length=50)
license = models.CharField(max_length=50)
class Meta:
model = CarRecord
fields = ('__all__')
def get_permissions_map(self, created):
current_user = self.context['request'].user
readers = Group.objects.get(name='readers')
editors = Group.objects.get(name='editors')
owners = Group.objects.get(name='owners')
return {
'view_car_record': [current_user, readers, owners],
'change_car_record': [current_user, editors],
'delete_car_record': [current_user, owners]
}
and your views should look like this:
from rest_framework_guardian import filters
class CarRecordModelViewSet(ModelViewSet):
queryset = CarRecord.objects.all()
serializer_class = CarRecordSerializer
filter_backends = [filters.ObjectPermissionsFilter]
Edit settings.py like this:
INSTALLED_APPS = [
'rest_framework',
'guardian',
]
AUTHENTICATION_BACKENDS = [
"django.contrib.auth.backends.ModelBackend",
"guardian.backends.ObjectPermissionBackend",
]
You can define filter backends globally in your settings, too:
REST_FRAMEWORK = {
"DEFAULT_FILTER_BACKENDS": [
"django_filters.rest_framework.DjangoFilterBackend",
"rest_framework_guardian.filters.ObjectPermissionsFilter",
],
}
Don't forget! If you define the ObjectPermissionsFilter in the settings.py, your all views are affected by this filter.
If you want to restrict post request per user, you shoul implement custom permission class, like this:
from rest_framework import permissions
class CustomObjectPermissions(permissions.DjangoObjectPermissions):
"""
Similar to `DjangoObjectPermissions`, but adding 'view' permissions.
"""
perms_map = {
'GET': ['%(app_label)s.view_%(model_name)s'],
'OPTIONS': ['%(app_label)s.view_%(model_name)s'],
'HEAD': ['%(app_label)s.view_%(model_name)s'],
'POST': ['%(app_label)s.add_%(model_name)s'],
'PUT': ['%(app_label)s.change_%(model_name)s'],
'PATCH': ['%(app_label)s.change_%(model_name)s'],
'DELETE': ['%(app_label)s.delete_%(model_name)s'],
}
Check this link to get the detailed information for the CustomObjectPermissions
You can write permission class car owner user.
Your model.
class CarRecord(models.Model):
date_created = models.DateTimeField()
type = models.CharField(max_length=50)
license = models.CharField(max_length=50)
owners = models.ManyToManyField(User, related_name='car_owners', verbose_name=('owners'), default=[]))
creator = models.ForeignKey(User,on_delete=models.DO_NOTHING)
Permission class permission.py
from rest_framework.permissions import BasePermission,
from cars.models import CarRecord
class isCarAccess(BaseCommand):
def has_permission(self, request, view):
if request.method == 'OPTIONS':
return True
check_user = CarRecord.objects.filter(owners__in=[request.user])
return request.user is not None and request.user.is_authenticated and check_user
this permission class will check that does user exists, user is authenticated and as well the user belongs to the card record or not.
And you can pass this permission in your view.
from .permission import isCarAccess
from .models import CarRecord
class CarRecordViews(APIView):
permission_classes = [isCarAccess]
def get(self, request):
car_record = CarRecord.objects.filter(owners__in=[request.user])
# return all records of cars that user some type of permission for
and your settings.py
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": (
"oauth2_provider.contrib.rest_framework.OAuth2Authentication",
"rest_framework.authentication.SessionAuthentication",
"rest_framework.authentication.TokenAuthentication",
),
}
Now the serializer displays all the data from the CoinCosts model in response (price,timestamp), but only every 5 element is needed, how to do it? Thanks
I need something like Entry.objects.all()[::5], but I don’t know how to do this with my code.
My code now:
serializers.py
class CoinCostsSerializer(serializers.ModelSerializer):
class Meta:
fields = ('price', 'timestamp')
model = CoinCosts
class CoinSerializer(serializers.ModelSerializer):
class Meta:
fields = ('symbol', 'crr', 'costs')
model = Coins
costs = CoinCostsSerializer(source='filtered_coincosts', many=True)
views.py
class DateTimeGteFilter(filters.IsoDateTimeFilter):
def filter(self, qs, value):
if value != None:
return qs.prefetch_related(Prefetch('coincosts_set', to_attr='filtered_coincosts', queryset=CoinCosts.objects.filter(timestamp__gte=value)
)
)
else:
return qs
class CoinCostFilterSet(filters.FilterSet):
timestamp = DateTimeGteFilter()
class Meta:
model = Coins
fields = {
'symbol': ['exact'],
}
You can use try and except like this in views.py in your def:
try:
obj = Entry.objects.all()[::5]
except IndexError:
obj = None
I'm coding some backend software for a second-hand selling app using Django and DjangoRestFramework. Right now, I'm trying to send a Response object that contains a list of products, but I seem not to be able to return an actual list of products, as I'm getting an error saying
ListSerializer is not JSON serializable.
I've tried both using the serializer constructor like this:
ProductoSerializer(products, many=True)
And by creating a list of ProductoSerializer.data and then creating the Response object with that.
Here's the serializers that I'm using:
class UserSerializer(serializers.HyperlinkedModelSerializer):
ciudad = serializers.SerializerMethodField()
conectado = serializers.SerializerMethodField()
class Meta:
model = Usuario
fields = ('uid', 'nombre', 'ciudad', 'conectado')
def get_ciudad(self, obj):
geolocator = Nominatim(user_agent="bookalo")
location = geolocator.reverse(str(obj.latitud_registro) + ',' + str(obj.longitud_registro))
return location.raw['address']['city']
def get_conectado(self, obj):
ahora = timezone.now()
result = relativedelta(ahora, obj.ultima_conexion)
return result.days == 0 and result.hours == 0 and result.months == 0 and result.years == 0 and result.minutes < 5
class TagSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Tag
fields = ('nombre')
class MultimediaSerializer(serializers.HyperlinkedModelSerializer):
contenido_url = serializers.SerializerMethodField()
class Meta:
model = ContenidoMultimedia
fields = ('contenido_url', 'orden_en_producto')
def get_contenido_url(self, obj):
return obj.contenido.url
class MiniProductoSerializer(serializers.HyperlinkedModelSerializer):
contenido_multimedia = serializers.SerializerMethodField()
class Meta:
model = Producto
fields = ('nombre', 'precio', 'estado_venta', 'contenido_multimedia')
def get_contenido_multimedia(self, obj):
contenido = ContenidoMultimedia.objects.get(producto=obj.pk, orden_en_producto=0)
return MultimediaSerializer(contenido)
class ProductoSerializer(serializers.HyperlinkedModelSerializer):
vendido_por = UserSerializer(read_only=True)
tiene_tags = TagSerializer(many=True, read_only=True)
contenido_multimedia = serializers.SerializerMethodField()
valoracion_media_usuario = serializers.SerializerMethodField()
class Meta:
model = Producto
fields = ('nombre', 'precio', 'estado_producto', 'estado_venta', 'latitud', 'longitud', 'tipo_envio', 'descripcion', 'vendido_por', 'tiene_tags', 'num_likes', 'contenido_multimedia')
def get_contenido_multimedia(self, obj):
contenido = ContenidoMultimedia.objects.filter(producto=obj.pk).order_by('orden_en_producto')
return MultimediaSerializer(contenido, many=True)
def get_valoracion_media_usuario(self, obj):
return Usuario.objects.get(pk=obj.vendido_por).media_valoraciones
class ValidacionEstrellaSerializer(serializers.HyperlinkedModelSerializer):
usuario_que_valora = UserSerializer(read_only=True)
producto_asociado = serializers.SerializerMethodField()
class Meta:
model = ValidacionEstrella
fields = ('estrellas', 'comentario', 'timestamp', 'usuario_que_valora', 'producto_asociado')
def get_producto_asociado(self, obj):
producto = Producto.objects.get(pk=obj.producto)
return MiniProductoSerializer(producto)
class UserProfileSerializer(serializers.HyperlinkedModelSerializer):
usuario_valorado_estrella = serializers.SerializerMethodField()
productos_favoritos = serializers.SerializerMethodField()
class Meta:
model = Usuario
fields = ('uid', 'nombre', 'esta_baneado', 'usuario_valorado_estrella', 'producto_del_usuario')
def get_usuario_valorado_estrella(self, obj):
validaciones = ValidacionEstrella.objects.filter(usuario_valorado=obj.pk).order_by('-timestamp')
return ValidacionEstrellaSerializer(validaciones, many=True, read_only=True)
def get_productos_favoritos(self, obj):
favoritos = Producto.objects.filter(le_gusta_a__in=[obj.pk])
return ProductoSerializer(favoritos, many=True, read_only=True)
class ReportSerializer(serializers.HyperlinkedModelSerializer):
#usuario_reportado = serializers.SerializerMethodField()
usuario_reportado = UserSerializer(read_only=True)
class Meta:
model = Report
fields = ('usuario_reportado', 'causa')
And here's the views.py function that I'm trying to code:
#api_view(['POST'])
#permission_classes((permissions.AllowAny,))
def SearchProduct(request, format=None):
if request.method != 'POST':
return Response(status=status.HTTP_400_BAD_REQUEST)
preposiciones = ['a','ante','bajo','cabe','con','contra','de','desde','en','entre',
'hacia','hasta','para','por','segun','sin','so','sobre','tras']
try:
search = request.POST.get('busqueda')
except:
return Response(status=status.HTTP_404_NOT_FOUND)
products = Producto.objects.none()
for word in search.split():
if word not in preposiciones:
productos_palabra = Producto.objects.filter(nombre__contains=word)
products = products | productos_palabra
products.distinct()
product_list = []
for prod in products:
product_list.append(ProductoSerializer(prod).data)
return Response({'productos': product_list}, status=status.HTTP_200_OK)
I'm using a request object because I also have to server a WebPage, and not only a mobile app, with the same function (the webpage part is still not coded though).
It should return all the products that contain at least one of the words from the user's search, and it all should be structured based on the ProductoSerializer object, but for some reason, it's outputting that error and I'm not quite sure how to fix it.
Thanks in advance, and if you need any extra information which I've missed, please do ask for it... It's been a long day and I probably missed something.
Seems like when you use SerializerMethodField you return serializer instance but not it's data:
For instance:
contenido_multimedia = serializers.SerializerMethodField()
def get_contenido_multimedia(self, obj):
contenido = ContenidoMultimedia.objects.filter(producto=obj.pk).order_by('orden_en_producto')
return MultimediaSerializer(contenido, many=True).data # <-- here try to add .data
It should be changed for all SerializerMethodField methods.
I want to create a udemy like web app and using django rest framework for the backend and mysql as database.
i have a model named 'Lessons' that contains list of all lessons and one of the fields is 'video-link'. also i have another model names Purchases that have two ForeignKey fields :Users and Lessons. i want to show the Lessons to all users but for the download field i have to lookup the pair (User , Lesson) in Purchases and if He has the course i will show him the download field.
My View Set
class LessonsViewSet(viewsets.ModelViewSet):
queryset = models.Assignments.objects.all()
authentication_classes = (TokenAuthentication,)
def get_serializer_class(self):
if self.request.user.is_staff :
print(self.request.user)
return serializers.FullAccessLessonsSerializer
elif self.request.user.is_active:
return serializers.PartialAccessLessonsSerializer
print(self.request.user)
return serializers.BasicAccessLessonsSerializer
My Serializers
Full access for admins:
class FullAccessLessonsSerializer(serializers.ModelSerializer):
class Meta:
model = models.Assignments
fields = ('id', 'title', 'description', 'dllink' )
Basic access for unauthenticated users:
class BasicAccessLessonsSerializer(serializers.ModelSerializer):
class Meta:
model = models.Assignments
fields = ('id', 'title', 'description')
and partial access for students :
class PartialAccessAssignmentsSerializer(serializers.ModelSerializer):
"""A serializer for all Lessons"""
def __init__(self, *args, **kwargs):
fields = kwargs.pop('fields', None)
super(PartialAccessAssignmentsSerializer, self).__init__(*args,**kwargs)
print(self.fields.get('id'))
self.fields.pop('dllink')
class Meta:
model = models.Assignments
fields = ('id','title','description','dllink' )
I have done anything I could found but I can not figure it out. I either an get error or removing all dl links.
If you want dllink is None when user can't access.use this:
class FullAccessLessonsSerializer(serializers.ModelSerializer):
dllink = serializers.SerializerMethodField()
def get_prescription_accept(self, instance):
result = True # lookup the pair (User , Lesson) in Purchases
if result:
return instance.dllink
else:
return ''
class Meta:
model = models.Assignments
fields = ('id', 'title', 'description', 'dllink' )
If you want pop dllink from data,use:
class FullAccessLessonsSerializer(serializers.ModelSerializer):
def to_representation(self, instance):
data = super(FullAccessLessonsSerializer, self).to_representation(instance)
result = True # lookup the pair (User , Lesson) in Purchases
if not result:
data.pop('dllink')
return data
class Meta:
model = models.Assignments
fields = ('id', 'title', 'description', 'dllink' )