I've put together basic login/register forms in a flask app using WTForms. I included parts of code from two files that I thought might be relevant.
I am getting the error:
Traceback (most recent call last):
File "/home/neon/flask-app/venv/lib/python3.8/site-packages/flask/app.py", line 2548, in __call__
return self.wsgi_app(environ, start_response)
File "/home/neon/flask-app/venv/lib/python3.8/site-packages/flask/app.py", line 2528, in wsgi_app
response = self.handle_exception(e)
File "/home/neon/flask-app/venv/lib/python3.8/site-packages/flask/app.py", line 2525, in wsgi_app
response = self.full_dispatch_request()
File "/home/neon/flask-app/venv/lib/python3.8/site-packages/flask/app.py", line 1822, in full_dispatch_request
rv = self.handle_user_exception(e)
File "/home/neon/flask-app/venv/lib/python3.8/site-packages/flask/app.py", line 1820, in full_dispatch_request
rv = self.dispatch_request()
File "/home/neon/flask-app/venv/lib/python3.8/site-packages/flask/app.py", line 1796, in dispatch_request
return self.ensure_sync(self.view_functions\[rule.endpoint\])(\*\*view_args)
File "/home/neon/flask-app/src/accounts/views.py", line 18, in register
if form.validate_on_submit():
File "/home/neon/flask-app/venv/lib/python3.8/site-packages/flask_wtf/form.py", line 86, in validate_on_submit
return self.is_submitted() and self.validate(extra_validators=extra_validators)
TypeError: validate() got an unexpected keyword argument 'extra_validators'
This is my code:
src/accounts/forms.py
from flask_wtf import FlaskForm
from wtforms import EmailField, PasswordField
from wtforms.validators import DataRequired, Email, EqualTo, Length
from src.accounts.models import User
class RegisterForm(FlaskForm):
email = EmailField(
"Email", validators=[DataRequired(), Email(message=None), Length(min=6, max=40)]
)
password = PasswordField(
"Password", validators=[DataRequired(), Length(min=6, max=25)]
)
confirm = PasswordField(
"Repeat password",
validators=[
DataRequired(),
EqualTo("password", message="Passwords must match."),
],
)
def validate(self):
initial_validation = super(RegisterForm, self).validate()
if not initial_validation:
return False
user = User.query.filter_by(email=self.email.data).first()
if user:
self.email.errors.append("Email already registered")
return False
if self.password.data != self.confirm.data:
self.password.errors.append("Passwords must match")
return False
return True
src/accounts/views.py
from flask import Blueprint, flash, redirect, render_template, request, url_for
from flask_login import login_required, login_user, logout_user, current_user
from src import bcrypt, db
from src.accounts.models import User
from .forms import LoginForm, RegisterForm
accounts_bp = Blueprint("accounts", __name__)
#accounts_bp.route("/register", methods=["GET", "POST"])
def register():
if current_user.is_authenticated:
flash("You are already registered.", "info")
return redirect(url_for("core.home"))
form = RegisterForm(request.form)
if form.validate_on_submit():
user = User(email=form.email.data, password=form.password.data)
db.session.add(user)
db.session.commit()
login_user(user)
flash("You registered and are now logged in. Welcome!", "success")
return redirect(url_for("core.home"))
return render_template("accounts/register.html", form=form)
It's throwing an error in the register function at the second conditional on form.validate_on_submit(). I looked at the WTForms documentation, and from what I could understand, the validate() function is supposed to take an argument extra_validators. So I don't understand why it would be throwing this error.
Full disclosure, I was following a tutorial, and did compare my code to their final code and it matched.
What am I missing here?
You ignore that validate takes additional parameters. If you add this in your implementation it should work.
def validate(self, extra_validators=None):
initial_validation = super(RegisterForm, self).validate(extra_validators)
# ...
Related
I am trying to send confirmation emails when a new user registers. When I send a confirmation email, everything works fine, however, when I try to run that task in the background I get the following error:
File "/Users/martifont/Dev/bz/./FlaskApp/utils.py", line 22, in send_confirmation_email
msg = Message()
I have tried running a different task in the background with no issues at all.
users.py
#imports
from .utils import send_reset_email, send_confirmation_email, example
users = Blueprint('users', __name__)
#SIGNUP
#users.route('/signup', methods=\['GET', 'POST'\])
def signup():
form = SignupForm()
if form.validate_on_submit():
user = User(email=form.email.data, username=form.username.data, confirmed=False, is_admin=False, password = generate_password_hash(form.password.data, method='sha256'))
db.session.add(user)
db.session.commit()
user_id=user.id
with app.app_context():
job = q.enqueue(send_confirmation_email, user)
login_user(user)
flash('A confirmation email has been sent to your inbox.', 'is-success')
return redirect(url_for('users.account', id=user.id))
return render_template('signup.html', form=form)
utils.py
def send_confirmation_email(user):
token = user.get_confirmation_token()
msg = Message()
msg.subject = ('Email Confirmation')
msg.sender = 'MAIL_DEFAULT_SENDER'
msg.recipients = [user.email]
msg.body = f'''
Welcome { user.username }!,
Thanks for signing up. Please follow this link to activate your account:
{url_for('users.confirm_email', token=token, _external=True)}
Thanks!
'''
mail.send(msg)
EDIT
After following Sebastian's advice, I think the issue is solved, however, I am still getting the following error message.
File "/Users/martifont/Dev/bz/venv/lib/python3.10/site-packages/flask/app.py", line 2548, in __call__
return self.wsgi_app(environ, start_response)
File "/Users/martifont/Dev/bz/venv/lib/python3.10/site-packages/flask/app.py", line 2528, in wsgi_app
response = self.handle_exception(e)
File "/Users/martifont/Dev/bz/venv/lib/python3.10/site-packages/flask/app.py", line 2525, in wsgi_app
response = self.full_dispatch_request()
File "/Users/martifont/Dev/bz/venv/lib/python3.10/site-packages/flask/app.py", line 1822, in full_dispatch_request
rv = self.handle_user_exception(e)
File "/Users/martifont/Dev/bz/venv/lib/python3.10/site-packages/flask/app.py", line 1820, in full_dispatch_request
rv = self.dispatch_request()
File "/Users/martifont/Dev/bz/venv/lib/python3.10/site-packages/flask/app.py", line 1796, in dispatch_request
return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)
File "/Users/martifont/Dev/bz/FlaskApp/users.py", line 30, in signup
job = q.enqueue(send_confirmation_email, args=(app, user))
File "/Users/martifont/Dev/bz/venv/lib/python3.10/site-packages/rq/queue.py", line 514, in enqueue
return self.enqueue_call(
File "/Users/martifont/Dev/bz/venv/lib/python3.10/site-packages/rq/queue.py", line 410, in enqueue_call
return self.enqueue_job(job, pipeline=pipeline, at_front=at_front)
File "/Users/martifont/Dev/bz/venv/lib/python3.10/site-packages/rq/queue.py", line 572, in enqueue_job
job.save(pipeline=pipe)
File "/Users/martifont/Dev/bz/venv/lib/python3.10/site-packages/rq/job.py", line 694, in save
mapping = self.to_dict(include_meta=include_meta)
File "/Users/martifont/Dev/bz/venv/lib/python3.10/site-packages/rq/job.py", line 629, in to_dict
'data': zlib.compress(self.data),
File "/Users/martifont/Dev/bz/venv/lib/python3.10/site-packages/rq/job.py", line 305, in data
self._data = self.serializer.dumps(job_tuple)
When you are in a view or inside the creat_app factory function there is no need to use the app_context() context manager.
The traceback defines where the error is been emited and that is when you instantiated the msg=Message() variable so instead of calling the app context inside the view i suggest you to refactor your view and send functions like so:
users.py
from flask import current_app
#users.route('/signup', methods=\['GET', 'POST'\])
def signup():
form = SignupForm()
if form.validate_on_submit():
user = User(email=form.email.data, username=form.username.data, confirmed=False, is_admin=False, password = generate_password_hash(form.password.data, method='sha256'))
db.session.add(user)
db.session.commit()
user_id=user.id
# pass the current app object to the send_confirmation_email function.
# Its important to pass the object and no the proxy
app = current_app._get_current_object()
job = q.enqueue(send_confirmation_email, args=(app, user))
login_user(user)
flash('A confirmation email has been sent to your inbox.', 'is-success')
return redirect(url_for('users.account', id=user.id))
return render_template('signup.html', form=form)
utils.py
def send_confirmation_email(app, user):
with app.app_context():
token = user.get_confirmation_token()
msg = Message()
msg.subject = ('Email Confirmation')
msg.sender = 'MAIL_DEFAULT_SENDER'
msg.recipients = [user.email]
msg.body = f'''
Welcome { user.username }!,
Thanks for signing up. Please follow this link to activate your account:
{url_for('users.confirm_email', token=token, _external=True)}
Thanks!'''
mail.send(msg)
I use Django and Graphene in my project. I wrote tests with GraphQLTestCase. When i try to authenticate users using JWT, i usually get errors.
Here is my code:
from django.test import TestCase
import json
from graphene_django.utils.testing import GraphQLTestCase
from resume.graph.schema import schema
from .models import Post
from django.contrib.auth import get_user_model
from graphql_jwt.shortcuts import get_token
User = get_user_model()
class PostTestCase(GraphQLTestCase):
GRAPHQL_SCHEMA = schema
def test_post_list(self):
token = get_token(User.objects.get(pk=1))
headers = {"HTTP_AUTHORIZATION": f"JWT {token}"}
response = self.query(
'''
query {
post{
user
text
}
}
''',
op_name = 'post',
headers=headers,
)
content = json.loads(response.content)
self.assertResponseNoErrors(response)
Here are the errors I get after running python manage.py test.
Traceback (most recent call last):
File "C:\Users\Udemezue\Desktop\resume\post\tests.py", line 25, in test_post_list
token = get_token(User.objects.get(pk=9))
File "C:\Users\Udemezue\Desktop\resume\env\lib\site-packages\django\db\models\manager.py", line 82, in manager_method
return getattr(self.get_queryset(), name)(*args, **kwargs)
File "C:\Users\Udemezue\Desktop\resume\env\lib\site-packages\django\db\models\query.py", line 415, in get
raise self.model.DoesNotExist(
accounts.models.User.DoesNotExist: User matching query does not exist.
Here is the errors i get.
======================================================================
FAIL: test_post_list (post.tests.PostTestCase)
Traceback (most recent call last):
File "C:\Users\Udemezue\Desktop\resume\post\tests.py", line 62, in test_post_list
self.assertResponseNoErrors(response)
File "C:\Users\Udemezue\Desktop\resume\env\lib\site-packages\graphene_django\utils\testing.py", line 75, in assertResponseNoErrors
self.assertEqual(resp.status_code, 200)
AssertionError: 400 != 200
Ran 1 test in 0.137s
FAILED (failures=1)
Destroying test database for alias 'default'...
This works for me.
from django.contrib.auth import get_user_model
from graphql_jwt.shortcuts import get_token
User = get_user_model()
import json
class PostTestCase(GraphQLTestCase):
def test_post_list(self):
user = get_user_model().objects.create(username='myuser')
token = get_token(user)
headers = {"HTTP_AUTHORIZATION": f"JWT {token}"}
response = self.query(
'''
query GetUser($username: String!) {
user(username: $username) {
id
}
}
''',
headers=headers,
)
content = json.loads(response.content)
self.assertResponseNoErrors(response)
I want to use #oidc.require_login to redirect login request to okta. I get AttributeError: '_AppCtxGlobals' object has no attribute 'oidc_id_token' error which I am unable to resolve
1) I built an app based on Flaskr tutorial that demonstrates use of flask factory method create_app.
2) I created a okta.py class as follows:
from oauth2client.client import OAuth2Credentials
from flask_oidc import OpenIDConnect
from okta import UsersClient
import click
from flask import current_app, g, session
from flask.cli import with_appcontext
oidc = OpenIDConnect()
def get_oidc():
"""
Connect to okta
"""
if 'oidc' not in g:
print('okta: get_oidc call')
g.oidc = OpenIDConnect(current_app)
g.okta_client = UsersClient("<okta-server-url>", "<secret>")
# fixing global oidc problem for decorator in rooms
oidc = g.oidc
return g.oidc
def close_oidc(app):
""" Release okta connection
"""
oidc = g.pop('oidc',None)
if oidc is not None:
oidc.logout()
# with app.app_context():
# session.clear()
def init_okta():
"""Connect to existing table"""
oidc = get_oidc()
""" Can do additional initialization if required """
#click.command('init-okta')
#with_appcontext
def init_okta_command():
"""Connect to existing oidc"""
init_okta()
click.echo (get_oidc())
click.echo('Initialized Okta.')
print('Initialized Okta.')
def init_app(app):
"""Register okta functions with the Flask app. This is called by
the application factory.
"""
app.teardown_appcontext(close_oidc)
app.cli.add_command(init_okta_command)
3) My goal is to use okta login to browse room listings
from flask import (
Blueprint, flash, g, redirect, render_template,
request, url_for, current_app, session, jsonify
)
from werkzeug.exceptions import abort
...
from hotel.okta import oidc, get_oidc, init_app
bp = Blueprint('rooms', __name__)
...
#bp.route('/login', methods=['GET', 'POST'])
#oidc.require_login
def login():
"""
Force the user to login, then redirect them to the get_books.
Currently this code DOES NOT work
Problem:
* oidc global object is not available to pass request to okta
Resolution:
* redirecting to rooms.calendar
"""
# info = oidc.user_getinfo(['preferred_username', 'email', 'sub'])
# id_token = OAuth2Credentials.from_json(oidc.credentials_store[info.get('sub')]).token_response['id_token']
return redirect(url_for("rooms.calendar"))
4) My __init__.py looks like this
import os
from flask import Flask
from flask_oidc import OpenIDConnect
from okta import UsersClient
# This is a factory method for productive deployment
# Use app specific configuration
# For any app local files, use /instnce Folder
def create_app(test_config=None):
"""Create and configure an instance of the Flask application."""
app = Flask(__name__, instance_relative_config=True)
app.config.from_mapping(
# a default secret that should be overridden by instance config
SECRET_KEY='dev',
# store the database in the instance folder
DATABASE=os.path.join(app.instance_path, 'hotel.sqlite'),
OIDC_CLIENT_SECRETS=os.path.join(app.instance_path, 'client_secrets.json'),
OIDC_COOKIE_SECURE=False,
OIDC_CALLBACK_ROUTE= '/oidc/callback',
OIDC_SCOPES=["openid", "email", "profile"],
OIDC_ID_TOKEN_COOKIE_NAME = 'oidc_token',
)
if test_config is None:
# load the instance config, if it exists, when not testing
app.config.from_pyfile('config.py', silent=True)
else:
# load the test config if passed in
app.config.update(test_config)
# ensure the instance folder exists
try:
os.makedirs(app.instance_path)
except OSError:
pass
# # register the database commands
from hotel import db
db.init_app(app)
# apply the blueprints to the app
from hotel import rooms
app.register_blueprint(rooms.bp)
# for Okta
# Ref: https://www.fullstackpython.com/blog/add-user-authentication-flask-apps-okta.html
from hotel import okta
with app.app_context():
okta.init_app(app)
#app.route('/hello') # For testing factory method
def hello():
return 'Hello, World!'
# make url_for('index') == url_for('blog.index')
# in another app, you might define a separate main index here with
# app.route, while giving the blog blueprint a url_prefix, but for
# the tutorial the blog will be the main index
app.add_url_rule('/', endpoint='index')
return app
5) Here is the code snippet of rooms.before_request
#bp.before_request
def before_request():
print ('rooms.before_request call reached')
with current_app.app_context():
print ('rooms.before_request in app_context',g)
oidc = g.pop('oidc',None)
okta_client = g.pop('okta_client',None)
if oidc is not None and okta_client is not None:
print ('rooms.before_request g.oidc and g.okta_client available')
if oidc.user_loggedin:
# OpenID Token as
g.user = okta_client.get_user(oidc.user_getfield("sub"))
g.oidc_id_token = OAuth2Credentials.from_json(g.oidc.credentials_store[info.get('sub')]).token_response['id_token']
else:
g.user = None
else:
print('rooms.beforerequest No user logged in')
g.user = None
My Analysis:
okta.init_app(app) is expected to use client_secrets.json in /instance folder to connect to okta.
In order to make #oidc.require_login to work, I exposed oidc in okta.get_oidc and import this into my rooms.py code
However, this does not work :(! Stack trace is:
File "/Users/athur/Code/cmpe272WarriorsHotel/Hotel/venv/lib/python3.7/site-packages/flask/app.py", line 2328, in __call__
return self.wsgi_app(environ, start_response)
File "/Users/athur/Code/cmpe272WarriorsHotel/Hotel/venv/lib/python3.7/site-packages/flask/app.py", line 2314, in wsgi_app
response = self.handle_exception(e)
File "/Users/athur/Code/cmpe272WarriorsHotel/Hotel/venv/lib/python3.7/site-packages/flask/app.py", line 1760, in handle_exception
reraise(exc_type, exc_value, tb)
File "/Users/athur/Code/cmpe272WarriorsHotel/Hotel/venv/lib/python3.7/site-packages/flask/_compat.py", line 36, in reraise
raise value
File "/Users/athur/Code/cmpe272WarriorsHotel/Hotel/venv/lib/python3.7/site-packages/flask/app.py", line 2311, in wsgi_app
response = self.full_dispatch_request()
File "/Users/athur/Code/cmpe272WarriorsHotel/Hotel/venv/lib/python3.7/site-packages/flask/app.py", line 1834, in full_dispatch_request
rv = self.handle_user_exception(e)
File "/Users/athur/Code/cmpe272WarriorsHotel/Hotel/venv/lib/python3.7/site-packages/flask/app.py", line 1737, in handle_user_exception
reraise(exc_type, exc_value, tb)
File "/Users/athur/Code/cmpe272WarriorsHotel/Hotel/venv/lib/python3.7/site-packages/flask/_compat.py", line 36, in reraise
raise value
File "/Users/athur/Code/cmpe272WarriorsHotel/Hotel/venv/lib/python3.7/site-packages/flask/app.py", line 1832, in full_dispatch_request
rv = self.dispatch_request()
File "/Users/athur/Code/cmpe272WarriorsHotel/Hotel/venv/lib/python3.7/site-packages/flask/app.py", line 1818, in dispatch_request
return self.view_functions[rule.endpoint](**req.view_args)
File "/Users/athur/Code/cmpe272WarriorsHotel/Hotel/venv/lib/python3.7/site-packages/flask_oidc/__init__.py", line 485, in decorated
if g.oidc_id_token is None:
File "/Users/athur/Code/cmpe272WarriorsHotel/Hotel/venv/lib/python3.7/site-packages/werkzeug/local.py", line 348, in __getattr__
return getattr(self._get_current_object(), name)
AttributeError: '_AppCtxGlobals' object has no attribute 'oidc_id_token'
My questions:
How can I call #oidc.require_login in flask application factory
method?
Is the way I initialize okta connection correct?
Is there anyway to manually call external authorization server without decorator approach? How?
Is setting flask stack variables a better way to go forward? If so, how?
Trying to set g in before_request does not seem to work. What are the other options to work in Flask's App Factory approach?
Any insights will be much appreciated!
Thanks!
Yuva
I'm using Django==1.9.2 and djangorestframework==3.3.2, and django.test.Client to make some tests. The problem is that when I execute my tests I'm gettting this error:
ERROR: test_view (main.tests.test_http.TestMainViewSet)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/vladir/work/all/project-django1.9/saxo-publish/publish/main/tests/test_http.py", line 111, in test_view
content_type='application/json'
File "/home/vladir/work/all/project-django1.9/venv/local/lib/python2.7/site-packages/django/test/client.py", line 515, in post
secure=secure, **extra)
File "/home/vladir/work/all/project-django1.9/venv/local/lib/python2.7/site-packages/django/test/client.py", line 314, in post
secure=secure, **extra)
File "/home/vladir/work/all/project-django1.9/venv/local/lib/python2.7/site-packages/django/test/client.py", line 380, in generic
return self.request(**r)
File "/home/vladir/work/all/project-django1.9/venv/local/lib/python2.7/site-packages/django/test/client.py", line 449, in request
response = self.handler(environ)
File "/home/vladir/work/all/project-django1.9/venv/local/lib/python2.7/site-packages/django/test/client.py", line 123, in __call__
response = self.get_response(request)
File "/home/vladir/work/all/project-django1.9/venv/local/lib/python2.7/site-packages/django/core/handlers/base.py", line 245, in get_response
response = self.handle_uncaught_exception(request, resolver, sys.exc_info())
File "/home/vladir/work/all/project-django1.9/venv/local/lib/python2.7/site-packages/django/core/handlers/base.py", line 296, in handle_uncaught_exception
return callback(request, **param_dict)
File "/home/vladir/work/all/project-django1.9/venv/local/lib/python2.7/site-packages/django/utils/decorators.py", line 166, in _wrapped_view
return middleware.process_response(request, response)
File "/home/vladir/work/all/project-django1.9/venv/local/lib/python2.7/site-packages/django/middleware/csrf.py", line 230, in process_response
request.META["CSRF_COOKIE"],
KeyError: u'CSRF_COOKIE'
My test code looks like this:
import json
from django.test import Client
from django.core.urlresolvers import reverse
from django.contrib.auth.models import User
class TestMainViewSet(TestCase):
def setUp(self):
self.client = Client(
HTTP_HOST='example.com' # I have also tried removing this
)
self.create_read_url = reverse('books-list')
User.objects.create_user(
username="username",
email="username#zunzun.se",
password="123"
)
def test_create(self):
self.client.login(username='username', password="123")
# In this case I'm doing a POST, but it is the same with a GET
response = self.client.post(
self.create_read_url,
data=json.dumps({'title': "Create"}), # I have also tried without the json.dumps
content_type='application/json'
)
data = json.loads(response.content)
print data
self.assertEqual(response.status_code, 201)
self.assertEquals(data['title'], "Create")
my view code is:
from django.contrib.auth.mixins import LoginRequiredMixin
from rest_framework import viewsets
from .serialiazers import (
BookSerializerRead,
BookSerializerWrite,
)
class MainViewSet(LoginRequiredMixin, viewsets.ModelViewSet):
queryset = Book.objects.all()
serializer_class_read = BookSerializerRead
serializer_class_write = BookSerializerWrite
on the urls.py:
from rest_framework import routers
router = routers.DefaultRouter()
router.register(r'books', MainViewSet, 'books')
urlpatterns = [
url(r'^api/', include(router.urls)),
]
According with the Django doc about it, I should not need anything additional to avoid the CSRF checks,
because as textually said there: "By default, the test client will disable any CSRF checks performed by your site.", and I also know that enforce_csrf_checks=False by default
on the Client.
I have found one detail though, if I create an instance of the client that way self.client = Client(HTTP_HOST='example.com', CSRF_COOKIE='xxxxxx') then it works, but is that
actually needed? It is not what the documentation says, so I suppose I'm doing something wrong. Could someone help me with that please? I will appreciate any help about.
Thanks in advance
Try to use DRF's APITestCase as base class for test cases:
from rest_framework.testing import APITestCase
class TestMainViewSet(APITestCase):
...
I have the following code that logs in a user on my Flask application:
#app.route('/login', methods = ['GET', 'POST'])
def login():
if request.method == 'GET':
return render_template('login.html')
db = get_db()
member = member_from_phone(request.form['phone_number'])
member_obj = Member(member)
if member is None:
g.result = json.dumps({'head' : 200, 'body' : "invalid phone" })
return 'no member...'
elif request.form['password'] == member['password']:
login_user(member_obj)
return 'login successful'
else:
return 'login failed'
return home_page()
However it throws a KeyError: 'security' when I try log in:
File "/Library/Python/2.7/site-packages/flask_security/utils.py", line 64, in login_user
if _security.trackable:
File "/Library/Python/2.7/site-packages/werkzeug/local.py", line 338, in __getattr__
return getattr(self._get_current_object(), name)
File "/Library/Python/2.7/site-packages/werkzeug/local.py", line 297, in _get_current_object
return self.__local()
File "/Library/Python/2.7/site-packages/flask_security/utils.py", line 35, in <lambda>
_security = LocalProxy(lambda: current_app.extensions['security'])
KeyError: 'security' when trying to call login_user
I have no idea what's going on, I think I might need to fix my flask configuration but I don't know how.