diff --git a/invenio_theme_iform/administration/views.py b/invenio_theme_iform/administration/views.py index f13eca6..059e54c 100644 --- a/invenio_theme_iform/administration/views.py +++ b/invenio_theme_iform/administration/views.py @@ -6,9 +6,13 @@ # modify it under the terms of the MIT License; see LICENSE file for more # details. -"""I-Form role management administration views.""" +"""I-Form administration views.""" -from flask import flash, redirect, request, url_for +import json +import os +from datetime import datetime + +from flask import current_app, flash, redirect, request, url_for from invenio_accounts.models import Role, User from invenio_accounts.proxies import current_accounts from invenio_administration.views.base import AdminView @@ -16,6 +20,32 @@ from invenio_db import db from invenio_i18n import lazy_gettext as _ +def get_site_settings_path(): + """Get the path to site_settings.json.""" + app_data_dir = os.path.join(current_app.instance_path, "app_data") + os.makedirs(app_data_dir, exist_ok=True) + return os.path.join(app_data_dir, "site_settings.json") + + +def get_site_settings(): + """Load site settings from JSON file.""" + path = get_site_settings_path() + if not os.path.exists(path): + return {"registration_enabled": True} + try: + with open(path, "r") as f: + return json.load(f) + except Exception: + return {"registration_enabled": True} + + +def set_site_settings(settings): + """Save site settings to JSON file.""" + path = get_site_settings_path() + with open(path, "w") as f: + json.dump(settings, f, indent=2) + + class RoleManagementView(AdminView): """Admin view for managing user roles.""" @@ -30,14 +60,14 @@ class RoleManagementView(AdminView): """Render the role management template.""" roles = Role.query.order_by(Role.name).all() selected_role_id = request.args.get("role_id") - + users = [] selected_role = None if selected_role_id: selected_role = Role.query.get(selected_role_id) if selected_role: users = User.query.order_by(User.email).all() - + return self.render( roles=roles, selected_role=selected_role, @@ -49,30 +79,141 @@ class RoleManagementView(AdminView): user_id = request.form.get("user_id") role_id = request.form.get("role_id") action = request.form.get("action") - + if not (user_id and role_id and action): flash(_("Missing required fields."), "error") return redirect(url_for("administration.roles", role_id=role_id)) - + try: user = User.query.get(int(user_id)) except ValueError: user = None - + role = Role.query.get(role_id) - + if not user or not role: flash(_("User or role not found."), "error") return redirect(url_for("administration.roles", role_id=role_id)) - + if action == "enable": if role not in user.roles: current_accounts.datastore.add_role_to_user(user, role) - flash(_("Role {role} enabled for {email}.").format(role=role.name, email=user.email), "success") + flash( + _("Role {role} enabled for {email}.").format( + role=role.name, email=user.email + ), + "success", + ) elif action == "disable": if role in user.roles: current_accounts.datastore.remove_role_from_user(user, role) - flash(_("Role {role} disabled for {email}.").format(role=role.name, email=user.email), "success") - + flash( + _("Role {role} disabled for {email}.").format( + role=role.name, email=user.email + ), + "success", + ) + db.session.commit() return redirect(url_for("administration.roles", role_id=role_id)) + + +class RegistrationSettingsView(AdminView): + """Admin view for controlling site registration settings.""" + + name = "registration-settings" + category = "Site Settings" + template = "invenio_theme_iform/administration/registration_settings.html" + url = "/registration-settings" + menu_label = _("Registration Settings") + icon = "setting" + + def get(self): + """Render registration settings page.""" + settings = get_site_settings() + return self.render( + registration_enabled=settings.get("registration_enabled", True) + ) + + def post(self): + """Handle registration settings update.""" + enabled = request.form.get("registration_enabled") == "true" + settings = get_site_settings() + settings["registration_enabled"] = enabled + set_site_settings(settings) + + status_str = _("enabled") if enabled else _("disabled") + flash( + _("Public user registration has been {status}.").format( + status=status_str + ), + "success", + ) + return redirect(url_for("administration.registration-settings")) + + +class UserCreateView(AdminView): + """Admin view for creating users.""" + + name = "create-user" + category = "User management" + template = "invenio_theme_iform/administration/user_create.html" + url = "/create-user" + menu_label = _("Create User") + icon = "user plus" + + def get(self): + """Render user creation page.""" + return self.render() + + def post(self): + """Handle manual user creation.""" + from flask_security.utils import hash_password + from invenio_access.permissions import system_identity + from invenio_users_resources.proxies import current_users_service + + email = request.form.get("email", "").strip() + password = request.form.get("password", "").strip() + confirm_email = request.form.get("confirm_email") == "true" + + if not email or not password: + flash(_("Email and Password are required."), "error") + return redirect(url_for("administration.create-user")) + + if User.query.filter_by(email=email).first(): + flash(_("A user with this email already exists."), "error") + return redirect(url_for("administration.create-user")) + + try: + user_kwargs = { + "email": email, + "password": hash_password(password), + "active": True, + } + if confirm_email: + user_kwargs["confirmed_at"] = datetime.utcnow() + + user = current_accounts.datastore.create_user(**user_kwargs) + db.session.commit() + + # Index the newly created user in search engine + try: + from invenio_search import current_search_client + current_users_service.reindex(system_identity, uids=[user.id]) + current_users_service.indexer.process_bulk_queue() + current_search_client.indices.refresh(index="*") + except Exception as ie: + current_app.logger.warning("Failed to index user %s: %s", user.id, str(ie)) + + flash( + _("User {email} created successfully.").format(email=email), + "success", + ) + except Exception as e: + db.session.rollback() + flash( + _("Failed to create user: {error}").format(error=str(e)), + "error", + ) + + return redirect(url_for("administration.create-user")) diff --git a/invenio_theme_iform/ext.py b/invenio_theme_iform/ext.py index 9a203bc..c76ca2e 100644 --- a/invenio_theme_iform/ext.py +++ b/invenio_theme_iform/ext.py @@ -8,7 +8,7 @@ """invenio module for I-Form theme.""" -from flask import g, has_request_context +from flask import g, has_request_context, request from flask_login import login_required from . import config @@ -32,8 +32,36 @@ class InvenioThemeIform(object): app.register_error_handler(423, locked) + @app.before_request + def block_registration_if_disabled(): + if request.endpoint == "security.register": + from flask import current_app, render_template + from .administration.views import get_site_settings + settings = get_site_settings() + if not settings.get("registration_enabled", True): + admin_email = ( + current_app.config.get("APP_RDM_ADMIN_EMAIL_RECIPIENT") + or current_app.config.get("SUPPORT_EMAIL") + or current_app.config.get("THEME_SUPPORT_EMAIL") + or current_app.config.get("SECURITY_EMAIL_SENDER") + or "info@repo.i-form.ie" + ) + return ( + render_template( + "invenio_theme_iform/accounts/registration_disabled.html", + admin_email=admin_email, + ), + 403, + ) + app.extensions["invenio-theme-iform"] = self + @app.context_processor + def inject_site_settings(): + from .administration.views import get_site_settings + settings = get_site_settings() + return dict(registration_enabled=settings.get("registration_enabled", True)) + def init_config(self, app): """Initialize configuration.""" for k in dir(config): diff --git a/invenio_theme_iform/templates/invenio_theme_iform/accounts/header_login.html b/invenio_theme_iform/templates/invenio_theme_iform/accounts/header_login.html index 62eb2bb..1bc81cc 100644 --- a/invenio_theme_iform/templates/invenio_theme_iform/accounts/header_login.html +++ b/invenio_theme_iform/templates/invenio_theme_iform/accounts/header_login.html @@ -2,7 +2,7 @@ {%- if not current_user.is_authenticated %} - {%- if config.SECURITY_REGISTERABLE %} + {%- if config.SECURITY_REGISTERABLE and registration_enabled %}