I am building a webapp using Flask. I imported the flask-login
library to handle user login. But it shows an ImportError.
Below is my folder structure:
>flask_blog1
>flaskblog
>static
>templates
>__init__.py
>forms.py
>models.py
>routes.py
>instance
>site.db
>venv
>requirements.txt
>run.py
My run.py
:
from flaskblog import app
if __name__ == "__main__":
app.run(debug=True)
My __init__.py
:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_bcrypt import Bcrypt
from flask_login import LoginManager
app = Flask(__name__)
app.config["SECRET_KEY"] = "5791628bb0b13ce0c676dfde280ba245"
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///site.db"
db = SQLAlchemy(app)
bcrypt = Bcrypt(app)
login_manager = LoginManager(app)
from flaskblog import routes
My models.py
:
from datetime import datetime
# from .extensions import db
from flaskblog import db, login_manager
from flask_login import UserMixin
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(20), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
image_file = db.Column(db.String(20), nullable=False, default="default.jpg")
password = db.Column(db.String(60), nullable=False)
posts = db.relationship("Post", backref="author", lazy=True)
def __repr__(self):
return f"User('{self.username}', '{self.email}', '{self.image_file}')"
class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(100), nullable=False)
date_posted = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
content = db.Column(db.Text, nullable=False)
user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False)
def __repr__(self):
return f"Post('{self.title}', '{self.date_posted}')"
My routes.py
:
from flask import render_template, flash, redirect, url_for
from flaskblog import app, db, bcrypt
from flaskblog.forms import RegistrationForm, LoginForm
from flaskblog.models import User, Post
from flask_login import login_user
posts = [
{
"author": "Ashutosh Chapagain",
"title": "Blog Post 1",
"content": "First Post Content",
"date_posted": "October 1, 2023",
},
{
"author": "Ash Dhakal",
"title": "Blog Post 2",
"content": "Second Post Content",
"date_posted": "October 2, 2023",
},
]
@app.route("/")
@app.route("/home")
def home():
return render_template("home.html", posts=posts)
@app.route("/about")
def about():
return render_template("about.html", title="About")
@app.route("/register", methods=["GET", "POST"])
def register():
form = RegistrationForm()
if form.validate_on_submit():
hashed_password = bcrypt.generate_password_hash(form.password.data).decode(
"utf-8"
)
user = User(
username=form.username.data, email=form.email.data, password=hashed_password
)
db.session.add(user)
db.session.commit()
flash(f"Your account has been created! You are now able to log in!", "success")
return redirect(url_for("login"))
return render_template("register.html", title="Register", form=form)
@app.route("/login", methods=["GET", "POST"])
def login():
form = LoginForm()
if form.validate_on_submit():
user = User.query.filter_by(email=form.email.data).first()
if user and bcrypt.check_password_hash(user.password, form.password.data):
login_user(user, remember=form.remember.data)
return redirect(url_for("home"))
else:
flash("Login Unsuccessful. Please check email and password", "danger")
return render_template("login.html", title="Login", form=form)
My forms.py
:
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField, BooleanField
from wtforms.validators import DataRequired, Length, Email, EqualTo, ValidationError
from flaskblog.models import User
class RegistrationForm(FlaskForm):
username = StringField(
"Username", validators=[DataRequired(), Length(min=2, max=20)]
)
email = StringField("Email", validators=[DataRequired(), Email()])
password = PasswordField("Password", validators=[DataRequired()])
confirm_password = PasswordField(
"Confirm Password", validators=[DataRequired(), EqualTo("password")]
)
submit = SubmitField("Sign Up")
def validate_username(self, username):
user = User.query.filter_by(username=username.data).first()
if user:
raise ValidationError(
"That username is taken. Please choose a different one."
)
def validate_email(self, email):
user = User.query.filter_by(email=email.data).first()
if user:
raise ValidationError("That email is taken. Please choose a different one.")
class LoginForm(FlaskForm):
email = StringField("Email", validators=[DataRequired(), Email()])
password = PasswordField("Password", validators=[DataRequired()])
remember = BooleanField("Remember Me")
submit = SubmitField("Login")
The exact error is:
(venv) asu@asu-Lenovo-Legion-5-15ARH05:/media/asu/Data/Projects/flask_blog1$ python3 run.py
Traceback (most recent call last):
File "/media/asu/Data/Projects/flask_blog1/run.py", line 1, in <module>
from flaskblog import app
File "/media/asu/Data/Projects/flask_blog1/flaskblog/__init__.py", line 4, in <module>
from flask_login import LoginManager
File "/media/asu/Data/Projects/flask_blog1/venv/lib/python3.10/site-packages/flask_login/__init__.py", line 12, in <module>
from .login_manager import LoginManager
File "/media/asu/Data/Projects/flask_blog1/venv/lib/python3.10/site-packages/flask_login/login_manager.py", line 33, in <module>
from .utils import _create_identifier
File "/media/asu/Data/Projects/flask_blog1/venv/lib/python3.10/site-packages/flask_login/utils.py", line 14, in <module>
from werkzeug.urls import url_decode
ImportError: cannot import name 'url_decode' from 'werkzeug.urls' (/media/asu/Data/Projects/flask_blog1/venv/lib/python3.10/site-packages/werkzeug/urls.py)
I can only assume you got the Werkzeug 3.0 update (as flask-login didn't up-bound their werkzeug dependency).
In their ongoing quest to remove all the non-core public APIs of werkzeug, the developers deprecated most of werkzeug.urls
in Werkzeug 2.3 (released April 25th 2023), and removed it in Werkzeug 3.0 (released September 30th 2023).
Your options are:
force werkzeug to a pre-3.0 version
wait for flask-login to release a version compatible with werkzeug 3, a fix of that and a bunch of other stuff was merged a few minutes ago
edit: flask-login 0.6.3 with the compatibility fix was released October 30th: https://github.com/maxcountryman/flask-login/releases/tag/0.6.3