splent_feature_legal
The legal feature of a SPLENT product line.
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.
What it does
The legal documents of the site: the privacy notice, the terms of use, the
cookie policy, the legal notice and any other. Each one is served at
/legal/<slug> in the version in force, and gets a link in the footer
through the layout.footer hook, privacy notice first, then terms, cookies,
legal notice and the rest. A document with no version in force answers 404
and has no footer link.
Versions
A document's words live in versions. A version is a draft until it is published; publishing gives it the next number (1, 2, 3...) and the date it comes into force, which is the day the panel planned for it or the moment it is published, never earlier. From then on it is final. The models check what the database holds when the session flushes, so nothing that goes through the ORM edits or deletes a published version or its texts, or adds a text to it: not the panel, a seeder, the admin feature's generic CRUD or code in a shell, and a bulk UPDATE, DELETE or INSERT through the ORM is refused for versions and texts altogether. SQL sent to the database past the ORM is beyond the models' reach. A typo fixed is a new version, so what a page said on a given day, and what each account accepted, can always be shown again.
The numbers follow the days the versions come into force. A version cannot come into force before one already scheduled, which it would replace for good: the panel refuses it and asks for that day or a later one. A version published for the same day as a scheduled one takes its place, which is how a scheduled version is corrected; the one it replaced is marked as never in force.
/legal/<slug>shows the version in force, and says when a later one is scheduled./legal/<slug>/versionslists every published version, newest first./legal/<slug>/versions/<number>shows one of them as it was, marked as replaced when a later one is in force.
Each version holds one text per language of the product
(BABEL_SUPPORTED_LOCALES). A reader gets the text in their language,
else in the product's default, else in any the version has. A text is
stored HTML, drawn through the framework's rich_text filter, which keeps
the formatting and drops scripts, event handlers and javascript: links.
Publishing sends legal-document-published with document, version and
requires_reacceptance, for a feature that tells the accounts about the
change.
Acceptance
A document can be asked at sign-up (its "Asked at sign-up" setting). Then:
- Sign-up. legal adds a box to signup's form
(
register_signup_field, signup 0.3.0), last, after the password: "I have read: Privacy notice (version 3). I accept: Terms of use (version 2).", with a link to each version. Without the box ticked, or when the versions changed while the form was open, no account is created. The acceptance is recorded on auth'suser-registered. - Afterwards. A signed-in account that never accepted a required
document, or that accepted an earlier version when a later one in force
asks for acceptance again ("Ask the accounts that accepted an earlier
version to accept this one"), is sent to
/legal/acceptbefore any other page, and comes back where it was going once it accepts. A request for JSON gets 403 withaccept_url. The legal pages, logout and the static files stay open, and so does any page a feature keeps open withregister_gate_exemption("profile.delete")(registry.py, looked up by name like the other two registries) or the product lists inLEGAL_GATE_OPEN_ENDPOINTS: an account that does not accept still deletes itself, exports its data or changes the language it reads in.LEGAL_ACCEPTANCE_GATE = falseturns this off, for a product that asks only at sign-up. A version that does not ask again (a typo fixed) asks nobody. - The record. Each acceptance names the account, the version, the
moment and the channel:
signup,prompt(the acceptance page, for a document the account had never accepted),reaccept(the same page, after a version asked again), or the name another feature passes. An account sees its own at/legal/accepted, and the panel counts them per version. They go with the account, on auth'suser-deleting.
A feature that opens accounts outside signup's form (an identity
provider's "complete your sign-up" step) asks LegalService through the
service locator, without importing legal: acceptance_box() draws the same
box as HTML, acceptance_error(posted) says why what was posted does not
count (or None), and accept_token(user_id, posted, "orcid") records it
once the account exists. See the docstring of services.py.
Third parties and cookies
Each feature declares, from its init_feature, the outside services it
sends personal data to and the cookies it sets:
register_third_party(
"zenodo",
"Zenodo (CERN)",
purpose=_l("Publishes the datasets you deposit and gives them a DOI."),
data=[_l("Names, affiliations and ORCID iDs of the authors")],
country="Switzerland",
legal_basis=_l("Performance of the deposit you ask for"),
privacy_url="https://about.zenodo.org/privacy-policy/",
hosts=["zenodo.org"],
)
register_cookie(
"remember_token",
category="necessary", # necessary, preferences or analytics
purpose=_l("Keeps you signed in when you ask the site to remember you."),
duration=_l("1 year"),
)
The privacy notice (a document of kind privacy) lists the declared third
parties under its own text, and the cookie policy (kind cookies) the
declared cookies by category, so nobody keeps a copy by hand that drifts
from the code. The lists are part of the version: publishing keeps them as
the features declare them at that moment, in every language of the
product, and each version shows its own, so an earlier version reads as it
did and the one in force changes only with a new version. When the
features declare something else (a deployment adds a service or a cookie),
the panel marks the document "List out of date" and says so on its page; a
new version, with the same text if nothing else changes, brings the list up
to date, and asks the accounts again when it is told to. An empty list is
not drawn (it would claim there are none). LegalService.third_party_hosts()
gives every declared host, for a content security policy or a test of the
hosts a page contacts. legal declares the framework's session cookie
itself, under the name and lifetime the product gives it.
legal is optional in the product line, so a feature that declares does not
import registry.py with an import statement: it looks it up by name and
does nothing when the product has no legal (the docstring of registry.py
has the five lines). The same goes for register_gate_exemption.
Seeding a product's texts
legal seeds a neutral privacy notice and cookie policy that name no
organisation, so a new site starts with the pages its footer links to. A
product with its own wording takes that seeder over from
src/<product>/seeders.py and seeds its documents with
seeding.seed_documents:
from splent_framework.seeders.BaseSeeder import BaseSeeder
from splent_io.splent_feature_legal.seeding import seed_documents
DOCUMENTS = [
{
"slug": "privacy",
"kind": "privacy",
"title": "Privacy notice",
"acceptance_required": True,
"versions": [
{"texts": {"en": ("Privacy notice", "<p>...</p>"),
"es": ("Aviso de privacidad", "<p>...</p>")}},
],
},
]
class ProductLegalSeeder(BaseSeeder):
replaces = ("splent_io.splent_feature_legal.seeders",)
def run(self):
seed_documents(DOCUMENTS)
Import the function, never a seeder class: db:seed runs every seeder
class it finds in the module. A document whose slug is there already is
left as it is. A version with "draft": True is seeded as a draft, for a
text someone has to review (the data protection officer, say) before it is
published from the panel.
Upgrading from 0.2
The migration turns each legal page into a document: a published page
becomes its version 1, in force since it was last updated, and an
unpublished one a draft. Its text is carried over as it is and, being
published, is final from then on, so a page that should not stay in force
(a text another organisation wrote, left in the database) is deleted from
legal_page before upgrading, and the product's seeder writes the new one.
The kind comes from the slug: the privacy-policy page the catalogue seeded
becomes the privacy notice, and so do politica-de-privacidad and the other
usual names in English and Spanish (terms, cookies, legal notice); any
other page is "other", for the panel to set. A migrated version lists
nothing, as before, and the panel marks it until a new version brings the
lists. No document is asked at sign-up until the panel says so.
LegalPageis gone. A product seeder that builtLegalPagerows seedsseeding.seed_documentsinstead (above), with the kind of each document, and replacessplent_io.splent_feature_legal.seeders.- A product that overrides
legal/page.htmlgetspage(theLegalDocument),version,text(theLegalDocumentTextin the reader's language),in_force,scheduled,never_in_force,third_partiesandcookie_groups.page.content,page.updated_atandpage.publishedno longer exist.
The downgrade puts legal_page back with the text of each version in
force, and keeps the four versioned tables, renamed with an _archive
suffix: legal_page cannot hold the history, the other languages, the lists
or the acceptances, and those are the record of what the site said and who
agreed. Nothing is deleted; drop the archive by hand once it is not needed.
Access
The panel manages the documents at /admin/legal: their settings, their
drafts, and publishing. A document that published a version is never
deleted. The screens under /admin/legal open for the accounts whose role is admin
or staff, the same pair the admin dashboard lets in. Any other signed-in
account gets 403 and does not see the Legal pages link in the sidebar or the
menu, and an anonymous visitor is sent to log in. The published versions
under /legal and their footer links stay open to everyone, signed in or
not, and a document with no version in force answers 404 to everyone.
/legal/accept and /legal/accepted open for any signed-in account.
There is no setting for these roles, and a refinement cannot replace the
views that check them, because it may add routes to this feature but never
replace one of its own. Another feature can decide for these screens with an
access policy registered for the prefix legal.admin
(register_access_policy, splent_framework 1.20.0). The framework asks the
policy before the roles. It answers True to let an account in, False to
refuse it, or None to leave the decision to the roles, and the sidebar link
follows the same answer. That is the way to let a group of accounts manage
only this section, for example the people who keep the legal texts, without
giving them a panel role.
See Access control.
Without such a feature, give the person's account the staff role. Staff
opens every panel screen that asks for PANEL_ROLES, not only this one, so
a person who should manage nothing but the legal pages needs a policy
instead.
splent feature:auth set-role someone@example.com staff
Use it in a product
splent feature:add splent_feature_legal
| Setting | Default | What it does |
|---|---|---|
LEGAL_ACCEPTANCE_GATE |
true |
Sends a signed-in account with a required text to accept to /legal/accept first. false asks only at sign-up. |
LEGAL_GATE_OPEN_ENDPOINTS |
empty | Endpoints, comma separated, that such an account still opens, besides legal's own pages and the ones the features keep open with register_gate_exemption. |
Develop
splent feature:test splent_feature_legal
splent feature:contract splent_feature_legal
Release
splent feature:release splent_feature_legal --dry-run --bump patch
splent feature:release splent_feature_legal
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-legal 0.3.0
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 | |
|---|---|---|---|---|
| splent_feature_legal-0.3.0.tar.gz | 69.3 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| splent_feature_legal-0.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 159.6 kB
Release files / splent_feature_legal-0.3.0.tar.gz
| Download URL | splent_feature_legal-0.3.0.tar.gz |
|---|---|
| Size | 69.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
ca084d7fdcfd77ec32c7c7204a1ab62622c18e6380dcca2f987c2ac0a3b2019e
|
|
BLAKE2b-256 checksum How to use checksums |
7b1b878b6da41f5370c80f8e431fd364863b44ebfb67043ba65051741e1173b4
|
| 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_legal-0.3.0-py3-none-any.whl
| Download URL | splent_feature_legal-0.3.0-py3-none-any.whl |
|---|---|
| Size | 90.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
24d448fb1639f10df60ebdfee65b95fbcdc84311840df5fa773c2c1b43bb08ad
|
|
BLAKE2b-256 checksum How to use checksums |
75cf4ab7746eaccfb3d2c83834a8f0a39c31c5e0059f01043e7fed2fa3e92788
|
| 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-legal 0.3.0
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.