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", () => { Cypress.Commands.add("setup", () => {
cy.logexec("flask test reset --seed"); 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( Cypress.Commands.add(
"createUser", "createUser",
(email = "test@test.de", password = "password", admin = false) => { (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) { if (admin) {
cmd += " --admin"; cmd += " --admin";
} }

View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PROJECT VERSION\n" "Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\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" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -229,21 +229,29 @@ msgstr ""
msgid "Save" msgid "Save"
msgstr "" 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 #: project/forms/admin_unit_member.py:34
msgid "Roles" msgid "Roles"
msgstr "" 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 #: project/templates/admin/update_user.html:8
msgid "Update user" msgid "Update user"
msgstr "" 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" msgid "Delete user"
msgstr "" 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:12 project/forms/admin_unit_member.py:25
#: project/forms/admin_unit_member.py:30 project/forms/event.py:107 #: project/forms/admin_unit_member.py:30 project/forms/event.py:107
#: project/forms/event_suggestion.py:38 project/forms/organizer.py:33 #: project/forms/event_suggestion.py:38 project/forms/organizer.py:33
@ -255,60 +263,60 @@ msgstr ""
msgid "Email" msgid "Email"
msgstr "" msgstr ""
#: project/forms/admin.py:32 #: project/forms/admin.py:39
msgid "Incoming reference requests allowed" msgid "Incoming reference requests allowed"
msgstr "" msgstr ""
#: project/forms/admin.py:33 #: project/forms/admin.py:40
msgid "" msgid ""
"If set, other organizations can ask this organization to reference their " "If set, other organizations can ask this organization to reference their "
"event." "event."
msgstr "" msgstr ""
#: project/forms/admin.py:39 #: project/forms/admin.py:46
msgid "Suggestions enabled" msgid "Suggestions enabled"
msgstr "" msgstr ""
#: project/forms/admin.py:40 #: project/forms/admin.py:47
msgid "If set, the organization can work with suggestions." msgid "If set, the organization can work with suggestions."
msgstr "" msgstr ""
#: project/forms/admin.py:44 #: project/forms/admin.py:51
msgid "Create other organizations" msgid "Create other organizations"
msgstr "" msgstr ""
#: project/forms/admin.py:45 #: project/forms/admin.py:52
msgid "If set, members of the organization can create other organizations." msgid "If set, members of the organization can create other organizations."
msgstr "" msgstr ""
#: project/forms/admin.py:51 #: project/forms/admin.py:58
msgid "Invite other organizations" msgid "Invite other organizations"
msgstr "" msgstr ""
#: project/forms/admin.py:52 #: project/forms/admin.py:59
msgid "If set, members of the organization can invite other organizations." msgid "If set, members of the organization can invite other organizations."
msgstr "" msgstr ""
#: project/forms/admin.py:58 #: project/forms/admin.py:65
msgid "Verify other organizations" msgid "Verify other organizations"
msgstr "" msgstr ""
#: project/forms/admin.py:59 #: project/forms/admin.py:66
msgid "If set, members of the organization can verify other organizations." msgid "If set, members of the organization can verify other organizations."
msgstr "" 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 #: project/templates/admin/update_admin_unit.html:8
msgid "Update organization" msgid "Update organization"
msgstr "" 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/request_deletion.html:6
#: project/templates/admin_unit/update.html:93 #: project/templates/admin_unit/update.html:93
msgid "Delete organization" msgid "Delete organization"
msgstr "" 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:142 project/forms/admin_unit.py:147
#: project/forms/admin_unit.py:152 project/forms/event.py:85 #: project/forms/admin_unit.py:152 project/forms/event.py:85
#: project/forms/event.py:114 project/forms/event_place.py:30 #: project/forms/event.py:114 project/forms/event_place.py:30
@ -323,27 +331,27 @@ msgstr ""
msgid "Name" msgid "Name"
msgstr "" msgstr ""
#: project/forms/admin.py:73 project/forms/admin.py:80 #: project/forms/admin.py:80 project/forms/admin.py:87
msgid "Recipient" msgid "Recipient"
msgstr "" msgstr ""
#: project/forms/admin.py:75 #: project/forms/admin.py:82
msgid "Send test mail synchronously" msgid "Send test mail synchronously"
msgstr "" msgstr ""
#: project/forms/admin.py:82 project/forms/admin.py:88 #: project/forms/admin.py:89 project/forms/admin.py:95
msgid "Test recipient" msgid "Test recipient"
msgstr "" msgstr ""
#: project/forms/admin.py:83 #: project/forms/admin.py:90
msgid "All users with enabled newsletter setting" msgid "All users with enabled newsletter setting"
msgstr "" msgstr ""
#: project/forms/admin.py:89 #: project/forms/admin.py:96
msgid "Message" msgid "Message"
msgstr "" msgstr ""
#: project/forms/admin.py:90 #: project/forms/admin.py:97
msgid "Send newsletter" msgid "Send newsletter"
msgstr "" msgstr ""
@ -1243,6 +1251,10 @@ msgstr ""
msgid "Deny" msgid "Deny"
msgstr "" msgstr ""
#: project/forms/security.py:79
msgid "Confirm"
msgstr ""
#: project/forms/user.py:9 project/templates/admin/admin.html:31 #: project/forms/user.py:9 project/templates/admin/admin.html:31
#: project/templates/admin/newsletter.html:4 #: project/templates/admin/newsletter.html:4
#: project/templates/admin/newsletter.html:93 #: 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:590 project/templates/_macros.html:633
#: project/templates/_macros.html:765 #: project/templates/_macros.html:765
#: project/templates/admin/admin_units.html:36 #: 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/events.html:116
#: project/templates/manage/members.html:35 #: project/templates/manage/members.html:35
#: project/templates/manage/organizers.html:33 #: project/templates/manage/organizers.html:33
@ -1725,7 +1737,7 @@ msgid "View"
msgstr "" msgstr ""
#: project/templates/admin/admin_units.html:37 #: 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/events.html:117
#: project/templates/manage/members.html:21 #: project/templates/manage/members.html:21
#: project/templates/manage/members.html:36 #: project/templates/manage/members.html:36
@ -1742,7 +1754,7 @@ msgstr ""
msgid "User" msgid "User"
msgstr "" 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" msgid "Mail sent successfully"
msgstr "" msgstr ""
@ -1758,6 +1770,13 @@ msgstr ""
msgid "Mails sent successfully" msgid "Mails sent successfully"
msgstr "" 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/create.html:58
#: project/templates/admin_unit/update.html:66 #: project/templates/admin_unit/update.html:66
#: project/templates/event/create.html:347 #: project/templates/event/create.html:347
@ -2293,6 +2312,10 @@ msgstr ""
msgid "Register for free" msgid "Register for free"
msgstr "" msgstr ""
#: project/templates/user/accept_tos.html:6
msgid "Confirmation required"
msgstr ""
#: project/templates/user/request_deletion.html:8 #: project/templates/user/request_deletion.html:8
msgid "" msgid ""
"The account is not deleted immediately. After a period of time, the " "The account is not deleted immediately. After a period of time, the "
@ -2319,43 +2342,43 @@ msgstr ""
msgid "Preview" msgid "Preview"
msgstr "" msgstr ""
#: project/views/admin.py:63 #: project/views/admin.py:64
msgid "Organization successfully updated" msgid "Organization successfully updated"
msgstr "" 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 #: project/views/admin_unit.py:220 project/views/manage.py:316
msgid "Entered name does not match organization name" msgid "Entered name does not match organization name"
msgstr "" msgstr ""
#: project/views/admin.py:89 #: project/views/admin.py:90
msgid "Organization successfully deleted" msgid "Organization successfully deleted"
msgstr "" msgstr ""
#: project/views/admin.py:113 project/views/manage.py:486 #: project/views/admin.py:134 project/views/manage.py:486
#: project/views/user.py:41 #: project/views/user.py:63
msgid "Settings successfully updated" msgid "Settings successfully updated"
msgstr "" msgstr ""
#: project/views/admin.py:133 #: project/views/admin.py:154
#, python-format #, python-format
msgid "Test mail from %(site_name)s" msgid "Test mail from %(site_name)s"
msgstr "" msgstr ""
#: project/views/admin.py:162 #: project/views/admin.py:183
#, python-format #, python-format
msgid "Newsletter from %(site_name)s" msgid "Newsletter from %(site_name)s"
msgstr "" msgstr ""
#: project/views/admin.py:212 #: project/views/admin.py:233
msgid "User successfully updated" msgid "User successfully updated"
msgstr "" msgstr ""
#: project/views/admin.py:232 #: project/views/admin.py:253
msgid "Entered email does not match user email" msgid "Entered email does not match user email"
msgstr "" msgstr ""
#: project/views/admin.py:236 #: project/views/admin.py:257
msgid "User successfully deleted" msgid "User successfully deleted"
msgstr "" msgstr ""
@ -2576,17 +2599,17 @@ msgid ""
"verified automatically." "verified automatically."
msgstr "" msgstr ""
#: project/views/user.py:85 #: project/views/user.py:107
msgid "" msgid ""
"You are administrator of at least one organization. Cancel your " "You are administrator of at least one organization. Cancel your "
"membership to delete your account." "membership to delete your account."
msgstr "" 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" msgid "Entered email does not match your email"
msgstr "" msgstr ""
#: project/views/user.py:138 #: project/views/user.py:160
msgid "User deletion requested" msgid "User deletion requested"
msgstr "" 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 logging
import os import os
from datetime import timedelta from datetime import datetime, timedelta
from flask import Flask from flask import Flask
from flask_babel import Babel from flask_babel import Babel
@ -9,7 +9,7 @@ from flask_gzip import Gzip
from flask_mail import Mail, email_dispatched from flask_mail import Mail, email_dispatched
from flask_migrate import Migrate from flask_migrate import Migrate
from flask_qrcode import QRcode 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_sqlalchemy import SQLAlchemy
from flask_wtf.csrf import CSRFProtect from flask_wtf.csrf import CSRFProtect
from sqlalchemy import MetaData from sqlalchemy import MetaData
@ -228,6 +228,16 @@ security = Security(
) )
app.session_interface = CustomSessionInterface() 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 # OAuth2
from project.oauth2 import config_oauth from project.oauth2 import config_oauth
@ -276,9 +286,9 @@ from project.views import (
reference_request, reference_request,
reference_request_review, reference_request_review,
root, root,
user,
widget,
) )
from project.views import user as user_view
from project.views import widget
if __name__ == "__main__": # pragma: no cover if __name__ == "__main__": # pragma: no cover
app.run() 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.oauth2_client import complete_oauth2_client
from project.services.organizer import get_event_organizer, upsert_event_organizer 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.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") test_cli = AppGroup("test")
@ -61,13 +66,19 @@ def _get_default_organizer_id(admin_unit_id):
def _create_user( 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) user = create_user(email, password)
if confirm: if confirm:
confirm_user(user) confirm_user(user)
if tos_accepted:
set_user_accepted_tos(user)
db.session.commit() db.session.commit()
return user.id return user.id

View File

@ -9,6 +9,7 @@ from project.services.user import (
add_admin_roles_to_user, add_admin_roles_to_user,
create_user, create_user,
find_user_by_email, find_user_by_email,
set_user_accepted_tos,
) )
user_cli = AppGroup("user") user_cli = AppGroup("user")
@ -27,7 +28,8 @@ def add_admin_roles(email):
@click.argument("password") @click.argument("password")
@click.option("--confirm/--no-confirm", default=False) @click.option("--confirm/--no-confirm", default=False)
@click.option("--admin/--no-admin", 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) user = create_user(email, password)
if confirm: if confirm:
@ -36,6 +38,9 @@ def create(email, password, confirm, admin):
if admin: if admin:
add_admin_roles_to_user(email) add_admin_roles_to_user(email)
if accept_tos:
set_user_accepted_tos(user)
db.session.commit() db.session.commit()
result = {"user_id": user.id} result = {"user_id": user.id}
click.echo(json.dumps(result)) click.echo(json.dumps(result))

