I am discovering Django (v2.1.1) and want to set up a signin page in which I have 2 emails fields, if the 2 fields are identical, I call form.is_valid().
The project tree :
├── manage.py
├── requirements.txt
├── project
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ ├── views.py
│ ├── wsgi.py
│ └── templates
│ ├── base.html
│ ├── project
│ │ └── home.html
│ └── registration
│ ├── logged_out.html
│ ├── login.html
│ └── signin.html
└── app
├── __init__.py
├── admin.py
├── apps.py
├── models.py
├── urls.py
├── views.py
├── migrations
└── templates
└── app
└── home.html
Internationalization config in project/settings.py :
# (…)
# Internationalization
LANGUAGE_CODE = 'fr-fr'
USE_I18N = True
USE_L10N = True
# (…)
With a basic django signin view it works well:
project/views.py :
from django.shortcuts import render, redirect
from django.contrib.auth import login, authenticate
from django.contrib.auth.forms import UserCreationForm
def index(request):
return render(request, 'project/home.html', {'context':'project index'})
def signin(request):
# (…)
form = UserCreationForm()
return render(request, 'registration/signin.html', {'form': form})
I have a nice French translated HTML form (except for the submit button) :
Let's add an email field in the form, email is a built-in user fields (as first_name & last_name) so I just add a new class SignInForm inheriting from UserCreationForm :
project/forms.py :
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User
class SignInForm(UserCreationForm):
class Meta:
model = User
fields = ('username', 'email', 'password1', 'password2')
I update project/views.py :
# (…)
from project.forms import SignInForm
# (…)
def signin(request):
# (…)
form = SignInForm()
return render(request, 'registration/signin.html', {'form': form})
It works : a nice French translated field is added but it do not have a help_text attribute :
Now I'm stuck…
If I want to set the attribute 'required': True to the email field (it is required and I want to show a translated help_text). The only way I found is overriding the built-in email field, but I loose translation and it do not shows the help_text :
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User
from django import forms
class SignInForm(UserCreationForm):
email = forms.EmailField(required=True)
class Meta:
model = User
fields = ('username', 'email', 'password1', 'password2')
If I add a second email2 field, I was hoping copy the original User.email with something like email2 = User.email, but it looks like this is not the good place were email is stored. I can use forms.EmailField() as above, but I'd really like to take benefit of the built-in translation.
Then here is my questions :
How to use an email field with build in attributes (name, required, help_text, …) and get translations ? (When I have an answer I assume that I will found the way to do the same with the submit, first_name & last_name)
Can I duplicate the email field with this constraint? (I do not want to store this second field, just checking before calling form.is_valid())
_
There are a few things at play here.
The UserCreationForm is a ModelForm, which derives its field from a model. In this case, the model is your current AUTH_USER_MODEL setting. When the email form field is created, it inspects the corresponding email field from the model, where it will find the help_text which is wrapped in a gettext (or gettext_lazy) call to allow for i18n to work, as well as any other attribute.
If I understand your question correctly, you want to duplicate the email form field to override some (if any) attributes and to allow you to validate it against the original one. While Django makes it a little harder than what you tried because it is using metaclasses to generate the form class, there has been a public API for doing just that since version 1.8: get_field
Here is an example of what you can do:
class SignInForm(UserCreationForm):
def __init__(self, *args, **kwargs):
super(SignInForm, self).__init__(*args, **kwargs)
email_field = settings.AUTH_USER_MODEL._meta.get_field('email')
self.fields['email2'] = email_field.formfield()
self.fields['email'].required = True
self.fields['email2'].required = True
class Meta:
model = settings.AUTH_USER_MODEL
fields = (...)
A few of remarks:
Using settings.AUTH_USER_MODEL instead of just User will save you from changing the reference to User in your entire codebase should you switch to a custom user model.
get_field also has a plural form (see docs) to retrieve multiple fields at the same time.
In general, any runtime modifications of forms (overriding field attributes, etc.) happens in the __init__ method, since it's where you first get the 'real' form after Django does its black magic with metaclasses, see the required
attribute override.
If you want to override a translatable text, you will have to go through gettext and learn about this mechanism if you want to do it properly. You could of course just hardcode it in French. ;)
Edit: Form validation happens in the validate and validate_{field} form methods, triggered by is_valid. You don't have to have this logic inside the view.
I hope that helps.
Related
I am working on building some basic functions. One of them what I am doing now is timeout-session. I set PERMANENT_SESSION_LIFETIME = timedelta(minutes=20) and I found it works well.
But what I am confused is How to tell users that the user's session was expired because of session lifetime using flash?
Or is there a way to redirect when the user's session was expired?
Below is Specifics
tree
.
├── __init__.py
├── admin
│ ├── __init__.py
│ ├── forms.py
│ └── views.py
├── app.py
├── commands.py
├── compat.py
├── database.py
├── dataset
│ ├── __init__.py
│ ├── forms.py
│ ├── models.py
│ └── views.py
├── decorators.py
├── extensions.py
├── public
│ ├── __init__.py
│ ├── __pycache__
│ ├── forms.py
│ └── views.py
├── settings.py
settings.py
from datetime import timedelta
from environs import Env
env = Env()
env.read_env()
ENV = env.str("FLASK_ENV", default="production")
DEBUG = ENV == "development"
SQLALCHEMY_DATABASE_URI = env.str("DATABASE_URL")
SECRET_KEY = env.str("SECRET_KEY")
SEND_FILE_MAX_AGE_DEFAULT = env.int("SEND_FILE_MAX_AGE_DEFAULT")
BCRYPT_LOG_ROUNDS = env.int("BCRYPT_LOG_ROUNDS", default=13)
DEBUG_TB_ENABLED = DEBUG
DEBUG_TB_INTERCEPT_REDIRECTS = False
CACHE_TYPE = "simple" # Can be "memcached", "redis", etc.
SQLALCHEMY_TRACK_MODIFICATIONS = False
MONGODB_URI = env.str("MONGODB_URI")
MONGODB_DATABASE_NAME = env.str("MONGODB_DATABASE_NAME")
UPLOAD_FOLDER = env.str("UPLOAD_FOLDER")
PERMANENT_SESSION_LIFETIME = timedelta(minutes=100)
app.py
# -*- coding: utf-8 -*-
"""The app module, containing the app factory function."""
import logging
import sys
from flask import Flask, render_template
from web import admin, commands, public, user, dataset
from web.extensions import (
bcrypt,
cache,
csrf_protect,
db,
debug_toolbar,
flask_static_digest,
login_manager,
migrate,
)
def create_app(config_object="web.settings"):
"""Create application factory
:param config_object: The configuration object to use.
"""
app = Flask(__name__.split(".")[0])
app.config.from_object(config_object)
register_extensions(app)
register_blueprints(app)
register_errorhandlers(app)
register_shellcontext(app)
register_commands(app)
configure_logger(app)
return app
``
I quoted this code from here.
login_mgr = LoginManager(app)
login_mgr.login_view = 'login'
login_mgr.refresh_view = 'relogin'
login_mgr.needs_refresh_message = (u"Session timedout, please re-login")
login_mgr.needs_refresh_message_category = "info"
Use a decorator before_request, it runs before each request.
https://flask.palletsprojects.com/en/2.0.x/api/#flask.Flask.before_request
#app.before_request
def load_user():
if "user_id" in session:
g.user = db.session.get(session["user_id"])
In your case, refer this answer.
#app.before_request
def before_request()
now = datetime.datetime.now()
try:
last_active = session['last_active']
delta = now - last_active
if delta.seconds > 1800:
session['last_active'] = now
return logout('Your session has expired after 30 minutes, you have been logged out')
except:
pass
try:
session['last_active'] = now
except:
pass
https://stackoverflow.com/a/48768278/1474183
Not sure if this is still something you are looking for, but I also had the same problem. I had implemented what Rafael Ribeiro was referring to above but needed a way to show the user before they started interacting with the page. My solution. I used the login_mgr settings:
login_mgr = LoginManager(app)
login_mgr.login_view = 'login'
login_mgr.refresh_view = 'relogin'
login_mgr.needs_refresh_message = (u"Session timedout, please re-login")
login_mgr.needs_refresh_message_category = "info
Then in the template, I have some JavaScript that makes a fetch to a route that requires a login to view. And on error, I refresh my page, which triggers the redirect.
Setting the expiry:
#main.before_request
def before_request():
session.permanent = True
current_app.permanent_session_lifetime = timedelta(hours=24)
Any route that has #login_required
#main.route("/check-session", methods=["Get", "Post"])
#login_required
def check_session():
return {"session": "checked"}
Javascript:
<script>
function fetchStatus() {
fetch(`/check-session`, {
method: "GET"
})
.then(response => response.json())
.catch(err => {
window.location.reload();
})
}
window.addEventListener('load', function () {
// Your document is loaded.
var fetchInterval = 86700000; // 24 hours 5 mins.
setInterval(fetchStatus, fetchInterval);
});
</script>
This seems to do the trick
I have the following setup for a simple href download page:
urls.py
urlpatterns = [
url(r'^kpis/$', InternalKPIView.as_view(), name='internal_kpis'),
url(r'^tenants/$', TenantListView.as_view(), name='tenant-list'),
url(r'^tenants/(?P<pk>[0-9]+)/$', TenantStatsView.as_view(), name='tenant-stats'),
url(r'^fileformaterror/$', FileFormatErrorView.as_view(), name='file-format-error'),
url(r'^fileformaterror/download/(?P<s3_key>.*)$', FileFormatErrorDownloadView.as_view(), name='file-format-error-download'),
]
template.html:
Download
views.py:
class FileFormatErrorDownloadView(View):
def get(self, request, s3_key):
pass
But when executing I get the following error:
django.urls.exceptions.NoReverseMatch: Reverse for 'file-format-error-download' not found. 'file-format-error-download' is not a valid view function or pattern name.
Tree output of the related files:
$ tree -I "*.pyc|__pycache__"
.
├── apps.py
├── __init__.py
├── migrations
│ └── __init__.py
├── templates
│ └── backoffice
│ ├── file_format_error.html
│ └── internal_kpis.html
├── urls.py
└── views.py
3 directories, 7 files
From what you've provided it seems like the urls.py you are showing belongs to one of the applications within the project. My guess is that URLs of that application are either not included properly or included with a namespace.
why not use django2.0+? then code may as below:
urls.py
path('fileformaterror/download/<s3_key>/', FileFormatErrorDownloadView.as_view(), name='file-format-error-download')
template.html
Download
views.py
from django.shortcuts import HttpResponse
class FileFormatErrorDownloadView(View):
def get(self, request, s3_key):
return HttpResponse('success')
I am planning to split my one big test file to smaller test based on the part of code it tests. And I have a custom assert function for one of my tests. If I split them out to a separate file, how should I import them to other test files.
TestSchemacase:
class TestSchemaCase(unittest.TestCase):
"""
This will test our schema against the JSONTransformer output
just to make sure the schema matches the model
"""
# pylint: disable=too-many-public-methods
_base_dir = os.path.realpath(os.path.dirname(__file__))
def assertJSONValidates(self, schema, data):
"""
This function asserts the validation works as expected
Args:
schema(dict): The schema to test against
data(dict): The data to validate using the schema
"""
# pylint: disable=invalid-name
validator = jsonschema.Draft4Validator(schema)
self.assertIsNone(validator.validate(data))
def assertJSONValidateFails(self, schema, data):
"""
This function will assertRaises an ValidationError Exception
is raised.
Args:
schema(dict): The schema to validate from
data(dict): The data to validate using the schema
"""
# pylint: disable=invalid-name
validator = jsonschema.Draft4Validator(schema)
with self.assertRaises(jsonschema.ValidationError):
validator.validate(data)
My question is,1. When I try to import them I get an Import error with No module name found. I am breaking the TestValidation to mentioned small files. 2. I know I can raise Validation error in the assertJSONValidateFails but what should I return in case of validation pass.
tests/schema
├── TestSchemaCase.py
├── TestValidation.py
├── __init__.py
└── models
├── Fields
│ ├── TestImplemen.py
│ ├── TestRes.py
│ └── __init__.py
├── Values
│ ├── TestInk.py
│ ├── TestAlue.py
│ └── __init__.py
└── __init__.py
3.And is this how we should inherit them?
class TestRes(unittest.TestCase, TestSchemaCase):
Thanks for your time. Sorry for the big post
I did see the post, But that doesn't solve the problem.
I would suggest using a test framework that doesn't force you to put your tests in classes, like pytest.
I would like to access an sqlite3 database from a Flask application (without using Flask-SQLAlchemy, since I require fts4 functionality). I am using Flask blueprints, and I am not sure where to put the following functions (shamelessly copied from a response to this stackoverflow question):
def request_has_connection():
return hasattr(flask.g, 'dbconn')
def get_request_connection():
if not request_has_connection():
flask.g.dbconn = sqlite3.connect(DATABASE)
# Do something to make this connection transactional.
# I'm not familiar enough with SQLite to know what that is.
return flask.g.dbconn
#app.teardown_request
def close_db_connection(ex):
if request_has_connection():
conn = get_request_connection()
# Rollback
# Alternatively, you could automatically commit if ex is None
# and rollback otherwise, but I question the wisdom
# of automatically committing.
conn.close()
My file structure is:
app
├── __init__.py
├── main
│ ├── forms.py
│ ├── __init__.py
│ ├── views.py
├── models.py
├── static
└── templates
├── base.html
├── index.html
└── login.html
I want the request_has_connection() and get_request_connection() functions accessible from all view functions and maybe from models.py as well. Right now, I'm thinking they all belong in my blueprint init.py, which currently contains:
from flask import Blueprint
main = Blueprint('main',__name__)
from . import views
and that my request teardown function would be registered as
#main.teardown_request
def close_db_connection(ex):
<blah-blah-blah>
Is this right?
Folder Structure
I'm currently learning Django from the Django book. I have the following directory structure:
.
└── mysite
├── books
│ ├── __init__.py
│ ├── models.py
│ ├── templates
│ │ ├── search_form.html
│ │ └── search_results.html
│ ├── tests.py
│ └── views.py
├── contact
│ ├── forms.py
│ ├── __init__.py
│ ├── templates
│ │ └── contact_form.html
│ └── views.py
├── manage.py
└── mysite
├── __init__.py
├── settings.py
├── templates
│ ├── base.html
│ ├── current_datetime.html
│ └── hours_ahead.html
├── urls.py
├── views.py
└── wsgi.py
Where books, contact, and mysite are folders. I also have a separate templates subfolder for each of those folders to store templates.
Story
Now, inside /mysite/settings.py, I have the following lines:
TEMPLATE_DIRS = (
os.path.join(os.path.dirname(__file__), 'templates').replace('\\', '/'),
)
Inside /contact/views.py, I have the following line (note that render here is from django.shortcuts):
return render(request, 'contact_form.html', {'form': form})
However, the line above gives me a TemplateDoesNotExist exception. This is because the template loader looks for the file contact_form.html under /mysite/templates/ instead of /contact/templates/.
Fair enough, even though I don't understand why that's the case. But, I have another case where it behaves differently.
Recall in the file structure above, I also have books/views.py. In it, I have the following lines (in different logical blocks):
return render(request, 'search_results.html',
{'books': books, 'query': q})
and
return render(request, 'search_form.html', {'errors': errors})
If you look back above, search_results.html and search_form.html are under the /books/templates/ folder, not /mysite/templates/ folder, and yet the template loader managed to find those files. Note that it still works even when I move search_results.html and search_form.html into /mysite/templates/.
So my question is, why is there a difference in where the loader looks for templates?
Files
Anyway, here's what my /contact/views.py looks like:
from django.shortcuts import render
from django.http import HttpResponseRedirect
from django.core.mail import send_mail
from forms import ContactForm
import os
def contact(request):
if request.method == 'POST':
form = ContactForm(request.POST)
if form.is_valid():
cd = form.cleaned_data
send_mail(
cd['subject'],
cd['message'],
cd.get('email', 'noreply#example.com'),
['siteowner#example.com'],
)
return HttpResponseRedirect('/contact/thanks/')
else:
form = ContactForm()
print os.path.join(os.path.dirname(__file__), 'templates')
return render(request, 'contact_form.html', {'form': form})
Here's my /books/views.py:
from django.shortcuts import render
from django.http import HttpResponse
from books.models import Book
def search(request):
errors = []
if 'q' in request.GET:
q = request.GET['q']
if not q:
errors.append('Enter a search term.')
elif len(q) > 20:
errors.append('Please enter at most 20 characters.')
else:
books = Book.objects.filter(title__icontains=q)
return render(request, 'search_results.html',
{'books': books, 'query': q})
return render(request, 'search_form.html', {'errors': errors})
Since you apparently do have the app directories template loader installed, and since it works on your books app, I can only conclude you don't have contact in settings.INSTALLED_APPS.