I'm having an issue trying to get valid form in pytest. I am testing data changes through a Django Admin action. No matter what data I post to the form, form.is_valid() will always return False. Passing a dictionary to the form directly works, however I would like to be able to test through the action to make sure that the action filters out the locked records.
# test_admin.py
#pytest.mark.django_db
class BaseTestCase(TestCase):
"""Base TestCase with utilites to create user and login client."""
def setUp(self):
"""Class setup."""
self.index_url = '/'
self.login()
self.django_db_setup()
def create_user(self):
"""Create user and returns username, password tuple."""
username, password = 'testadmin', 'password123'
user = User.objects.create_superuser(
username,
'admin#test.com',
password,
first_name='Admin',
last_name='Account',
)
self.user = user
return (username, password)
def login(self):
"""Log in client session."""
username, password = self.create_user()
self.client.login(username=username, password=password)
#staticmethod
def django_db_setup():
call_command('loaddata', 'fixtures/fixture.json')
class AdminTestCase(BaseTestCase):
def test_responsible_list(self):
products = Product.objects.filter(pk__in=[230005, 229724])
form_data = {
'action': 'set_product_class',
'apply': 'Submit',
'product_class': '1',
ACTION_CHECKBOX_NAME: products.values_list('pk', flat=True),
}
self.client.post('/admin/store/product/', form_data, follow=True)
# Assert product_classes have changed
# actions.py
def set_product_class(modeladmin, request, queryset):
# Exclude products that are locked
queryset = queryset.exclude(is_locked=True)
form = None
if 'apply' in request.POST:
form = SetProductClassForm(data=request.POST)
if form.is_valid():
# action code
# forms.py
class SetProductClassForm(forms.Form):
_selected_action = forms.CharField(widget=forms.MultipleHiddenInput)
product_class = forms.ModelChoiceField(ProductClass.objects.all())
I put a pdb trace above the line in the action where it checks if form.is_valid():
When I run this in pytest:
(Pdb) form.is_valid()
False
(Pdb) request.POST
<QueryDict: {u'action': [u'set_product_class'], u'apply': [u'Submit'], u'product_class': [u'1'], u'_selected_action': [u'230005', u'229724']}>
However if I run the same code through the Django shell, the form is valid:
(Pdb) form.is_valid()
True
(Pdb) request.POST
<QueryDict: {u'action': [u'set_product_class'], u'apply': [u'Submit'], u'product_class': [u'1'], u'_selected_action': [u'230005', u'229724']}>
Why can I not get a valid form through pytest?
Checking form.errors gave the following:
{'product_class': [u'Select a valid choice. That choice is not one of the available choices.']}
The issue was being caused by the data for the ProductClass being selected in the form was missing in the fixture. Adding the data to the test db fixed the issue.
Related
The user creation is using an email address as USERNAME_FIELD and it is extracted from session and save in the form save(). It seems it is not going further down to the redirection. How can I test the redirection in this case?
tests.py:
class RegistraionViewTest(TestCase):
valid_data = {
'email': 'good#day.com',
'password1': 'test1234',
}
kwargs = {
'email': 'good#day.com'
}
def test_registration(self):
response = self.client.post(reverse('registration'), data=self.valid_data, follow=True)
self.assertTrue(response.context['form'].is_valid())
# mocking the session input
response.context['form'].save(email=self.kwargs['email'])
self.assertTrue(account.check_password(self.valid_data['password1']))
# working so far, but it seems there is no redirect url in response
self.assertRedirects(response, reverse('next_url'))
In views.py:
if request.method == 'POST':
form = RegistraionForm(request.POST)
if form.is_valid():
email = request.session.get('email')
try:
account = form.save(email=email)
return HttpResponseRedirect('next_url'))
In forms.py:
def save(self, **kwargs):
user = super(RegistrationForm, self).save(commit=False)
user.email = kwargs.pop('email')
user.save()
return user
It seems there is no url in the response in tests.py. What went wrong here?
Your response may be a 500, not a 302, which would mean there is no Location header.
The call for request.session.get('email') will likely throw a KeyError, as your test does not appear to set the session['email'] field, and there is no default.
Note that when using a session in a test case, you need to assign it to a variable in the beginning, as in the example below (from Django Testing Tool docs):
def test_registration(self):
session = self.client.session
session['email'] = self.kwargs['email']
session.save()
# and now make your call to self.client.post
response = self.client.post(...)
self.assertEqual(response.status_code,302)
# .. and the rest
In a Django unit test using TestCase I want to POST data to a view that has the #login_required decorator.
test_views.py:
class ExportQuestionnaireTest(TestCase):
def setUp(self):
self.user = User.objects.create_user(
username='jose', email='jose#test.com', password='passwd'
)
# self.user.is_superuser = True
# self.user.save()
self.client.login(username=self.user.username, password='passwd')
def test_same_questionnaire_used_in_different_steps_return_correct_content(self):
data = {
'per_participant': 'on',
'action': 'run'
}
response = self.client.post(reverse('export_view'), data)
views.py:
#login_required
def export_view(request, template_name="export/export_data.html"):
export_form = ExportForm()
...
As can be seen in test_views.py, I'm creating a user and logged in through self.client.login, then POSTing to the view. When running the test, the request doesn't achieve the view.
But when I uncomment the two lines in test_views.py (i. e., make self.user superuser), the request achieves the view (verified that debbuging with Pycharm).
What could be the reason?
I want test login redirect, but I use client post username and password, Login-authenticate failed, I adjusted for a long time. I don't know why!
view:
def index(request, **kwargs):
username = kwargs.get('username', 'anyone')
return render(request, "index.html", {'username': username})
def login(request):
"""user sign in"""
if request.method == "GET":
form = LoginForm()
return render(request, "login.html", {'uf': form})
else:
form = LoginForm(request.POST)
if form.is_valid():
username = request.POST.get("username")
password = request.POST.get("password")
user = auth.authenticate(username=username, password=password)
if user is not None and user.is_active:
auth.login(request, user)
request.session['uid'] = user.id
return render(request, 'index.html', {'username': username})
else:
return render(request,
'login.html',
{'form': form, 'password_is_wrong': True, 'error': 'Pls, Resubmit'})
else:
return render(request, 'login.html', {'form': form})
test:
def test_login_right(self, username='admin', password='zg123456'):
""" POST right username or password """
setup_test_environment()
client = Client(enforce_csrf_checks=False)
url = '/login/'
response = self.client.post(url,
data={'username': username,
'password': password},
secure=True,
follow=True)
status_code = response.status_code
self.assertNotIn(str(status_code)[0], ['4', '5'])
self.assertIn('Welcome', response.content.decode('utf8'))
I find do test, auth.authenticate return None, but I use brower authenticate return not None, I don't know why, a protection mechanism? I must use client.login func ?
thanks #Daniel Roseman #ohannes
I did not create a test environment for the user,
add a test user, it worked!
this my added code:
from django.contrib.auth.models import User
class LoginViewsTests(TestCase):
def setUp(self):
self.adminuser = User.objects.create_user('test_001',
'test001#test.com',
'test1234')
manage.py createsuperuser, creates a user in your default database. manage.py test creates a new empty database (which does not contain that superuser). To have a user in the test database, you must create it within your test code (either in the test function or in setUp) - ohannes
by ohannes
In django, for class-based view like ListView and DetailView, methods like get() or post() or other functions defined by developer take parameters include self and request. I learnt that in self these is actually a self.request field, so wha's the difference between self.request and request?
Example, this is the function in a class based view and used to handle user's login requirement:
def login(self, request):
name = request.POST['name']
pwd = request.POST['password']
user = authenticate(username=name, password=pwd)
if user is not None:
request.session.set_expiry(0)
login(request, user)
log_message = 'Login successfully.'
else:
log_message = 'Fail to login.'
return HttpResponseRedirect(reverse('blog:testindex'))
This is the function used to handle user's register:
def register(self, request):
user_name = self.request.POST['username']
firstname = self.request.POST['firstname']
lastname = self.request.POST['lastname']
pwd = self.request.POST['password']
e_mail = self.request.POST['email']
user = User.objects.create(username=user_name, first_name=firstname, last_name=lastname, email=e_mail)
user.set_password(pwd)
try:
user.save()
user = authenticate(username=user_name, password=pwd)
login(self.request, user)
except Exception:
pass
else:
return HttpResponseRedirect(reverse('blog:testindex'))
In the first function, it used data stored in request and in the second one, it used self.request, both work functionally. What's the difference?
For a subclass of View, they're the same object. self.request = request is set in view function that as_view() returns. I looked into the history, but only found setting self.request and then immediately passing request into the view function.
I can't seem to work out how to log in users in Django. I'm confused because the documentation tells you explicitely how to do it, but still somehow I must be making a mistake.
The link
https://docs.djangoproject.com/en/dev/topics/auth/default/#django.contrib.auth.login
says
"To log a user in, from a view, use login(). It takes an HttpRequest object and a User object. login() saves the user’s ID in the session, using Django’s session framework."
So I have the following views.py:
def login_view(request):
if request.method == 'GET':
return render(request, 'app/login.htm')
if request.method == 'POST':
username = request.POST.get('username', '')
password = request.POST.get('password', '')
user = auth.authenticate(username=username, password=password)
if user is None:
return HttpResponseRedirect(reverse('error'))
if not user.is_active:
return HttpResponseRedirect(reverse('error'))
# Correct password, and the user is marked "active"
auth.login(request, user)
# Redirect to a success page.
return HttpResponseRedirect(reverse('home'))
def home(request):
contextdict = {}
if request.session.user.is_authenticated():
contextdict['username'] = request.session.user.username
context = RequestContext(request, contextdict )
return render(request, 'app/home.htm', context)
Now, by using print 'qqq' I know for a fact that 'is None' and 'not is_active' have been evaluated to True, and so auth.login is evaluated and the HttpResponseRedirect is returned. I expected everythin to go normally and the user to be logged in, and the user name to be passed as context in the home view. However, Django gives me the following error:
AttributeError at /app/home/
'SessionStore' object has no attribute 'user'
Yeah, I have no idea what I'm doing.
You should use request.user to get user object, not request.session.user.
The data in session is used to retrieve the user object, but the session does not contain the actual user