Django TestCase for group permissions? - python

I have a TestCase for some views, but I am also trying to incorporate tests for my post admin group. They have permissions to post.change_post and post.delete_post, just like the user can.
Here is a little code snippet,
from django.contrib.auth import get_user_model
from django.test import TestCase, Client
from django.urls import reverse
from fixtureless.factory import create
from .models import Post
class TestPostViews(TestCase):
def setUp(self):
super().setUp()
self.user = get_user_model().objects.create(username='test_user')
self.form = create(Post, {'title': 'test_title'})
self.client = Client()
def test_update_post(self):
"""
Test UpdateView updates users post
"""
self.client.force_login(user=self.user)
response = self.client.put(reverse('post:update', kwargs={'pk': self.form.pk}),
{'title': 'testing'})
self.assertEqual(response.status_code, 200)
# reloads a models value from the database
self.form.refresh_from_db()
self.assertEqual(self.form.title, 'test_title')
def test_delete_confirm_page(self):
"""
Test DeleteView takes user to confirmation page
"""
self.client.force_login(user=self.user)
response = self.client.get(reverse('post:delete', kwargs={'pk': self.form.pk}))
self.assertContains(response, 'delete')
def test_delete_post(self):
"""
Test DeleteView deletes a post
"""
self.client.force_login(user=self.user)
response = self.client.post(reverse('post:delete', kwargs={'pk': self.form.pk}))
self.assertRedirects(response, reverse('post:list'))
self.assertFalse(Post.objects.filter(pk=self.form.pk).exists())
Any tips on this? Should I create a separate TestCase for the post admin, or is there a way to add this in the initial setUp() method?

I think I am getting a little closer with this snippet...
class TestPostViews(TestCase):
def setUp(self):
super().setUp()
self.user1 = get_user_model().objects.create(username='test_user1')
self.user2 = get_user_model().objects.create(username='test_user2',
password='test_pw')
self.form = create(Post, {'user': self.user1, 'title': 'test_title'})
self.client = Client()
# Group setup
group_name = "post admin"
self.group = Group(name=group_name)
self.group.save()
def test_admin_can_delete(self):
self.user2.groups.add(self.group)
self.user2.save()
self.client.login(username='test_user2', password='test_pw')
response = self.client.post(reverse('posts:delete', kwargs={'pk': self.form.pk}))
self.assertRedirects(response, reverse('posts:list'))
still getting an error
Failure
Expected :'/accounts/login/?next=%2Fpost%2Fdelete%2F2143257160%2F'
Actual :'/post/list/'

Related

How can I unit test saving user input in Django?

I am working on a small project and I want to be able to test the post methods, saving to the database if the email does not exist, and rendering to index.html for the view below. I've looked at YouTube videos and blog tutorials and I can't seem to find the answer that I am looking for. I have included the Model, View, and my current tests.
Model:
class EmailList(models.Model):
email = models.TextField(unique=True)
View:
def home(request):
# Join Email List
if request.method == 'POST':
if request.POST.get('email'):
email = request.POST.get('email')
if not EmailList.objects.filter(email=email).exists():
emailInput = EmailList()
emailInput.email = email
emailInput.save()
return render(request, 'email/index.html', {})
else:
return render(request, 'email/index.html', {})
Test I have so far:
from django.test import TestCase
from email.models import EmailList
class HomeViewTest(TestCase):
#classmethod
def setUpTestData(cls):
# Create a new email to test
EmailList.objects.create(email="test1#example.com")
def test_home_view_url_exists_at_desired_location(self):
response = self.client.get('')
self.assertEqual(response.status_code, 200)
def test_home_view_post_request_method(self):
response = self.client.post('', {'email' : 'test1#example.com'})
self.assertEqual(response.status_code, 200)
def test_home_view_save_email(self):
self.assertEqual(EmailList.objects.count(), 1)
You should not create an instance of EmailList in your setUpTestData method, because you want to check if your view correctly creates a new instance, right now you manually create a new one before testing. Try this:
from django.test import TestCase
from tasckq.models import EmailList
class HomeViewTest(TestCase):
def test_home_view(self):
# Tests the get method
response = self.client.get('')
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, template_name='email/index.html')
# Tests the post method
email = 'test1#example.com'
response = self.client.post('', {'email' : email})
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, template_name='email/index.html')
# A new EmailList instance has been successfully created
self.assertTrue(EmailList.objects.filter(email=email).exists())
response = self.client.post('', {'email' : email})
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, template_name='email/index.html')
# A new EmailList instance has not been created because the email was already in use
self.assertEqual(EmailList.objects.count(), 1)

Django - TDD: 'HttpRequest' has no attribute 'POST'

