My view of user profile
#api_view(['GET'])
#permission_classes((IsAuthenticated, ))
def user_profile(request,id):
try:
up = get_object_or_404(User_Profile, pk=id)
except User_Profile.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
if request.method == 'GET':
account=request.user
post=Post.objects.all().filter(author=account)
post_serializer=PostSerializer(post)
follower_serializer = FollowerSerializer(up)
following_serializer = FollowingSerializer(account)
serializer=[follower_serializer.data,following_serializer.data,post_serializer.data]
content = {
'status': 1,
'responseCode' : status.HTTP_200_OK,
'data': serializer,
}
return Response(content)
When I run this it says:
'QuerySet' object has no attribute 'author'
but when I run in shell without using serializer just using this code post=Post.objects.all().filter(author=account) it shows me the posts related to user. So what is problem with my serializer?
In your case, post is a QuerySet, so you should initialize the serializer with many=True
post_serializer=PostSerializer(post, many=True)
I'm filtering real estates queryset dependent on user status and district (last one with GET param).
In views.py I have this:
class RealEstateView(APIView):
serializer_class = RealEstateSerializer
permission_classes = [RealEstatePermission]
def get(self, request):
district = self.request.query_params.get('pk')
if district:
serializer = RealEstateSerializer(RealEstate.objects.filter(owner_id=district), many=True)
else:
serializer = RealEstateSerializer(RealEstate.objects.all(), many=True)
return Response(serializer.data)
If user is superuser, he have access to all information. If user in not superuser, he can get access only to real estates from district which he is responsible. If user is responsible to district with id=1, but sends a get param with id=2, I need to raise an exception. But the problem is I don't know how to get access to get parameter in has_permission function. Doing this inside views get function seems not good idea.
I already tried request.resolver_match.kwargs.get('id') and view.kwargs.get('id'), both of them are empty.
in permissions.py:
class RealEstatePermission(permissions.BasePermission):
def has_permission(self, request, view):
if request.user.is_authenticated:
if request.user.is_staff:
return True
## HERE I need something like request.user.district.id == kwargs('id')
if request.user.role == 'district_municipality':
return True
Using Django 3.0.5 and DRF 3.11.0.
Thank you for your help.
To get access to get parametersfrom url query you can use GET dict.
Example
url:
/district?id=2
access:
district_id = request.GET['id']
You can use this as well:
Url:
/district?id=2
Access:
district_id = view.kwargs['id']
I have nested data; a List contains many Items. For security, I filter Lists by whether the current user created the list, and whether the list is public. I would like to do the same for items, so that items can only be updated by authenticated users, but can be viewed by anybody if the list is public.
Here's my viewset code, adapted from the List viewset code which works fine. This of course doesn't work for Items because the item doesn't have the properties "created_by" or "is_public" - those are properties of the parent list.
Is there a way I can replace "created_by" and "is_public" with the list properties? i.e. can I get hold of the parent list object in the item's get_queryset method, and check it's properties?
The alternative is that I assign "created_by" and "is_public" to the item as well, but I would prefer not to do that because it is duplicated data. The lists's properties should control the item's permissions.
class ItemViewSet(viewsets.ModelViewSet):
permission_classes = [permissions.AllowAny, ]
model = Item
serializer_class = ItemSerializer
def get_queryset(self):
# restrict any method that can alter a record
restricted_methods = ['POST', 'PUT', 'PATCH', 'DELETE']
if self.request.method in restricted_methods:
# if you are not logged in you cannot modify any list
if not self.request.user.is_authenticated:
return Item.objects.none()
# you can only modify your own lists
# only a logged-in user can create a list and view the returned data
return Item.objects.filter(created_by=self.request.user)
# GET method (view item) is available to owner and for items in public lists
if self.request.method == 'GET':
if not self.request.user.is_authenticated:
return Item.objects.filter(is_public__exact=True)
return Item.objects.filter(Q(created_by=self.request.user) | Q(is_public__exact=True))
# explicitly refuse any non-handled methods
return Item.objects.none()
Many thanks for any help!
Edit: between Lucas Weyne's answer and this post I think I have got this sorted now. Here's my working code in api.py:
from rest_framework import viewsets, permissions
from .models import List, Item
from .serializers import ListSerializer, ItemSerializer
from django.db.models import Q
class IsOwnerOrReadOnly(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
# handle permissions based on method
# Read permissions are allowed to any request,
# so we'll always allow GET, HEAD or OPTIONS requests.
if request.method in permissions.SAFE_METHODS:
return True
if hasattr(obj, 'created_by'):
return obj.created_by == request.user
if hasattr(obj, 'list'):
if hasattr(obj.list, 'created_by'):
return obj.list.created_by == request.user
class ListViewSet(viewsets.ModelViewSet):
permission_classes = [IsOwnerOrReadOnly]
model = List
serializer_class = ListSerializer
def get_queryset(self):
# can view public lists and lists the user created
if self.request.user.is_authenticated:
return List.objects.filter(
Q(created_by=self.request.user) |
Q(is_public=True)
)
return List.objects.filter(is_public=True)
def pre_save(self, obj):
obj.created_by = self.request.user
class ItemViewSet(viewsets.ModelViewSet):
permission_classes = [IsOwnerOrReadOnly]
model = Item
serializer_class = ItemSerializer
def get_queryset(self):
# can view items belonging to public lists and lists the usesr created
if self.request.user.is_authenticated:
return Item.objects.filter(
Q(list__created_by=self.request.user) |
Q(list__is_public=True)
)
return Item.objects.filter(list__is_public=True)
Django allows lookups that span relationships. You can filter Item objects across List properties, just use the field name of related fields across models, separated by double underscores, until you get to the field you want.
class ItemViewSet(viewsets.ModelViewSet):
permission_classes = [IsOwnerOrReadyOnly]
serializer_class = ItemSerializer
def get_queryset(self):
if self.request.user.is_authenticated
return Item.objects.filter(
Q(list__created_by=self.request.user) |
Q(list__is_public__exact=True)
)
return Item.objects.filter(list__is_public=True)
To allow items to be updated only by its owners, write a custom object-level permission class.
class IsOwnerOrReadOnly(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
# Read permissions are allowed to any request,
# so we'll always allow GET, HEAD or OPTIONS requests.
if request.method in permissions.SAFE_METHODS:
return True
# Instance must have an attribute named `created_by`.
return obj.list.created_by == request.user
hi im trying to save a form data into db.
i provided print of requset.data for you as you see requirement have two items.
i want to save each item in database i used for loop to save each item of list but the loop will save each character of item like h-e-l,... in table row...
where is my mistake ... thanks
also print of request.data.get('requirement') will retun second item
this is print of request.data in sever:
<QueryDict: {'requirement': ['hello', 'bye'], 'audience': ['adasd'], 'achievement': ['asdasd'], 'section': ['410101010'], 'title': ['asdasd'], 'mini_description': ['asdad'], 'full_description': ['asdasd'], 'video_length': ['10101'], 'video_level': ['P'], 'price': [''], 'free': ['true'], 'image': [<InMemoryUploadedFile: p.gif (image/gif)>]}>
view:
class StoreCreateAPIView(generics.CreateAPIView):
parser_classes = (MultiPartParser, FormParser)
permission_classes = [IsAuthenticated]
def perform_create(self, serializer):
serializer.save(author=self.request.user)
def post(self, request, *args, **kwargs):
if request.method == 'POST':
print(request.data)
file_serial = ProductSerializer(data=request.data, context={"request": request})
if file_serial.is_valid():
file_serial.save(author_id=request.user.id)
requirement = request.data['requirement']
audience = request.data.get('audience')
achievement = request.data.get('achievement')
sections = request.data.get('section')
print(request.data['requirement'])
pid = file_serial.data.get('product_id')
for item in requirement :
req = ProductRequiredItems(
item = item,
product_id = pid
)
req.save()
First of all, overriding CreateAPIView's post method in your code makes your custom perform_create method useless, unless you explicitly call it from within your customized post method. Otherwise it will never be called.
also print of request.data.get('requirement') will retun second item
It does return the last item as per Django docs for QueryDict.__getitem__(key).
i want to save each item in database i used for loop to save each item of list but the loop will save each character of item like h-e-l,...
This is because of the above functionality of QueryDict. When you do:
requirement = request.data['requirement']
# requirement = request.__getitem__('requirement')
it will call QueryDict.__getitem__(key) method and thus return only the last item (which is string in you example).
Answer:
You can simply override CreateAPIView's create method, and let your serializer handle all the rest.
# views.py
from django.shortcuts import render
from rest_framework import generics, status
from rest_framework.response import Response
from .models import MyObj
from .serializers import MyObjSerializer
class MyObjView(generics.CreateAPIView):
serializer_class = MyObjSerializer
queryset = MyObj.objects.all()
def create(self, request, *args, **kwargs):
# The QueryDicts at request.POST and request.GET will be immutable
# when accessed in a normal request/response cycle.
# To get a mutable version you need to use QueryDict.copy().
req_data = request.data.copy()
requirements = req_data.pop('requirement')
serializers_data = []
for requirement in requirements:
req_data ['requirement'] = requirement
serializer = self.get_serializer(data=req_data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
serializers_data.append(serializer.data)
return Response(serializers_data, status=status.HTTP_201_CREATED)
# serializers.py
from rest_framework import serializers
from .models import MyObj
class MyObjSerializer(serializers.ModelSerializer):
class Meta:
model = MyObj
fields = '__all__'
Have a look at DRF CreateModelMixin. It defines create & perform_create methods that are used used in CreateAPIView upon executing POST request. I just altered them slightly to handle your specific case.
Hope it helps.
I'm trying to make my User model RESTful via Django Rest Framework API calls, so that I can create users as well as update their profiles.
However, as I go through a particular verification process with my users, I do not want the users to have the ability to update the username after their account is created. I attempted to use read_only_fields, but that seemed to disable that field in POST operations, so I was unable to specify a username when creating the user object.
How can I go about implementing this? Relevant code for the API as it exists now is below.
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = ('url', 'username', 'password', 'email')
write_only_fields = ('password',)
def restore_object(self, attrs, instance=None):
user = super(UserSerializer, self).restore_object(attrs, instance)
user.set_password(attrs['password'])
return user
class UserViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows users to be viewed or edited.
"""
serializer_class = UserSerializer
model = User
def get_permissions(self):
if self.request.method == 'DELETE':
return [IsAdminUser()]
elif self.request.method == 'POST':
return [AllowAny()]
else:
return [IsStaffOrTargetUser()]
Thanks!
It seems that you need different serializers for POST and PUT methods. In the serializer for PUT method you are able to just except the username field (or set the username field as read only).
class UserViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows users to be viewed or edited.
"""
serializer_class = UserSerializer
model = User
def get_serializer_class(self):
serializer_class = self.serializer_class
if self.request.method == 'PUT':
serializer_class = SerializerWithoutUsernameField
return serializer_class
def get_permissions(self):
if self.request.method == 'DELETE':
return [IsAdminUser()]
elif self.request.method == 'POST':
return [AllowAny()]
else:
return [IsStaffOrTargetUser()]
Check this question django-rest-framework: independent GET and PUT in same URL but different generics view
Another option (DRF3 only)
class MySerializer(serializers.ModelSerializer):
...
def get_extra_kwargs(self):
extra_kwargs = super(MySerializer, self).get_extra_kwargs()
action = self.context['view'].action
if action in ['create']:
kwargs = extra_kwargs.get('ro_oncreate_field', {})
kwargs['read_only'] = True
extra_kwargs['ro_oncreate_field'] = kwargs
elif action in ['update', 'partial_update']:
kwargs = extra_kwargs.get('ro_onupdate_field', {})
kwargs['read_only'] = True
extra_kwargs['ro_onupdate_field'] = kwargs
return extra_kwargs
Another method would be to add a validation method, but throw a validation error if the instance already exists and the value has changed:
def validate_foo(self, value):
if self.instance and value != self.instance.foo:
raise serializers.ValidationError("foo is immutable once set.")
return value
In my case, I wanted a foreign key to never be updated:
def validate_foo_id(self, value):
if self.instance and value.id != self.instance.foo_id:
raise serializers.ValidationError("foo_id is immutable once set.")
return value
See also: Level-field validation in django rest framework 3.1 - access to the old value
My approach is to modify the perform_update method when using generics view classes. I remove the field when update is performed.
class UpdateView(generics.UpdateAPIView):
...
def perform_update(self, serializer):
#remove some field
rem_field = serializer.validated_data.pop('some_field', None)
serializer.save()
I used this approach:
def get_serializer_class(self):
if getattr(self, 'object', None) is None:
return super(UserViewSet, self).get_serializer_class()
else:
return SerializerWithoutUsernameField
UPDATE:
Turns out Rest Framework already comes equipped with this functionality. The correct way of having a "create-only" field is by using the CreateOnlyDefault() option.
I guess the only thing left to say is Read the Docs!!!
http://www.django-rest-framework.org/api-guide/validators/#createonlydefault
Old Answer:
Looks I'm quite late to the party but here are my two cents anyway.
To me it doesn't make sense to have two different serializers just because you want to prevent a field from being updated. I had this exact same issue and the approach I used was to implement my own validate method in the Serializer class. In my case, the field I don't want updated is called owner. Here is the relevant code:
class BusinessSerializer(serializers.ModelSerializer):
class Meta:
model = Business
pass
def validate(self, data):
instance = self.instance
# this means it's an update
# see also: http://www.django-rest-framework.org/api-guide/serializers/#accessing-the-initial-data-and-instance
if instance is not None:
originalOwner = instance.owner
# if 'dataOwner' is not None it means they're trying to update the owner field
dataOwner = data.get('owner')
if dataOwner is not None and (originalOwner != dataOwner):
raise ValidationError('Cannot update owner')
return data
pass
pass
And here is a unit test to validate it:
def test_owner_cant_be_updated(self):
harry = User.objects.get(username='harry')
jack = User.objects.get(username='jack')
# create object
serializer = BusinessSerializer(data={'name': 'My Company', 'owner': harry.id})
self.assertTrue(serializer.is_valid())
serializer.save()
# retrieve object
business = Business.objects.get(name='My Company')
self.assertIsNotNone(business)
# update object
serializer = BusinessSerializer(business, data={'owner': jack.id}, partial=True)
# this will be False! owners cannot be updated!
self.assertFalse(serializer.is_valid())
pass
I raise a ValidationError because I don't want to hide the fact that someone tried to perform an invalid operation. If you don't want to do this and you want to allow the operation to be completed without updating the field instead, do the following:
remove the line:
raise ValidationError('Cannot update owner')
and replace it with:
data.update({'owner': originalOwner})
Hope this helps!
More universal way to "Disable field update after object is created"
- adjust read_only_fields per View.action
1) add method to Serializer (better to use your own base cls)
def get_extra_kwargs(self):
extra_kwargs = super(BasePerTeamSerializer, self).get_extra_kwargs()
action = self.context['view'].action
actions_readonly_fields = getattr(self.Meta, 'actions_readonly_fields', None)
if actions_readonly_fields:
for actions, fields in actions_readonly_fields.items():
if action in actions:
for field in fields:
if extra_kwargs.get(field):
extra_kwargs[field]['read_only'] = True
else:
extra_kwargs[field] = {'read_only': True}
return extra_kwargs
2) Add to Meta of serializer dict named actions_readonly_fields
class Meta:
model = YourModel
fields = '__all__'
actions_readonly_fields = {
('update', 'partial_update'): ('client', )
}
In the example above client field will become read-only for actions: 'update', 'partial_update' (ie for PUT, PATCH methods)
This post mentions four different ways to achieve this goal.
This was the cleanest way I think: [collection must not be edited]
class DocumentSerializer(serializers.ModelSerializer):
def update(self, instance, validated_data):
if 'collection' in validated_data:
raise serializers.ValidationError({
'collection': 'You must not change this field.',
})
return super().update(instance, validated_data)
Another solution (apart from creating a separate serializer) would be to pop the username from attrs in the restore_object method if the instance is set (which means it's a PATCH / PUT method):
def restore_object(self, attrs, instance=None):
if instance is not None:
attrs.pop('username', None)
user = super(UserSerializer, self).restore_object(attrs, instance)
user.set_password(attrs['password'])
return user
If you don't want to create another serializer, you may want to try customizing get_serializer_class() inside MyViewSet. This has been useful to me for simple projects.
# Your clean serializer
class MySerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = '__all__'
# Your hardworking viewset
class MyViewSet(MyParentViewSet):
serializer_class = MySerializer
model = MyModel
def get_serializer_class(self):
serializer_class = self.serializer_class
if self.request.method in ['PUT', 'PATCH']:
# setting `exclude` while having `fields` raises an error
# so set `read_only_fields` if request is PUT/PATCH
setattr(serializer_class.Meta, 'read_only_fields', ('non_updatable_field',))
# set serializer_class here instead if you have another serializer for finer control
return serializer_class
setattr(object, name, value)
This is the counterpart of getattr(). The
arguments are an object, a string and an arbitrary value. The string
may name an existing attribute or a new attribute. The function
assigns the value to the attribute, provided the object allows it. For
example, setattr(x, 'foobar', 123) is equivalent to x.foobar = 123.
class UserUpdateSerializer(UserSerializer):
class Meta(UserSerializer.Meta):
fields = ('username', 'email')
class UserViewSet(viewsets.ModelViewSet):
def get_serializer_class(self):
return UserUpdateSerializer if self.action == 'update' else super().get_serializer_class()
djangorestframework==3.8.2
I would suggest also looking at Django pgtrigger
This allows you to install triggers for validation. I started using it and was very pleased with its simplicity:
Here's one of their examples that prevents a published post from being updated:
import pgtrigger
from django.db import models
#pgtrigger.register(
pgtrigger.Protect(
operation=pgtrigger.Update,
condition=pgtrigger.Q(old__status='published')
)
)
class Post(models.Model):
status = models.CharField(default='unpublished')
content = models.TextField()
The advantage of this approach is it also protects you from .update() calls that bypass .save()