View File

@ -17,6 +17,13 @@ class AdminSettingsForm(FlaskForm):
submit = SubmitField(lazy_gettext("Save")) 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): class UpdateUserForm(FlaskForm):
roles = MultiCheckboxField(lazy_gettext("Roles")) roles = MultiCheckboxField(lazy_gettext("Roles"))
submit = SubmitField(lazy_gettext("Update user")) submit = SubmitField(lazy_gettext("Update user"))

View File

@ -72,3 +72,12 @@ class ExtendedForgotPasswordForm(ForgotPasswordForm):
class AuthorizeForm(FlaskForm): class AuthorizeForm(FlaskForm):
allow = SubmitField(lazy_gettext("Allow")) allow = SubmitField(lazy_gettext("Allow"))
deny = SubmitField(lazy_gettext("Deny")) 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", 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)) created_at = deferred(Column(DateTime, default=datetime.datetime.utcnow))
deletion_requested_at = deferred(Column(DateTime, nullable=True)) deletion_requested_at = deferred(Column(DateTime, nullable=True))

View File

@ -1,11 +1,29 @@
from datetime import datetime, timedelta 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_login.utils import encode_cookie
from flask_security import current_user
from project import app 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 @app.after_request
def set_manage_admin_unit_cookie(response): def set_manage_admin_unit_cookie(response):
admin_unit = getattr(g, "manage_admin_unit", None) 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 import db
from project.models import Settings from project.models import Settings, User
def upsert_settings(): def upsert_settings():
@ -17,3 +17,8 @@ def has_tos():
return db.session.scalar( return db.session.scalar(
exists().where(func.coalesce(Settings.tos, "") != "").select() 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() ).first()
is not None 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) }} {{ render_field(form.submit) }}
</form> </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 %} {% endblock %}

