Django Tastypie Import Error on resources - django

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

Related

Store extra data like refresh_token when signing up via access_token

I am trying to signup using access token for google. My frontend is next.js with next-auth.js fetching the access_token, refresh_token, etc. I am using python social auth with Django to persist the user information. I use the access_token provided by next-auth to signup the user in Django. How do I make sure that the other response fields like refresh_token, and expires are saved in the Django DB? I can pass the required fields from next-auth to an API in Django but not sure what the expected format is.
https://python-social-auth.readthedocs.io/en/latest/use_cases.html#signup-by-oauth-access-token
#psa('social:complete')
def register_by_access_token(request, backend):
# This view expects an access_token GET parameter, if it's needed,
# request.backend and request.strategy will be loaded with the current
# backend and strategy.
token = request.GET.get('access_token')
user = request.backend.do_auth(token)
if user:
return HttpResponse(json.dumps(get_tokens_for_user(user)))
else:
return HttpResponse('Unauthorized', status=401)
Sounds like you need a table to store these tokens (not sure if you have one already), lets take this model as an example:
class StoredToken(models.Model):
refresh_token = models.CharField()
access_token = models.CharField()
# Maybe you need the related user too?
user = models.ForeignKey(User, on_delete=models.CASCADE)
After you created your table, all you need is to save your data when you have it, from your example:
def get_tokens_for_user(user):
refresh = RefreshToken.for_user(user)
update_last_login(None, user)
StoredToken.objects.create(refresh_token=refresh, access_token=refresh.access_token, user=user)
return {
'refresh': str(refresh),
'access': str(refresh.access_token),
}
You obviously need to do migrations to generate your table, but I assume you have the knowledge to do that. Otherwise refer to the django documentation to create models
I was able to store the refresh token along with other EXTRA_DATA in python social auth by adding response kwarg. This response kwarg is nothing but the dictionary containing the extra data.
For example: With Google, response is the return value of https://developers.google.com/identity/protocols/oauth2/web-server#exchange-authorization-code
#psa
def register_by_access_token(request, backend):
# If it's needed, request.backend and request.strategy will be loaded with the current
# backend and strategy.
response = json.loads(request.body.decode('utf-8'))
user = request.backend.do_auth(response['access_token'], response=response)
if user:
return HttpResponse(json.dumps(get_tokens_for_user(user)))
else:
return HttpResponse('Unauthorized', status=401)

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

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.

Django testing - test if logged in user is owner of userdetails

So I've made this little app where users have an account page were they can see and update their own details.
Now I'm writing some tests for the DetailView and UpdateView. I would like to verify that the logged in user is indeed the owner of the user details.
I've created a testdatabase with different users and added three tests.
Check if the user is logged in
When a user is logged in does he get the right template
When a user is logged in does he see his own details only
The problem is in the last test, I tried to retrieve the logged in user and compare this with the current user details but this isn't working. Any ideas?
Edit: The test now passes successfully, so the user data belongs to the user who is logged in.
However this doesn't feel like a valid test method. Even though the user matches the owner of the details I'm looking for a way to verify if a user would be able to access the details of someone else.
Before, I would use the user id in the url like;
urls.py
path('account/<int:pk>/', views.AccountDetailView.as_view(), name='account_detail'),
So someone would be able to edit the urlpath and access someone else his details if the LoginRequiredMixin wasn't added.
By using get_object(self):, this is no longer possible, what's the best way to test this possibility?
views.py
class AccountDetailView(LoginRequiredMixin, DetailView):
model = User
template_name = 'account.html'
'''
Retrieve user id from "self.request.user" instead of retrieving the user_id
from the URL. This way we can eliminate the user id in the URL.
'''
def get_object(self):
return self.request.user
test_views.py
class LoggedInTestCase(TestCase):
'''
Setup LoginInTestCase
'''
def setUp(self):
guest = User.objects.create_user(username='john.doe', email='john#doe.com', password='1234')
guest_other = User.objects.create_user(username='david.doe', email='david#doe.com', password='5678')
class AccountDetailViewTests(LoggedInTestCase):
'''
Test the UserDetailView which shows the user details
'''
def test_login_required_redirection(self):
'''
Test if only logged in users can view the user detail page
'''
self.url = reverse('account_detail')
login_url = reverse('account_login')
response = self.client.get(self.url)
self.assertRedirects(response, '{login_url}?next={url}'.format(login_url=login_url, url=self.url))
def test_logged_in_uses_correct_template(self):
'''
Test if logged in user gets to see the correct template
'''
login = self.client.login(username='john.doe', password='1234')
response = self.client.get(reverse('account_detail'))
# Check if our guest is logged in
self.assertEqual(str(response.context['user']), 'john.doe')
# Check for response "succes"
self.assertEqual(response.status_code, 200)
# Check if we get the correct template
self.assertTemplateUsed(response, 'account.html')
def test_accountdetails_belong_to_logged_in_user(self):
'''
Test if logged in user can only see the details that belong to him
'''
login = self.client.login(username='john.doe', password='1234')
response = self.client.get(reverse('account_detail'))
# Check if our guest is logged in matches the
user = User.objects.get(username='john.doe') #edited
self.assertEqual(response.context['user'], user)
# Check for response "success"
self.assertEqual(response.status_code, 200)
The client.login method returns True if the login succeeded, it does not return the user.
You can fetch the user from the database.
user = User.objects.get(username='john.doe')
self.assertEqual(response.context['user'], user)
Or compare the string as in your other test.
self.assertEqual(str(response.context['user']), 'john.doe')

