Merge pull request #474 from eventcally/issues/473

User.tos_accepted_at #473
This commit is contained in:
Daniel Grams 2023-05-11 15:07:55 +02:00 committed by GitHub
commit b874550a64
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 464 additions and 140 deletions

View File

@ -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";
}

View File

@ -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 <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\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 ""

View File

@ -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")

View File

@ -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()

View File

@ -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

View File

@ -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))

View File

@ -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"))

View File

@ -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()

View File

@ -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))

View File

@ -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)

View File

@ -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()

View File

@ -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()

View File

@ -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 %}
<h1>{{ _('Reset acceptance of terms of service and privacy') }}</h1>
<form action="" method="POST">
{{ form.hidden_tag() }}
{{ render_field_with_errors(form.reset_for_users, ri="checkbox") }}
{{ render_field(form.submit) }}
</form>
{% endblock %}

View File

@ -24,4 +24,8 @@
{{ render_field(form.submit) }}
</form>
<div class="my-4">
<a class="btn btn-outline-danger m-1" href="{{ url_for('admin_reset_tos_accepted') }}" role="button">{{ _('Reset acceptance of terms of service and privacy') }}&hellip;</a>
</div>
{% endblock %}

View File

@ -19,6 +19,7 @@
<th>{{ _('Email') }}</th>
<th>created_at</th>
<th>confirmed_at</th>
<th>tos_accepted_at</th>
<th>deletion_requested_at</th>
<th></th>
</tr>
@ -29,6 +30,7 @@
<td>{{ user.email }}</td>
<td>{% if user.created_at %}{{ user.created_at | dateformat }}{% endif %}</td>
<td>{% if user.confirmed_at %}{{ user.confirmed_at | dateformat }}{% endif %}</td>
<td>{% if user.tos_accepted_at %}{{ user.tos_accepted_at | dateformat }}{% endif %}</td>
<td>{% if user.deletion_requested_at %}{{ user.deletion_requested_at | dateformat }}{% endif %}</td>
<td>
<a href="{{ url_for('admin_user_update', id=user.id) }}">{{ _('Edit') }}</a>
@ -42,4 +44,9 @@
<div class="my-4">{{ render_pagination(pagination) }}</div>
<div class="my-4">
<a class="btn btn-outline-danger m-1" href="{{ url_for('admin_reset_tos_accepted') }}" role="button">{{ _('Reset acceptance of terms of service and privacy') }}&hellip;</a>
</div>
{% endblock %}

View File

@ -0,0 +1,22 @@
{% extends "layout.html" %}
{% from "_macros.html" import render_field %}
{% block content %}
<h1>{{ _('Confirmation required') }}</h1>
<div>
<form action="" method="POST">
{{ form.hidden_tag() }}
<div class="form-group form-check">
<div class="input-group">
{{ form.accept_tos(class="form-check-input")|safe }}
{{ form.accept_tos.label(class="form-check-label") }}
</div>
</div>
{{ render_field(form.submit) }}
</form>
</div>
{% endblock %}

View File

@ -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 <EMAIL@ADDRESS>\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"

View File

@ -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: 2021-04-30 15:04+0200\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\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 ""

View File

@ -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():

View File

@ -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():

View File

@ -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)

View File

@ -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):

View File

@ -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)

View File

@ -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

View File

@ -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")

View File

@ -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")