''' views for user accounts This includes login, logout, registration, forgotten passwords, changing settings and passwords ''' import deform from pyramid.httpexceptions import HTTPFound from pyramid.security import remember, forget from pyramid.view import view_config from sqlalchemy import func, or_ from ordr.events import ( ChangeEmailNotification, PasswordResetNotification, RegistrationNotification ) from ordr.models.account import Role, TokenSubject, User # account resource root @view_config( context='ordr.resources.account.AccountResource', permission='view' ) def account(context, request): ''' redirect if '/account' was requested directly ''' return HTTPFound(request.resource_url(request.root)) # login and logout @view_config( context='ordr.resources.account.AccountResource', name='login', request_method='GET', permission='login', renderer='ordr:templates/account/login.jinja2', ) def login(context, request): ''' shows the login page ''' context.nav_active = 'welcome' return {'loginerror': False} @view_config( context='ordr.resources.account.AccountResource', name='login', request_method='POST', permission='login', renderer='ordr:templates/account/login.jinja2', ) def check_login(context, request): ''' check user credentials ''' username = request.POST.get('username') password = request.POST.get('password') user = ( request.dbsession .query(User) .filter_by(username=username) .first() ) if user and user.is_active and user.check_password(password): headers = remember(request, user.id) return HTTPFound(request.resource_url(request.root), headers=headers) context.nav_active = 'welcome' return {'loginerror': True} @view_config( context='ordr.resources.account.AccountResource', name='logout', permission='logout' ) def logout(context, request): ''' log out of an user ''' headers = forget(request) return HTTPFound(request.resource_url(request.root), headers=headers) # registration process @view_config( context='ordr.resources.account.RegistrationResource', permission='register', request_method='GET', renderer='ordr:templates/account/registration_form.jinja2' ) def registration_form(context, request): ''' show registration form ''' form = context.get_registration_form() return {'form': form} @view_config( context='ordr.resources.account.RegistrationResource', permission='register', request_method='POST', renderer='ordr:templates/account/registration_form.jinja2' ) def registration_form_processing(context, request): ''' process registration form ''' if 'create' not in request.POST: return HTTPFound(request.resource_url(request.root)) form = context.get_registration_form() data = request.POST.items() try: appstruct = form.validate(data) except deform.ValidationFailure as e: return {'form': form} # form validation successfull, create user account = User( username=appstruct['username'], first_name=appstruct['first_name'], last_name=appstruct['last_name'], email=appstruct['email'], role=Role.UNVALIDATED ) account.set_password(appstruct['password']) request.dbsession.add(account) # create a verify-new-account token and send email token = account.issue_token(request, TokenSubject.REGISTRATION) notification = RegistrationNotification(request, account, {'token': token}) request.registry.notify(notification) return HTTPFound(request.resource_url(context, 'verify')) @view_config( context='ordr.resources.account.RegistrationResource', name='verify', permission='register', request_method='GET', renderer='ordr:templates/account/registration_verify.jinja2' ) def registration_verify_email(context, request): ''' show email verification text ''' return {} @view_config( context='ordr.resources.account.RegistrationTokenResource', permission='register', request_method='GET', renderer='ordr:templates/account/registration_completed.jinja2' ) def registration_completed(context, request): ''' registration is completed, awaiting activation by admin ''' token = context.model account = token.owner account.role = Role.NEW request.dbsession.delete(token) return {} # forgotten password process @view_config( context='ordr.resources.account.PasswordResetResource', permission='reset', request_method='GET', renderer='ordr:templates/account/forgotten_password_form.jinja2' ) def forgotten_password_form(context, request): ''' show forgotten password form ''' return {'formerror': False} @view_config( context='ordr.resources.account.PasswordResetResource', permission='reset', request_method='POST', renderer='ordr:templates/account/forgotten_password_form.jinja2' ) def forgotten_password_form_processing(context, request): ''' process forgotten password form ''' if 'cancel' in request.POST: return HTTPFound(request.resource_url(request.root)) identifier = request.POST.get('identifier', '') account = ( request.dbsession .query(User) .filter(or_( func.lower(User.username) == identifier.lower(), func.lower(User.email) == identifier.lower() )) .first() ) if account is None or not account.is_active: return {'formerror': True} # create a verify-new-account token and send email token = account.issue_token(request, TokenSubject.RESET_PASSWORD) notification = PasswordResetNotification( request, account, {'token': token} ) request.registry.notify(notification) return HTTPFound(request.resource_url(context, 'verify')) @view_config( context='ordr.resources.account.PasswordResetResource', name='verify', permission='reset', request_method='GET', renderer='ordr:templates/account/forgotten_password_verify.jinja2' ) def forgotten_password_verify_email(context, request): ''' show email verification text ''' return {} @view_config( context='ordr.resources.account.PasswordResetResource', name='completed', permission='reset', request_method='GET', renderer='ordr:templates/account/forgotten_password_completed.jinja2' ) def forgotten_password_completed(context, request): ''' user is verified, process reset password form ''' return {} @view_config( context='ordr.resources.account.PasswordResetTokenResource', permission='reset', request_method='GET', renderer='ordr:templates/account/forgotten_password_reset.jinja2' ) def reset_password_form(context, request): ''' user is verified, show reset password form ''' form = context.get_reset_form() return {'form': form} @view_config( context='ordr.resources.account.PasswordResetTokenResource', permission='reset', request_method='POST', renderer='ordr:templates/account/forgotten_password_reset.jinja2' ) def reset_password_form_processing(context, request): ''' process the password reset form ''' if 'change' not in request.POST: return HTTPFound(request.resource_url(request.root)) form = context.get_reset_form() data = request.POST.items() try: appstruct = form.validate(data) except deform.ValidationFailure as e: return {'form': form} # set new password token = context.model account = token.owner account.set_password(appstruct['password']) request.dbsession.delete(token) return HTTPFound(request.resource_url(context.__parent__, 'completed')) # account settings @view_config( context='ordr.resources.account.AccountResource', permission='edit', name='settings', request_method='GET', renderer='ordr:templates/account/settings_form.jinja2' ) def settings_form(context, request): ''' show the settings form ''' prefill = { 'username': request.user.username, 'first_name': request.user.first_name, 'last_name': request.user.last_name, 'email': request.user.email, } form = context.get_settings_form(prefill=prefill) return {'form': form} @view_config( context='ordr.resources.account.AccountResource', permission='edit', name='settings', request_method='POST', renderer='ordr:templates/account/settings_form.jinja2' ) def settings_form_processing(context, request): ''' process the settings form ''' if 'change' not in request.POST: return HTTPFound(request.resource_url(request.root)) form = context.get_settings_form() data = request.POST.items() try: appstruct = form.validate(data) except deform.ValidationFailure as e: return {'form': form} # form validation successfull, change user request.user.first_name = appstruct['first_name'] request.user.last_name = appstruct['last_name'] if appstruct['email'] == request.user.email: # email was not changed return HTTPFound(request.resource_url(request.root)) # create a verify-new-email token and send email token = request.user.issue_token( request, TokenSubject.CHANGE_EMAIL, payload={'email': appstruct['email']} ) notification = ChangeEmailNotification( request, account, {'token': token}, send_to=appstruct['email'] ) request.registry.notify(notification) return HTTPFound(request.resource_url(context, 'verify')) @view_config( context='ordr.resources.account.ChangeEmailTokenResource', permission='edit', request_method='GET', renderer='ordr:templates/account/settings_mail_changed.jinja2' ) def verify_email_change(context, request): ''' show email verification text ''' payload = context.model.payload request.user.email = payload['email'] request.dbsession.delete(context.model) return {} # change password @view_config( context='ordr.resources.account.AccountResource', permission='edit', name='password', request_method='GET', renderer='ordr:templates/account/password_form.jinja2' ) def password_form(context, request): ''' show the change password form ''' form = context.get_password_form() return {'form': form} @view_config( context='ordr.resources.account.AccountResource', permission='edit', name='password', request_method='POST', renderer='ordr:templates/account/password_form.jinja2' ) def password_form_processing(context, request): ''' process the change password form ''' if 'change' not in request.POST: return HTTPFound(request.resource_url(request.root)) form = context.get_password_form() data = request.POST.items() try: appstruct = form.validate(data) except deform.ValidationFailure as e: return {'form': form} # form validation successfull, change the password request.user.set_password(appstruct['password']) return HTTPFound(request.resource_url(context, 'changed')) @view_config( context='ordr.resources.account.AccountResource', permission='edit', name='changed', request_method='GET', renderer='ordr:templates/account/password_changed.jinja2' ) def password_changed(context, request): ''' the password changed message ''' return {}