Working on custom API function that works on creating and updating Ratings
when I try to test run the function on Postman I get the following error:
IntegrityError at /api/movies/1/rate_movie/
UNIQUE constraint failed: API_rating.user_id, API_rating.movie_id
so I do not know if the flaw could be on code or what
here is the code below
#action(detail=True, methods=['POST'])
def rate_movie(self, request, pk=None):
if 'stars' in request.data:
movie = Movie.objects.get(id=pk)
#user = request.user #Can not use now due to lack of auth, login
user = User.objects.get(id=1)
stars = request.data['stars']
try:
rating = Rating.objects.get(user=user.id, movie=movie.id)
rating.stars = stars
rating.save()
serializer = RatingSerializer
response = {'message': 'Rating Updated', 'results': serializer.data}
return Response(response, status=HTTP_200_OK)
except:
rating = Rating.objects.create(user=user, movie=movie, stars=stars)
serializer = RatingSerializer
response = {'message': 'Rating Created', 'results': serializer.data}
return Response(response, status=HTTP_200_OK)
else:
response = {'message':'Stars not selected for rating'}
return Response(response, status=HTTP_400_bad_request)
Lastly here is a picture of the sample test I made when I got the error
Something is failing silently and caught by your generic except
Looks like your Rating model has a unique constraint that only allows a user to rate a movie once, which makes sense. Therefore you can't do Rating.objects.create(user=user, movie=movie, stars=stars) if another rating exists from user to movie.
Instead, you need to update the existing one if it exists, otherwise create a new one. Something like:
Rating.objects.update_or_create(user=user, movie=movie, defaults={stars:stars})
It looks like this is what you're trying to do, but
Don't use generic except unless really necessary! It can hide your errors and lead to bugs. In this case it hides the fact that:
You need to do serializer = RatingSerializer() (with the parens()), or better yet serializer = self.get_serializer() and define a serializer_class class attribute
Related
Please be gentle. I'm a Django newb and I find the level of abstraction just plain overwhelming.
My ultimate goal is to modify an image file on its way into the model. That part may or may not be relevant, but assistance came my way in this post which advised me that I should be making changes inside a validator:
REST Django - How to Modify a Serialized File Before it is Put Into Model
Anyway, at the moment I am simply trying to get the context of the request so I can be sure to do the things to the thing only when the request is a POST. However, inside my validator, the self.context is just an empty dictionary. Based on what I have found out there, there should be a value for self.context['request'].
Here is what I have:
Serializer with validator method:
class MediaSerializer(serializers.ModelSerializer):
class Meta:
model = Media
fields = '__all__'
def validate_media(self, data):
print(self.context)
#todo: why is self.context empty?
#if self.context['request'].method == 'POST':
# print('do a thing here')
return data
def to_representation(self, instance):
data = super(MediaSerializer, self).to_representation(instance)
return data
The view along with the post method
class MediaView(APIView):
queryset = Media.objects.all()
parser_classes = (MultiPartParser, FormParser)
permission_classes = [permissions.IsAuthenticated, ]
serializer_class = MediaSerializer
def post(self, request, *args, **kwargs):
user = self.request.user
print(user.username)
request.data.update({"username": user.username})
media_serializer = MediaSerializer(data=request.data)
# media_serializer.update('username', user.username)
if media_serializer .is_valid():
media_serializer.save()
return Response(media_serializer.data, status=status.HTTP_201_CREATED)
else:
print('error', media_serializer.errors)
return Response(media_serializer.errors, status=status.HTTP_400_BAD_REQUEST)
The Model:
class Media(models.Model):
objects = None
username = models.ForeignKey(User, to_field='username',
related_name="Upload_username",
on_delete=models.DO_NOTHING)
date = models.DateTimeField(auto_now_add=True)
#temp_media = models.FileField(upload_to='upload_temp', null=True)
media = models.FileField(upload_to='albumMedia', null=True)
#todo: potentially this will go to a temp folder, optimize will be called and then permananent home will be used -jjb
#MEDIA_ROOT path must be /src/upload
file_type = models.CharField(max_length=12)
MEDIA_TYPES = (
('I', "Image"),
('V', "Video")
)
media_type = models.CharField(max_length=1, choices=MEDIA_TYPES, default='I')
ACCESSIBILITY = (
('P', "Public"),
('T', "Tribe"),
('F', "Flagged")
)
user_access = models.CharField(max_length=1, choices=ACCESSIBILITY, default='P')
So I'm just trying to figure out how to fix this context problem. Plus if there are any other tips on how to get where I'm going, I'd be most appreciative.
PS I'm pretty new here. If I wrote this question in a way that is inappropriate for stack overflow, please be kind, and I will correct it. Thanks.
I don't think you need to worry about checking if the request is a POST inside the validate_media() method. Generally validation only occurs during POST, PATCH, and PUT requests. On top of that, validation only occurs when you call is_valid() on the serializer, often explicitly in a view, as you do in your post() function. As long as you never call is_valid() from anywhere other than post(), you know that it is a POST. Since you don't support patch() or put() in your view, then this shouldn't be a problem.
inside my validator, the self.context is just an empty dictionary
You must explicitly pass in context when creating a serializer for it to exist. There is no magic here. As you can see in the source code context defaults to {} when you don't pass it in.
To pass in context, you can do this:
context = {'request': request}
media_serializer = MediaSerializer(data=request.data, context=context)
Even better, just pass in the method:
context = {'method': request.method}
media_serializer = MediaSerializer(data=request.data, context=context)
You can make the context dictionary whatever you want.
I am creating first rest api in django using django rest framework
I am unable to get object in json format. Serializer always returns empty object {}
models.py
class Shop(models.Model):
shop_id = models.PositiveIntegerField(unique=True)
name = models.CharField(max_length=1000)
address = models.CharField(max_length=4000)
serializers.py
class ShopSerializer(serializers.ModelSerializer):
class Meta:
model = Shop
fields = '__all__'
views.py
#api_view(['GET'])
def auth(request):
username = request.data['username']
password = request.data['password']
statusCode = status.HTTP_200_OK
try:
user = authenticate(username=username, password=password)
if user:
if user.is_active:
context_data = request.data
shop = model_to_dict(Shop.objects.get(retailer_id = username))
shop_serializer = ShopSerializer(data=shop)
if shop:
try:
if shop_serializer.is_valid():
print('is valid')
print(shop_serializer.data)
context_data = shop_serializer.data
else:
print('is invalid')
print(shop_serializer.errors)
except Exception as e:
print(e)
else:
print('false')
else:
pass
else:
context_data = {
"Error": {
"status": 401,
"message": "Invalid credentials",
}
}
statusCode = status.HTTP_401_UNAUTHORIZED
except Exception as e:
pass
return Response(context_data, status=statusCode)
When i try to print print(shop_data) it always returns empty object
Any help, why object is empty rather than returning Shop object in json format?
Edited:
I have updated the code with below suggestions mentioned. But now, when shop_serializer.is_valid() is executed i get below error
{'shop_id': [ErrorDetail(string='shop with this shop shop_id already exists.', code='unique')]}
With the error it seems it is trying to update the record but it should only get the record and serialize it into json.
You're using a standard Serializer class in this code fragment:
class ShopSerializer(serializers.Serializer):
class Meta:
model = Shop
fields = '__all__'
This class won't read the contend of the Meta subclass and won't populate itself with fields matching the model class. You probably meant to use ModelSerializer instead.
If you really want to use the Serializer class here, you need to populate it with correct fields on your own.
.data - Only available after calling is_valid(), Try to check if serializer is valid than take it's data
I have recently started implementing dry-rest-permissions, but I can't seem to get it to check the has_object_permissions, it appears that only the global permissions work for me.
I am fairly new to implementing permissions and this is my first time implementing DRY-rest-permissions and have only recently started coding in django rest framework, so apologies for the lack of knowledge in advance.
At the moment I am trying to delete a company object by simply having a user call a URL, that URL then gets the current user's active_company and then deletes it only if the current user is the active_company's company_owner.
But what I discovered, is that I somehow can't get has_object_permissions to work anywhere?
I have noticed that if I delete has_write_permission(request), and hit the company_delete URL it gives me the following error:
'<class 'company.models.Company'>' does not have 'has_write_permission' or 'has_company_delete_permission' defined.
This means that it doesn't even look for the has_object_company_delete_permission. Meaning it only checks the global permissions rather than any of the object permissions, what am I possibly doing wrong here?
My model:
class Company(models.Model):
company_name = models.CharField(max_length=100)
company_orders = models.IntegerField(blank=True, null=True)
company_icon = models.ImageField(
upload_to='media/company_icon', blank=True)
company_owner = models.ForeignKey(
User, on_delete=models.SET_NULL, blank=True, null=True)
company_employees = models.ManyToManyField(
User, blank=True, null=True, related_name="company_employees")
def __str__(self):
return self.company_name
#staticmethod
def has_write_permission(request):
return False
def has_object_company_delete_permission(self, request):
return self.company_owner == request.user
My views
class CompanyView(viewsets.ModelViewSet): # made for viewing details
permission_classes = (DRYPermissions, )
queryset = Company.objects.all()
serializer_class = CompanySerializer
def create(self, request):
try:
company_name = request.data['company_name']
company_orders = request.data['company_orders']
company_owner = request.data['company_owner']
company_owner_obj = User.objects.get(id=company_owner)
company = Company(company_name=company_name,
company_orders=company_orders, company_owner=company_owner_obj)
company.save()
except Exception as error:
response = {
'error': str(error)
}
return Response(response, status=status.HTTP_400_BAD_REQUEST)
response = {
'message': 'Company created'
}
return Response(response, status=status.HTTP_201_CREATED)
def company_details(self, request):
try:
company_id = request.user.active_company.id
company = Company.objects.get(id=company_id)
serialized_data = CompanySerializer(company)
except Exception as error:
response = {
'error': str(error)
}
return Response(response)
return Response(serialized_data.data)
def company_edit(self, request, **kwargs):
try:
company_id = request.user.active_company.id
company = Company.objects.get(id=company_id)
serializer = CompanySerializer(
company, data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
except Exception as error:
response = {
'message': str(error)
}
return Response(response)
response = {
'message': 'Edited Successfully'
}
return Response(response)
def company_delete(self, request):
try:
company_id = request.user.active_company.id
company = Company.objects.filter(id=company_id)
company.delete()
except Exception as error:
response = {
'message': str(error)
}
return Response(response)
response = {
'message': 'Deleted Successfully'
}
return Response(response)
My urls
urlpatterns = [
# Company URLs
path('company_create/',
CompanyView.as_view({'post': 'create'}), name='company_create'), # Create company
path('company_edit/',
CompanyView.as_view(), name='company_edit'), # Edit company details
path('company_delete/',
CompanyView.as_view({'delete': 'company_delete'}), name='company_delete'), # Delete company
path('company_details/',
CompanyView.as_view({'get': 'company_details'}), name='company_details'), # get company details (owner, employees etc)
]
My serializer
class CompanySerializer(serializers.ModelSerializer):
company_owner = LimitedUserSerializer(read_only=True)
class Meta:
model = Company
fields = ['id', 'company_name', 'company_orders',
'company_icon', 'company_owner']
As described in this part of the documentation the Global permissions are always checked first and Object permissions are checked ONLY if global permissions pass.
Documentation Source:
DRY Rest Permissions allows you to define both global and object level permissions.
Global permissions are always checked first and define the ability of a user to take an action on an entire model. For example you can define whether a user has the ability to update any projects from the database.
Object permissions are checked if global permissions pass and define whether a user has the ability to perform a specific action on a single object. These are also known as row level permissions. Note: list and create actions are the only standard actions that are only global. There is no such object level permission call because they are whole table actions.
In this context you have multiple problem actually that you should correct:
Make sure has_write_permission return True for all users that own their active companies
Make sure to rename has_object_company_delete_permission since we don't need the name of the model inside the function name
Example:
#staticmethod
def has_write_permission(request):
# Everybody can create/update/delete if no specific rule says otherwise
return True
def has_object_delete_permission(self, request):
# Only owner can delete
return self.company_owner == request.user
def has_object_update_permission(self, request):
# Only owner can update
return self.company_owner == request.user
Output:
Everybody can create
Only owner can update
Only owner can delete
I know that it seems a little bit overkill just to delete an object, but with some experience it allow you to clearly define ans setup permission but also to easily share the generic rules with the Frontend by using DryPermissionsField and DRYGlobalPermissionsField
PS: This answer came from my origin answer on Github to allow people finding a solution easily from StackOverFlow
It appears that it somehow does not check for object permissions in any of my custom actions, thus adding the following check in either the get_object() or the custom action itself fixes this.
self.check_object_permissions(self.request, obtainedObject)
An example of what it looks like in my code:
#action(detail=True, methods=['patch'], pk=None)
def company_edit(self, request, **kwargs):
try:
company_id = request.user.active_company.id
company = Company.objects.get(id=company_id)
serializer = CompanySerializer(
company, data=request.data, partial=True)
self.check_object_permissions(self.request, company)
if serializer.is_valid():
serializer.save()
except Exception as error:
response = {
'message': str(error)
}
return Response(response)
response = {
'message': 'Edited Successfully'
}
return Response(response)
In my view(CreateView) I overriding my method def create, but in my validate I cant get logged user by self.context.get('request').user, so, how can I get the user logged in my validate?
UPDATE:
The Error is:
line 293, in validate
user = self.context.get('request').user
AttributeError: 'NoneType' object has no attribute 'user'
UPDATE 2
class OrderAPIPost(CreateAPIView):
permission_classes = (permissions.IsAuthenticated, )
serializer_class = MultipleOrderSerializer
queryset = Order.objects
def create(self, request, *args, **kwargs):
write_serializer = MultipleOrderSerializer(data=request.data)
write_serializer.is_valid(raise_exception=True)
orders = write_serializer.data.get('items')
orders = list(map(lambda order: Order.create_order(order, self.request.user), orders))
read_serializer = list(map(lambda order: OrderSerializerList(order), orders))
read_serializer = list(map(lambda order: order.data, read_serializer))
return Response(read_serializer, status=status.HTTP_201_CREATED)
So, from what I can see in your code, you are creating the serializer manually without adding the context. In most cases, allowing the CreateView create the serializer by itself suffices but if you really need to create it by yourself, then you need to remember to pass the context. Somthing like this:
context = {'request': self.request}
write_serializer = MultipleOrderSerializer(data=request.data, context=context)
You can check the view's get_serializer() method to see how a serializer is properly created. I really advice that you refactor your code and try to use the existing solution for creating serializers
I'm using Django Rest Framework 3.0 and I have a model:
class Vote(models.Model):
name = ...
token = models.CharField(max_length=50)
where token is a unique identifier that I generate from the request IP information to prevent the same user voting twice
I have a serializer:
class VoteSerializer(serializers.ModelSerializer):
name = ...
token = serializers.SerializerMethodField()
class Meta:
model = Vote
fields = ("id", "name", "token")
def validate(self, data):
if Rating.objects.filter(token=data['token'], name=data['name']).exists():
raise serializers.ValidationError("You have already voted for this")
return data
def get_token(self, request):
s = ''.join((self.context['request'].META['REMOTE_ADDR'], self.context['request'].META.get('HTTP_USER_AGENT', '')))
return md5(s).hexdigest()
and a CreateView
But I am getting a
KeyError: 'token'
when I try to post and create a new Vote. Why is the token field not included in the data when validating?
The docs mention:
It can be used to add any sort of data to the serialized representation of your object.
So I would have thought it would be also available during validate?
Investigating, it seems that SerializerMethodField fields are called after validation has occurred (without digging into the code, I don't know why this is - it seems counter intuitive).
I have instead moved the relevant code into the view (which actually makes more sense conceptually to be honest).
To get it working, I needed to do the following:
class VoteCreateView(generics.CreateAPIView):
serializer_class = VoteSerializer
def get_serializer(self, *args, **kwargs):
# kwarg.data is a request MergedDict which is immutable so we have
# to copy the data to a dict first before inserting our token
d = {}
for k, v in kwargs['data'].iteritems():
d[k] = v
d['token'] = self.get_token()
kwargs['data'] = d
return super(RatingCreateView, self).get_serializer(*args, **kwargs)
def get_token(self):
s = ''.join((self.request.META['REMOTE_ADDR'], self.request.META.get('HTTP_USER_AGENT', '')))
return md5(s).hexdigest()
I really hope this isn't the correct way to do this as it seems totally convoluted for what appears to be a pretty straight forward situation. Hopefully someone else can post a better approach to this.