Cannot assign "...'": "TestData.user" must be a "User" instance - django

Very new to the Django Rest Framework, so would appreciate some help with this one. I get the error in the title when I try and do a POST request in Postman with an appropriate auth token.
I've made a table that I want to send a POST request to, but having issues with getting a user FK to be accepted as one of the columns. Plz see model/serializer/view below:
Model
class TestData (models.Model):
TestSDG = models.DecimalField(decimal_places=0, max_digits=2, default=0)
user = models.ForeignKey("auth.User", related_name="testdata", on_delete=models.CASCADE)
Serializer
class TestDataSerializer(serializers.ModelSerializer):
class Meta:
model = TestData
fields = ('id', 'TestSDG')
View
#csrf_exempt
def testDataApi(request, id=0):
if request.method == 'GET':
testdata = TestData.objects.all()
testdata_serializer = TestDataSerializer(testdata,many=True)
return JsonResponse(testdata_serializer.data,safe=False)
elif request.method == 'POST':
testdata_data=JSONParser().parse(request)
testdata_serializer=TestDataSerializer(data=testdata_data)
if testdata_serializer.is_valid():
testdata_serializer.save(user=request.user)
return JsonResponse("Added Successfully", safe=False)
The POST request works fine if I don't use the user as a foreign key, and I change testdata_serializer.save(user=request.user) back to testdata_serializer.save(), but I want the table to require a user's id.
Appreciate any help, thank you.

You should be using a ModelViewset in your views.py file - then you can override the update method on your serializer:
views.py
from rest_framework.viewsets import ModelViewSet
class TestDataViewSet(ModelViewSet):
queryset = TestData.objects.all()
serializer_class = TestDataSerializer
serializers.py
class TestDataSerializer(serializers.ModelSerializer):
...
def update(self, instance, validated_data):
# get user id from validated data:
user_id = validated_data.pop('user_id')
# get user:
user = User.objects.get(id=user_id)
# set user on instance:
instance.user = user
instance.save()
# continue with update method:
super().update(instance, validated_data)

You mentioned that you are using an auth token. Try verifying in your view testDataApi if request.user was correctly set with an auth.User object. Try logging it with something like below to make sure that it is correctly set to the user for the provided token:
#csrf_exempt
def testDataApi(request, id=0):
print(type(request.user), request.user) # Should display the user for the provided token.
...
If it isn't set, then you have to configure how it would correctly map an auth.User object from a provided token. You might want to look at the following:
AuthenticationMiddleware - Sets the request.user object.
AUTHENTICATION_BACKENDS - Custom authentication of a token and then return the associated auth.User object
DEFAULT_AUTHENTICATION_CLASSES - Only if using djangorestframework. Sets the request.user object.
TokenAuthentication, JSONWebTokenAuthentication, etc. - Only if using djangorestframework. Some implementations that authenticates tokens. Perhaps they weren't configured correctly.

Related

Can I provide automatically current user in Django rest API like this?

