Flask-login current user disappearing on page refresh/redirect - flask-login

I have a login route:
#bp.route('/login', methods=['GET', 'POST'])
def login():
form = LoginForm()
if form.validate_on_submit():
guest = Guest(**mongo.db.guests.find_one({'username': form.username.data}))
if guest is None or not guest.check_password(form.password.data):
flash('Invalid username or password')
return redirect(url_for('auth.login'))
login_user(guest, remember=form.remember_me.data)
print(current_user)
flash(f'Logged in {guest.name} successfully')
next_url = request.args.get('next')
if not next_url or url_parse(next_url).netloc != '':
next_url = url_for('main.index')
return redirect(next_url)
return render_template('login.html', form=form)
When a user logins in 'successfully' it flashes and redirects the user correctly. However on the destination page (main.index or profile.view) the user is not logged in.
index route/html
#bp.route('/')
def index():
return render_template('index.html')
{% block content %}
<h2>Home page</h2>
<h1>Hi, {{ current_user.username }}!</h1>
<p><a href={{ url_for('auth.register') }}>Register</a></p>
<p><a href={{ url_for('auth.login') }}>Sign in</a></p>
<p><a href={{ url_for('main.index') }}>Home page</a> (accessible to anyone)</p>
<p><a href={{ url_for('profile.view') }}>Profile View</a> (login required)</p>
<p><a href={{ url_for('profile.edit') }}>Profile Edit</a> (login required)</p>
{% endblock %}
When redirecting to profile.view
#bp.route('/view')
#login_required
def view():
return 'This is the profile view'
It hits the #login_required decorator and redirects back to the login.
I'm using Flask-login (duh), and Flask-Pymongo for my database.
Why does the flask-login current user disappear on page refresh/redirect? Thanks!

My #login_manager.user_loader method was incorrect.
Changed:
#login_manager.user_loader
def load_user(user_id):
from flaskr.models import Guest
guest = mongo.db.guests.find_one({'_id': user_id})
if guest is None:
return None
return Guest(**guest)
To:
#login_manager.user_loader
def load_user(user_id):
from flaskr.models import Guest
from flask_pymongo import ObjectId
guest = mongo.db.guests.find_one({'_id': ObjectId(user_id)})
if guest is None:
return None
return Guest(**guest)
The database search was always failing because the user_id wasn't a Flask_Pymongo.ObjectId.

Related

Flask-WTF not validating with FieldList subforms

I am trying to get a test form that includes subforms to work but the form does not validate on submission. The form itself is submitting a recipe with ingredients as its subforms using FieldList(). I have also made sure to include hidden_tag() in the HTML.
Forms:
class IngredientsForm(FlaskForm):
ingredient_name = StringField("Ingredient", validators=[DataRequired()])
class RecipeForm(FlaskForm):
recipe_name = StringField("Recipe name", validators=[DataRequired()])
ingredients = FieldList(FormField(IngredientsForm), min_entries=2, max_entries=5)
submit = SubmitField("Submit")
Views:
#app.route("/", methods=["GET", "POST"])
def index():
form = RecipeForm()
return render_template("index.html", form=form)
#app.route("/submit", methods=["POST"])
def submit():
form = RecipeForm()
print(f"ERRORS: {form.errors}")
print(f"DATA: {form.data}")
if form.validate_on_submit():
print("Validated!")
print(form.recipe_name)
for ingredient in form.ingredients.data:
print(ingredient)
return redirect("/")
else:
print("Form not validated")
return render_template("index.html", form=form)
HTML:
<h1>Enter recipe:</h1>
<form action="/submit" method="POST">
{{ form.hidden_tag() }}
<p>
{{ form.recipe_name.label }} <br>
{{ form.recipe_name() }}
</p>
<p>
{{ form.ingredients.label }} <br>
{% for ing in form.ingredients %}
{{ ing.ingredient_name.label }}
{{ ing.ingredient_name() }}
<br>
{% endfor %}
</p>
<p>
{{ form.submit() }}
</p>
</form>
Output:
ERRORS: {}
DATA: {'recipe_name': 'butterbread', 'ingredients': [{'ingredient_name': 'butter', 'csrf_token': None}, {'ingredient_name': 'bread', 'csrf_token': None}], 'submit': True, 'csrf_token': 'Ijg1NmVjZjIwODY3MTJkNDNkMTFiNDQ2YzdiNzYyYzYyNmUzNGUzMWMi.YtaF7g.WRn25PWYMFplr_KV7RoZq1uLgrI'}
Form not validated
127.0.0.1 - - [19/Jul/2022 03:22:44] "POST /submit HTTP/1.1" 200 -
So far, no errors show up but it looks like in the data that since each subform has None as its csrf_token, maybe that's messing up the validation? I've tried getting this to validate for a while but to no avail.
You can disable csrf protection for the FlaskForm by setting the flag to false within the class Meta. CSRF protection is not necessary for nested forms as long as the parent form takes over this task.
class IngredientsForm(FlaskForm):
class Meta:
csrf = False
ingredient_name = StringField("Ingredient", validators=[DataRequired()])
class RecipeForm(FlaskForm):
recipe_name = StringField("Recipe name", validators=[DataRequired()])
ingredients = FieldList(FormField(IngredientsForm), min_entries=2, max_entries=5)
submit = SubmitField("Submit")
An alternative is to inherit from Form.
Figured it out. The problem is that the subform IngredientsForm inherits from FlaskForm which is a subclass that's csrf secure, and was the reason preventing validation. You don't need this as you have already the token in the main form.
Instead, have it inherit from wtforms.Form which doesn't have that. Another way is to disable csrf during init, but the previous method is preferred.

Flask Error: “Method Not Allowed The method is not allowed for the requested URL” (Login and Home Page)

#app.route("/")
#app.route("/home",methods=['GET', 'POST'])
def home():
if current_user.is_authenticated:
posts=mongo.db.articles
#allpost=posts.find().limit(5)
it=current_user.user_json['interest']
allpost=posts.find( {'NewsType': it } ).limit(10)
#flash(session['email'])
return render_template('home.html', posts=allpost)
return render_template('login.html', title='Login',form=LoginForm())
This is my code for the Home Page
#app.route("/login", methods=['GET', 'POST'])
def login():
if current_user.is_authenticated:
return redirect(url_for('home'))
form = LoginForm()
if form.validate_on_submit():
users = mongo.db.users
loginuser_json = users.find_one({'email': form.email.data})
if loginuser_json and bcrypt.check_password_hash(loginuser_json['password'], form.password.data):
# Create a custom user and pass it to login_user:
loginuser = User(loginuser_json)
login_user(loginuser,duration=d)
next_page = request.args.get('next')
return redirect(next_page) if next_page else redirect(url_for('home'))
return redirect(url_for('home'))
else:
flash('Login Unsuccessful. Please check username and password', 'danger')
return render_template('login.html', title='Login', form=form)
and this for the login route
When i write localhost:5000 in the browser it opens Login Page (because if user is not authenticated it should go to login page first)
But when i try to login from this page , it is giving an error "Method Not Allowed.The method is not allowed for the requested URL."
What should i do
In your code, it is not clear which code is called when the method is GET (usually when rendering your login page) and which is to be called when method is POST (called when you are submitting your form). To remove the mix up, do like this in your login
#app.route("/login", methods=['GET', 'POST'])
def login():
# code that runs for both GET and POST goes here
if request.method == 'POST':
if form.validate_on_submit():
#authenticate user and redirect to next page/home page
return render_template('login.html', title='Login', form=form) #this runs when method is get
Note the indentation and the respective code running under it
NOTE on your login template ensure the form method is post something like this
<form method=post>
<dl>
{{ render_field(form.email) }}
{{ render_field(form.password) }}
{{form.hidden_tag()}}
</dl>
<input type=submit value=Submit>
</form>
Lastly, on you home page, you have two routes that form the url_for(home) - though I do not think this is where your error is coming from - ensure both of them accept the method POST if you intend to use it there
#app.route("/",methods=['GET', 'POST'])#add post on this route
#app.route("/home",methods=['GET', 'POST'])
def home():
Hope this helps you sort out the error