View File

@ -19,6 +19,7 @@
<th>{{ _('Email') }}</th> <th>{{ _('Email') }}</th>
<th>created_at</th> <th>created_at</th>
<th>confirmed_at</th> <th>confirmed_at</th>
<th>tos_accepted_at</th>
<th>deletion_requested_at</th> <th>deletion_requested_at</th>
<th></th> <th></th>
</tr> </tr>
@ -29,6 +30,7 @@
<td>{{ user.email }}</td> <td>{{ user.email }}</td>
<td>{% if user.created_at %}{{ user.created_at | dateformat }}{% endif %}</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.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>{% if user.deletion_requested_at %}{{ user.deletion_requested_at | dateformat }}{% endif %}</td>
<td> <td>
<a href="{{ url_for('admin_user_update', id=user.id) }}">{{ _('Edit') }}</a> <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">{{ 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 %} {% 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 "" msgstr ""
"Project-Id-Version: PROJECT VERSION\n" "Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\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" "PO-Revision-Date: 2020-06-07 18:51+0200\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: de\n" "Language: de\n"
@ -230,21 +230,29 @@ msgstr "Startseite"
msgid "Save" msgid "Save"
msgstr "Speichern" 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 #: project/forms/admin_unit_member.py:34
msgid "Roles" msgid "Roles"
msgstr "Rollen" 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 #: project/templates/admin/update_user.html:8
msgid "Update user" msgid "Update user"
msgstr "Nutzer aktualisieren" 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" msgid "Delete user"
msgstr "Nutzer löschen" 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:12 project/forms/admin_unit_member.py:25
#: project/forms/admin_unit_member.py:30 project/forms/event.py:107 #: project/forms/admin_unit_member.py:30 project/forms/event.py:107
#: project/forms/event_suggestion.py:38 project/forms/organizer.py:33 #: project/forms/event_suggestion.py:38 project/forms/organizer.py:33
@ -256,11 +264,11 @@ msgstr "Nutzer löschen"
msgid "Email" msgid "Email"
msgstr "Email" msgstr "Email"
#: project/forms/admin.py:32 #: project/forms/admin.py:39
msgid "Incoming reference requests allowed" msgid "Incoming reference requests allowed"
msgstr "Eingehende Empfehlungsanfragen erlauben" msgstr "Eingehende Empfehlungsanfragen erlauben"
#: project/forms/admin.py:33 #: project/forms/admin.py:40
msgid "" msgid ""
"If set, other organizations can ask this organization to reference their " "If set, other organizations can ask this organization to reference their "
"event." "event."
@ -268,56 +276,56 @@ msgstr ""
"Wenn gesetzt, können andere Organisationen diese Organisation bitten, " "Wenn gesetzt, können andere Organisationen diese Organisation bitten, "
"deren Veranstaltungen zu empfehlen." "deren Veranstaltungen zu empfehlen."
#: project/forms/admin.py:39 #: project/forms/admin.py:46
msgid "Suggestions enabled" msgid "Suggestions enabled"
msgstr "Vorschläge aktiv" msgstr "Vorschläge aktiv"
#: project/forms/admin.py:40 #: project/forms/admin.py:47
msgid "If set, the organization can work with suggestions." msgid "If set, the organization can work with suggestions."
msgstr "Wenn gesetzt, kann die Organisation mit Vorschlägen arbeiten." msgstr "Wenn gesetzt, kann die Organisation mit Vorschlägen arbeiten."
#: project/forms/admin.py:44 #: project/forms/admin.py:51
msgid "Create other organizations" msgid "Create other organizations"
msgstr "Andere Organisationen erstellen" 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." msgid "If set, members of the organization can create other organizations."
msgstr "" msgstr ""
"Wenn gesetzt, können Mitglieder der Organisation andere Organisationen " "Wenn gesetzt, können Mitglieder der Organisation andere Organisationen "
"erstellen." "erstellen."
#: project/forms/admin.py:51 #: project/forms/admin.py:58
msgid "Invite other organizations" msgid "Invite other organizations"
msgstr "Andere Organisationen einladen" 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." msgid "If set, members of the organization can invite other organizations."
msgstr "" msgstr ""
"Wenn gesetzt, können Mitglieder der Organisation andere Organisationen " "Wenn gesetzt, können Mitglieder der Organisation andere Organisationen "
"einladen." "einladen."
#: project/forms/admin.py:58 #: project/forms/admin.py:65
msgid "Verify other organizations" msgid "Verify other organizations"
msgstr "Andere Organisationen verifizieren" 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." msgid "If set, members of the organization can verify other organizations."
msgstr "" msgstr ""
"Wenn gesetzt, können Mitglieder der Organisation andere Organisationen " "Wenn gesetzt, können Mitglieder der Organisation andere Organisationen "
"verifizieren." "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 #: project/templates/admin/update_admin_unit.html:8
msgid "Update organization" msgid "Update organization"
msgstr "Organisation aktualisieren" 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/request_deletion.html:6
#: project/templates/admin_unit/update.html:93 #: project/templates/admin_unit/update.html:93
msgid "Delete organization" msgid "Delete organization"
msgstr "Organisation löschen" 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:142 project/forms/admin_unit.py:147
#: project/forms/admin_unit.py:152 project/forms/event.py:85 #: project/forms/admin_unit.py:152 project/forms/event.py:85
#: project/forms/event.py:114 project/forms/event_place.py:30 #: project/forms/event.py:114 project/forms/event_place.py:30
@ -332,27 +340,27 @@ msgstr "Organisation löschen"
msgid "Name" msgid "Name"
msgstr "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" msgid "Recipient"
msgstr "Empfänger" msgstr "Empfänger"
#: project/forms/admin.py:75 #: project/forms/admin.py:82
msgid "Send test mail synchronously" msgid "Send test mail synchronously"
msgstr "Test-Mail synchron senden" 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" msgid "Test recipient"
msgstr "Test-Empfänger" msgstr "Test-Empfänger"
#: project/forms/admin.py:83 #: project/forms/admin.py:90
msgid "All users with enabled newsletter setting" msgid "All users with enabled newsletter setting"
msgstr "Alle Nutzer mit aktiviertem Newsletter" msgstr "Alle Nutzer mit aktiviertem Newsletter"
#: project/forms/admin.py:89 #: project/forms/admin.py:96
msgid "Message" msgid "Message"
msgstr "Nachricht" msgstr "Nachricht"
#: project/forms/admin.py:90 #: project/forms/admin.py:97
msgid "Send newsletter" msgid "Send newsletter"
msgstr "Newsletter senden" msgstr "Newsletter senden"
@ -561,9 +569,8 @@ msgstr ""
#, python-format #, python-format
msgid "I read and accept %(privacy_open)sPrivacy%(privacy_close)s." msgid "I read and accept %(privacy_open)sPrivacy%(privacy_close)s."
msgstr "" msgstr ""
"Ich habe die " "Ich habe die %(privacy_open)sDatenschutzerklärung%(privacy_close)s "
"%(privacy_open)sDatenschutzerklärung%(privacy_close)s gelesen und " "gelesen und akzeptiere diese."
"akzeptiere diese."
#: project/forms/common.py:95 #: project/forms/common.py:95
msgid "0 (Little relevant)" msgid "0 (Little relevant)"
@ -1301,6 +1308,10 @@ msgstr "Erlauben"
msgid "Deny" msgid "Deny"
msgstr "Ablehnen" msgstr "Ablehnen"
#: project/forms/security.py:79
msgid "Confirm"
msgstr "Bestätigen"
#: project/forms/user.py:9 project/templates/admin/admin.html:31 #: project/forms/user.py:9 project/templates/admin/admin.html:31
#: project/templates/admin/newsletter.html:4 #: project/templates/admin/newsletter.html:4
#: project/templates/admin/newsletter.html:93 #: 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:590 project/templates/_macros.html:633
#: project/templates/_macros.html:765 #: project/templates/_macros.html:765
#: project/templates/admin/admin_units.html:36 #: 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/events.html:116
#: project/templates/manage/members.html:35 #: project/templates/manage/members.html:35
#: project/templates/manage/organizers.html:33 #: project/templates/manage/organizers.html:33
@ -1785,7 +1796,7 @@ msgid "View"
msgstr "Anzeigen" msgstr "Anzeigen"
#: project/templates/admin/admin_units.html:37 #: 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/events.html:117
#: project/templates/manage/members.html:21 #: project/templates/manage/members.html:21
#: project/templates/manage/members.html:36 #: project/templates/manage/members.html:36
@ -1802,7 +1813,7 @@ msgstr "Löschen"
msgid "User" msgid "User"
msgstr "Nutzer" 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" msgid "Mail sent successfully"
msgstr "Mail erfolgreich gesendet" msgstr "Mail erfolgreich gesendet"
@ -1818,6 +1829,13 @@ msgstr "Test-Mail asynchron senden"
msgid "Mails sent successfully" msgid "Mails sent successfully"
msgstr "Mails erfolgreich gesendet" 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/create.html:58
#: project/templates/admin_unit/update.html:66 #: project/templates/admin_unit/update.html:66
#: project/templates/event/create.html:347 #: project/templates/event/create.html:347
@ -2366,6 +2384,10 @@ msgstr "Du hast noch keinen Account? Kein Problem!"
msgid "Register for free" msgid "Register for free"
msgstr "Kostenlos registrieren" msgstr "Kostenlos registrieren"
#: project/templates/user/accept_tos.html:6
msgid "Confirmation required"
msgstr "Bestätigung erforderlich"
#: project/templates/user/request_deletion.html:8 #: project/templates/user/request_deletion.html:8
msgid "" msgid ""
"The account is not deleted immediately. After a period of time, the " "The account is not deleted immediately. After a period of time, the "
@ -2394,43 +2416,43 @@ msgstr "Optionale Details"
msgid "Preview" msgid "Preview"
msgstr "Vorschau" msgstr "Vorschau"
#: project/views/admin.py:63 #: project/views/admin.py:64
msgid "Organization successfully updated" msgid "Organization successfully updated"
msgstr "Organisation erfolgreich aktualisiert" 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 #: project/views/admin_unit.py:220 project/views/manage.py:316
msgid "Entered name does not match organization name" msgid "Entered name does not match organization name"
msgstr "Der eingegebene Name entspricht nicht dem Namen der Organisation" msgstr "Der eingegebene Name entspricht nicht dem Namen der Organisation"
#: project/views/admin.py:89 #: project/views/admin.py:90
msgid "Organization successfully deleted" msgid "Organization successfully deleted"
msgstr "Organisation erfolgreich gelöscht" msgstr "Organisation erfolgreich gelöscht"
#: project/views/admin.py:113 project/views/manage.py:486 #: project/views/admin.py:134 project/views/manage.py:486
#: project/views/user.py:41 #: project/views/user.py:63
msgid "Settings successfully updated" msgid "Settings successfully updated"
msgstr "Einstellungen erfolgreich aktualisiert" msgstr "Einstellungen erfolgreich aktualisiert"
#: project/views/admin.py:133 #: project/views/admin.py:154
#, python-format #, python-format
msgid "Test mail from %(site_name)s" msgid "Test mail from %(site_name)s"
msgstr "Test-Mail von %(site_name)s" msgstr "Test-Mail von %(site_name)s"
#: project/views/admin.py:162 #: project/views/admin.py:183
#, python-format #, python-format
msgid "Newsletter from %(site_name)s" msgid "Newsletter from %(site_name)s"
msgstr "Newsletter von %(site_name)s" msgstr "Newsletter von %(site_name)s"
#: project/views/admin.py:212 #: project/views/admin.py:233
msgid "User successfully updated" msgid "User successfully updated"
msgstr "Nutzer erfolgreich aktualisiert" msgstr "Nutzer erfolgreich aktualisiert"
#: project/views/admin.py:232 #: project/views/admin.py:253
msgid "Entered email does not match user email" msgid "Entered email does not match user email"
msgstr "Die eingegebene Email passt nicht zur Email des Nutzers" msgstr "Die eingegebene Email passt nicht zur Email des Nutzers"
#: project/views/admin.py:236 #: project/views/admin.py:257
msgid "User successfully deleted" msgid "User successfully deleted"
msgstr "Nutzer erfolgreich gelöscht" msgstr "Nutzer erfolgreich gelöscht"
@ -2660,7 +2682,7 @@ msgstr ""
"Ob alle zukünftigen Empfehlungsanfragen von %(admin_unit_name)s " "Ob alle zukünftigen Empfehlungsanfragen von %(admin_unit_name)s "
"automatisch verifiziert werden sollen." "automatisch verifiziert werden sollen."
#: project/views/user.py:85 #: project/views/user.py:107
msgid "" msgid ""
"You are administrator of at least one organization. Cancel your " "You are administrator of at least one organization. Cancel your "
"membership to delete your account." "membership to delete your account."
@ -2668,11 +2690,11 @@ msgstr ""
"Du bist Administrator von mindestens einer Organisation. Beende deine " "Du bist Administrator von mindestens einer Organisation. Beende deine "
"Mitgliedschaft, um deinen Account zu löschen." "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" msgid "Entered email does not match your email"
msgstr "Die eingegebene Email entspricht nicht deiner Email" msgstr "Die eingegebene Email entspricht nicht deiner Email"
#: project/views/user.py:138 #: project/views/user.py:160
msgid "User deletion requested" msgid "User deletion requested"
msgstr "Löschung des Nutzers beantragt" msgstr "Löschung des Nutzers beantragt"