I have a Django rest api. In my model I have a user as a foreign key. When I do a post with the, I do not want that the user needs to provide his own user. But if the user does not provide his user credentials, the serializer won't be valid and then won't be saved. I have found that we can access to serialized dat before validation with initial_data so I am doing like this to save the user automatically from the token provided. The user need to provide everything except his own user. Is it ok or am I doing something not recommended ?
#api_view(['POST'])
#permission_classes([IsAuthenticated])
def add_mesure(request):
serializer = MesureSerializer(data=request.data)
serializer.initial_data['user'] = request.user.id
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
As you are already taking the token as a form of user verification, hence you can omit the user field from serializer (otherwise user might put someone else's id for example) and then pass the request object to serializer to get user from it during saving. Like this:
#serializer
class MesureSerializer(ModelSerializer):
class Meta:
exclude = ['user',]
...
def create(self, validated_data):
validated_data['user'] = self.context['request'].user
return super().create(validated_data)
Also, to pass the value of request, you can use context parameter of the serializer.
#view
serializer = MesureSerializer(data=request.data, context={'request':request})

How to add custom field when verifying access token in rest_framework_simplejwt

I tried to override validate method at TokenVerifySerializer but this raises AttributeError.
from rest_framework_simplejwt.serializers import TokenVerifySerializer
from rest_framework_simplejwt.views import TokenVerifyView
class CustomTokenVerifySerializer(TokenVerifySerializer):
def validate(self, attrs):
data = super(CustomTokenVerifySerializer, self).validate(attrs)
data.update({'fullname': self.user.fullname})
return data
class CustomTokenVerifyView(TokenVerifyView):
serializer_class = CustomTokenVerifySerializer
But that does work when using TokenObtainPairSerializer and TokenObtainPairView.
The above snippet raises AttributeError with 'CustomTokenVerifySerializer' object has no attribute 'user'.
The way adding custom fields in data is right I think. The problem is in self.user.fullname. Because the serializer doesn't have the user field. If you need user info in the serializer you have to get the user from DB.
But In this case, I think you need a current user or authenticated user. To get the authenticated user in the serializer you need to pass the user or request object as a context to the serializer. Here is an example:
def validate(self, attrs):
request = self.context.get('request', None)
if request:
user = request.user
And you have to initialize the serializer as below:
serializer = MySerializer(
data=request.data,
context={
'request': request
}
)

make view accessible to only specific users (i.e. who created that model) in django rest

I have one model which has user as its ForeignKey attribute which is auto fill ie. logged in user is filled there. I have made token authentication. Only Authenticated // i mean authorized users can visit that view. But i am planning to make such that only the user which had created that model object can only update the content of that object.
For example:
class Something(models.Model):
sth_name = models.CharField(max_length=18)
sth_qty = models.IntegerField()
user = models.ForeignKey(User)
on my View:
I override perform_create() to associate to above model automaticall.
def perform_create(self, serializer):
return serializer.save(user=self.request.user)
What do i exactly need to do? I have to write some permissions method, But I am really stuck.
Yes, you need to create an object level permission. The DRF tutorial covers this nicely here: http://www.django-rest-framework.org/tutorial/4-authentication-and-permissions/#object-level-permissions
Specifically, create a file permissions.py in your app, and add this permission there:
class IsOwnerOrReadOnly(permissions.BasePermission):
"""
Custom permission to only allow owners of an object to edit it.
"""
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return True
return obj.user == request.user
Then, in your view class which has the update resource for the Something model (probably SomethingDetail), add the permission_classes field:
class SomethingDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Something.objects.all()
serializer_class = SomethingSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,
IsOwnerOrReadOnly,)
Just add the user when retrieving the object
obj = get_object_or_404(Something, pk=pk, user=request.user)
Note that this will throw 404. If you want 403 error, use custom condition to check the user and raise PermissionDenied. If you want to do this for multiple views, put the condition logic in a decorator.

Django Tastypie Import Error on resources

