From bcaeefc12ea46fc22c554a3193bd039293ab587f Mon Sep 17 00:00:00 2001 From: Daniel Grams Date: Thu, 11 May 2023 13:57:06 +0200 Subject: [PATCH 1/2] User.tos_accepted_at #473 --- messages.pot | 103 ++++++++++------- migrations/versions/becc71f97606_.py | 27 +++++ project/__init__.py | 18 ++- project/cli/test.py | 15 ++- project/forms/admin.py | 7 ++ project/forms/security.py | 9 ++ project/models/user.py | 5 +- project/requests.py | 20 +++- project/services/admin.py | 9 +- project/services/user.py | 4 + .../templates/admin/reset_tos_accepted.html | 16 +++ project/templates/admin/settings.html | 4 + project/templates/admin/users.html | 7 ++ project/templates/user/accept_tos.html | 22 ++++ .../translations/de/LC_MESSAGES/messages.mo | Bin 41233 -> 41571 bytes .../translations/de/LC_MESSAGES/messages.po | 108 +++++++++++------- .../translations/en/LC_MESSAGES/messages.mo | Bin 4079 -> 4079 bytes .../translations/en/LC_MESSAGES/messages.po | 103 ++++++++++------- project/views/admin.py | 21 ++++ project/views/user.py | 24 +++- tests/api/test_event.py | 4 +- tests/seeder.py | 5 + tests/views/test_admin.py | 23 ++++ tests/views/test_manage.py | 2 +- tests/views/test_user.py | 26 +++++ 25 files changed, 446 insertions(+), 136 deletions(-) create mode 100644 migrations/versions/becc71f97606_.py create mode 100644 project/templates/admin/reset_tos_accepted.html create mode 100644 project/templates/user/accept_tos.html diff --git a/messages.pot b/messages.pot index 124cf87..8542934 100644 --- a/messages.pot +++ b/messages.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2023-05-09 22:22+0200\n" +"POT-Creation-Date: 2023-05-11 11:19+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -229,21 +229,29 @@ msgstr "" msgid "Save" msgstr "" -#: project/forms/admin.py:21 project/forms/admin_unit_member.py:14 +#: project/forms/admin.py:22 +msgid "Reset for all users" +msgstr "" + +#: project/forms/admin.py:24 +msgid "Reset" +msgstr "" + +#: project/forms/admin.py:28 project/forms/admin_unit_member.py:14 #: project/forms/admin_unit_member.py:34 msgid "Roles" msgstr "" -#: project/forms/admin.py:22 project/templates/admin/update_user.html:4 +#: project/forms/admin.py:29 project/templates/admin/update_user.html:4 #: project/templates/admin/update_user.html:8 msgid "Update user" msgstr "" -#: project/forms/admin.py:26 project/templates/admin/delete_user.html:6 +#: project/forms/admin.py:33 project/templates/admin/delete_user.html:6 msgid "Delete user" msgstr "" -#: project/forms/admin.py:27 project/forms/admin_unit.py:53 +#: project/forms/admin.py:34 project/forms/admin_unit.py:53 #: project/forms/admin_unit_member.py:12 project/forms/admin_unit_member.py:25 #: project/forms/admin_unit_member.py:30 project/forms/event.py:107 #: project/forms/event_suggestion.py:38 project/forms/organizer.py:33 @@ -255,60 +263,60 @@ msgstr "" msgid "Email" msgstr "" -#: project/forms/admin.py:32 +#: project/forms/admin.py:39 msgid "Incoming reference requests allowed" msgstr "" -#: project/forms/admin.py:33 +#: project/forms/admin.py:40 msgid "" "If set, other organizations can ask this organization to reference their " "event." msgstr "" -#: project/forms/admin.py:39 +#: project/forms/admin.py:46 msgid "Suggestions enabled" msgstr "" -#: project/forms/admin.py:40 +#: project/forms/admin.py:47 msgid "If set, the organization can work with suggestions." msgstr "" -#: project/forms/admin.py:44 +#: project/forms/admin.py:51 msgid "Create other organizations" msgstr "" -#: project/forms/admin.py:45 +#: project/forms/admin.py:52 msgid "If set, members of the organization can create other organizations." msgstr "" -#: project/forms/admin.py:51 +#: project/forms/admin.py:58 msgid "Invite other organizations" msgstr "" -#: project/forms/admin.py:52 +#: project/forms/admin.py:59 msgid "If set, members of the organization can invite other organizations." msgstr "" -#: project/forms/admin.py:58 +#: project/forms/admin.py:65 msgid "Verify other organizations" msgstr "" -#: project/forms/admin.py:59 +#: project/forms/admin.py:66 msgid "If set, members of the organization can verify other organizations." msgstr "" -#: project/forms/admin.py:64 project/templates/admin/update_admin_unit.html:4 +#: project/forms/admin.py:71 project/templates/admin/update_admin_unit.html:4 #: project/templates/admin/update_admin_unit.html:8 msgid "Update organization" msgstr "" -#: project/forms/admin.py:68 project/templates/admin/delete_admin_unit.html:6 +#: project/forms/admin.py:75 project/templates/admin/delete_admin_unit.html:6 #: project/templates/admin_unit/request_deletion.html:6 #: project/templates/admin_unit/update.html:93 msgid "Delete organization" msgstr "" -#: project/forms/admin.py:69 project/forms/admin_unit.py:34 +#: project/forms/admin.py:76 project/forms/admin_unit.py:34 #: project/forms/admin_unit.py:142 project/forms/admin_unit.py:147 #: project/forms/admin_unit.py:152 project/forms/event.py:85 #: project/forms/event.py:114 project/forms/event_place.py:30 @@ -323,27 +331,27 @@ msgstr "" msgid "Name" msgstr "" -#: project/forms/admin.py:73 project/forms/admin.py:80 +#: project/forms/admin.py:80 project/forms/admin.py:87 msgid "Recipient" msgstr "" -#: project/forms/admin.py:75 +#: project/forms/admin.py:82 msgid "Send test mail synchronously" msgstr "" -#: project/forms/admin.py:82 project/forms/admin.py:88 +#: project/forms/admin.py:89 project/forms/admin.py:95 msgid "Test recipient" msgstr "" -#: project/forms/admin.py:83 +#: project/forms/admin.py:90 msgid "All users with enabled newsletter setting" msgstr "" -#: project/forms/admin.py:89 +#: project/forms/admin.py:96 msgid "Message" msgstr "" -#: project/forms/admin.py:90 +#: project/forms/admin.py:97 msgid "Send newsletter" msgstr "" @@ -1243,6 +1251,10 @@ msgstr "" msgid "Deny" msgstr "" +#: project/forms/security.py:79 +msgid "Confirm" +msgstr "" + #: project/forms/user.py:9 project/templates/admin/admin.html:31 #: project/templates/admin/newsletter.html:4 #: project/templates/admin/newsletter.html:93 @@ -1337,7 +1349,7 @@ msgstr "" #: project/templates/_macros.html:590 project/templates/_macros.html:633 #: project/templates/_macros.html:765 #: project/templates/admin/admin_units.html:36 -#: project/templates/admin/users.html:34 +#: project/templates/admin/users.html:36 #: project/templates/manage/events.html:116 #: project/templates/manage/members.html:35 #: project/templates/manage/organizers.html:33 @@ -1725,7 +1737,7 @@ msgid "View" msgstr "" #: project/templates/admin/admin_units.html:37 -#: project/templates/admin/users.html:35 +#: project/templates/admin/users.html:37 #: project/templates/manage/events.html:117 #: project/templates/manage/members.html:21 #: project/templates/manage/members.html:36 @@ -1742,7 +1754,7 @@ msgstr "" msgid "User" msgstr "" -#: project/templates/admin/email.html:47 project/views/admin.py:144 +#: project/templates/admin/email.html:47 project/views/admin.py:165 msgid "Mail sent successfully" msgstr "" @@ -1758,6 +1770,13 @@ msgstr "" msgid "Mails sent successfully" msgstr "" +#: project/templates/admin/reset_tos_accepted.html:4 +#: project/templates/admin/reset_tos_accepted.html:8 +#: project/templates/admin/settings.html:28 +#: project/templates/admin/users.html:48 +msgid "Reset acceptance of terms of service and privacy" +msgstr "" + #: project/templates/admin_unit/create.html:58 #: project/templates/admin_unit/update.html:66 #: project/templates/event/create.html:347 @@ -2293,6 +2312,10 @@ msgstr "" msgid "Register for free" msgstr "" +#: project/templates/user/accept_tos.html:6 +msgid "Confirmation required" +msgstr "" + #: project/templates/user/request_deletion.html:8 msgid "" "The account is not deleted immediately. After a period of time, the " @@ -2319,43 +2342,43 @@ msgstr "" msgid "Preview" msgstr "" -#: project/views/admin.py:63 +#: project/views/admin.py:64 msgid "Organization successfully updated" msgstr "" -#: project/views/admin.py:85 project/views/admin_unit.py:187 +#: project/views/admin.py:86 project/views/admin_unit.py:187 #: project/views/admin_unit.py:220 project/views/manage.py:316 msgid "Entered name does not match organization name" msgstr "" -#: project/views/admin.py:89 +#: project/views/admin.py:90 msgid "Organization successfully deleted" msgstr "" -#: project/views/admin.py:113 project/views/manage.py:486 -#: project/views/user.py:41 +#: project/views/admin.py:134 project/views/manage.py:486 +#: project/views/user.py:63 msgid "Settings successfully updated" msgstr "" -#: project/views/admin.py:133 +#: project/views/admin.py:154 #, python-format msgid "Test mail from %(site_name)s" msgstr "" -#: project/views/admin.py:162 +#: project/views/admin.py:183 #, python-format msgid "Newsletter from %(site_name)s" msgstr "" -#: project/views/admin.py:212 +#: project/views/admin.py:233 msgid "User successfully updated" msgstr "" -#: project/views/admin.py:232 +#: project/views/admin.py:253 msgid "Entered email does not match user email" msgstr "" -#: project/views/admin.py:236 +#: project/views/admin.py:257 msgid "User successfully deleted" msgstr "" @@ -2576,17 +2599,17 @@ msgid "" "verified automatically." msgstr "" -#: project/views/user.py:85 +#: project/views/user.py:107 msgid "" "You are administrator of at least one organization. Cancel your " "membership to delete your account." msgstr "" -#: project/views/user.py:92 project/views/user.py:119 +#: project/views/user.py:114 project/views/user.py:141 msgid "Entered email does not match your email" msgstr "" -#: project/views/user.py:138 +#: project/views/user.py:160 msgid "User deletion requested" msgstr "" diff --git a/migrations/versions/becc71f97606_.py b/migrations/versions/becc71f97606_.py new file mode 100644 index 0000000..f94db50 --- /dev/null +++ b/migrations/versions/becc71f97606_.py @@ -0,0 +1,27 @@ +"""empty message + +Revision ID: becc71f97606 +Revises: cceaf9b28134 +Create Date: 2023-05-10 14:25:57.157442 + +""" +import sqlalchemy as sa +import sqlalchemy_utils +from alembic import op + +from project import dbtypes + +# revision identifiers, used by Alembic. +revision = "becc71f97606" +down_revision = "cceaf9b28134" +branch_labels = None +depends_on = None + + +def upgrade(): + op.add_column("user", sa.Column("tos_accepted_at", sa.DateTime(), nullable=True)) + op.execute("UPDATE public.user SET tos_accepted_at = CURRENT_TIMESTAMP;") + + +def downgrade(): + op.drop_column("user", "tos_accepted_at") diff --git a/project/__init__.py b/project/__init__.py index 155b7db..932f37c 100644 --- a/project/__init__.py +++ b/project/__init__.py @@ -1,6 +1,6 @@ import logging import os -from datetime import timedelta +from datetime import datetime, timedelta from flask import Flask from flask_babel import Babel @@ -9,7 +9,7 @@ from flask_gzip import Gzip from flask_mail import Mail, email_dispatched from flask_migrate import Migrate from flask_qrcode import QRcode -from flask_security import Security, SQLAlchemySessionUserDatastore +from flask_security import Security, SQLAlchemySessionUserDatastore, user_registered from flask_sqlalchemy import SQLAlchemy from flask_wtf.csrf import CSRFProtect from sqlalchemy import MetaData @@ -228,6 +228,16 @@ security = Security( ) app.session_interface = CustomSessionInterface() + +@user_registered.connect_via(app) +def user_registered_sighandler(app, user, confirm_token, confirmation_token, form_data): + if "accept_tos" in form_data and form_data["accept_tos"]: + from project.services.user import set_user_accepted_tos + + set_user_accepted_tos(user) + db.session.commit() + + # OAuth2 from project.oauth2 import config_oauth @@ -276,9 +286,9 @@ from project.views import ( reference_request, reference_request_review, root, - user, - widget, ) +from project.views import user as user_view +from project.views import widget if __name__ == "__main__": # pragma: no cover app.run() diff --git a/project/cli/test.py b/project/cli/test.py index 7c83bfb..ff0048e 100644 --- a/project/cli/test.py +++ b/project/cli/test.py @@ -35,7 +35,12 @@ from project.services.event_suggestion import insert_event_suggestion from project.services.oauth2_client import complete_oauth2_client from project.services.organizer import get_event_organizer, upsert_event_organizer from project.services.place import get_event_places, upsert_event_place -from project.services.user import create_user, find_user_by_email, get_user +from project.services.user import ( + create_user, + find_user_by_email, + get_user, + set_user_accepted_tos, +) test_cli = AppGroup("test") @@ -61,13 +66,19 @@ def _get_default_organizer_id(admin_unit_id): def _create_user( - email="test@test.de", password="MeinPasswortIstDasBeste", confirm=True + email="test@test.de", + password="MeinPasswortIstDasBeste", + confirm=True, + tos_accepted=True, ): user = create_user(email, password) if confirm: confirm_user(user) + if tos_accepted: + set_user_accepted_tos(user) + db.session.commit() return user.id diff --git a/project/forms/admin.py b/project/forms/admin.py index 1d8c8ca..93b7cb1 100644 --- a/project/forms/admin.py +++ b/project/forms/admin.py @@ -17,6 +17,13 @@ class AdminSettingsForm(FlaskForm): submit = SubmitField(lazy_gettext("Save")) +class ResetTosAceptedForm(FlaskForm): + reset_for_users = BooleanField( + lazy_gettext("Reset for all users"), validators=[DataRequired()] + ) + submit = SubmitField(lazy_gettext("Reset")) + + class UpdateUserForm(FlaskForm): roles = MultiCheckboxField(lazy_gettext("Roles")) submit = SubmitField(lazy_gettext("Update user")) diff --git a/project/forms/security.py b/project/forms/security.py index 093021d..7c77570 100644 --- a/project/forms/security.py +++ b/project/forms/security.py @@ -72,3 +72,12 @@ class ExtendedForgotPasswordForm(ForgotPasswordForm): class AuthorizeForm(FlaskForm): allow = SubmitField(lazy_gettext("Allow")) deny = SubmitField(lazy_gettext("Deny")) + + +class AcceptTosForm(FlaskForm): + accept_tos = BooleanField(validators=[DataRequired()]) + submit = SubmitField(lazy_gettext("Confirm")) + + def __init__(self, **kwargs): + super(AcceptTosForm, self).__init__(**kwargs) + self._fields["accept_tos"].label.text = get_accept_tos_markup() diff --git a/project/models/user.py b/project/models/user.py index 1158d7d..0978bcd 100644 --- a/project/models/user.py +++ b/project/models/user.py @@ -59,7 +59,10 @@ class User(db.Model, UserMixin): server_default="1", ) ) - created_at = Column(DateTime, default=datetime.datetime.utcnow) + tos_accepted_at = Column( + DateTime(), + nullable=True, + ) created_at = deferred(Column(DateTime, default=datetime.datetime.utcnow)) deletion_requested_at = deferred(Column(DateTime, nullable=True)) diff --git a/project/requests.py b/project/requests.py index 609910e..d1f72d0 100644 --- a/project/requests.py +++ b/project/requests.py @@ -1,11 +1,29 @@ from datetime import datetime, timedelta -from flask import g, request +from flask import g, redirect, request, url_for from flask_login.utils import encode_cookie +from flask_security import current_user from project import app +@app.after_request +def check_tos_accepted(response): + if ( + response.status_code == 200 + and request.endpoint + and not request.endpoint.startswith("api_") + and not request.endpoint.startswith("widget_") + and request.endpoint not in ["static", "user_accept_tos"] + and current_user + and current_user.is_authenticated + and not current_user.tos_accepted_at + ): + return redirect(url_for("user_accept_tos", next=request.url)) + + return response + + @app.after_request def set_manage_admin_unit_cookie(response): admin_unit = getattr(g, "manage_admin_unit", None) diff --git a/project/services/admin.py b/project/services/admin.py index 5a130eb..37eb4c5 100644 --- a/project/services/admin.py +++ b/project/services/admin.py @@ -1,7 +1,7 @@ -from sqlalchemy import exists, func +from sqlalchemy import exists, func, update from project import db -from project.models import Settings +from project.models import Settings, User def upsert_settings(): @@ -17,3 +17,8 @@ def has_tos(): return db.session.scalar( exists().where(func.coalesce(Settings.tos, "") != "").select() ) + + +def reset_tos_accepted_for_users(): + db.session.execute(update(User).values(tos_accepted_at=None)) + db.session.commit() diff --git a/project/services/user.py b/project/services/user.py index d13611d..fd86a7b 100644 --- a/project/services/user.py +++ b/project/services/user.py @@ -122,3 +122,7 @@ def is_user_admin_member(user: User) -> bool: ).first() is not None ) + + +def set_user_accepted_tos(user: User): + user.tos_accepted_at = datetime.datetime.utcnow() diff --git a/project/templates/admin/reset_tos_accepted.html b/project/templates/admin/reset_tos_accepted.html new file mode 100644 index 0000000..6149a40 --- /dev/null +++ b/project/templates/admin/reset_tos_accepted.html @@ -0,0 +1,16 @@ +{% extends "layout.html" %} +{% from "_macros.html" import render_field_with_errors, render_field %} +{%- block title -%} +{{ _('Reset acceptance of terms of service and privacy') }} +{%- endblock -%} +{% block content %} + +

