I have a flask application that has a view function that looks like this:
#login_required
#app.route('/suspect_tracker/new_list', methods=['GET', 'POST'])
def new_list():
form = ListForm()
if form.validate_on_submit():
if form.private:
privacy_setting = 1
else:
privacy_setting = 0
new_list = List(name=form.name.data, last_updated=datetime.utcnow(), user_id=g.user.id, private=privacy_setting)
db.session.add(new_list)
db.session.commit()
flash('New list %s added' % new_list.name)
return redirect('/suspect_tracker/' + form.name.data)
else:
flash(form.name.errors)
return render_template('newlist.html', title="Make a new list!", form=form)
And I am attempting to write a test for it, with the test looking like this:
from config import basedir
from app import app, db
from app.models import User, List, Suspect, SuspectList
from flask import g
from flask_testing import TestCase
class TestViews(TestCase):
def create_app(self):
app.config['TESTING'] = True
app.config['WTF_CSRF_ENABLED'] = False
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(basedir, 'test.db')
self.client = app.test_client()
return app
def setUp(self):
self.app = self.create_app()
db.create_all()
test_user = User(nickname="test")
db.session.add(test_user)
db.session.commit()
g.user = User.query.filter_by(id=1).first()
def tearDown(self):
db.session.remove()
db.drop_all()
def test_new_list_view(self):
self.client.get('/suspect_tracker/new_list/')
form = {'name':'Test', 'private':False}
g.user = User.query.filter_by(id=1).first()
self.client.post('/suspect_tracker/new_list', data=form)
assert List.query.filter_by(name="Test").first() != None
After running the test, the assertion fails, and I have tested to see that after running self.client.post, List does not contain a new list, and is still empty. This leads me to believe that form.validate_on_submit() returned false, which leads me to believe that I am not passing the correct data in the self.client.post() function in test_new_list_view(). My question is, how do I correctly create a ListForm() in the testing function and then POST it with self.client.post() to test the new_list() function?
I've fixed the issue with getting the form data to properly be sent, so now the form validates but I have no idea how to set g.user to be the mock user so when I try running the test I get an "AttributeError: 'AnonymousUserMixin' object has no attribute 'id'" error.
Related
'I use the route food_search to get data from api and return an array'
import os
from unittest import TestCase
from models import db, User
# BEFORE we import our app, let's set an environmental variable
# to use a different database for tests (we need to do this
# before we import our app, since that will have already
# connected to the database
os.environ['DATABASE_URL'] = "postgresql:///mealplan_test"
from app import app
class FoodSearchTestCase(TestCase):
def setUp(self):
self.app = app
self.client = self.app.test_client()
with self.app.app_context():
db.drop_all()
db.create_all()
# Create a test user and log them in
user = User(username='testuser', password='testpassword', email='testing#test.com', firstname='fname', lastname='lname')
db.session.add(user)
db.session.commit()
def tearDown(self):
with self.app.app_context():
db.drop_all()
def test_food_search(self):
with self.client as c:
response = c.get("/food_search?q=banana")
self.assertEqual(response.status_code, 302)
self.assertIn(b'banana', response.data)
AssertionError:response.status_code 302 != 200
AssertionError: b'banana' not found in b'\nRedirecting...\nRedirecting...\nYou should be redirected automatically to target URL: /login. If not click the link.'
I am creating a basic application to demonstrate the register and login activities in Flask using sessions. For the below code, each user available in the dictionary should be able to login. However, the application only accepts the login for the first user named 'Mary'. I don't understand where did it went wrong.
from flask import Flask, app, session, render_template, request,redirect, url_for, jsonify
app = Flask(__name__)
app.config['SECRET_KEY'] = "Zd9TUg1aE03bHc28"
#app.route('/')
def load():
return render_template("authusers.html")
class Mydatabase:
appdb=[{'username':'Mary','password':'Password#123'},
{'username':'John','password':'Password#456'},
{'username':'Tara','password':'Password#789'}]
mydbusers=Mydatabase()
#app.route('/login',methods = ['GET','POST'])
def success():
if request.method == "POST":
username_val = request.form['user_id']
userpassword_val = request.form['user_password']
for authuser in Mydatabase.appdb:
for authpassword in authuser.values():
if authuser['username'] == username_val and authpassword['password'] == userpassword_val:
session['reg_user'] = username_val
return f'{session.get("reg_user")} have successfully logged into the application';
else:
return redirect(url_for('register'))
#app.route('/logout', methods=['GET','POST'])
def logout():
if 'reg_user' in session:
session.pop('reg_user',None)
return render_template("authusers.html")
#app.route('/register', methods=['GET','POST'])
def register():
return render_template('register.html')
#app.route('/reg_success',methods=['GET','POST'])
def reg_success():
newusercred={'username':request.form['user_id'], 'password':request.form['user_password']}
mydbusers.appdb.append(newusercred)
# return jsonify(Mydatabase.appdb)
return render_template("authusers.html")
if __name__=="__main__":
app.run(debug=True)
I see some logical issue in your code
Try like this ->
class Mydatabase:
appdb=[{'username':'Mary','password':'Password#123'},
{'username':'John','password':'Password#456'},
{'username':'Tara','password':'Password#789'}]
username_val = 'Tara'
userpassword_val = 'Password#789'
d = [a for a in Mydatabase.appdb if a["username"] == username_val and a["password"]==userpassword_val]
if d:
print(f'have successfully logged into the application')
else:
print(f'Wrong credentials')
There are some unwanted loops in your code, and you do return even in the else of part.
I am getting RuntimeError: working outside of request context error while running a test in Flask. I've tried multiple suggestions from other threads, but none has worked for me.
Part of my views.py:
#user_app.route('/login', methods =('GET', 'POST'))
def login():
form = LoginForm()
error = None
if form.validate_on_submit():
user = User.objects.filter(username=form.username.data).first()
if user:
if bc.hashpw(form.password.data, user.password) == user.password:
session['username']=form.username.data
return 'User Logged In'
else:
user = None
if not user:
error = 'Incorrect credentials'
return render_template('user/login.html',form=form,error=error)
Relevant part of my tests.py:
from application import create_app as create_app_base
def create_app(self):
self.db_name = 'flaskbook_test'
return create_app_base(
MONGODB_SETTINGS={'DB':self.db_name},
TESTING=True,
WTF_CSRF_ENABLED=False,
SECRET_KEY='SecretKey'
)
def setUp(self):
self.app_factory = self.create_app()
self.app = self.app_factory.test_client()
#self.app.application.app_context().push() <-- this did not help
def tearDown(self):
db = _get_db()
db.client.drop_database(db)
def test_login_user(self):
#create user
self.app.post('/register', data=self.user_dict())
#login user
rv = self.app.post('/login',data=dict(
username='username',
password='password'
))
#check session is set
with self.app as c:
rv = c.get('/')
assert session.get('username') == self.user_dict()['username']
I have already tried adding app_context and self.app.application.app_context().push() as mentioned above:
with self.app.application.app_context():
assert session.get('username') == self.user_dict()['username']
But it didn't work. Whenever I call session['username'] I get RuntimeError: working outside of request context.
My requirements.txt: Flask0.10.1 Flask-Script 2.0.5 flask-mongoengine 0.7.4
Please help.
What you want is the request context, not the app context.
Flask includes some handy functions to push a request context for you - check out the Flask testing docs and you'll see a lot of relevant info, including the test_request_context method on the app object.
Combine that with app.test_client to push a request context and then simulate client behaviour such as POSTing to your endpoint. Try this:
with self.app.test_request_context('/'), self.app.test_client() as c:
rv = c.post('/')
assert session.get('username') == self.user_dict()['username']
When using Flask Login (for the first time) and following every tutorial example I came across, after correctly POSTing credentials, calling login_user(), and redirecting to a protected login_required route, I'm still getting a 401 Unauthorised response. Only when I explicitly set the is_authorised property to True does it work. But this seems like it should be unnecessary, and no tutorial example I can find does this. What am I overlooking? Sample code below. Thanks!
from flask import Flask, render_template, url_for, jsonify, session, redirect, request
from flask_login import LoginManager, login_required, login_user, current_user
login_manager = LoginManager()
app = Flask(__name__)
app.secret_key = b'_5#y2L"F4Q8z\n\xecgwefweligli]/'
login_manager.init_app(app)
class User:
_users = {
'me': 'mypass'
}
_active_users = {}
def __init__(self, user_id):
self.user_id = user_id
self.password = self._users[user_id]
self._authenticated = False
self._active = True
#property
def is_active(self):
return self._active
#property
def is_authenticated(self):
return self._authenticated
#is_authenticated.setter
def is_authenticated(self, value):
if value:
self._authenticated = True
else:
self._authenticated = False
#property
def is_anonymous(self):
return False
def get(user_id):
if user_id in User._active_users:
return User._active_users[user_id]
if user_id in User._users:
User._active_users[user_id] = User(user_id)
return User._active_users[user_id]
return None
def get_id(self):
return self.user_id
#login_manager.user_loader
def load_user(user_id):
return User.get(user_id)
#app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
u = User.get(request.form['username'])
app.logger.debug(u)
if u and u.password == request.form['password']:
app.logger.debug('authenticated')
r = login_user(u)
u.is_authenticated = True # HERE: this shouldn't be necessary?
app.logger.debug(u.is_authenticated)
app.logger.debug(current_user.is_authenticated)
return redirect(url_for('index'))
return redirect(url_for('login'))
return render_template('login.html')
#app.route('/')
#login_required
def index():
return render_template('main.html')
When I remove:
u.is_authenticated = True
the user is not authenticated. I've looked at the source for login_user() and it doesn't seem to set is_authenticated. Am I doing it right? I'm just confused because no documentation / tutorial includes the above line, but all claim to just work.
Your original code needed to explicitly set is_authenticated because from one request to the next, a new instance of the User object is created each time, so the information that the user already authenticated is lost.
To avoid the loss of this information we need a place to store it. While Flask-login helpfully provides a UserMixin class that provides default implementations of this, you can just add authenticated field in the User model:
authenticated = db.Column(db.Boolean, default=False)
When the user login, you should change authenticated to true and save change in database and When the user logout, you should change authenticated to false and save change in database.
The is_authenticated function will be similar to this:
#property
def is_authenticated(self):
return self.authenticated
Please find this tutorial.
I am trying to run my create_db.py file to create the posts.db database, but it will not get created in my project directory. And when I run the main file for the blog and try to login in I get the error below.
I have looked up this error and seen that other people have gotten it as well and asked about it here on Stackoverflow, but none of them seems to help me. I have read that this could be because something in my blog.py file is running main before the database gets created. But, I am thinking that it has something to do with the configuration. Mainly the PATH of the database could be getting mixed up with the app.config['SQLAlCHEMY_DATABASE_URI'] = 'sqlite:///' line.
Any ideas?
Here is the error
sqlalchemy.exc.OperationalError
OperationalError: (OperationalError) no such table: posts u'SELECT posts.id AS posts_id, posts.title AS posts_title, posts.post AS posts_post \nFROM posts' ()
OperationalError: (OperationalError) no such table: posts u'SELECT posts.id AS posts_id, posts.title AS posts_title, posts.post AS posts_post \nFROM posts' ()
Here is my code. There are three files here: blog.py, models.py, create_db.py
blog.py
# controller of blog app
from flask import Flask, render_template, request, session,\
flash, redirect, url_for, g
from flask.ext.sqlalchemy import SQLAlchemy
import sqlite3
from functools import wraps
# create the application object
app = Flask(__name__)
# configuration
app.secret_key = 'x13xa8xf5}[xfexd4Zxb8+x07=7xc9xe1Bxcfxbdt.ox87oxc9'
app.config['SQLAlCHEMY_DATABASE_URI'] = 'sqlite:///'
# create sqlalchemy object
db = SQLAlchemy(app)
from models import *
# login required decorator
def login_required(test):
#wraps(test)
def wrap(*args, **kwargs):
if 'logged_in' in session:
return test(*args, **kwargs)
else:
flash('You need to login first.')
return redirect(url_for('login'))
return wrap
#app.route('/', methods = ['GET','POST'])
def login():
error = None
if request.method == 'POST':
if request.form['username'] != 'admin' or request.form['password'] != 'admin':
error = 'Invalid Credentils. Please try again.'
else:
session['logged_in'] = True
return redirect(url_for('main'))
return render_template('login.html', error=error)
#app.route('/main')
#login_required
def main():
posts = db.session.query(BlogPost).all()
return render_template('main.html', posts=posts)
#app.route('/add', methods=['POST'])
#login_required
def add():
title = request.form['title']
post = request.form['post']
if not title or not post:
flash("All fields are required. Please try again.")
return redirect(url_for('main'))
else:
db.session.add(title)
db.session.add(post)
db.session.commit()
db.session.close()
flash('New entry was successfully posted!')
return redirect(url_for('main'))
#app.route('/logout')
def logout():
session.pop('logged_in', None)
flash('You were logged out')
return redirect(url_for('login'))
def connect_db():
return sqlite.connect('posts.db')
if __name__ == '__main__':
app.run(debug = True)
models.py:
from blog import db
class BlogPost(db.Model):
__tablename__ = "posts"
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String, nullable=False)
post = db.Column(db.String, nullable=False)
def __init__(self, title, post):
self.title = title
self.post = post
def __repr__(self):
return '<title {}'.format(self.title)
create_db.py
from blog import db
from models import BlogPost
# create the database and the db tables
db.create_all()
# insert
db.session.add(BlogPost("Good","I\'m good."))
db.session.add(BlogPost("Well","I\'m well."))
db.session.add(BlogPost("Post","I\'m a post."))
# commit the changes
db.session.commit()
There's a typo in SQLAlCHEMY_DATABASE_URI, should be SQLALCHEMY_DATABASE_URI, the 2nd l.
When running from blog import db some statements in blog.py get executed, including the one with sqlite:/// which is where the path is set. Modifying this line
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///posts.db'
and then after running create_db.py should create a database in app's root directory. There doesn't seem to be another place in the code where the db path is set.
It looks like you may also run into some circular import problems (will throw ImportError: cannot import name [name]). Some solutions are putting the app init stuff like db = SQLAlchemy.. into a separate file or importing at the end of the file.