I have something strange going on that I can't seem to crack. I'm building an API with Tastypie and when I issue this call in my browser against localserver, it works fine: localserver/api/v1/userfavorite/?user__username=testowner
However, in my code, I'm getting an error: "int() argument must be a string or a number, not 'SimpleLazyObject'". I realize it has to do with the user being treated as a request.user object, but I can't figure out where/why. I'm very confused why it works when issuing the API call in the browser, but in the code it is not working.
Here is my code:
# views.py
#login_required
def favorites(request):
'''
display a list of posts that a user has marked as favorite
'''
user = request.user
favorites_url = settings.BASE_URL + "/api/v1/userfavorite/?user__username=" + user.username
favorites = get_json(favorites_url)
return render(request, "maincontent/favorites.html", {'favorites':favorites})
# resources.py
class UserFavoriteResource(ModelResource):
'''
manage post favorites by a user. Users can use a favorites list
to easily view posts that they have liked or deemed important.
'''
user = fields.ForeignKey(UserResource, 'user')
post = fields.ForeignKey('blog.api.resources.PostResource', 'post', full=True)
class Meta:
queryset = UserFavorite.objects.all()
allowed_methods = ['get', 'post', 'delete']
authentication = Authentication()
authorization = Authorization()
filtering = {
'user':ALL_WITH_RELATIONS
}
def hydrate_user(self, bundle):
# build the current user to save for the favorite instance
bundle.data['user'] = bundle.request.user
return bundle
def get_object_list(self, request):
# filter results to the current user
return super(UserFavoriteResource, self).get_object_list(request)\
.filter(user=request.user)
# utils.py
def get_json(url):
# return the raw json from a request without any extraction
data = requests.get(url).json()
return data
Some notes:
1. I have the post method working to create the UserFavorite item
2. I can verify that the favorites_url is being generated correctly
3. I have tried hardcoding the favorites_url as well, same error.
EDIT: 4. I am logged in while doing this, and have verified that request.user returns the user
This doesn't work because there is Anonymous user in your request.user. You are using Authentication it does not require user to be logged in. So if you perform requests call that request is not authenticated and request.user is AnonymousUser and that error occurs when you try to save Anonymous user to db. Tastypie documentation advices to not using browsers to testing things up, just curl instead. Browsers stores a lot of data and yours one probably remembered you have been logged to admin panel in localhost:8000 on another tab that's why it worked in browser.
I would prefer something like this:
def hydrate_user(self, bundle):
"""\
Currently logged user is default.
"""
if bundle.request.method in ['POST', 'PUT']:
if not bundle.request.user.is_authenticated():
raise ValidationError('Must be logged in')
bundle.obj.user = bundle.request.user
bundle.data['user'] = \
'/api/v1/userauth/user/{}'.format(bundle.request.user.pk)
return bundle

How would I create a CBV using django rest framework which will either retrieve a model instance or create a new one?

I have a person model with the fields first_name, last_name and email. I'd like to send these fields to a view that would check the DB for an existing instance. If there is one the ID will be returned, if not a new instance will be created and the new ID returned. Using a standard FBV I would do this (cut down version, minus validation etc):
from django.http import HttpResponse
from mysite.models import Person
import json
def get_or_create_person(request):
try:
person = Person.objects.get(first_name=request.POST['first_name'],
last_name=request.POST['last_name'], email=request.POST['email'])
except Person.DoesNotExist:
person = Person(first_name=request.POST['first_name'],
last_name=request.POST['last_name'], email=request.POST['email'])
person.save()
response = {'id': person.id}
return HttpResponse(json.dumps(response))
Is there any real point in converting it to use a CBV and tie it in with the rest framework and if so how would I go about doing it? The main reason I want it as a CBV is so I can use mixins etc that I already include in other parts of my app.
Edit: I thought about using the ViewSets available through the rest framework but they split this functionality into GET and POST methods, with get retrieving a record and post either updating or creating one. I basically need a view which can accept either.
I had to kinda combine the get and post functions as follows:
class GetOrCreateCustomerView(APIView):
'''
API endpoint that takes an email address, first name & surname and then
either returns the matching customer id or creates a new customer and
returns that id
'''
required_fields = ('first_name', 'last_name', 'email')
def get(self, request, format=None):
request_data = request.GET
response = self.get_customer_id(request_data)
return JSONResponse(response)
def post(self, request, format=None):
request_data = request.POST
response = self.get_customer_id(request_data)
return JSONResponse(response)
def get_customer_id(self, data):
kwargs = {}
for f in self.required_fields:
if f in data:
kwargs[f] = data[f]
else:
return None
try:
customer = Customer.objects.get(**kwargs)
except Customer.DoesNotExist:
customer = Customer(**kwargs)
customer.save()
if customer.id is not None:
response = {'customer_id': customer.id}
else:
response = {
'error': 'Please provide a first name, surname and email address'
}
return response
The JSONResponse referenced here is the one from the rest framework docs.
for classbased views in django-rest-framework this is usually done by:
a GET request to the resource will retrieve records
a POST request to the same resource will create a record
I believe there are a couple examples of this in the django rest framework documentation