So I'm doing TDD with Django and I'm stuck on the following problem.
Test method for class from TestCase
def test_home_page_can_save_POST_request(self):
request = HttpRequest
request.method = 'POST'
request.POST['item_text'] = 'A new list item'
response = home_page(request)
self.assertIn('A new list item', response.content.decode())
And I'm getting error as:
request.POST['item_text'] = 'A new list item'
AttributeError: type object 'HttpRequest' has no attribute 'POST'
But HttpRequest has the attribute 'POST' as per Django docs.
Thansk guys!
You forgot the parentheses after HttpRequest :)
That's why django is saying HttpRequest has no attribute 'POST'
You should consider using RequestFactory for your test cases.
Example from the docs:
from django.contrib.auth.models import AnonymousUser, User
from django.test import TestCase, RequestFactory
from .views import MyView, my_view
class SimpleTest(TestCase):
def setUp(self):
# Every test needs access to the request factory.
self.factory = RequestFactory()
self.user = User.objects.create_user(
username='jacob', email='jacob#…', password='top_secret')
def test_details(self):
# Create an instance of a GET request.
request = self.factory.get('/customer/details')
# Recall that middleware are not supported. You can simulate a
# logged-in user by setting request.user manually.
request.user = self.user
# Or you can simulate an anonymous user by setting request.user to
# an AnonymousUser instance.
request.user = AnonymousUser()
# Test my_view() as if it were deployed at /customer/details
response = my_view(request)
# Use this syntax for class-based views.
response = MyView.as_view()(request)
self.assertEqual(response.status_code, 200)
When testing, if you need a request object, you can also use the request factory to generate one
Reference from the docs:
https://docs.djangoproject.com/en/1.11/topics/testing/advanced/
from django.contrib.auth.models import AnonymousUser, User
from django.test import TestCase, RequestFactory
from .views import MyView, my_view
class SimpleTest(TestCase):
def setUp(self):
# Every test needs access to the request factory.
self.factory = RequestFactory()
self.user = User.objects.create_user(
username='jacob', email='jacob#…', password='top_secret')
def test_details(self):
# Create an instance of a GET request.
request = self.factory.get('/customer/details')
# Recall that middleware are not supported. You can simulate a
# logged-in user by setting request.user manually.
request.user = self.user
# Or you can simulate an anonymous user by setting request.user to
# an AnonymousUser instance.
request.user = AnonymousUser()
# Test my_view() as if it were deployed at /customer/details
response = my_view(request)
# Use this syntax for class-based views.
response = MyView.as_view()(request)
self.assertEqual(response.status_code, 200)
In your case, your test could look like this:
from django.test import RequestFactory
...
def test_home_page_can_save_POST_request(self):
factory = RequestFactory()
response = factory.post('PATH_TO_YOUR_VIEW', data={
'item_text': 'A new list item',
})
self.assertIn('A new list item', response.content.decode())
I have two solution.
1.- Create request object with RequestFactory and calling at views.
def test_home_page_can_save_a_POST_request(self):
request = self.factory.post("/", data={'item_text': 'A new list item',})
response = home_page(request)
self.assertIn('A new list item', response.content.decode())
2.- Or use the response of RequestFactory().
def test_home_page_can_save_a_POST_request(self):
response = self.factory.post("/", data={'item_text': 'A new list item',})
self.assertIn('A new list item', response.readlines()[3].decode())

Hide Flask-Admin route