{{ _('Reset acceptance of terms of service and privacy') }}

+ +
+ {{ form.hidden_tag() }} + {{ render_field_with_errors(form.reset_for_users, ri="checkbox") }} + {{ render_field(form.submit) }} +
+ +{% endblock %} diff --git a/project/templates/admin/settings.html b/project/templates/admin/settings.html index 00887e4..286681c 100644 --- a/project/templates/admin/settings.html +++ b/project/templates/admin/settings.html @@ -24,4 +24,8 @@ {{ render_field(form.submit) }} + + {% endblock %} \ No newline at end of file diff --git a/project/templates/admin/users.html b/project/templates/admin/users.html index db4f6c8..fafe959 100644 --- a/project/templates/admin/users.html +++ b/project/templates/admin/users.html @@ -19,6 +19,7 @@ {{ _('Email') }} created_at confirmed_at + tos_accepted_at deletion_requested_at @@ -29,6 +30,7 @@ {{ user.email }} {% if user.created_at %}{{ user.created_at | dateformat }}{% endif %} {% if user.confirmed_at %}{{ user.confirmed_at | dateformat }}{% endif %} + {% if user.tos_accepted_at %}{{ user.tos_accepted_at | dateformat }}{% endif %} {% if user.deletion_requested_at %}{{ user.deletion_requested_at | dateformat }}{% endif %} {{ _('Edit') }} @@ -42,4 +44,9 @@
{{ render_pagination(pagination) }}
+ + + {% endblock %} \ No newline at end of file diff --git a/project/templates/user/accept_tos.html b/project/templates/user/accept_tos.html new file mode 100644 index 0000000..54201a9 --- /dev/null +++ b/project/templates/user/accept_tos.html @@ -0,0 +1,22 @@ +{% extends "layout.html" %} +{% from "_macros.html" import render_field %} + +{% block content %} + +

