splent_feature_profile
The profile feature of a SPLENT product line.
Every account has a profile with a name and a surname. The person edits it at
/profile/edit, and a client reads it as JSON at /api/v1/profile/me. Other
features extend all three without touching profile's code. They add fields to
the edit form, sections below it and keys to the JSON. It needs auth, which
owns the account. With signup in the product, the sign-up asks for the name
and surname as well.
A SPLENT feature is a Python package that a product composes with others.
Its models, routes, services, templates and hooks are registered when the
product starts. What this feature provides and requires is listed in the
[tool.splent.contract] section of pyproject.toml, which
splent feature:contract --write keeps in sync with the source.
A profile for every account
auth sends user-registered when an account is opened through
AuthenticationService.create_user, as the sign-up and the external sign-in
features do, and profile creates its UserProfile then. The name and surname
come with the signal when the sign-up asked for them, and the profile is empty
otherwise.
An account opened without that signal, from auth's accounts screen, with its
create-user command or by a seeder, has no profile at first. It gets an empty
one the first time it is needed, when the person opens the edit page or a
client asks /api/v1/profile/me, and the person fills it in on the edit page.
An account that already has a profile keeps it.
Name and surname at sign-up
When the product has signup, profile registers two fields on its form with
register_signup_field, name at order 10 and surname at order 20, so both
are asked for before the address. Both are required. signup is optional, and
without it nothing is registered. A signup older than register_signup_field
(0.2.0 and before) asks for no name, and the product starts all the same.
Fields other features add
A feature that keeps more about the person asks for it on the edit page with
register_profile_field. The field is drawn on the page, validated with the
form, filled from the profile attribute of the same name and saved onto it
when UserProfile has a column of that name. The feature that registers the
field adds the column, with a declared model extension of UserProfile and a
migration of its own.
# __init__.py of a feature that extends UserProfile with an affiliation
from flask_babel import lazy_gettext as _l
from wtforms import StringField
from wtforms.validators import Length, Optional
from splent_io.splent_feature_profile.forms import register_profile_field
def init_feature(app):
register_profile_field(
"affiliation",
StringField(_l("Affiliation"), validators=[Optional(), Length(max=255)]),
)
order places the field. Lower comes first.
| Field | Order |
|---|---|
| A registered field that says so | its own |
name |
100 (NAME_ORDER) |
surname |
200 (SURNAME_ORDER) |
| A registered field that does not say | 300 (DEFAULT_FIELD_ORDER) |
The registry is read when a request needs the form, so a field registered
after profile has loaded is asked for too. The form class is built once for
each set of registered fields and reused while that set stays the same.
Registering a name again replaces the earlier field, and remove_profile_field(name) stops asking for one. A field's own
render_kw (a placeholder) and description (a line of help) are shown. A
field without a column is validated and drawn, and profile does not store it.
Some names are refused with a ValueError when the field is registered. id,
user_id and user say whose profile it is, which is never the person's to
edit. The form's own fields (name, surname, submit, csrf_token) and the
form's attributes (data, errors, meta...) cannot be taken either.
Sections below the form
The edit page opens the slot profile.edit.sections, right below the profile
form. Linked accounts, notification preferences and the like go there. Each
section is a block of its own, outside the profile form, so it can carry forms
of its own. A callback takes no arguments and returns HTML, or an empty string
to add nothing on this request. When no callback returns anything, nothing is
drawn.
# hooks.py of a feature that links external accounts
from flask import render_template
from splent_framework.hooks.template_hooks import register_template_hook
def linked_accounts():
return render_template("hooks/linked_accounts.html")
register_template_hook("profile.edit.sections", linked_accounts, order=10)
The profile as JSON
GET /api/v1/profile/me answers the signed-in person's profile.
{"name": "Ada", "surname": "Lovelace", "email": "ada@example.com"}
Nobody signed in gets 401, as JSON with an error key, so a page that calls
it with fetch never receives the login page instead. An account without a
profile gets an empty one, and answers with empty name and surname. Every
answer carries Cache-Control: no-store, so no browser or proxy keeps one
person's data for the next.
A feature adds its own keys with register_profile_payload. The function
takes the user and their profile and returns a dict, and runs on every
request.
from splent_io.splent_feature_profile.payload import register_profile_payload
def affiliation(user, profile):
return {"affiliation": profile.affiliation}
def init_feature(app):
register_profile_payload(affiliation)
The functions run in the order they were registered, so a later one wins a key
two of them send. name, surname and email always hold the profile's and
the account's own values. Registering a function again does nothing, and
remove_profile_payload(fn) takes one out.
Menus and the account menu
The feature declares Edit profile in the framework's menu registry, first in
the Account section, and an application shell that draws menu_sections()
and user_menu_sections() shows it to every signed-in account.
| Key | Menu | Section | Order | Opens |
|---|---|---|---|---|
profile |
Sidebar | account |
10 | profile.edit_profile |
user_profile |
User menu | account |
10 | profile.edit_profile |
A product hides or reorders them by key in [tool.splent.layout.menu]. The
layout.sidebar.authenticated.items and layout.navbar.authenticated hooks
still draw the same entry and the name for one minor, for the shells that
draw those slots. A shell draws the registry or those slots, never both. The
next minor drops the hooks.
A shell that opens the layout.navbar.user slot at the top of its account
menu, inside the .dropdown-menu and before the entries of
user_menu_sections(), gets who is signed in from this feature: the
initials, the name ("Surname, Name") and the address, so the shell draws the
account's entries without knowing anything of profiles. A visitor gets
nothing. The block is only that: the button that opens the menu and the
divider under the block are the shell's, and it draws the divider when the
slot renders something.
{% set who = render_template_hooks("layout.navbar.user") %}
{% if who %}{{ who }}<div class="dropdown-divider"></div>{% endif %}
The edit page extends shell('app'), so it follows SPLENT_SHELL_APP.
Translations
The edit page, its form and the sign-up fields ship with a Spanish catalog.
Use it in a product
splent feature:add splent_feature_profile
Develop
splent feature:test splent_feature_profile
splent feature:contract splent_feature_profile
Release
splent feature:release splent_feature_profile --dry-run --bump patch
splent feature:release splent_feature_profile
The first command rehearses the release and changes nothing. The second publishes it to the git forge and to PyPI, and a PyPI version is permanent.
Metadata
Release files for splent-feature-profile 1.7.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| splent_feature_profile-1.7.1.tar.gz | 32.4 kB | Without provenance | Details for splent_feature_profile-1.7.1.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| splent_feature_profile-1.7.1-py3-none-any.whl | Python 3 | none | any | Details for splent_feature_profile-1.7.1-py3-none-any.whl |
Total release size: 71.7 kB
Release files / splent_feature_profile-1.7.1.tar.gz
| Download URL | splent_feature_profile-1.7.1.tar.gz |
|---|---|
| Size | 32.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
96d0ec3d068524fd36bf12cf798ed697899d28e8133dc6152206fa117bcc3d7e
|
|
BLAKE2b-256 checksum How to use checksums |
5bb7d4678ee504cbbef4ef88505beb9da9f0b53e1dae29bf6aa6173ae6f589e4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.13.16
|
Release files / splent_feature_profile-1.7.1-py3-none-any.whl
| Download URL | splent_feature_profile-1.7.1-py3-none-any.whl |
|---|---|
| Size | 39.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
547a5fbc482b07bf5fe46dc08f2eb843c7bfb48f20e54ba03027e52ff2206fef
|
|
BLAKE2b-256 checksum How to use checksums |
30114a9a0c37e87a509fc4b45893bad9562ac4021d4d6a7d046e149bc53504d3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.13.16
|
Security for splent-feature-profile 1.7.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.