From f83092865bce130404de25bdec9e5c94c08d2e84 Mon Sep 17 00:00:00 2001 From: Eskil Heyn Olsen Date: Thu, 3 Jan 2013 22:00:29 -0800 Subject: [PATCH] Configurable forms, issue:49 --- docs/customizing.rst | 33 +++++++++++++++++++++++++++++- flask_security/core.py | 25 +++++++++++++++++++++++ flask_security/views.py | 45 +++++++++++++++++++++++++++++++---------- 3 files changed, 91 insertions(+), 12 deletions(-) diff --git a/docs/customizing.rst b/docs/customizing.rst index d10a522..3aff81c 100644 --- a/docs/customizing.rst +++ b/docs/customizing.rst @@ -59,6 +59,37 @@ The following is a list of all the available context processor decorators: * ``send_login_context_processor``: Send login view +Forms +----- + +All forms can be overridden in much the same way as context +processors. For each form used, you can specify a function that +returns the form class. This allows you to override/extend forms. For +example, to add extra fields to the register form:: + + security = Security(app, user_datastore) + + from flask_security.forms import RegisterForm + + class ExtendedRegisterForm(RegisterForm): + first_name = TextField('First Name', [Required()]) + last_name = TextField('Last Name', [Required()]) + + @security.register_form + def security_register_form(): + return ExtendedRegisterForm + +The following is a list of all the available form override decorators: + +* ``login_form``: Login form +* ``confirm_register_form``: Confirmable register form +* ``register_form``: Register form +* ``forgot_password_form``: Forgot password form +* ``reset_password_form``: Reset password form +* ``send_confirmation_form``: Send confirmation form +* ``passwordless_login_form``: Passwordless login form + + Emails ------ @@ -93,4 +124,4 @@ templates you can specify an email context processor with the # This processor is added to all emails @security.email_context_processor def security_mail_processor(): - return dict(hello="world") \ No newline at end of file + return dict(hello="world") diff --git a/flask_security/core.py b/flask_security/core.py index 94d60c8..aee6093 100644 --- a/flask_security/core.py +++ b/flask_security/core.py @@ -167,6 +167,7 @@ def _get_state(app, datastore, **kwargs): reset_serializer=_get_serializer(app, 'reset'), confirm_serializer=_get_serializer(app, 'confirm'), _context_processors={}, + _form_fns={}, _send_mail_task=None )) @@ -236,6 +237,9 @@ class _SecurityState(object): rv.update(fn()) return rv + def _get_form_cls(self, endpoint): + return self._form_fns.get(endpoint, lambda: None)() + def context_processor(self, fn): self._add_ctx_processor(None, fn) @@ -263,6 +267,27 @@ class _SecurityState(object): def send_mail_task(self, fn): self._send_mail_task = fn + def login_form(self, fn): + self._form_fns['login_form'] = fn + + def confirm_register_form(self, fn): + self._form_fns['confirm_register_form'] = fn + + def register_form(self, fn): + self._form_fns['register_form'] = fn + + def forgot_password_form(self, fn): + self._form_fns['forgot_password_form'] = fn + + def reset_password_form(self, fn): + self._form_fns['reset_password_form'] = fn + + def send_confirmation_form(self, fn): + self._form_fns['send_confirmation_form'] = fn + + def passwordless_login_form(self, fn): + self._form_fns['passwordless_login_form'] = fn + class Security(object): """The :class:`Security` class initializes the Flask-Security extension. diff --git a/flask_security/views.py b/flask_security/views.py index c079ea7..72a493a 100644 --- a/flask_security/views.py +++ b/flask_security/views.py @@ -35,6 +35,17 @@ _security = LocalProxy(lambda: current_app.extensions['security']) _datastore = LocalProxy(lambda: _security.datastore) +_default_forms = { + 'login_form': LoginForm, + 'confirm_register_form': ConfirmRegisterForm, + 'register_form': RegisterForm, + 'forgot_password_form': ForgotPasswordForm, + 'reset_password_form': ResetPasswordForm, + 'send_confirmation_form': SendConfirmationForm, + 'passwordless_login_form': PasswordlessLoginForm, +} + + def _render_json(form, include_auth_token=False): has_errors = len(form.errors) > 0 @@ -60,14 +71,20 @@ def _ctx(endpoint): return _security._run_ctx_processor(endpoint) +def _form_cls(endpoint): + return _security._get_form_cls(endpoint) or _default_forms[endpoint] + + @anonymous_user_required def login(): """View function for login view""" + form_class = _form_cls('login_form') + if request.json: - form = LoginForm(MultiDict(request.json)) + form = form_class(MultiDict(request.json)) else: - form = LoginForm() + form = form_class() if form.validate_on_submit(): login_user(form.user, remember=form.remember.data) @@ -101,9 +118,9 @@ def register(): """View function which handles a registration request.""" if _security.confirmable or request.json: - form_class = ConfirmRegisterForm + form_class = _form_cls('confirm_register_form') else: - form_class = RegisterForm + form_class = _form_cls('register_form') if request.json: form_data = MultiDict(request.json) @@ -136,10 +153,12 @@ def register(): def send_login(): """View function that sends login instructions for passwordless login""" + form_class = _form_cls('passwordless_login_form') + if request.json: - form = PasswordlessLoginForm(MultiDict(request.json)) + form = form_class(MultiDict(request.json)) else: - form = PasswordlessLoginForm() + form = form_class() if form.validate_on_submit(): send_login_instructions(form.user) @@ -179,10 +198,12 @@ def token_login(token): def send_confirmation(): """View function which sends confirmation instructions.""" + form_class = _form_cls('send_confirmation_form') + if request.json: - form = SendConfirmationForm(MultiDict(request.json)) + form = form_class(MultiDict(request.json)) else: - form = SendConfirmationForm() + form = form_class() if form.validate_on_submit(): send_confirmation_instructions(form.user) @@ -225,10 +246,12 @@ def confirm_email(token): def forgot_password(): """View function that handles a forgotten password request.""" + form_class = _form_cls('forgot_password_form') + if request.json: - form = ForgotPasswordForm(MultiDict(request.json)) + form = form_class(MultiDict(request.json)) else: - form = ForgotPasswordForm() + form = form_class() if form.validate_on_submit(): send_reset_password_instructions(form.user) @@ -257,7 +280,7 @@ def reset_password(token): if invalid or expired: return redirect(url_for('forgot_password')) - form = ResetPasswordForm() + form = _form_cls('reset_password_form')() if form.validate_on_submit(): after_this_request(_commit)