Django2 forms nothing happend when submit post article

I have forms to Post Article in my blog Django2. When I run django server there is no error, but when I Post and submit Article in my frontEnd apps nothing happens and doesn't save any data.
Any help on this would be highly appreciated!
Template HTML
<div class="create-article">
<h2>Create an Awesome New Articles</h2>
<form class="site-form" action="{% url 'articles:create' %}" method="POST" enctype="multipart/form-data">
{% csrf_token %}
{{ form }}
<input type="submit" value="Create">
</form>
</div>
forms.py
from django import forms
from . import models
class CreateArticle(forms.ModelForm):
class Meta:
model = models.Article
fields = ['title', 'body', 'slug', 'thumb']
views.py
def article_create(request):
if request.method == 'POST':
form = forms.CreateArticle(request.POST, request.FILES)
if form.is_valid():
# save article to db
instance = form.save(commit=False)
instance.author = request.user
instance.save
return redirect('articles:list')
else:
form = forms.CreateArticle()
return render(request, 'articles/article_create.html', {'form': form})
urls.py
from django.urls import path
from . import views
app_name = 'articles'
urlpatterns = [
path('', views.article_list, name="list"),
path('create/', views.article_create, name="create"),
path('<slug>/', views.article_detail, name="detail"),
]
Thanks.
It starts here: instance = form.save(commit=False), where you are not commiting the save.
Further down you have instance.save as if you were setting a model field, but with not value given to it.
Make that line instance.save() instead.

Django 2 UserCreationForm not creating user

I'm using django-email-as-username so users can authenticate without a username and use their email instead. It seems that when I try to register a new user, I'm redirected back to the form page and the user isn't created. Am I supposed to be including the cleaned_data in my view?
Any advice?
forms.py
from django import forms
from django.contrib.auth import (authenticate, get_user_model,
password_validation)
from django.contrib.auth.forms import ReadOnlyPasswordHashField
from django.utils.translation import ugettext_lazy as _
from cuser.models import CUser
UserModel = get_user_model()
class AuthenticationForm(forms.Form):
"""
Base class for authenticating users. Extend this to get a form that accepts
email/password logins.
"""
email = forms.EmailField(
label=_("Email address"),
max_length=254,
widget=forms.EmailInput(attrs={'autofocus': True}),
)
password = forms.CharField(
label=_("Password"),
strip=False,
widget=forms.PasswordInput,
)
error_messages = {
'invalid_login': _(
"Please enter a correct %(username)s and password. Note that both "
"fields may be case-sensitive."
),
'inactive': _("This account is inactive."),
}
def __init__(self, request=None, *args, **kwargs):
"""
The 'request' parameter is set for custom auth use by subclasses.
The form data comes in via the standard 'data' kwarg.
"""
self.request = request
self.user_cache = None
super().__init__(*args, **kwargs)
self.username_field = UserModel._meta.get_field(UserModel.USERNAME_FIELD)
def clean(self):
email = self.cleaned_data.get('email')
password = self.cleaned_data.get('password')
if email and password:
self.user_cache = authenticate(self.request, email=email, password=password)
if self.user_cache is None:
# An authentication backend may reject inactive users. Check
# if the user exists and is inactive, and raise the 'inactive'
# error if so.
try:
self.user_cache = UserModel._default_manager.get_by_natural_key(email)
except UserModel.DoesNotExist:
pass
else:
self.confirm_login_allowed(self.user_cache)
raise forms.ValidationError(
self.error_messages['invalid_login'],
code='invalid_login',
params={'username': self.username_field.verbose_name},
)
else:
self.confirm_login_allowed(self.user_cache)
return self.cleaned_data
def confirm_login_allowed(self, user):
"""
Controls whether the given User may log in. This is a policy setting,
independent of end-user authentication. This default behavior is to
allow login by active users, and reject login by inactive users.
If the given user cannot log in, this method should raise a
``forms.ValidationError``.
If the given user may log in, this method should return None.
"""
if not user.is_active:
raise forms.ValidationError(
self.error_messages['inactive'],
code='inactive',
)
def get_user_id(self):
if self.user_cache:
return self.user_cache.id
return None
def get_user(self):
return self.user_cache
class UserCreationForm(forms.ModelForm):
"""
A form that creates a user, with no privileges, from the given email and
password.
"""
error_messages = {
'password_mismatch': _("The two password fields didn't match."),
}
email = forms.EmailField(
label=_("Email address"),
max_length=254,
widget=forms.EmailInput(attrs={'autofocus': True}),
)
password1 = forms.CharField(
label=_("Password"),
strip=False,
widget=forms.PasswordInput,
help_text=password_validation.password_validators_help_text_html(),
)
password2 = forms.CharField(
label=_("Password confirmation"),
widget=forms.PasswordInput,
strip=False,
help_text=_("Enter the same password as before, for verification."),
)
class Meta:
model = CUser
fields = []
def clean_password2(self):
password1 = self.cleaned_data.get("password1")
password2 = self.cleaned_data.get("password2")
if password1 and password2 and password1 != password2:
raise forms.ValidationError(
self.error_messages['password_mismatch'],
code='password_mismatch',
)
return password2
def _post_clean(self):
super()._post_clean()
# Validate the password after self.instance is updated with form data
# by super().
password = self.cleaned_data.get('password2')
if password:
try:
password_validation.validate_password(password, self.instance)
except forms.ValidationError as error:
self.add_error('password2', error)
def save(self, commit=True):
user = super().save(commit=False)
user.set_password(self.cleaned_data["password1"])
if commit:
user.save()
return user
class UserChangeForm(forms.ModelForm):
email = forms.EmailField(
label=_("Email address"),
max_length=254,
widget=forms.EmailInput(),
)
password = ReadOnlyPasswordHashField(
label=_("Password"),
help_text=_(
"Raw passwords are not stored, so there is no way to see this "
"user's password, but you can change the password using "
"this form."
),
)
class Meta:
model = CUser
fields = '__all__'
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['password'].help_text = self.fields['password'].help_text.format('../password/')
f = self.fields.get('user_permissions')
if f is not None:
f.queryset = f.queryset.select_related('content_type')
def clean_password(self):
# Regardless of what the user provides, return the initial value.
# This is done here, rather than on the field, because the
# field does not have access to the initial value
return self.initial["password"]
views.py
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import render, redirect
from django.urls import reverse
from .forms import UserCreationForm
def index(request):
return HttpResponse("This will be the profile homepage.")
def register(request):
if request.method == 'POST':
form = UserCreationForm(request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect('/accounts')
else:
form = UserCreationForm()
return render(request, 'accounts/register.html', {'form': form})
urls.py
from django.urls import path
from django.conf.urls import include, url
from accounts import views
urlpatterns = [
path('', views.index, name='index'),
path('register/', views.register, name='register'),
register.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div>
<h1>Register</h1>
<form method="post" action="">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Submit</button>
</form>
</div>
</body>
</html>
Have you looked at custom user documentation? Seems to mention exactly what you mentioned, but as a caveat it requires you to do it on the first migration

How can I keep field data after validating false in flask wtform?

I am new to flask and don't know how to keep field data after a failing post.
Thanks for your helps ^_^.
Example:
views.py:
#app.route('/', methods=['GET', 'POST', ])
def index():
form = MyForm()
if request.method == 'GET':
return render_template('index.html', form=form)
elif request.method == 'POST':
if form.validate_on_submit():
# blabla...
return redirect('/')
else: # validate false
# how to keep field data in new page?
return render_template('index.html', form=form) # it failed
It didn't work because I implement my own html form fields, in order to solve the problem, I should write template like this:
index.html:
<form ...>
{{ form.fieldname(class_='form-control', placeholder='hint') }}
</form>
Instead of inheriting your Form class from wtforms.Form, inherit it from flask_wtf.FlaskForm
For example, Replace this
from wtforms import Form
class RegistrationForm(Form):
#fields...
With this
from flask_wtf import FlaskForm
class RegistrationForm(FlaskForm):
#fields...