{{ _('Confirmation required') }}

+
+
+ {{ form.hidden_tag() }} + +
+
+ {{ form.accept_tos(class="form-check-input")|safe }} + {{ form.accept_tos.label(class="form-check-label") }} +
+
+ + {{ render_field(form.submit) }} +
+
+ +{% endblock %} diff --git a/project/translations/de/LC_MESSAGES/messages.mo b/project/translations/de/LC_MESSAGES/messages.mo index 21fa5e7f61cda219c71dea2babc8b6ca3ac8538d..e0a79270947ca00dcacf7f618bd2627e823517e8 100644 GIT binary patch delta 9275 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 OCjFUI6S#bI-2VW(d{H<6 diff --git a/project/translations/de/LC_MESSAGES/messages.po b/project/translations/de/LC_MESSAGES/messages.po index 7f3a608..4e3b2c7 100644 --- a/project/translations/de/LC_MESSAGES/messages.po +++ b/project/translations/de/LC_MESSAGES/messages.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2023-05-09 22:22+0200\n" +"POT-Creation-Date: 2023-05-11 11:19+0200\n" "PO-Revision-Date: 2020-06-07 18:51+0200\n" "Last-Translator: FULL NAME \n" "Language: de\n" @@ -230,21 +230,29 @@ msgstr "Startseite" msgid "Save" msgstr "Speichern" -#: project/forms/admin.py:21 project/forms/admin_unit_member.py:14 +#: project/forms/admin.py:22 +msgid "Reset for all users" +msgstr "Für alle Nutzer zurücksetzen" + +#: project/forms/admin.py:24 +msgid "Reset" +msgstr "Zurücksetzen" + +#: project/forms/admin.py:28 project/forms/admin_unit_member.py:14 #: project/forms/admin_unit_member.py:34 msgid "Roles" msgstr "Rollen" -#: project/forms/admin.py:22 project/templates/admin/update_user.html:4 +#: project/forms/admin.py:29 project/templates/admin/update_user.html:4 #: project/templates/admin/update_user.html:8 msgid "Update user" msgstr "Nutzer aktualisieren" -#: project/forms/admin.py:26 project/templates/admin/delete_user.html:6 +#: project/forms/admin.py:33 project/templates/admin/delete_user.html:6 msgid "Delete user" msgstr "Nutzer löschen" -#: project/forms/admin.py:27 project/forms/admin_unit.py:53 +#: project/forms/admin.py:34 project/forms/admin_unit.py:53 #: project/forms/admin_unit_member.py:12 project/forms/admin_unit_member.py:25 #: project/forms/admin_unit_member.py:30 project/forms/event.py:107 #: project/forms/event_suggestion.py:38 project/forms/organizer.py:33 @@ -256,11 +264,11 @@ msgstr "Nutzer löschen" msgid "Email" msgstr "Email" -#: project/forms/admin.py:32 +#: project/forms/admin.py:39 msgid "Incoming reference requests allowed" msgstr "Eingehende Empfehlungsanfragen erlauben" -#: project/forms/admin.py:33 +#: project/forms/admin.py:40 msgid "" "If set, other organizations can ask this organization to reference their " "event." @@ -268,56 +276,56 @@ msgstr "" "Wenn gesetzt, können andere Organisationen diese Organisation bitten, " "deren Veranstaltungen zu empfehlen." -#: project/forms/admin.py:39 +#: project/forms/admin.py:46 msgid "Suggestions enabled" msgstr "Vorschläge aktiv" -#: project/forms/admin.py:40 +#: project/forms/admin.py:47 msgid "If set, the organization can work with suggestions." msgstr "Wenn gesetzt, kann die Organisation mit Vorschlägen arbeiten." -#: project/forms/admin.py:44 +#: project/forms/admin.py:51 msgid "Create other organizations" msgstr "Andere Organisationen erstellen" -#: project/forms/admin.py:45 +#: project/forms/admin.py:52 msgid "If set, members of the organization can create other organizations." msgstr "" "Wenn gesetzt, können Mitglieder der Organisation andere Organisationen " "erstellen." -#: project/forms/admin.py:51 +#: project/forms/admin.py:58 msgid "Invite other organizations" msgstr "Andere Organisationen einladen" -#: project/forms/admin.py:52 +#: project/forms/admin.py:59 msgid "If set, members of the organization can invite other organizations." msgstr "" "Wenn gesetzt, können Mitglieder der Organisation andere Organisationen " "einladen." -#: project/forms/admin.py:58 +#: project/forms/admin.py:65 msgid "Verify other organizations" msgstr "Andere Organisationen verifizieren" -#: project/forms/admin.py:59 +#: project/forms/admin.py:66 msgid "If set, members of the organization can verify other organizations." msgstr "" "Wenn gesetzt, können Mitglieder der Organisation andere Organisationen " "verifizieren." -#: project/forms/admin.py:64 project/templates/admin/update_admin_unit.html:4 +#: project/forms/admin.py:71 project/templates/admin/update_admin_unit.html:4 #: project/templates/admin/update_admin_unit.html:8 msgid "Update organization" msgstr "Organisation aktualisieren" -#: project/forms/admin.py:68 project/templates/admin/delete_admin_unit.html:6 +#: project/forms/admin.py:75 project/templates/admin/delete_admin_unit.html:6 #: project/templates/admin_unit/request_deletion.html:6 #: project/templates/admin_unit/update.html:93 msgid "Delete organization" msgstr "Organisation löschen" -#: project/forms/admin.py:69 project/forms/admin_unit.py:34 +#: project/forms/admin.py:76 project/forms/admin_unit.py:34 #: project/forms/admin_unit.py:142 project/forms/admin_unit.py:147 #: project/forms/admin_unit.py:152 project/forms/event.py:85 #: project/forms/event.py:114 project/forms/event_place.py:30 @@ -332,27 +340,27 @@ msgstr "Organisation löschen" msgid "Name" msgstr "Name" -#: project/forms/admin.py:73 project/forms/admin.py:80 +#: project/forms/admin.py:80 project/forms/admin.py:87 msgid "Recipient" msgstr "Empfänger" -#: project/forms/admin.py:75 +#: project/forms/admin.py:82 msgid "Send test mail synchronously" msgstr "Test-Mail synchron senden" -#: project/forms/admin.py:82 project/forms/admin.py:88 +#: project/forms/admin.py:89 project/forms/admin.py:95 msgid "Test recipient" msgstr "Test-Empfänger" -#: project/forms/admin.py:83 +#: project/forms/admin.py:90 msgid "All users with enabled newsletter setting" msgstr "Alle Nutzer mit aktiviertem Newsletter" -#: project/forms/admin.py:89 +#: project/forms/admin.py:96 msgid "Message" msgstr "Nachricht" -#: project/forms/admin.py:90 +#: project/forms/admin.py:97 msgid "Send newsletter" msgstr "Newsletter senden" @@ -561,9 +569,8 @@ msgstr "" #, python-format msgid "I read and accept %(privacy_open)sPrivacy%(privacy_close)s." msgstr "" -"Ich habe die " -"%(privacy_open)sDatenschutzerklärung%(privacy_close)s gelesen und " -"akzeptiere diese." +"Ich habe die %(privacy_open)sDatenschutzerklärung%(privacy_close)s " +"gelesen und akzeptiere diese." #: project/forms/common.py:95 msgid "0 (Little relevant)" @@ -1301,6 +1308,10 @@ msgstr "Erlauben" msgid "Deny" msgstr "Ablehnen" +#: project/forms/security.py:79 +msgid "Confirm" +msgstr "Bestätigen" + #: project/forms/user.py:9 project/templates/admin/admin.html:31 #: project/templates/admin/newsletter.html:4 #: project/templates/admin/newsletter.html:93 @@ -1397,7 +1408,7 @@ msgstr "Merkzettel" #: project/templates/_macros.html:590 project/templates/_macros.html:633 #: project/templates/_macros.html:765 #: project/templates/admin/admin_units.html:36 -#: project/templates/admin/users.html:34 +#: project/templates/admin/users.html:36 #: project/templates/manage/events.html:116 #: project/templates/manage/members.html:35 #: project/templates/manage/organizers.html:33 @@ -1785,7 +1796,7 @@ msgid "View" msgstr "Anzeigen" #: project/templates/admin/admin_units.html:37 -#: project/templates/admin/users.html:35 +#: project/templates/admin/users.html:37 #: project/templates/manage/events.html:117 #: project/templates/manage/members.html:21 #: project/templates/manage/members.html:36 @@ -1802,7 +1813,7 @@ msgstr "Löschen" msgid "User" msgstr "Nutzer" -#: project/templates/admin/email.html:47 project/views/admin.py:144 +#: project/templates/admin/email.html:47 project/views/admin.py:165 msgid "Mail sent successfully" msgstr "Mail erfolgreich gesendet" @@ -1818,6 +1829,13 @@ msgstr "Test-Mail asynchron senden" msgid "Mails sent successfully" msgstr "Mails erfolgreich gesendet" +#: project/templates/admin/reset_tos_accepted.html:4 +#: project/templates/admin/reset_tos_accepted.html:8 +#: project/templates/admin/settings.html:28 +#: project/templates/admin/users.html:48 +msgid "Reset acceptance of terms of service and privacy" +msgstr "Akzeptanz der Nutzungsbedingungen und des Datenschutzes zurücksetzen" + #: project/templates/admin_unit/create.html:58 #: project/templates/admin_unit/update.html:66 #: project/templates/event/create.html:347 @@ -2366,6 +2384,10 @@ msgstr "Du hast noch keinen Account? Kein Problem!" msgid "Register for free" msgstr "Kostenlos registrieren" +#: project/templates/user/accept_tos.html:6 +msgid "Confirmation required" +msgstr "Bestätigung erforderlich" + #: project/templates/user/request_deletion.html:8 msgid "" "The account is not deleted immediately. After a period of time, the " @@ -2394,43 +2416,43 @@ msgstr "Optionale Details" msgid "Preview" msgstr "Vorschau" -#: project/views/admin.py:63 +#: project/views/admin.py:64 msgid "Organization successfully updated" msgstr "Organisation erfolgreich aktualisiert" -#: project/views/admin.py:85 project/views/admin_unit.py:187 +#: project/views/admin.py:86 project/views/admin_unit.py:187 #: project/views/admin_unit.py:220 project/views/manage.py:316 msgid "Entered name does not match organization name" msgstr "Der eingegebene Name entspricht nicht dem Namen der Organisation" -#: project/views/admin.py:89 +#: project/views/admin.py:90 msgid "Organization successfully deleted" msgstr "Organisation erfolgreich gelöscht" -#: project/views/admin.py:113 project/views/manage.py:486 -#: project/views/user.py:41 +#: project/views/admin.py:134 project/views/manage.py:486 +#: project/views/user.py:63 msgid "Settings successfully updated" msgstr "Einstellungen erfolgreich aktualisiert" -#: project/views/admin.py:133 +#: project/views/admin.py:154 #, python-format msgid "Test mail from %(site_name)s" msgstr "Test-Mail von %(site_name)s" -#: project/views/admin.py:162 +#: project/views/admin.py:183 #, python-format msgid "Newsletter from %(site_name)s" msgstr "Newsletter von %(site_name)s" -#: project/views/admin.py:212 +#: project/views/admin.py:233 msgid "User successfully updated" msgstr "Nutzer erfolgreich aktualisiert" -#: project/views/admin.py:232 +#: project/views/admin.py:253 msgid "Entered email does not match user email" msgstr "Die eingegebene Email passt nicht zur Email des Nutzers" -#: project/views/admin.py:236 +#: project/views/admin.py:257 msgid "User successfully deleted" msgstr "Nutzer erfolgreich gelöscht" @@ -2660,7 +2682,7 @@ msgstr "" "Ob alle zukünftigen Empfehlungsanfragen von %(admin_unit_name)s " "automatisch verifiziert werden sollen." -#: project/views/user.py:85 +#: project/views/user.py:107 msgid "" "You are administrator of at least one organization. Cancel your " "membership to delete your account." @@ -2668,11 +2690,11 @@ msgstr "" "Du bist Administrator von mindestens einer Organisation. Beende deine " "Mitgliedschaft, um deinen Account zu löschen." -#: project/views/user.py:92 project/views/user.py:119 +#: project/views/user.py:114 project/views/user.py:141 msgid "Entered email does not match your email" msgstr "Die eingegebene Email entspricht nicht deiner Email" -#: project/views/user.py:138 +#: project/views/user.py:160 msgid "User deletion requested" msgstr "Löschung des Nutzers beantragt" diff --git a/project/translations/en/LC_MESSAGES/messages.mo b/project/translations/en/LC_MESSAGES/messages.mo index febbb7c8d20d98be1fc58a2554cead59a5b7973b..846c438995d69af5220e9f067aecae80619d66ff 100644 GIT binary patch delta 21 dcmaDa|6YE>D^3nWLj^-aD?`i8pE&=r0svh22mk;8 delta 21 dcmaDa|6YE>D^3mrO9dk%D\n" "Language: en\n" @@ -230,21 +230,29 @@ msgstr "" msgid "Save" msgstr "" -#: project/forms/admin.py:21 project/forms/admin_unit_member.py:14 +#: project/forms/admin.py:22 +msgid "Reset for all users" +msgstr "" + +#: project/forms/admin.py:24 +msgid "Reset" +msgstr "" + +#: project/forms/admin.py:28 project/forms/admin_unit_member.py:14 #: project/forms/admin_unit_member.py:34 msgid "Roles" msgstr "" -#: project/forms/admin.py:22 project/templates/admin/update_user.html:4 +#: project/forms/admin.py:29 project/templates/admin/update_user.html:4 #: project/templates/admin/update_user.html:8 msgid "Update user" msgstr "" -#: project/forms/admin.py:26 project/templates/admin/delete_user.html:6 +#: project/forms/admin.py:33 project/templates/admin/delete_user.html:6 msgid "Delete user" msgstr "" -#: project/forms/admin.py:27 project/forms/admin_unit.py:53 +#: project/forms/admin.py:34 project/forms/admin_unit.py:53 #: project/forms/admin_unit_member.py:12 project/forms/admin_unit_member.py:25 #: project/forms/admin_unit_member.py:30 project/forms/event.py:107 #: project/forms/event_suggestion.py:38 project/forms/organizer.py:33 @@ -256,60 +264,60 @@ msgstr "" msgid "Email" msgstr "" -#: project/forms/admin.py:32 +#: project/forms/admin.py:39 msgid "Incoming reference requests allowed" msgstr "" -#: project/forms/admin.py:33 +#: project/forms/admin.py:40 msgid "" "If set, other organizations can ask this organization to reference their " "event." msgstr "" -#: project/forms/admin.py:39 +#: project/forms/admin.py:46 msgid "Suggestions enabled" msgstr "" -#: project/forms/admin.py:40 +#: project/forms/admin.py:47 msgid "If set, the organization can work with suggestions." msgstr "" -#: project/forms/admin.py:44 +#: project/forms/admin.py:51 msgid "Create other organizations" msgstr "" -#: project/forms/admin.py:45 +#: project/forms/admin.py:52 msgid "If set, members of the organization can create other organizations." msgstr "" -#: project/forms/admin.py:51 +#: project/forms/admin.py:58 msgid "Invite other organizations" msgstr "" -#: project/forms/admin.py:52 +#: project/forms/admin.py:59 msgid "If set, members of the organization can invite other organizations." msgstr "" -#: project/forms/admin.py:58 +#: project/forms/admin.py:65 msgid "Verify other organizations" msgstr "" -#: project/forms/admin.py:59 +#: project/forms/admin.py:66 msgid "If set, members of the organization can verify other organizations." msgstr "" -#: project/forms/admin.py:64 project/templates/admin/update_admin_unit.html:4 +#: project/forms/admin.py:71 project/templates/admin/update_admin_unit.html:4 #: project/templates/admin/update_admin_unit.html:8 msgid "Update organization" msgstr "" -#: project/forms/admin.py:68 project/templates/admin/delete_admin_unit.html:6 +#: project/forms/admin.py:75 project/templates/admin/delete_admin_unit.html:6 #: project/templates/admin_unit/request_deletion.html:6 #: project/templates/admin_unit/update.html:93 msgid "Delete organization" msgstr "" -#: project/forms/admin.py:69 project/forms/admin_unit.py:34 +#: project/forms/admin.py:76 project/forms/admin_unit.py:34 #: project/forms/admin_unit.py:142 project/forms/admin_unit.py:147 #: project/forms/admin_unit.py:152 project/forms/event.py:85 #: project/forms/event.py:114 project/forms/event_place.py:30 @@ -324,27 +332,27 @@ msgstr "" msgid "Name" msgstr "" -#: project/forms/admin.py:73 project/forms/admin.py:80 +#: project/forms/admin.py:80 project/forms/admin.py:87 msgid "Recipient" msgstr "" -#: project/forms/admin.py:75 +#: project/forms/admin.py:82 msgid "Send test mail synchronously" msgstr "" -#: project/forms/admin.py:82 project/forms/admin.py:88 +#: project/forms/admin.py:89 project/forms/admin.py:95 msgid "Test recipient" msgstr "" -#: project/forms/admin.py:83 +#: project/forms/admin.py:90 msgid "All users with enabled newsletter setting" msgstr "" -#: project/forms/admin.py:89 +#: project/forms/admin.py:96 msgid "Message" msgstr "" -#: project/forms/admin.py:90 +#: project/forms/admin.py:97 msgid "Send newsletter" msgstr "" @@ -1251,6 +1259,10 @@ msgstr "" msgid "Deny" msgstr "" +#: project/forms/security.py:79 +msgid "Confirm" +msgstr "" + #: project/forms/user.py:9 project/templates/admin/admin.html:31 #: project/templates/admin/newsletter.html:4 #: project/templates/admin/newsletter.html:93 @@ -1345,7 +1357,7 @@ msgstr "" #: project/templates/_macros.html:590 project/templates/_macros.html:633 #: project/templates/_macros.html:765 #: project/templates/admin/admin_units.html:36 -#: project/templates/admin/users.html:34 +#: project/templates/admin/users.html:36 #: project/templates/manage/events.html:116 #: project/templates/manage/members.html:35 #: project/templates/manage/organizers.html:33 @@ -1733,7 +1745,7 @@ msgid "View" msgstr "" #: project/templates/admin/admin_units.html:37 -#: project/templates/admin/users.html:35 +#: project/templates/admin/users.html:37 #: project/templates/manage/events.html:117 #: project/templates/manage/members.html:21 #: project/templates/manage/members.html:36 @@ -1750,7 +1762,7 @@ msgstr "" msgid "User" msgstr "" -#: project/templates/admin/email.html:47 project/views/admin.py:144 +#: project/templates/admin/email.html:47 project/views/admin.py:165 msgid "Mail sent successfully" msgstr "" @@ -1766,6 +1778,13 @@ msgstr "" msgid "Mails sent successfully" msgstr "" +#: project/templates/admin/reset_tos_accepted.html:4 +#: project/templates/admin/reset_tos_accepted.html:8 +#: project/templates/admin/settings.html:28 +#: project/templates/admin/users.html:48 +msgid "Reset acceptance of terms of service and privacy" +msgstr "" + #: project/templates/admin_unit/create.html:58 #: project/templates/admin_unit/update.html:66 #: project/templates/event/create.html:347 @@ -2301,6 +2320,10 @@ msgstr "" msgid "Register for free" msgstr "" +#: project/templates/user/accept_tos.html:6 +msgid "Confirmation required" +msgstr "" + #: project/templates/user/request_deletion.html:8 msgid "" "The account is not deleted immediately. After a period of time, the " @@ -2327,43 +2350,43 @@ msgstr "" msgid "Preview" msgstr "" -#: project/views/admin.py:63 +#: project/views/admin.py:64 msgid "Organization successfully updated" msgstr "" -#: project/views/admin.py:85 project/views/admin_unit.py:187 +#: project/views/admin.py:86 project/views/admin_unit.py:187 #: project/views/admin_unit.py:220 project/views/manage.py:316 msgid "Entered name does not match organization name" msgstr "" -#: project/views/admin.py:89 +#: project/views/admin.py:90 msgid "Organization successfully deleted" msgstr "" -#: project/views/admin.py:113 project/views/manage.py:486 -#: project/views/user.py:41 +#: project/views/admin.py:134 project/views/manage.py:486 +#: project/views/user.py:63 msgid "Settings successfully updated" msgstr "" -#: project/views/admin.py:133 +#: project/views/admin.py:154 #, python-format msgid "Test mail from %(site_name)s" msgstr "" -#: project/views/admin.py:162 +#: project/views/admin.py:183 #, python-format msgid "Newsletter from %(site_name)s" msgstr "" -#: project/views/admin.py:212 +#: project/views/admin.py:233 msgid "User successfully updated" msgstr "" -#: project/views/admin.py:232 +#: project/views/admin.py:253 msgid "Entered email does not match user email" msgstr "" -#: project/views/admin.py:236 +#: project/views/admin.py:257 msgid "User successfully deleted" msgstr "" @@ -2584,17 +2607,17 @@ msgid "" "verified automatically." msgstr "" -#: project/views/user.py:85 +#: project/views/user.py:107 msgid "" "You are administrator of at least one organization. Cancel your " "membership to delete your account." msgstr "" -#: project/views/user.py:92 project/views/user.py:119 +#: project/views/user.py:114 project/views/user.py:141 msgid "Entered email does not match your email" msgstr "" -#: project/views/user.py:138 +#: project/views/user.py:160 msgid "User deletion requested" msgstr "" diff --git a/project/views/admin.py b/project/views/admin.py index fde5905..f8afdb3 100644 --- a/project/views/admin.py +++ b/project/views/admin.py @@ -13,6 +13,7 @@ from project.forms.admin import ( AdminTestEmailForm, DeleteAdminUnitForm, DeleteUserForm, + ResetTosAceptedForm, UpdateAdminUnitForm, UpdateUserForm, ) @@ -99,6 +100,26 @@ def admin_admin_unit_delete(id): ) +@app.route("/admin/reset-tos-accepted", methods=("GET", "POST")) +@roles_required("admin") +def admin_reset_tos_accepted(): + from project.services.admin import reset_tos_accepted_for_users + + form = ResetTosAceptedForm() + + if form.validate_on_submit(): + try: + reset_tos_accepted_for_users() + return redirect(url_for("admin")) + except SQLAlchemyError as e: # pragma: no cover + db.session.rollback() + flash(handleSqlError(e), "danger") + else: + flash_errors(form) + + return render_template("admin/reset_tos_accepted.html", form=form) + + @app.route("/admin/settings", methods=("GET", "POST")) @roles_required("admin") def admin_settings(): diff --git a/project/views/user.py b/project/views/user.py index bd82a2a..2041ffb 100644 --- a/project/views/user.py +++ b/project/views/user.py @@ -3,16 +3,18 @@ import datetime from flask import flash, redirect, render_template, url_for from flask_babel import gettext from flask_security import auth_required, current_user +from flask_security.utils import get_post_login_redirect from sqlalchemy.exc import SQLAlchemyError from project import app, db +from project.forms.security import AcceptTosForm from project.forms.user import ( CancelUserDeletionForm, NotificationForm, RequestUserDeletionForm, ) from project.models import AdminUnitInvitation, User -from project.services.user import is_user_admin_member +from project.services.user import is_user_admin_member, set_user_accepted_tos from project.views.utils import ( flash_errors, get_invitation_access_result, @@ -28,6 +30,26 @@ def profile(): return render_template("profile.html") +@app.route("/user/accept-tos", methods=("GET", "POST")) +@auth_required() +def user_accept_tos(): + form = AcceptTosForm() + + if current_user.tos_accepted_at: # pragma: no cover + return redirect(get_post_login_redirect()) + + if form.validate_on_submit(): + try: + set_user_accepted_tos(current_user) + db.session.commit() + return redirect(get_post_login_redirect()) + except SQLAlchemyError as e: # pragma: no cover + db.session.rollback() + flash(handleSqlError(e), "danger") + + return render_template("user/accept_tos.html", form=form) + + @app.route("/user/notifications", methods=("GET", "POST")) @auth_required() def user_notifications(): diff --git a/tests/api/test_event.py b/tests/api/test_event.py index 6af16c4..7a380c6 100644 --- a/tests/api/test_event.py +++ b/tests/api/test_event.py @@ -2,6 +2,8 @@ import base64 import pytest +from tests.seeder import Seeder + def test_read(client, app, db, seeder, utils): user_id, admin_unit_id = seeder.setup_base() @@ -412,7 +414,7 @@ def test_put_invalidDateFormat(client, seeder, utils, app): utils.assert_response_unprocessable_entity(response) -def test_put_startAfterEnd(client, seeder, utils, app): +def test_put_startAfterEnd(client, seeder: Seeder, utils, app): user_id, admin_unit_id = seeder.setup_api_access() event_id = seeder.create_event(admin_unit_id) place_id = seeder.upsert_default_event_place(admin_unit_id) diff --git a/tests/seeder.py b/tests/seeder.py index f599a28..586c7b5 100644 --- a/tests/seeder.py +++ b/tests/seeder.py @@ -34,6 +34,7 @@ class Seeder(object): password="MeinPasswortIstDasBeste", admin=False, confirm=True, + tos_accepted=True, ): from flask_security.confirmable import confirm_user @@ -41,6 +42,7 @@ class Seeder(object): add_admin_roles_to_user, create_user, find_user_by_email, + set_user_accepted_tos, ) with self._app.app_context(): @@ -52,6 +54,9 @@ class Seeder(object): if confirm: confirm_user(user) + if tos_accepted: + set_user_accepted_tos(user) + if admin: add_admin_roles_to_user(email) diff --git a/tests/views/test_admin.py b/tests/views/test_admin.py index 29c48f1..528e3e0 100644 --- a/tests/views/test_admin.py +++ b/tests/views/test_admin.py @@ -1,5 +1,8 @@ import pytest +from tests.seeder import Seeder +from tests.utils import UtilActions + def test_normal_user(client, seeder, utils): seeder.create_user() @@ -285,3 +288,23 @@ def test_admin_unit_delete(client, seeder, utils, app, db, mocker, db_error, non admin_unit = db.session.get(AdminUnit, admin_unit_id) assert admin_unit is None + + +def test_admin_reset_tos_accepted(client, app, db, seeder: Seeder, utils: UtilActions): + seeder.setup_base(admin=True) + + response = utils.get_endpoint_ok("admin_reset_tos_accepted") + response = utils.post_form( + response.request.url, + response, + { + "reset_for_users": "y", + "submit": "Reset", + }, + ) + utils.assert_response_redirect(response, "admin") + + with app.app_context(): + from project.models.user import User + + assert len(User.query.filter(User.tos_accepted_at.isnot(None)).all()) == 0 diff --git a/tests/views/test_manage.py b/tests/views/test_manage.py index 37c480d..2cf9016 100644 --- a/tests/views/test_manage.py +++ b/tests/views/test_manage.py @@ -4,7 +4,7 @@ from tests.seeder import Seeder from tests.utils import UtilActions -def test_index_noCookie(client, seeder, utils): +def test_index_noCookie(client, seeder: Seeder, utils): user_id, admin_unit_id = seeder.setup_base() response = utils.get_endpoint("manage") diff --git a/tests/views/test_user.py b/tests/views/test_user.py index e2d0fae..93092ca 100644 --- a/tests/views/test_user.py +++ b/tests/views/test_user.py @@ -1,6 +1,7 @@ import pytest from tests.seeder import Seeder +from tests.utils import UtilActions def test_profile(client, seeder, utils): @@ -245,3 +246,28 @@ def test_user_cancel_deletion( user = db.session.get(User, user_id) assert user.deletion_requested_at is None + + +def test_user_accept_tos(client, app, db, seeder: Seeder, utils: UtilActions): + seeder.setup_base() + + with app.app_context(): + from project.services.admin import reset_tos_accepted_for_users + + reset_tos_accepted_for_users() + + response = utils.get_endpoint("profile") + utils.assert_response_redirect( + response, "user_accept_tos", next="http://localhost/profile" + ) + + response = utils.get_endpoint_ok("user_accept_tos", next="/profile") + response = utils.post_form( + response.request.url, + response, + { + "accept_tos": "y", + "submit": "Confirm", + }, + ) + utils.assert_response_redirect(response, "profile") From 66a3a60558b9189c6cee4bdc869b9d9a19e86c8d Mon Sep 17 00:00:00 2001 From: Daniel Grams Date: Thu, 11 May 2023 14:53:37 +0200 Subject: [PATCH 2/2] User.tos_accepted_at #473 --- cypress/support/commands.js | 4 ++-- project/cli/user.py | 7 ++++++- tests/cli/test_user.py | 11 ++++++++++- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/cypress/support/commands.js b/cypress/support/commands.js index 51e20df..5079958 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -13,13 +13,13 @@ Cypress.Commands.add("logexec", (command) => { Cypress.Commands.add("setup", () => { cy.logexec("flask test reset --seed"); - cy.logexec("flask user create test@test.de password --confirm"); + cy.logexec("flask user create test@test.de password --confirm --accept-tos"); }); Cypress.Commands.add( "createUser", (email = "test@test.de", password = "password", admin = false) => { - let cmd = 'flask user create "' + email + '" "' + password + '" --confirm'; + let cmd = 'flask user create "' + email + '" "' + password + '" --confirm --accept-tos'; if (admin) { cmd += " --admin"; } diff --git a/project/cli/user.py b/project/cli/user.py index 9f8a046..a0a8cfb 100644 --- a/project/cli/user.py +++ b/project/cli/user.py @@ -9,6 +9,7 @@ from project.services.user import ( add_admin_roles_to_user, create_user, find_user_by_email, + set_user_accepted_tos, ) user_cli = AppGroup("user") @@ -27,7 +28,8 @@ def add_admin_roles(email): @click.argument("password") @click.option("--confirm/--no-confirm", default=False) @click.option("--admin/--no-admin", default=False) -def create(email, password, confirm, admin): +@click.option("--accept-tos/--no-accept-tos", default=False) +def create(email, password, confirm, admin, accept_tos): user = create_user(email, password) if confirm: @@ -36,6 +38,9 @@ def create(email, password, confirm, admin): if admin: add_admin_roles_to_user(email) + if accept_tos: + set_user_accepted_tos(user) + db.session.commit() result = {"user_id": user.id} click.echo(json.dumps(result)) diff --git a/tests/cli/test_user.py b/tests/cli/test_user.py index 23d4f43..0df7b1a 100644 --- a/tests/cli/test_user.py +++ b/tests/cli/test_user.py @@ -37,7 +37,15 @@ def test_create(client, seeder, app): runner = app.test_cli_runner() result = runner.invoke( - args=["user", "create", "test@test.de", "password", "--confirm", "--admin"] + args=[ + "user", + "create", + "test@test.de", + "password", + "--confirm", + "--admin", + "--accept-tos", + ] ) assert "user_id" in result.output @@ -46,6 +54,7 @@ def test_create(client, seeder, app): user = find_user_by_email("test@test.de") assert user.confirmed_at is not None + assert user.tos_accepted_at is not None def test_confirm(client, seeder, app):