I'm building a Flask blog and setting up an admin interface now. I've read about setting up security for Flask-Admin. I've managed to set up security (access is restricted only to logged-in users) for all my models, but users can still access the '/admin' route which has just a bare home button in it.
My question is: is there any way I could hide or protect the '/admin' route, so an unauthenticated user is just redirected to the login page/ denied access?
Thanks a lot!
Attaching my current admin setup:
from flask_admin import Admin
from flask_login import current_user
from flask_admin.contrib import sqla
from wtforms.widgets import TextArea
from wtforms import TextAreaField
from samo.models import User, Post, Tag
from samo import app,db
admin = Admin(app, name='Admin', template_mode='bootstrap3')
class CKTextAreaWidget(TextArea):
def __call__(self, field, **kwargs):
if kwargs.get('class'):
kwargs['class'] += ' ckeditor'
else:
kwargs.setdefault('class', 'ckeditor')
return super(CKTextAreaWidget, self).__call__(field, **kwargs)
class CKTextAreaField(TextAreaField):
widget = CKTextAreaWidget()
class PostAdmin(sqla.ModelView):
form_overrides = dict(content=CKTextAreaField)
create_template = 'blog/ckeditor.html'
edit_template = 'blog/ckeditor.html'
form_excluded_columns = ('slug')
def is_accessible(self):
return current_user.is_authenticated
admin.add_view(PostAdmin(Post, db.session))
class TagAdmin(sqla.ModelView):
def is_accessible(self):
return current_user.is_authenticated
admin.add_view(TagAdmin(Tag, db.session))
class UserAdmin(sqla.ModelView):
def is_accessible(self):
return current_user.is_authenticated
admin.add_view(UserAdmin(User, db.session))
I use such a configuration like you described it for all my Websites. Use an AdminIndexView. Here is an example of how handle login, logout and redirection in case the user is not authorized.
class FlaskyAdminIndexView(AdminIndexView):
#expose('/')
def index(self):
if not login.current_user.is_authenticated:
return redirect(url_for('.login'))
return super(FlaskyAdminIndexView, self).index()
#expose('/login', methods=['GET', 'POST'])
def login(self):
form = LoginForm(request.form)
if helpers.validate_form_on_submit(form):
user = form.get_user()
if user is not None and user.verify_password(form.password.data):
login.login_user(user)
else:
flash('Invalid username or password.')
if login.current_user.is_authenticated:
return redirect(url_for('.index'))
self._template_args['form'] = form
return super(FlaskyAdminIndexView, self).index()
#expose('/logout')
#login_required
def logout(self):
login.logout_user()
return redirect(url_for('.login'))
In your __init__.py where you create your admin object do this:
admin = Admin(index_view=FlaskyAdminIndexView())

Getting 403 response in test script using Django REST and OAuth2

