splent-feature-signup 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,8 @@
1
+ # Compiled frontend bundles
2
+ recursive-include src/splent_io/splent_feature_signup/assets/dist *.js *.css *.map
3
+
4
+ # Jinja templates (feature views + hook fragments)
5
+ recursive-include src/splent_io/splent_feature_signup/templates *.html
6
+
7
+ # Alembic migration config and scripts
8
+ recursive-include src/splent_io/splent_feature_signup/migrations *.py *.ini *.mako
@@ -0,0 +1,5 @@
1
+ Metadata-Version: 2.4
2
+ Name: splent_feature_signup
3
+ Version: 0.1.0
4
+ Requires-Python: >=3.13
5
+ Description-Content-Type: text/markdown
@@ -0,0 +1,55 @@
1
+ [build-system]
2
+ requires = ["setuptools>=80.3.1", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "splent_feature_signup"
7
+ version = "0.1.0"
8
+ readme = "README.md"
9
+ requires-python = ">=3.13"
10
+
11
+ dependencies = [
12
+
13
+ ]
14
+
15
+ [tool.setuptools]
16
+ package-dir = { "" = "src" }
17
+ include-package-data = true
18
+
19
+ [tool.setuptools.packages.find]
20
+ where = ["src"]
21
+ exclude = ["*.tests", "*.tests.*"]
22
+
23
+ [tool.splent]
24
+ cli_version = "1.11.0"
25
+
26
+ # ── Feature Contract (auto-generated) ────────────────────────────────────────
27
+ # Do not edit manually — re-run `splent feature:contract --write` to refresh.
28
+ # Preserved across refreshes: description, category, tags, env, and
29
+ # requires.features_manual (dependencies invisible to static analysis).
30
+ [tool.splent.contract]
31
+ description = "Public self-registration for closed products (extends the auth login flow)"
32
+ archetype = "light"
33
+
34
+ [tool.splent.contract.provides]
35
+ routes = ["/signup/"]
36
+ blueprints = []
37
+ models = []
38
+ commands = []
39
+ hooks = ["auth.login.form_footer", "layout.anonymous_sidebar", "layout.navbar.anonymous"]
40
+ services = []
41
+ signals = []
42
+ translations = []
43
+ docker = []
44
+
45
+ [tool.splent.contract.requires]
46
+ features = ["auth"]
47
+ env_vars = []
48
+ signals = []
49
+
50
+ [tool.splent.contract.extensible]
51
+ services = []
52
+ templates = ["hooks/login_footer.html", "hooks/navbar_signup.html", "hooks/sidebar_signup.html", "signup/signup_form.html"]
53
+ models = []
54
+ hooks = ["auth.login.form_footer", "layout.anonymous_sidebar", "layout.navbar.anonymous", "signup.form_footer"]
55
+ routes = false
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,5 @@
1
+ Metadata-Version: 2.4
2
+ Name: splent_feature_signup
3
+ Version: 0.1.0
4
+ Requires-Python: >=3.13
5
+ Description-Content-Type: text/markdown
@@ -0,0 +1,14 @@
1
+ MANIFEST.in
2
+ pyproject.toml
3
+ src/splent_feature_signup.egg-info/PKG-INFO
4
+ src/splent_feature_signup.egg-info/SOURCES.txt
5
+ src/splent_feature_signup.egg-info/dependency_links.txt
6
+ src/splent_feature_signup.egg-info/top_level.txt
7
+ src/splent_io/splent_feature_signup/__init__.py
8
+ src/splent_io/splent_feature_signup/forms.py
9
+ src/splent_io/splent_feature_signup/hooks.py
10
+ src/splent_io/splent_feature_signup/routes.py
11
+ src/splent_io/splent_feature_signup/templates/hooks/login_footer.html
12
+ src/splent_io/splent_feature_signup/templates/hooks/navbar_signup.html
13
+ src/splent_io/splent_feature_signup/templates/hooks/sidebar_signup.html
14
+ src/splent_io/splent_feature_signup/templates/signup/signup_form.html
@@ -0,0 +1,11 @@
1
+ from splent_framework.blueprints.base_blueprint import create_blueprint
2
+
3
+ signup_bp = create_blueprint(__name__)
4
+
5
+
6
+ def init_feature(app):
7
+ pass
8
+
9
+
10
+ def inject_context_vars(app):
11
+ return {}
@@ -0,0 +1,11 @@
1
+ from flask_wtf import FlaskForm
2
+ from wtforms import StringField, SubmitField, PasswordField
3
+ from wtforms.validators import DataRequired, Email, Length
4
+
5
+
6
+ class SignupForm(FlaskForm):
7
+ name = StringField("Name", validators=[DataRequired(), Length(max=100)])
8
+ surname = StringField("Surname", validators=[DataRequired(), Length(max=100)])
9
+ password = PasswordField("Password", validators=[DataRequired()])
10
+ email = StringField("Email", validators=[DataRequired(), Email()])
11
+ submit = SubmitField("Submit")
@@ -0,0 +1,28 @@
1
+ """Template hooks for splent_feature_signup.
2
+
3
+ This feature is OPTIONAL in the SPL. When it is installed it advertises the
4
+ public self-registration route by injecting a "Sign up" link wherever auth shows
5
+ its "Login" link: the login form footer, the anonymous navbar and the anonymous
6
+ sidebar. If the product does not include signup, none of these appear and there
7
+ is simply no public registration — access is granted by the admin.
8
+ """
9
+
10
+ from splent_framework.hooks.template_hooks import register_template_hook
11
+ from flask import render_template
12
+
13
+
14
+ def signup_login_footer():
15
+ return render_template("hooks/login_footer.html")
16
+
17
+
18
+ def signup_navbar():
19
+ return render_template("hooks/navbar_signup.html")
20
+
21
+
22
+ def signup_sidebar():
23
+ return render_template("hooks/sidebar_signup.html")
24
+
25
+
26
+ register_template_hook("auth.login.form_footer", signup_login_footer)
27
+ register_template_hook("layout.navbar.anonymous", signup_navbar)
28
+ register_template_hook("layout.anonymous_sidebar", signup_sidebar)
@@ -0,0 +1,40 @@
1
+ from flask import flash, render_template, redirect
2
+ from flask_babel import gettext as _
3
+ from flask_login import current_user
4
+ from sqlalchemy.exc import IntegrityError
5
+
6
+ from splent_io.splent_feature_signup import signup_bp
7
+ from splent_io.splent_feature_signup.forms import SignupForm
8
+ from splent_io.splent_feature_auth.routes import landing_url
9
+ from splent_io.splent_feature_auth.services import AuthenticationService
10
+ from splent_framework.db import db
11
+
12
+
13
+ authentication_service = AuthenticationService()
14
+
15
+
16
+ @signup_bp.route("/signup/", methods=["GET", "POST"])
17
+ def show_signup_form():
18
+ if current_user.is_authenticated:
19
+ return redirect(landing_url())
20
+
21
+ form = SignupForm()
22
+ if form.validate_on_submit():
23
+ email = form.email.data
24
+ if not authentication_service.is_email_available(email):
25
+ flash(_("Email %(email)s is already in use", email=email), "danger")
26
+ return render_template("signup/signup_form.html", form=form)
27
+
28
+ try:
29
+ authentication_service.create_user(**form.data)
30
+ except IntegrityError as exc:
31
+ db.session.rollback()
32
+ if "Duplicate entry" in str(exc):
33
+ flash(_("Email %(email)s is already in use", email=email), "danger")
34
+ else:
35
+ flash(_("Error creating user"), "danger")
36
+ return render_template("signup/signup_form.html", form=form)
37
+
38
+ return redirect(landing_url())
39
+
40
+ return render_template("signup/signup_form.html", form=form)
@@ -0,0 +1,5 @@
1
+ <div class="mt-3">
2
+ <div class="col-lg-6 col-12">
3
+ {{ _('Don\'t have an account?') }} <a href="{{ url_for('signup.show_signup_form') }}">{{ _('Sign up!') }}</a>
4
+ </div>
5
+ </div>
@@ -0,0 +1 @@
1
+ <a class="nav-link d-none d-sm-inline-block" href="{{ url_for('signup.show_signup_form') }}">{{ _('Sign up') }}</a>
@@ -0,0 +1,5 @@
1
+ <li class="sidebar-item {{ 'active' if request.endpoint == 'signup.show_signup_form' else '' }}">
2
+ <a class="sidebar-link" href="{{ url_for('signup.show_signup_form') }}">
3
+ <i class="align-middle" data-feather="user-plus"></i> <span class="align-middle">{{ _('Sign Up') }}</span>
4
+ </a>
5
+ </li>
@@ -0,0 +1,96 @@
1
+ {% extends "base_template.html" %}
2
+
3
+ {% block title %}Sign up{% endblock %}
4
+
5
+ {% block content %}
6
+
7
+ <h1 class="h2 mb-3"><b>Sign</b> Up</h1>
8
+
9
+ <div class="row">
10
+ <div class="col-12">
11
+
12
+ {% with messages = get_flashed_messages(with_categories=true) %}
13
+ {% if messages %}
14
+ <div class="mt-3">
15
+ {% for category, message in messages %}
16
+ <div class="alert alert-{{ category }}">{{ message }}</div>
17
+ {% endfor %}
18
+ </div>
19
+ {% endif %}
20
+ {% endwith %}
21
+
22
+ <form action="" method="post" novalidate>
23
+ {{ form.hidden_tag() }}
24
+
25
+ <div class="row">
26
+ <div class="col-md-6 col-lg-6 col-xs-12">
27
+ <div class="mb-3">
28
+ {{ form.name.label(class="form-label") }} *
29
+ {{ form.name(class="form-control", placeholder="Enter name") }}
30
+ {% for error in form.name.errors %}
31
+ <span style="color: red;">{{ error }}</span>
32
+ {% endfor %}
33
+ </div>
34
+ </div>
35
+ </div>
36
+
37
+ <div class="row">
38
+ <div class="col-md-6 col-lg-6 col-xs-12">
39
+ <div class="mb-3">
40
+ {{ form.surname.label(class="form-label") }} *
41
+ {{ form.surname(class="form-control", placeholder="Enter surname") }}
42
+ {% for error in form.surname.errors %}
43
+ <span style="color: red;">{{ error }}</span>
44
+ {% endfor %}
45
+ </div>
46
+ </div>
47
+ </div>
48
+
49
+ <div class="row">
50
+ <div class="col-md-6 col-lg-6 col-xs-12">
51
+ <div class="mb-3">
52
+ {{ form.email.label(class="form-label") }}
53
+ {{ form.email(class="form-control", placeholder="Enter email") }}
54
+ {% for error in form.email.errors %}
55
+ <span style="color: red;">{{ error }}</span>
56
+ {% endfor %}
57
+ </div>
58
+ </div>
59
+ </div>
60
+
61
+ <div class="row">
62
+ <div class="col-md-6 col-lg-6 col-xs-12">
63
+ <div class="mb-3">
64
+ {{ form.password.label(class="form-label") }}
65
+ {{ form.password(class="form-control", placeholder="Enter password") }}
66
+ {% for error in form.password.errors %}
67
+ <span style="color: red;">{{ error }}</span>
68
+ {% endfor %}
69
+ </div>
70
+ </div>
71
+ </div>
72
+
73
+ <div class="row">
74
+ <div class="col-lg-6 col-12 d-flex justify-content-between align-items-center">
75
+ {{ form.submit(class="btn btn-primary me-3") }}
76
+ </div>
77
+ </div>
78
+
79
+ <div class="row">
80
+ <div class="mt-3">
81
+ <div class="col-lg-6 col-12">
82
+ {{ _('Already an account?') }} <a href="{{ url_for('auth.login') }}">{{ _('Login') }}</a>
83
+ </div>
84
+ </div>
85
+
86
+ {% for hook in get_template_hooks("signup.form_footer") %}
87
+ {{ hook() | safe }}
88
+ {% endfor %}
89
+ </div>
90
+
91
+ </form>
92
+
93
+ </div>
94
+ </div>
95
+
96
+ {% endblock %}