View File

@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PROJECT VERSION\n" "Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\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" "PO-Revision-Date: 2021-04-30 15:04+0200\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: en\n" "Language: en\n"
@ -230,21 +230,29 @@ msgstr ""
msgid "Save" msgid "Save"
msgstr "" 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 #: project/forms/admin_unit_member.py:34
msgid "Roles" msgid "Roles"
msgstr "" 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 #: project/templates/admin/update_user.html:8
msgid "Update user" msgid "Update user"
msgstr "" 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" msgid "Delete user"
msgstr "" 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:12 project/forms/admin_unit_member.py:25
#: project/forms/admin_unit_member.py:30 project/forms/event.py:107 #: project/forms/admin_unit_member.py:30 project/forms/event.py:107
#: project/forms/event_suggestion.py:38 project/forms/organizer.py:33 #: project/forms/event_suggestion.py:38 project/forms/organizer.py:33
@ -256,60 +264,60 @@ msgstr ""
msgid "Email" msgid "Email"
msgstr "" msgstr ""
#: project/forms/admin.py:32 #: project/forms/admin.py:39
msgid "Incoming reference requests allowed" msgid "Incoming reference requests allowed"
msgstr "" msgstr ""
#: project/forms/admin.py:33 #: project/forms/admin.py:40
msgid "" msgid ""
"If set, other organizations can ask this organization to reference their " "If set, other organizations can ask this organization to reference their "
"event." "event."
msgstr "" msgstr ""
#: project/forms/admin.py:39 #: project/forms/admin.py:46
msgid "Suggestions enabled" msgid "Suggestions enabled"
msgstr "" msgstr ""
#: project/forms/admin.py:40 #: project/forms/admin.py:47
msgid "If set, the organization can work with suggestions." msgid "If set, the organization can work with suggestions."
msgstr "" msgstr ""
#: project/forms/admin.py:44 #: project/forms/admin.py:51
msgid "Create other organizations" msgid "Create other organizations"
msgstr "" msgstr ""
#: project/forms/admin.py:45 #: project/forms/admin.py:52
msgid "If set, members of the organization can create other organizations." msgid "If set, members of the organization can create other organizations."
msgstr "" msgstr ""
#: project/forms/admin.py:51 #: project/forms/admin.py:58
msgid "Invite other organizations" msgid "Invite other organizations"
msgstr "" msgstr ""
#: project/forms/admin.py:52 #: project/forms/admin.py:59
msgid "If set, members of the organization can invite other organizations." msgid "If set, members of the organization can invite other organizations."
msgstr "" msgstr ""
#: project/forms/admin.py:58 #: project/forms/admin.py:65
msgid "Verify other organizations" msgid "Verify other organizations"
msgstr "" msgstr ""
#: project/forms/admin.py:59 #: project/forms/admin.py:66
msgid "If set, members of the organization can verify other organizations." msgid "If set, members of the organization can verify other organizations."
msgstr "" 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 #: project/templates/admin/update_admin_unit.html:8
msgid "Update organization" msgid "Update organization"
msgstr "" 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/request_deletion.html:6
#: project/templates/admin_unit/update.html:93 #: project/templates/admin_unit/update.html:93
msgid "Delete organization" msgid "Delete organization"
msgstr "" 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:142 project/forms/admin_unit.py:147
#: project/forms/admin_unit.py:152 project/forms/event.py:85 #: project/forms/admin_unit.py:152 project/forms/event.py:85
#: project/forms/event.py:114 project/forms/event_place.py:30 #: project/forms/event.py:114 project/forms/event_place.py:30
@ -324,27 +332,27 @@ msgstr ""
msgid "Name" msgid "Name"
msgstr "" msgstr ""
#: project/forms/admin.py:73 project/forms/admin.py:80 #: project/forms/admin.py:80 project/forms/admin.py:87
msgid "Recipient" msgid "Recipient"
msgstr "" msgstr ""
#: project/forms/admin.py:75 #: project/forms/admin.py:82
msgid "Send test mail synchronously" msgid "Send test mail synchronously"
msgstr "" msgstr ""
#: project/forms/admin.py:82 project/forms/admin.py:88 #: project/forms/admin.py:89 project/forms/admin.py:95
msgid "Test recipient" msgid "Test recipient"
msgstr "" msgstr ""
#: project/forms/admin.py:83 #: project/forms/admin.py:90
msgid "All users with enabled newsletter setting" msgid "All users with enabled newsletter setting"
msgstr "" msgstr ""
#: project/forms/admin.py:89 #: project/forms/admin.py:96
msgid "Message" msgid "Message"
msgstr "" msgstr ""
#: project/forms/admin.py:90 #: project/forms/admin.py:97
msgid "Send newsletter" msgid "Send newsletter"
msgstr "" msgstr ""
@ -1251,6 +1259,10 @@ msgstr ""
msgid "Deny" msgid "Deny"
msgstr "" msgstr ""
#: project/forms/security.py:79
msgid "Confirm"
msgstr ""
#: project/forms/user.py:9 project/templates/admin/admin.html:31 #: project/forms/user.py:9 project/templates/admin/admin.html:31
#: project/templates/admin/newsletter.html:4 #: project/templates/admin/newsletter.html:4
#: project/templates/admin/newsletter.html:93 #: 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:590 project/templates/_macros.html:633
#: project/templates/_macros.html:765 #: project/templates/_macros.html:765
#: project/templates/admin/admin_units.html:36 #: 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/events.html:116
#: project/templates/manage/members.html:35 #: project/templates/manage/members.html:35
#: project/templates/manage/organizers.html:33 #: project/templates/manage/organizers.html:33
@ -1733,7 +1745,7 @@ msgid "View"
msgstr "" msgstr ""
#: project/templates/admin/admin_units.html:37 #: 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/events.html:117
#: project/templates/manage/members.html:21 #: project/templates/manage/members.html:21
#: project/templates/manage/members.html:36 #: project/templates/manage/members.html:36
@ -1750,7 +1762,7 @@ msgstr ""
msgid "User" msgid "User"
msgstr "" 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" msgid "Mail sent successfully"
msgstr "" msgstr ""
@ -1766,6 +1778,13 @@ msgstr ""
msgid "Mails sent successfully" msgid "Mails sent successfully"
msgstr "" 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/create.html:58
#: project/templates/admin_unit/update.html:66 #: project/templates/admin_unit/update.html:66
#: project/templates/event/create.html:347 #: project/templates/event/create.html:347
@ -2301,6 +2320,10 @@ msgstr ""
msgid "Register for free" msgid "Register for free"
msgstr "" msgstr ""
#: project/templates/user/accept_tos.html:6
msgid "Confirmation required"
msgstr ""
#: project/templates/user/request_deletion.html:8 #: project/templates/user/request_deletion.html:8
msgid "" msgid ""
"The account is not deleted immediately. After a period of time, the " "The account is not deleted immediately. After a period of time, the "
@ -2327,43 +2350,43 @@ msgstr ""
msgid "Preview" msgid "Preview"
msgstr "" msgstr ""
#: project/views/admin.py:63 #: project/views/admin.py:64
msgid "Organization successfully updated" msgid "Organization successfully updated"
msgstr "" 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 #: project/views/admin_unit.py:220 project/views/manage.py:316
msgid "Entered name does not match organization name" msgid "Entered name does not match organization name"
msgstr "" msgstr ""
#: project/views/admin.py:89 #: project/views/admin.py:90
msgid "Organization successfully deleted" msgid "Organization successfully deleted"
msgstr "" msgstr ""
#: project/views/admin.py:113 project/views/manage.py:486 #: project/views/admin.py:134 project/views/manage.py:486
#: project/views/user.py:41 #: project/views/user.py:63
msgid "Settings successfully updated" msgid "Settings successfully updated"
msgstr "" msgstr ""
#: project/views/admin.py:133 #: project/views/admin.py:154
#, python-format #, python-format
msgid "Test mail from %(site_name)s" msgid "Test mail from %(site_name)s"
msgstr "" msgstr ""
#: project/views/admin.py:162 #: project/views/admin.py:183
#, python-format #, python-format
msgid "Newsletter from %(site_name)s" msgid "Newsletter from %(site_name)s"
msgstr "" msgstr ""
#: project/views/admin.py:212 #: project/views/admin.py:233
msgid "User successfully updated" msgid "User successfully updated"
msgstr "" msgstr ""
#: project/views/admin.py:232 #: project/views/admin.py:253
msgid "Entered email does not match user email" msgid "Entered email does not match user email"
msgstr "" msgstr ""
#: project/views/admin.py:236 #: project/views/admin.py:257
msgid "User successfully deleted" msgid "User successfully deleted"
msgstr "" msgstr ""
@ -2584,17 +2607,17 @@ msgid ""
"verified automatically." "verified automatically."
msgstr "" msgstr ""
#: project/views/user.py:85 #: project/views/user.py:107
msgid "" msgid ""
"You are administrator of at least one organization. Cancel your " "You are administrator of at least one organization. Cancel your "
"membership to delete your account." "membership to delete your account."
msgstr "" 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" msgid "Entered email does not match your email"
msgstr "" msgstr ""
#: project/views/user.py:138 #: project/views/user.py:160
msgid "User deletion requested" msgid "User deletion requested"
msgstr "" msgstr ""

