diff --git a/calsocial/__init__.py b/calsocial/__init__.py index 0c172bf..217dce1 100644 --- a/calsocial/__init__.py +++ b/calsocial/__init__.py @@ -25,6 +25,7 @@ from flask_babelex import Babel, get_locale as babel_get_locale from flask_security import SQLAlchemyUserDatastore, current_user, login_required from sqlalchemy.orm.exc import NoResultFound, MultipleResultsFound +from calsocial.account import AccountBlueprint from calsocial.utils import RoutedMixin @@ -90,6 +91,8 @@ class CalendarSocialApp(Flask, RoutedMixin): RoutedMixin.register_routes(self) + AccountBlueprint().init_app(self, '/accounts/') + self.before_request(self.goto_first_steps) @staticmethod @@ -100,7 +103,7 @@ class CalendarSocialApp(Flask, RoutedMixin): if current_user.is_authenticated and \ not current_user.profile and \ request.endpoint != 'first_steps': - return redirect(url_for('first_steps')) + return redirect(url_for('account.first_steps')) return None @@ -181,36 +184,6 @@ class CalendarSocialApp(Flask, RoutedMixin): return render_template('index.html', calendar=calendar, user_only=True) - @staticmethod - @RoutedMixin.route('/register', methods=['POST', 'GET']) - def register(): - """View for user registration - - If the ``REGISTRATION_FAILED`` configuration value is set to ``True`` it displays the - registration disabled template. Otherwise, it performs user registration. - """ - - if not current_app.config['REGISTRATION_ENABLED']: - return render_template('registration-disabled.html') - - from .forms import RegistrationForm - from .models import db, User - - form = RegistrationForm() - - if form.validate_on_submit(): - # TODO: This might become False later, if we want registrations to be confirmed via - # e-mail - user = User(active=True) - - form.populate_obj(user) - db.session.add(user) - db.session.commit() - - return redirect(url_for('hello')) - - return render_template('registration.html', form=form) - @staticmethod @RoutedMixin.route('/new-event', methods=['GET', 'POST']) @login_required @@ -236,27 +209,6 @@ class CalendarSocialApp(Flask, RoutedMixin): return render_template('event-edit.html', form=form) - @staticmethod - @RoutedMixin.route('/settings', methods=['GET', 'POST']) - @login_required - def settings(): - """View for user settings - """ - - from .forms import SettingsForm - from .models import db - - form = SettingsForm(current_user) - - if form.validate_on_submit(): - form.populate_obj(current_user) - - db.session.commit() - - return redirect(url_for('hello')) - - return render_template('user-settings.html', form=form) - @staticmethod @RoutedMixin.route('/event/', methods=['GET', 'POST']) def event_details(event_uuid): @@ -320,21 +272,6 @@ class CalendarSocialApp(Flask, RoutedMixin): return redirect(url_for('display_profile', username=username)) - @staticmethod - @RoutedMixin.route('/notifications') - def notifications(): - """View to list the notifications for the current user - """ - - from .models import Notification - - if current_user.is_authenticated: - notifs = Notification.query.filter(Notification.profile == current_user.profile) - else: - notifs = [] - - return render_template('notifications.html', notifs=notifs) - @staticmethod @RoutedMixin.route('/accept/') def accept_invite(invite_id): @@ -365,54 +302,6 @@ class CalendarSocialApp(Flask, RoutedMixin): return redirect(url_for('event_details', event_uuid=invitation.event.event_uuid)) - @staticmethod - @RoutedMixin.route('/first-steps', methods=['GET', 'POST']) - @login_required - def first_steps(): - """View to set up a new registrant’s profile - """ - - from .forms import FirstStepsForm - from .models import db, Profile - - if current_user.profile: - return redirect(url_for('hello')) - - form = FirstStepsForm() - - if form.validate_on_submit(): - profile = Profile(user=current_user, display_name=form.display_name.data) - db.session.add(profile) - - current_user.settings['timezone'] = str(form.time_zone.data) - - db.session.commit() - - return redirect(url_for('hello')) - - return render_template('first-steps.html', form=form) - - @staticmethod - @RoutedMixin.route('/edit-profile', methods=['GET', 'POST']) - @login_required - def edit_profile(): - """View for editing one’s profile - """ - - from .forms import ProfileForm - from .models import db - - form = ProfileForm(current_user.profile) - - if form.validate_on_submit(): - form.populate_obj(current_user.profile) - db.session.add(current_user.profile) - db.session.commit() - - return redirect(url_for('edit_profile')) - - return render_template('profile-edit.html', form=form) - @staticmethod @RoutedMixin.route('/all-events') def all_events(): @@ -430,45 +319,5 @@ class CalendarSocialApp(Flask, RoutedMixin): return render_template('index.html', calendar=calendar, user_only=False) - @staticmethod - @RoutedMixin.route('/follow-requests') - @login_required - def follow_requests(): - """View for listing follow requests - """ - - from .models import UserFollow - - requests = UserFollow.query \ - .filter(UserFollow.followed == current_user.profile) \ - .filter(UserFollow.accepted_at.is_(None)) - - return render_template('follow-requests.html', requests=requests) - - @staticmethod - @RoutedMixin.route('/follow-request//accept') - @login_required - def accept_follow(follower_id): - """View for accepting a follow request - """ - - from .models import db, UserFollow - - try: - req = UserFollow.query \ - .filter(UserFollow.followed == current_user.profile) \ - .filter(UserFollow.follower_id == follower_id) \ - .one() - except NoResultFound: - abort(404) - - if req.accepted_at is None: - req.accept() - - db.session.add(req) - db.session.commit() - - return redirect(url_for('follow_requests')) - app = CalendarSocialApp(__name__) diff --git a/calsocial/account.py b/calsocial/account.py new file mode 100644 index 0000000..52dbbd7 --- /dev/null +++ b/calsocial/account.py @@ -0,0 +1,199 @@ +# Calendar.social +# Copyright (C) 2018 Gergely Polonkai +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +"""Main module for the Calendar.social app +""" + +from flask import Blueprint, abort, current_app, redirect, render_template, url_for +from flask_security import current_user, login_required + +from sqlalchemy.orm.exc import NoResultFound + +from calsocial.utils import RoutedMixin + + +class AccountBlueprint(Blueprint, RoutedMixin): + """Blueprint for account management + """ + + def __init__(self): + Blueprint.__init__(self, 'account', __name__) + + self.app = None + + RoutedMixin.register_routes(self) + + def init_app(self, app, url_prefix=None): + """Initialise the blueprint, registering it with ``app``. + """ + + self.app = app + + app.register_blueprint(self, url_prefix=url_prefix) + + @staticmethod + @RoutedMixin.route('/register') + def register_account(): + """View for user registration + + If the ``REGISTRATION_FAILED`` configuration value is set to ``True`` it displays the + registration disabled template. Otherwise, it performs user registration. + """ + + if not current_app.config['REGISTRATION_ENABLED']: + return render_template('registration-disabled.html') + + from .forms import RegistrationForm + from .models import db, User + + form = RegistrationForm() + + if form.validate_on_submit(): + # TODO: This might become False later, if we want registrations to be confirmed via + # e-mail + user = User(active=True) + + form.populate_obj(user) + db.session.add(user) + db.session.commit() + + return redirect(url_for('hello')) + + return render_template('account/registration.html', form=form) + + @staticmethod + @RoutedMixin.route('/settings', methods=['GET', 'POST']) + @login_required + def settings(): + """View for user settings + """ + + from .forms import SettingsForm + from .models import db + + form = SettingsForm(current_user) + + if form.validate_on_submit(): + form.populate_obj(current_user) + + db.session.commit() + + return redirect(url_for('hello')) + + return render_template('account/user-settings.html', form=form) + + @staticmethod + @RoutedMixin.route('/notifications') + def notifications(): + """View to list the notifications for the current user + """ + + from .models import Notification + + if current_user.is_authenticated: + notifs = Notification.query.filter(Notification.profile == current_user.profile) + else: + notifs = [] + + return render_template('account/notifications.html', notifs=notifs) + + @staticmethod + @RoutedMixin.route('/first-steps', methods=['GET', 'POST']) + @login_required + def first_steps(): + """View to set up a new registrant’s profile + """ + + from .forms import FirstStepsForm + from .models import db, Profile + + if current_user.profile: + return redirect(url_for('hello')) + + form = FirstStepsForm() + + if form.validate_on_submit(): + profile = Profile(user=current_user, display_name=form.display_name.data) + db.session.add(profile) + + current_user.settings['timezone'] = str(form.time_zone.data) + + db.session.commit() + + return redirect(url_for('hello')) + + return render_template('account/first-steps.html', form=form) + + @staticmethod + @RoutedMixin.route('/edit-profile', methods=['GET', 'POST']) + @login_required + def edit_profile(): + """View for editing one’s profile + """ + + from .forms import ProfileForm + from .models import db + + form = ProfileForm(current_user.profile) + + if form.validate_on_submit(): + form.populate_obj(current_user.profile) + db.session.add(current_user.profile) + db.session.commit() + + return redirect(url_for('account.edit_profile')) + + return render_template('account/profile-edit.html', form=form) + + @staticmethod + @RoutedMixin.route('/follow-requests') + @login_required + def follow_requests(): + """View for listing follow requests + """ + + from .models import UserFollow + + requests = UserFollow.query \ + .filter(UserFollow.followed == current_user.profile) \ + .filter(UserFollow.accepted_at.is_(None)) + + return render_template('account/follow-requests.html', requests=requests) + + @staticmethod + @RoutedMixin.route('/follow-request//accept') + @login_required + def accept_follow(follower_id): + """View for accepting a follow request + """ + + from .models import db, UserFollow + + try: + req = UserFollow.query \ + .filter(UserFollow.followed == current_user.profile) \ + .filter(UserFollow.follower_id == follower_id) \ + .one() + except NoResultFound: + abort(404) + + if req.accepted_at is None: + req.accept() + + db.session.add(req) + db.session.commit() + + return redirect(url_for('account.follow_requests')) diff --git a/calsocial/templates/first-steps.html b/calsocial/templates/account/first-steps.html similarity index 100% rename from calsocial/templates/first-steps.html rename to calsocial/templates/account/first-steps.html diff --git a/calsocial/templates/follow-requests.html b/calsocial/templates/account/follow-requests.html similarity index 79% rename from calsocial/templates/follow-requests.html rename to calsocial/templates/account/follow-requests.html index ef7247a..0d4c3fa 100644 --- a/calsocial/templates/follow-requests.html +++ b/calsocial/templates/account/follow-requests.html @@ -7,7 +7,7 @@ {% for req in requests %}
  • {{ req.follower }} - {% trans %}Accept{% endtrans %} + {% trans %}Accept{% endtrans %}
  • {% endfor %} diff --git a/calsocial/templates/notifications.html b/calsocial/templates/account/notifications.html similarity index 100% rename from calsocial/templates/notifications.html rename to calsocial/templates/account/notifications.html diff --git a/calsocial/templates/profile-edit.html b/calsocial/templates/account/profile-edit.html similarity index 92% rename from calsocial/templates/profile-edit.html rename to calsocial/templates/account/profile-edit.html index a35c131..ecb38a1 100644 --- a/calsocial/templates/profile-edit.html +++ b/calsocial/templates/account/profile-edit.html @@ -1,4 +1,4 @@ -{% extends 'settings-base.html' %} +{% extends 'account/settings-base.html' %} {% from '_macros.html' import field %} {% block settings_content %} diff --git a/calsocial/templates/registration.html b/calsocial/templates/account/registration.html similarity index 100% rename from calsocial/templates/registration.html rename to calsocial/templates/account/registration.html diff --git a/calsocial/templates/account/settings-base.html b/calsocial/templates/account/settings-base.html new file mode 100644 index 0000000..46122b2 --- /dev/null +++ b/calsocial/templates/account/settings-base.html @@ -0,0 +1,15 @@ +{% extends 'base.html' %} + +{% block content %} +
    + +
    + {% block settings_content %}{% endblock %} +
    +
    +{% endblock %} diff --git a/calsocial/templates/user-settings.html b/calsocial/templates/account/user-settings.html similarity index 91% rename from calsocial/templates/user-settings.html rename to calsocial/templates/account/user-settings.html index 3fc4b1a..876efd3 100644 --- a/calsocial/templates/user-settings.html +++ b/calsocial/templates/account/user-settings.html @@ -1,4 +1,4 @@ -{% extends 'settings-base.html' %} +{% extends 'account/settings-base.html' %} {% from '_macros.html' import field %} {% block settings_content %} diff --git a/calsocial/templates/base.html b/calsocial/templates/base.html index 4d6bdbf..bd21da2 100644 --- a/calsocial/templates/base.html +++ b/calsocial/templates/base.html @@ -34,8 +34,8 @@ {%- endtrans %} {% trans %}Calendar view{% endtrans %} - {% trans %}Notifications{% endtrans %} - {% trans %}Settings{% endtrans %} + {% trans %}Notifications{% endtrans %} + {% trans %}Settings{% endtrans %} {% trans %}Logout{% endtrans %} {% endif %} @@ -50,6 +50,6 @@ {% block scripts %}{% endblock %} - + diff --git a/calsocial/templates/settings-base.html b/calsocial/templates/settings-base.html deleted file mode 100644 index 9fa7484..0000000 --- a/calsocial/templates/settings-base.html +++ /dev/null @@ -1,15 +0,0 @@ -{% extends 'base.html' %} - -{% block content %} -
    - -
    - {% block settings_content %}{% endblock %} -
    -
    -{% endblock %} diff --git a/calsocial/templates/welcome.html b/calsocial/templates/welcome.html index 95eefa1..2c6b9a7 100644 --- a/calsocial/templates/welcome.html +++ b/calsocial/templates/welcome.html @@ -18,7 +18,7 @@
    {% trans %}Or{% endtrans %}
    - {% trans %}Register an account{% endtrans %} + {% trans %}Register an account{% endtrans %} {% endif %}
    {% endif %}