Custom django authentication backend doesn't log user in first time, but works second time

So I'm using Rdio to login and create users, and wrote a backend to handle its oauth. The first time you try to sign in using Rdio, it creates a user and an attached Rdio user, but it doesn't create a session and return the session cookie.
The flow is like any oauth2 flow: you press a button on my app, it redirects w/ get params to Rdio, and Rdio calls a callback view on my app (along with a code in the GET params). In that callback view, I call authenticate:
class RdioCallbackView(View):
def get(self, request):
""" here, you need to create and auth a django user and create and tie the rdio user's stuff to it """
if request.user.is_authenticated() == False:
try:
rdio_code = request.GET['code']
except KeyError:
return redirect(reverse('login'))
# authenticate
user = auth.authenticate(rdio_code=rdio_code)
if user is not None and user.is_active:
auth.login(request, user)
else:
return render(request, 'home/login.html', {'rdio_url': create_rdio_auth_url(), 'message': "That code didn't seem to work"})
else:
# user exists!
user = request.user
return HttpResponseRedirect(reverse('the-next-view'))
The custom auth backend looks like this:
class RdioBackend(object):
def authenticate(self, rdio_code=None):
token_info = exchange_rdio_code(rdio_code)
try:
access_token = token_info['access_token']
refresh_token = token_info['refresh_token']
except KeyError:
return None
except TypeError:
# the code was probably already used.
return None
rdio_user_dict = get_rdio_user_for_access_token(access_token)
rdio_key = rdio_user_dict['key']
try:
rdio_user = RdioUser.objects.get(rdio_id=rdio_key)
rdio_user.access_token = access_token
rdio_user.refresh_token = refresh_token
rdio_user.save()
user = rdio_user.user
except RdioUser.DoesNotExist:
user = User.objects.create(username=rdio_key)
user.set_unusable_password()
rdio_user = RdioUser.objects.create(
rdio_id = rdio_key,
access_token = access_token,
refresh_token = token_info['refresh_token'],
user = user,
)
return user
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
And that's where things get weird. It doesn't seem to make a new Session object, and definitely doesn't return a session cookie. However, when I go back and do the Rdio login again for a second time, it returns a session cookie, makes the session on the backend, and login and auth work perfectly.
And I think my AUTHENTICATION_BACKENDS settings is right:
AUTHENTICATION_BACKENDS = (
'appname.backend.RdioBackend',
'django.contrib.auth.backends.ModelBackend',
)
Edit: More possibly relevant info:
The views that it's redirecting to have a LoginRequiredMixin:
class LoginRequiredMixin(object):
#classmethod
def as_view(cls, **initkwargs):
view = super(LoginRequiredMixin, cls).as_view(**initkwargs)
return login_required(view)
And in RdioCallbackView, when I change the final line from return HttpResponseRedirect(reverse('the-next-view')) to instead just serve the template directly with return render(request, 'path/to.html', param_dict), it does serve the cookie and make a sessionid, but then it deletes it from the DB and from the browser the moment I navigate away from that screen.
This might be the dumbest bug ever. It turns out that if you create a user without a password, you don't need to call user.set_unusable_password(). And if you do call user.set_unusable_password(), it somehow messes with any auth you do (even AFTER you call that).
So to fix this, I just got rid of the call to user.set_unusable_password() in my custom django auth backend.

Build tastypie resource in view?

I already know how to create ModelResource in tastypie. For example, I have UserResource in resources.py and User in models.py. However in views.py, I have a view called match_user where it takes the list of all user and match to request.user. It return a render_to_response html called mymatch.html. Everything works on the browser but I want to create an API for this particular match_user. How can I do that?
Thank you
I think the following answers your question:
class UserResource(ModelResource):
class Meta:
queryset = User.objects.all()
resource_name = "user"
authentication = SessionAuthentication()
# User is only authorized to view his own details
def apply_authorization_limits(self, request, object_list):
return object_list.filter(pk=request.user.pk)
Session authentication would work if the user has an active session and is already logged in. For more authentication options, see https://django-tastypie.readthedocs.org/en/latest/authentication_authorization.html?highlight=authentication#authentication-options