I am getting a 403 response in my test script which uses Django REST and OAuth2. I am using force_authenticate.
In urls.py:
urlpatterns = [
url(r'^user-id/$', views.UserIDView.as_view(), name='user-id'),
...
In views.py:
from oauth2_provider.ext.rest_framework import TokenHasReadWriteScope
class StdPerm(TokenHasReadWriteScope):
pass
StdPermClasses = (IsAuthenticated, StdPerm)
class UserIDView(APIView):
permission_classes = StdPermClasses
renderer_classes = (JSONRenderer,)
def get(self, request, format=None):
return Response({'id': request.user.id})
In tests.py:
from django.contrib.auth.models import User
from django.core.urlresolvers import reverse
from rest_framework import status
from rest_framework.test import APITestCase
class CreateUserTest(APITestCase):
def setUp(self):
self.user = User.objects.create_user('daniel', 'daniel#test.com',
password='daniel')
self.user.save()
def test_get_user_id(self):
self.client.login(username='daniel', password='daniel')
self.client.force_authenticate(user=self.user)
response = self.client.get(reverse('user-id'))
self.assertEqual(response.status_code, status.HTTP_200_OK)
Usually I use curl which works no problem:
curl -X GET "http://127.0.0.1:8000/user-id/" -H "Authorization: Bearer b3SlzXlpRSxURyh2BltwdHmhrlqNyt"
Update I changed some lines in test_get_user_id:
token = Token.objects.create(user=self.user)
self.client.force_authenticate(user=self.user, token=token)
Now I get the error:
assert False, ('TokenHasScope requires either the' AssertionError:
TokenHasScope requires either the`oauth2_provider.rest_framework
.OAuth2Authentication` authentication class to be used.
I found a solution to this problem. Basically my code was missing two things, namely the OAuth2 application record and an access token specific to OAuth2. I added the following to setUp:
app = Application(
client_type='confidential',
authorization_grant_type='password',
name='MyAppTest',
user_id=1
)
app.save()
...for generating a suitable access token:
app = Application.objects.get(name='MyAppTest')
token = generate_token()
expires = now() + timedelta(seconds=oauth2_settings. \
ACCESS_TOKEN_EXPIRE_SECONDS)
scope = 'read write'
access_token = AccessToken.objects.create(
user=self.user,
application=app,
expires=expires,
token=token,
scope=scope
)
...and then to use the token:
self.client.force_authenticate(user=self.user, token=access_token)
The import section ended up like so:
from django.contrib.auth.models import User
from django.core.urlresolvers import reverse
from rest_framework import status
from rest_framework.test import APITestCase
from oauth2_provider.settings import oauth2_settings
from oauthlib.common import generate_token
from oauth2_provider.models import AccessToken, Application
from django.utils.timezone import now, timedelta
This worked for me
from oauth2_provider.settings import oauth2_settings
from oauth2_provider.models import get_access_token_model,
get_application_model
from django.contrib.auth import get_user_model
from django.utils import timezone
from rest_framework.test import APITestCase
Application = get_application_model()
AccessToken = get_access_token_model()
UserModel = get_user_model()
class Test_mytest(APITestCase):
def setUp(self):
oauth2_settings._SCOPES = ["read", "write", "scope1", "scope2", "resource1"]
self.test_user = UserModel.objects.create_user("test_user", "test#example.com", "123456")
self.application = Application.objects.create(
name="Test Application",
redirect_uris="http://localhost http://example.com http://example.org",
user=self.test_user,
client_type=Application.CLIENT_CONFIDENTIAL,
authorization_grant_type=Application.GRANT_AUTHORIZATION_CODE,
)
self.access_token = AccessToken.objects.create(
user=self.test_user,
scope="read write",
expires=timezone.now() + timezone.timedelta(seconds=300),
token="secret-access-token-key",
application=self.application
)
# read or write as per your choice
self.access_token.scope = "read"
self.access_token.save()
# correct token and correct scope
self.auth = "Bearer {0}".format(self.access_token.token)
def test_success_response(self):
url = reverse('my_url',)
# Obtaining the POST response for the input data
response = self.client.get(url, HTTP_AUTHORIZATION=self.auth)
# checking wether the response is success
self.assertEqual(response.status_code, status.HTTP_200_OK)
Now everything will work as expected. Thanks

login() in Django testing framework

I have started using Django's testing framework, and everything was working fine until I started testing authenticated pages.
For the sake of simplicity, let's say that this is a test:
class SimpleTest(TestCase):
def setUp(self):
user = User.objects.create_user('temporary', 'temporary#gmail.com', 'temporary')
def test_secure_page(self):
c = Client()
print c.login(username='temporary', password='temporary')
response = c.get('/users/secure/', follow=True)
user = User.objects.get(username='temporary')
self.assertEqual(response.context['email'], 'temporary#gmail.com')
After I run this test, it fails, and I see that printing return value of login() returns True, but response.content gets redirected to login page (if login fails authentication decorator redirects to login page). I have put a break point in decorator that does authentication:
def authenticate(user):
if user.is_authenticated():
return True
return False
and it really returns False. Line 4 in test_secure_page() properly retrieves user.
This is the view function:
#user_passes_test(authenticate, login_url='/users/login')
def secure(request):
user = request.user
return render_to_response('secure.html', {'email': user.email})
Of course, if I try to login through application (outside of test), everything works fine.
The problem is that you're not passing RequestContext to your template.
Also, you probably should use the login_required decorator and the client built in the TestCase class.
I'd rewrite it like this:
#views.py
from django.contrib.auth.decorators import login_required
from django.shortcuts import render
from django.contrib.auth import get_user_model
#login_required(login_url='/users/login')
def secure(request):
user = request.user
return render(request, 'secure.html', {'email': user.email})
#tests.py
class SimpleTest(TestCase):
def setUp(self):
User = get_user_model()
user = User.objects.create_user('temporary', 'temporary#gmail.com', 'temporary')
def test_secure_page(self):
User = get_user_model()
self.client.login(username='temporary', password='temporary')
response = self.client.get('/manufacturers/', follow=True)
user = User.objects.get(username='temporary')
self.assertEqual(response.context['email'], 'temporary#gmail.com')
It can often be useful to use a custom auth backend that bypassess any sort of authentication during testing:
from django.contrib.auth import get_user_model
class TestcaseUserBackend(object):
def authenticate(self, testcase_user=None):
return testcase_user
def get_user(self, user_id):
User = get_user_model()
return User.objects.get(pk=user_id)
Then, during tests, add yourapp.auth_backends.TestcaseUserBackend to your AUTHENTICATION_BACKENDS:
AUTHENTICATION_BACKENDS = [
"akindi.testing.auth_backends.TestcaseUserBackend",
]
Then, during tests, you can simply call:
from django.contrib.auth import login
user = User.objects.get(…)
login(testcase_user=user)
Token based authentication:
I was in same situation. I found solution in which actually I did generate a user for login purpose in setUp method. Then later in the test methods, I tried to get the token and passed it along with request data.
setUp:
create a user
self.pravesh = User.objects.create(
email='psj.aaabbb#gmail.com',
first_name='Pravesh',
last_name='aaabbb',
phone='5456165156',
phonecountrycode='91'
)
set password for the user
self.password = 'example password'
self.pravesh.set_password(self.password)
test_method:
create client
client.login(email=self.pravesh.email, password=self.password)
get token (in case of token auth)
token = Token.objects.create(user=self.pravesh)
pass login information
response = client.post(
reverse('account:post-data'),
data = json.dumps(self.data),
HTTP_AUTHORIZATION='Token {}'.format(token),
content_type = 'application/json'
)

Categories