-
-
Save firdaus-aziz/76760eae5d9dd4a9772c69bc8efb53a3 to your computer and use it in GitHub Desktop.
Class-based views for Flask
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from flask import Blueprint | |
from flask import url_for | |
from flask.ext.login import current_user | |
from flask.ext.login import login_user | |
from flask.ext.login import logout_user | |
from flask.ext.login import login_required | |
from forms import LoginForm | |
from forms import ForgotPasswordForm | |
from forms import ResetPasswordForm | |
from forms import ChangePasswordForm | |
from forms import RegisterForm | |
from forms import ProfileForm | |
from models import User | |
from utils import send_password_reset_email | |
from utils import generate_user_token | |
from utils import get_user_token_status | |
from utils.views import FormView | |
from utils.views import CreateView | |
from utils.views import RedirectView | |
from utils.views import UpdateView | |
views = Blueprint('auth', __name__, template_folder='templates') | |
class Login(FormView): | |
template_name = 'form.html' | |
form_class = LoginForm | |
def get_success_url(self): | |
return url_for('.profile') | |
def form_valid(self): | |
login_user(self.form.obj, remember=self.form.remember_me.data) | |
class Register(CreateView): | |
template_name = 'form.html' | |
form_class = RegisterForm | |
success_message = 'Your account has been created. You may now login.' | |
def get_success_url(self): | |
return url_for('.login') | |
def get_object(self): | |
return User() | |
class Logout(RedirectView): | |
success_message = 'You have been logged out. Thank you for visiting.' | |
def dispatch(self): | |
logout_user() | |
def get_url(self): | |
return url_for('.login') | |
class PasswordForgot(FormView): | |
template_name = 'form.html' | |
form_class = ForgotPasswordForm | |
success_message = 'An e-mail has been sent with instructions how to reset your password. Please check your inbox.' | |
def get_success_url(self): | |
return url_for('.login') | |
def form_valid(self): | |
token = generate_user_token(self.form.obj) | |
send_password_reset_email(self.form.obj, token) | |
class PasswordReset(UpdateView): | |
template_name = 'form.html' | |
form_class = ResetPasswordForm | |
success_message = 'Your password has been updated. You may now login with your new password.' | |
expired_message = 'The link to reset your password is either invalid or it has expired.' | |
def dispatch(self): | |
expired, invalid, user = get_user_token_status(self.kwargs['token']) | |
self.user = user | |
if invalid or expired: | |
self.flash_message('error', self.expired_message) | |
return self.redirect(url_for('.login')) | |
def get_object(self): | |
return self.user | |
def get_success_url(self): | |
return url_for('.login') | |
class Profile(UpdateView): | |
template_name = 'auth/profile.html' | |
form_class = ProfileForm | |
success_message = 'Your profile has been successfully updated.' | |
decorators = [login_required, ] | |
def get_object(self): | |
return current_user | |
def get_success_url(self): | |
return url_for('.profile') | |
class PasswordChange(UpdateView): | |
template_name = 'auth/password_change.html' | |
form_class = ChangePasswordForm | |
success_message = 'Your password has been successfully updated.' | |
decorators = [login_required, ] | |
def get_object(self): | |
return current_user | |
def get_success_url(self): | |
return url_for('.profile') | |
Login.register(views, '/login') | |
Logout.register(views, '/logout') | |
Register.register(views, '/register') | |
PasswordChange.register(views, '/profile/password') | |
Profile.register(views, '/profile') | |
PasswordForgot.register(views, '/password-reset') | |
PasswordReset.register(views, '/password-reset/<token>') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import re | |
from flask import render_template | |
from flask import redirect | |
from flask import flash | |
from flask.views import MethodView | |
camel_to_underscore = re.compile('((?<=[a-z0-9])[A-Z]|(?!^)[A-Z](?=[a-z]))') | |
class ImproperlyConfigured(NotImplementedError): | |
pass | |
class FlashMessageMixin: | |
success_message = None | |
def get_success_message(self): | |
""" | |
Return the success message. | |
""" | |
return self.success_message | |
def flash_success_message(self): | |
""" | |
Flash the success message if it's not empty. | |
""" | |
self.flash_message('success', self.get_success_message()) | |
def flash_message(self, category, message): | |
""" | |
Flash the message if it's not empty. | |
""" | |
if message: | |
flash(message, category) | |
class View(MethodView): | |
""" | |
Base view for all other views. Doesn't do a whole lot. | |
""" | |
@classmethod | |
def register(cls, blueprint, route, name=None): | |
""" | |
A shortcut method for registering this view to an app or blueprint. | |
Assuming we have a blueprint and a CourseAdd view, then these two | |
lines are identical in functionality: | |
views.add_url_rule('/courses/add', view_func=CourseAdd.as_view('course_add')) | |
CourseAdd.register(views, '/courses/add', 'course_add') | |
""" | |
if name is None: | |
# Convert 'ViewName' to 'view_name' and use it | |
name = camel_to_underscore.sub(r'_\1', cls.__name__).lower() | |
blueprint.add_url_rule(route, view_func=cls.as_view(name)) | |
def dispatch(self): | |
""" | |
Hook for a subclass to call before dispatch actually happens. | |
""" | |
pass | |
def dispatch_request(self, *args, **kwargs): | |
""" | |
Save args and kwargs, then dispatch the request as a normal MethodView, | |
calling get() or post(). | |
""" | |
self.args = args | |
self.kwargs = kwargs | |
# If dispatch returns a value, use it. This most likely means it was a | |
# redirect, or a custom result entirely. | |
return self.dispatch() or super().dispatch_request(*args, **kwargs) | |
def redirect(self, location, code=None): | |
""" | |
Shortcut to redirect so you don't need to import flask.redirect. | |
""" | |
return redirect(location, code) | |
class TemplateView(View): | |
""" | |
A view that will simply display a template with the given context. | |
""" | |
template_name = None | |
def get_template_names(self): | |
""" | |
Get the template_name. If this method is not overwritten, then a | |
template_name variable must be declared. | |
""" | |
if self.template_name is None: | |
error = "%s must define either `template_name` or `get_template_names()`" | |
raise ImproperlyConfigured(error % self.__class__.__name__) | |
return self.template_name | |
def get_default_context(self): | |
""" | |
Get the default context, which contains this view instance along with | |
the kwargs. | |
""" | |
context = { | |
'view': self, | |
'kwargs': self.kwargs, | |
} | |
context.update(self.get_context()) | |
return context | |
def get_context(self): | |
""" | |
Hook for a sublcass to add variables to request context. | |
""" | |
return {} | |
def get(self, *args, **kwargs): | |
""" | |
Simply render the template with the context. | |
""" | |
return render_template(self.get_template_names(), | |
**self.get_default_context()) | |
class RedirectView(FlashMessageMixin, View): | |
""" | |
A view for redirecting to another URL. | |
""" | |
url = None | |
def get_url(self): | |
""" | |
Get the success URL. If this method is not overwritten, then a | |
url variable must be declared. | |
""" | |
if self.url is None: | |
error = "%s must define either `url` or `get_url()`" | |
raise ImproperlyConfigured(error % self.__class__.__name__) | |
return self.url | |
def get(self, *args, **kwargs): | |
""" | |
Simply redirect to the location specified. | |
""" | |
self.flash_success_message() | |
return redirect(self.get_url()) | |
class ListView(TemplateView): | |
""" | |
A view that will render a template with a list of objects. | |
""" | |
model = None | |
context_object_list_name = 'object_list' | |
def get_context_object_list_name(self): | |
""" | |
Get context_object_list_name. | |
""" | |
return self.context_object_list_name | |
def get_object_list(self): | |
""" | |
Get the list of objects. If this method is not overwritten, then a | |
model variable must be declared, and it must have query.all(). | |
""" | |
if self.model is None: | |
error = "%s must define either `model` or `get_object_list()`" | |
raise ImproperlyConfigured(error % self.__class__.__name__) | |
return self.model.query.all() | |
def get_default_context(self): | |
""" | |
Add the object list to the context. | |
""" | |
context = super().get_default_context() | |
context[self.get_context_object_list_name()] = self.get_object_list() | |
return context | |
class DetailView(TemplateView): | |
""" | |
A view that will display details in a template for a single object. | |
""" | |
context_object_name = 'object' | |
def get_context_object_name(self): | |
""" | |
Get context_object_name. | |
""" | |
return self.context_object_name | |
def get_object(self): | |
""" | |
Get the object. We don't make any assumptions, so this must be | |
overwritten by the subclass. | |
""" | |
error = "%s must define `get_object()`" | |
raise ImproperlyConfigured(error % self.__class__.__name__) | |
def get_default_context(self): | |
""" | |
Add the object to the context. | |
""" | |
context = super().get_default_context() | |
context[self.get_context_object_name()] = self.get_object() | |
return context | |
def get(self, *args, **kwargs): | |
""" | |
Set the object to an instance variable, then process as normal. | |
""" | |
self.object = self.get_object() | |
return super().get(*args, **kwargs) | |
class FormView(FlashMessageMixin, TemplateView): | |
""" | |
A view for processing a form. | |
""" | |
context_form_name = 'form' | |
form_class = None | |
success_url = None | |
def get_success_url(self): | |
""" | |
Get the success URL. If this method is not overwritten, then a | |
success_url variable must be declared. | |
""" | |
if self.success_url is None: | |
error = "%s must define either `success_url` or `get_success_url()`" | |
raise ImproperlyConfigured(error % self.__class__.__name__) | |
return self.success_url | |
def get_context_form_name(self): | |
""" | |
Get context_form_name. | |
""" | |
return self.context_form_name | |
def get_default_context(self): | |
""" | |
Add the form to the context. | |
""" | |
context = super().get_default_context() | |
context[self.get_context_form_name()] = self.form | |
return context | |
def get_form(self): | |
""" | |
Return the form instance. | |
""" | |
return self.form_class() | |
def form_valid(self): | |
""" | |
Hook for custom processing when a form is valid. Allows for custom | |
code without having to call super() in this method. | |
""" | |
pass | |
def form_valid_process(self): | |
""" | |
Form is valid so redirect to success URL. | |
""" | |
return redirect(self.get_success_url()) | |
def form_invalid(self): | |
""" | |
Hook for custom processing when a form is invalid. Allows for custom | |
code without having to call super() in this method. | |
""" | |
pass | |
def form_invalid_process(self): | |
""" | |
Form is invalid so show it again using get(). | |
""" | |
return super().get(*self.args, **self.kwargs) | |
def get(self, *args, **kwargs): | |
""" | |
Set the form to an instance variable, then process as normal. | |
""" | |
self.form = self.get_form() | |
return super().get(*args, **kwargs) | |
def post(self, *args, **kwargs): | |
""" | |
Set the form to an instance variable, then process as normal. If form | |
is valid, then redirect. If invalid, the redisplay. | |
""" | |
self.form = self.get_form() | |
if self.form.validate_on_submit(): | |
# If form_valid() returns a result, use it. | |
# This means subclass has custom functionality it wants to use. | |
# Otherwise redirect to the success URL. | |
self.flash_success_message() | |
return self.form_valid() or self.form_valid_process() | |
# If form_invalid() returns a result, use it. | |
# This means subclass has custom functionality it wants to use. | |
# Otherwise just show the form again using get(). | |
return self.form_invalid() or self.form_invalid_process() | |
class CreateView(FormView, DetailView): | |
""" | |
A view for creating an object. Designed to work with a ModelForm. | |
""" | |
def get_form(self): | |
""" | |
Return the form instance with obj set. | |
""" | |
return self.form_class(obj=self.get_object()) | |
def get_db_session(self): | |
""" | |
Get the database session used to save the object. By default this is | |
the session from the object query. | |
""" | |
return self.form.obj.query.session | |
def form_valid_process(self): | |
""" | |
Save the object to the session, set the object to an instance variable, | |
then process as normal (default is a redirect). | |
""" | |
self.form.save_obj(self.get_db_session()) | |
self.object = self.form.obj | |
return super().form_valid_process() | |
class UpdateView(CreateView): | |
""" | |
A view for updating an object. Designed to work with a ModelForm. | |
Works exactly the same as CreateView right now. | |
""" | |
pass | |
class DeleteView(FormView, DetailView): | |
""" | |
A view for deleting an object. Designed to work with a ModelForm. | |
""" | |
def get_form(self): | |
""" | |
Return the form instance with obj set. | |
""" | |
return self.form_class(obj=self.get_object()) | |
def get_db_session(self): | |
""" | |
Get the database session used to save the object. By default this is | |
the session from the object query. | |
""" | |
return self.form.obj.query.session | |
def form_valid_process(self): | |
""" | |
Delete the object in the session, remove the instance variable, then | |
process as normal (default is a redirect). | |
""" | |
self.form.delete_obj(self.get_db_session()) | |
self.object = None | |
return super().form_valid_process() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment