I have a view as such:
def ProjectInfo(request):
if request.method == 'POST':
form = ProjectInfoForm(request.POST)
if form.is_valid():
# if form is valid, iterate cleaned form data
# and save data to session
for k, v in form.cleaned_data.iteritems():
request.session[k] = v
return HttpResponseRedirect('/next/')
else:
...
else:
...
And in my tests:
from django.test import TestCase, Client
from django.core.urlresolvers import reverse
from tool.models import Module, Model
from django.contrib.sessions.models import Session
def test_project_info_form_post_submission(self):
"""
Test if project info form can be submitted via post.
"""
# set up our POST data
post_data = {
'zipcode': '90210',
'module': self.module1.name,
'model': self.model1.name,
'orientation': 1,
'tilt': 1,
'rails_direction': 1,
}
...
self.assertEqual(response.status_code, 302)
# test if 'zipcode' in session is equal to posted value.
So, where the last comment is in my test, I want to test if a specific value is in the session dict and that the key:value pairing is correct. How do I go about this? Can I use request.session?
Any help much appreciated.
According to the docs:
from django.test import TestCase
class SimpleTest(TestCase):
def test_details(self):
# Issue a GET request.
self.client.get('/customer/details/')
session = self.client.session
self.assertEqual(session["somekey"], "test")
Related
i am using KNeighborsClassifier to classify flowers with the famous iris dataset.
I have done the training, and now going to deploy it as web app.
But the problem is, when i test the app locally, it shows this,
AttributeError: 'KNeighborsClassifier' object has no attribute 'n_samples_fit_'
I googled a lot, and still can't find the solution.
The app is here, https://github.com/josephofiowa/GA_iris, it wasn't written by me by the way.
i found a post here, but it is about image recognition https://github.com/pliablepixels/zmeventnotification/issues/188
i am not sure it is related, but i still post it here. It says it is a version problem, but how do I downgrade?
Thank you guys!
from flask import Flask, render_template, session, redirect, url_for
from flask.ext.wtf import Form
from wtforms import IntegerField, StringField, SubmitField, SelectField, DecimalField
from wtforms.validators import Required
import pickle
from sklearn import datasets
# Initialize Flask App
app = Flask(__name__)
print("loading my model")
with open('model.pkl', 'rb') as handle:
machine_learning_model = pickle.load(handle)
print("model loaded")
# Initialize Form Class
class theForm(Form):
param1 = DecimalField(label='Sepal Length (cm):', places=2, validators=[Required()])
param2 = DecimalField(label='Sepal Width (cm):', places=2, validators=[Required()])
param3 = DecimalField(label='Petal Length (cm):', places=2, validators=[Required()])
param4 = DecimalField(label='Petal Width (cm):', places=2, validators=[Required()])
submit = SubmitField('Submit')
#app.route('/', methods=['GET', 'POST'])
def home():
print(session)
form = theForm(csrf_enabled=False)
if form.validate_on_submit(): # activates this if when i hit submit!
# Retrieve values from form
session['sepal_length'] = form.param1.data
session['sepal_width'] = form.param2.data
session['petal_length'] = form.param3.data
session['petal_width'] = form.param4.data
# Create array from values
flower_instance = [[(session['sepal_length']),(session['sepal_width']),(session['petal_length']),(session['petal_width'])]]
# Return only the Predicted iris species
flowers = ['setosa', 'versicolor', 'virginica']
session['prediction'] = flowers[machine_learning_model.predict(flower_instance)[0]]
# Implement Post/Redirect/Get Pattern
return redirect(url_for('home'))
return render_template('home.html', form=form, **session)
# Handle Bad Requests
#app.errorhandler(404)
def page_not_found(e):
return render_template('404.html'), 404
app.secret_key = 'super_secret_key_shhhhhh'
if __name__ == '__main__':
app.run(debug=True)
try with machine_learning_model.knn_.n_samples_fit_=flower_instance.shape[0]
recently tried similar solution in one of
I can't seem to be able to mock the behaviour of a form when unit testing views.
My form is a simple ModelForm and resides in profiles.forms. The view is (again) a simple view that checks whether the form is valid and then redirects.
views.py
from django.http import HttpResponseRedirect
from django.shortcuts import render
from django.urls import reverse
from profiles.forms import ProfileForm
def home(request):
form = ProfileForm()
if request.method == 'POST':
form = ProfileForm(request.POST)
if form.is_valid():
profile = form.save()
return HttpResponseRedirect(reverse("thanks"))
My test looks like this:
class TestViewHomePost(TestCase):
def setUp(self):
self.factory = RequestFactory()
def test_form(self):
with mock.patch('profiles.views.ProfileForm') as mock_profile_form:
mock_profile_form.is_valid.return_value = True
request = self.factory.post(reverse("home"), data={})
response = home(request)
logger.debug(mock_profile_form.is_valid.call_count) # "0"
is_valid is not being called on the mock, which means ProfileForm is not patched.
Where did I make a mistake?
I was able to fix mocking is_valid as following:
def test_form(self):
with mock.patch('profiles.views.ProfileForm.is_valid') as mock_profile_form:
mock_profile_form.return_value = True
request = self.factory.post(reverse("home"), data={})
response = home(request)
note: and you could use mock_profile_form.assert_called_once() to check if the mock has been called.
I have many django forms in which I pass the request as kwarg.
I've just started dive into testing, and it seems that testing forms which require the request as argument makes the testing harder. As I have to somehow create a request and I cant test my forms without it.
So is it best to avoid passing the request to the form at all? Or another workaround?
The reason I do that on first place is that sometimes I need request.user, or request.session and do some cleaning/setting based on that info in the form.
UPDATE:
This is an example form:
class OrderForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request')
self.user = self.request.user
def clean(self):
# Here I have some cross session-field validation
if self.request.session['has_response'] and self.cleaned_data('status') == 'NEW':
raise ValidationError()
def save(self, commit=False):
self.instance.user = self.user
return super(OrderForm, self).save(commit=True)
class Meta:
model = Order
fields = ('address', 'city', 'status', ) # more fields
The view code is simple:
form = OrderForm(request.POST, request=request)
The Order model also have a clean() method with some validation logic.
The session is populated at most during the user login.
The point it I need the session/user there.
But most important, the question - is it a bad design to pass the request and session to the form, considering options for testing this form? I find it more logical when the form take care for saving the object, including the request.user. But maybe I should try to split that between the form and view?
Passing request to the form is okay if you need it in the clean() method. You can use a request/session/user in a test like this:
from django.test import TestCase, Client
from django.test.client import RequestFactory
from django.contrib.auth.models import AnonymousUser, User
from .views import my_view
from .forms import MyForm
from django.contrib.sessions.middleware import SessionMiddleware
# If Python >= 3.4
from unittest.mock import patch, MagicMock
# Else
from mock import patch, MagicMock
class SimpleTest(TestCase):
def setUp(self):
# Create a RequestFactory accessible by the entire class.
self.factory = RequestFactory()
# Create a new user object accessible by the entire class.
self.user = User.objects.create_user(username='username',
email='email', password='password')
def test_my_view(self):
# Create an instance of a GET request.
request = self.factory.get('/my-url/')
# Middleware is not supported so simulate a
# logged-in user by setting request.user.
request.user = self.user
# Or add anonymous user to request.
request.user = AnonymousUser()
# Test view() at '/my-url/'
response = my_view(request)
self.assertEqual(response.status_code, 200)
#patch('app.models.ModelName.save', MagicMock(name="save"))
def test_my_form_view_with_factory(self):
# Set up form data.
form_data = {'something': 'something'}
# Create an instance of a POST request.
request = self.factory.post('/my-form-url/', form_data)
# Simulate logged-in user
request.user = self.user
# Setup session.
middleware = SessionMiddleware()
middleware.process_request(request)
request.session.save()
# Or you should just be able to do
request.session['somekey'] = 'test'
request.session.save()
# Get response from form view, and test passing
# request/data to form.
form = MyForm(request=request, data=form_data)
response = my_form_view(request)
self.assertTrue(form.is_valid())
self.assertEqual(response.status_code, 200)
# If model form you can do
self.assertTrue(ModelName.save.called)
#patch('app.models.ModelName.save', MagicMock(name="save"))
def test_my_form_view_with_client(self):
# Use Client instead of RequestFactory.
self.client = Client()
# Login with Client.
self.client.login(username='username', password='password')
# Set up form data.
form_data = {'something': 'something'}
# Get/set session.
session = self.client.session
session['somekey'] = 'test'
session.save()
# Get response with Client.
response = self.client.post('/my-form-url/', form_data)
self.assertEqual(response.status_code, 200)
# If model form you can do
self.assertTrue(ModelName.save.called)
Should give a general idea of what you can do, not specifically tested.
I'm new to django and I'm having trouble testing custom actions(e.g actions=['mark_as_read']) that are in the drop down on the app_model_changelist, it's the same dropdown with the standard "delete selected". The custom actions work in the admin view, but I just dont know how to call it in my mock request, I know I need to post data but how to say I want "mark_as_read" action to be done on the data I posted?
I want to reverse the changelist url and post the queryset so the "mark_as_read" action function will process the data I posted.
change_url = urlresolvers.reverse('admin:app_model_changelist')
response = client.post(change_url, <QuerySet>)
Just pass the parameter action with the action name.
response = client.post(change_url, {'action': 'mark_as_read', ...})
Checked items are passed as _selected_action parameter. So code will be like this:
fixtures = [MyModel.objects.create(read=False),
MyModel.objects.create(read=True)]
should_be_untouched = MyModel.objects.create(read=False)
#note the unicode() call below
data = {'action': 'mark_as_read',
'_selected_action': [unicode(f.pk) for f in fixtures]}
response = client.post(change_url, data)
This is what I do:
data = {'action': 'mark_as_read', '_selected_action': Node.objects.filter(...).values_list('pk', flat=True)}
response = self.client.post(reverse(change_url), data, follow=True)
self.assertContains(response, "blah blah...")
self.assertEqual(Node.objects.filter(field_to_check=..., pk__in=data['_selected_action']).count(), 0)
A few notes on that, comparing to the accepted answer:
We can use values_list instead of list comprehension to obtain the ids.
We need to specify follow=True because it is expected that a successfull post will lead to a redirect
Optionally assert for a successful message
Check that the changes indeed are reflected on the db.
Here is how you do it with login and everything, a complete test case:
from django.test import TestCase
from django.urls import reverse
from content_app.models import Content
class ContentModelAdminTests(TestCase):
def setUp(self):
# Create some object to perform the action on
self.content = Content.objects.create(titles='{"main": "test tile", "seo": "test seo"}')
# Create auth user for views using api request factory
self.username = 'content_tester'
self.password = 'goldenstandard'
self.user = User.objects.create_superuser(self.username, 'test#example.com', self.password)
def shortDescription(self):
return None
def test_actions1(self):
"""
Testing export_as_json action
App is content_app, model is content
modify as per your app/model
"""
data = {'action': 'export_as_json',
'_selected_action': [self.content._id, ]}
change_url = reverse('admin:content_app_content_changelist')
self.client.login(username=self.username, password=self.password)
response = self.client.post(change_url, data)
self.client.logout()
self.assertEqual(response.status_code, 200)
Just modify to use your model and custom action and run your test.
UPDATE: If you get a 302, you may need to use follow=True in self.client.post().
Note that even if the POST is successful, you still need to test that your action performed the operations intended successfully.
Here's another method to test the action directly from the Admin class:
from django.contrib.auth.models import User
from django.contrib.admin.sites import AdminSite
from django.shortcuts import reverse
from django.test import RequestFactory, TestCase
from django.contrib.messages.storage.fallback import FallbackStorage
from myapp.models import MyModel
from myapp.admin import MyModelAdmin
class MyAdminTestCase(TestCase):
def setUp(self) -> None:
self.site = AdminSite()
self.factory = RequestFactory()
self.superuser = User.objects.create_superuser(username="superuser", is_staff=True)
def test_admin_action(self):
ma = MyModelAdmin(MyModel, self.site)
url = reverse("admin:myapp_mymodel_changelist")
superuser_request = self.factory.get(url)
superuser_request.user = self.superuser
# if using 'messages' in your actions
setattr(superuser_request, 'session', 'session')
messages = FallbackStorage(superuser_request)
setattr(superuser_request, '_messages', messages)
qs = MyModel.objects.all()
ma.mymodeladminaction(superuser_request, queryset=qs)
# check that your action performed the operations intended
...
The following test code does not pass even though manually submitting the form on my web interface actually does work.
import os
from flask.ext.testing import TestCase
from flask import url_for
from config import _basedir
from app import app, db
from app.users.models import User
class TestUser(TestCase):
def create_app(self):
"""
Required method. Always implement this so that app is returned with context.
"""
app.config['TESTING'] = True
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(_basedir, 'test.db')
app.config['WTF_CSRF_ENABLED'] = False # This must be disabled for post to succeed during tests
self.client = app.test_client()
ctx = app.app_context()
ctx.push()
return app
def setUp(self):
db.create_all()
#pass
#app.teardown_appcontext
def tearDown(self):
db.session.remove()
db.drop_all()
#pass
def test_admin_home(self):
# url is the admin home page
url = url_for('admin.index')
resp = self.client.get(url)
self.assertTrue(resp.status_code == 200)
def test_admin_registration(self):
url = url_for('admin.register_view')
data = {'username': 'admin', 'email': 'admin#example.com', 'password': 'admin'}
resp = self.client.post(url, data)
self.assertTrue(resp.status_code == 200)
u = User.query.filter_by(username=u'admin').first()
self.assertTrue(u.username == 'admin') # <----- This fails. Why?
After the test client has post to the register_view url and returns a 200 OK response, I fail to retrieve the 'admin' user from the test database. Why is this so?
Here's the view code (this is a flask admin view)
from flask import request
from flask.ext.admin import expose, AdminIndexView, helpers
from app.auth.forms import LoginForm, RegistrationForm
from app.users.models import User
from app import db
class MyAdminIndexView(AdminIndexView):
#expose('/', methods=('GET', 'POST'))
def index(self):
# handle user login
form = LoginForm(request.form)
self._template_args['form'] = form
return super(MyAdminIndexView, self).index()
#expose('/register/', methods=('GET', 'POST'))
def register_view(self):
# handle user registration
form = RegistrationForm(request.form)
if helpers.validate_form_on_submit(form):
user = User()
form.populate_obj(user)
db.session.add(user)
db.session.commit()
self._template_args['form'] = form
return super(MyAdminIndexView, self).index()
Dumbest mistake ever.
The offending line in my test code is
resp = self.client.post(url, data)
It should be
resp = self.client.post(url, data=data)
I managed to track it down by painstakingly walking through the logic and inserting ipdb.set_trace() step by step until I found the bad POST request made by my client.