View File

@ -13,6 +13,7 @@ from project.forms.admin import (
AdminTestEmailForm, AdminTestEmailForm,
DeleteAdminUnitForm, DeleteAdminUnitForm,
DeleteUserForm, DeleteUserForm,
ResetTosAceptedForm,
UpdateAdminUnitForm, UpdateAdminUnitForm,
UpdateUserForm, 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")) @app.route("/admin/settings", methods=("GET", "POST"))
@roles_required("admin") @roles_required("admin")
def admin_settings(): def admin_settings():

View File

@ -3,16 +3,18 @@ import datetime
from flask import flash, redirect, render_template, url_for from flask import flash, redirect, render_template, url_for
from flask_babel import gettext from flask_babel import gettext
from flask_security import auth_required, current_user from flask_security import auth_required, current_user
from flask_security.utils import get_post_login_redirect
from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.exc import SQLAlchemyError
from project import app, db from project import app, db
from project.forms.security import AcceptTosForm
from project.forms.user import ( from project.forms.user import (
CancelUserDeletionForm, CancelUserDeletionForm,
NotificationForm, NotificationForm,
RequestUserDeletionForm, RequestUserDeletionForm,
) )
from project.models import AdminUnitInvitation, User 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 ( from project.views.utils import (
flash_errors, flash_errors,
get_invitation_access_result, get_invitation_access_result,
@ -28,6 +30,26 @@ def profile():
return render_template("profile.html") 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")) @app.route("/user/notifications", methods=("GET", "POST"))
@auth_required() @auth_required()
def user_notifications(): def user_notifications():

View File

@ -2,6 +2,8 @@ import base64
import pytest import pytest
from tests.seeder import Seeder
def test_read(client, app, db, seeder, utils): def test_read(client, app, db, seeder, utils):
user_id, admin_unit_id = seeder.setup_base() 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) 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() user_id, admin_unit_id = seeder.setup_api_access()
event_id = seeder.create_event(admin_unit_id) event_id = seeder.create_event(admin_unit_id)
place_id = seeder.upsert_default_event_place(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() runner = app.test_cli_runner()
result = runner.invoke( 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 assert "user_id" in result.output
@ -46,6 +54,7 @@ def test_create(client, seeder, app):
user = find_user_by_email("test@test.de") user = find_user_by_email("test@test.de")
assert user.confirmed_at is not None assert user.confirmed_at is not None
assert user.tos_accepted_at is not None
def test_confirm(client, seeder, app): def test_confirm(client, seeder, app):

View File

@ -34,6 +34,7 @@ class Seeder(object):
password="MeinPasswortIstDasBeste", password="MeinPasswortIstDasBeste",
admin=False, admin=False,
confirm=True, confirm=True,
tos_accepted=True,
): ):
from flask_security.confirmable import confirm_user from flask_security.confirmable import confirm_user
@ -41,6 +42,7 @@ class Seeder(object):
add_admin_roles_to_user, add_admin_roles_to_user,
create_user, create_user,
find_user_by_email, find_user_by_email,
set_user_accepted_tos,
) )
with self._app.app_context(): with self._app.app_context():
@ -52,6 +54,9 @@ class Seeder(object):
if confirm: if confirm:
confirm_user(user) confirm_user(user)
if tos_accepted:
set_user_accepted_tos(user)
if admin: if admin:
add_admin_roles_to_user(email) add_admin_roles_to_user(email)

View File

@ -1,5 +1,8 @@
import pytest import pytest
from tests.seeder import Seeder
from tests.utils import UtilActions
def test_normal_user(client, seeder, utils): def test_normal_user(client, seeder, utils):
seeder.create_user() 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) admin_unit = db.session.get(AdminUnit, admin_unit_id)
assert admin_unit is None 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 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() user_id, admin_unit_id = seeder.setup_base()
response = utils.get_endpoint("manage") response = utils.get_endpoint("manage")

View File

@ -1,6 +1,7 @@
import pytest import pytest
from tests.seeder import Seeder from tests.seeder import Seeder
from tests.utils import UtilActions
def test_profile(client, seeder, utils): def test_profile(client, seeder, utils):
@ -245,3 +246,28 @@ def test_user_cancel_deletion(
user = db.session.get(User, user_id) user = db.session.get(User, user_id)
assert user.deletion_requested_at is None 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")