The site

Table of contents

  1. Introduction
  2. Naming the site
  3. Telling the time
  4. Building links for mails and documents
  5. Sending the reader back safely
  6. Linking to a page that may not be there
  7. Using another feature when it is there
  8. Choosing who manages a screen
  9. Reference
  10. See also

Introduction

Every feature sooner or later asks something about the site it runs in. What the site is called, what time it is there, which address a link in a mail should use, or whether another feature is installed. These helpers answer, so you never read the config for it yourself.

from splent_framework.site import local_today, site_name
from splent_framework.urls import external_url_for

subject = _("A reminder from %(site)s", site=site_name())
link = external_url_for("notes.show", note_id=note.id)
overdue = note.due_on < local_today()

Naming the site

from splent_framework.site import site_name

site_name()   # "Example Lab"

site_name() is never empty. It looks in this order and takes the first answer.

  1. The name stored under site_name in the site’s settings, which an editor in the panel can change.
  2. The SITE_NAME config key.
  3. The product’s name, SPLENT_APP.
  4. The app’s package.

Telling the time

from splent_framework.site import local_now, local_today, site_timezone

local_now()             # now, on the site's clock, with its zone
local_now(naive=True)   # the same, without the zone, for a column that keeps local time
local_today()           # the site's calendar day
site_timezone()         # ZoneInfo("Europe/Madrid")

Use these instead of datetime.now(), which is the server’s clock, or a zone written by hand. The zone is the product’s TIMEZONE, Europe/Madrid unless the product says otherwise. A name the system does not know falls back to that default.

The date, datetime and time_tag template filters show times in the same zone, see Internationalization.

Those filters read a datetime without a zone as UTC. A column you fill with local_now(naive=True) shows shifted through them, so keep UTC in the columns a page shows that way.


from splent_framework.urls import external_url_for

link = external_url_for("notes.show", note_id=note.id)
# "https://notes.example.org/notes/12"

A link that leaves the site, in a mail, a feed or a PDF, must point at the site’s real address. url_for(..., _external=True) takes the host from the request’s Host header, which the client writes, so a reset link built that way can point anywhere.

external_url_for builds on the address the deployment names in SERVER_NAME instead. It is a template global too.

<a href="{{ external_url_for('notes.show', note_id=note.id) }}">{{ note.title }}</a>

Set the address in the deployment’s environment.

SERVER_NAME=notes.example.org
PREFERRED_URL_SCHEME=https

https is the default scheme. Behind a path prefix, name it too, SERVER_NAME=https://example.org/notes, and the prefix appears once in every link.

Without SERVER_NAME, in development, links use the address the request reached. Outside a request, in a command or a task, there is no address to use, so external_url_for raises RuntimeError. Set SERVER_NAME wherever tasks send mail.

site_root() gives the address alone, and absolute_url("/terms") puts a path on it.

Sending the reader back safely

from splent_framework.urls import safe_next_url

target = safe_next_url(request.args.get("next")) or url_for("notes.index")
return redirect(target)

safe_next_url answers the next only when it is a path on this site. A full URL, //elsewhere.example, its backslash spelling, a scheme such as javascript: or a control character gives None, so your form is never an open redirect.

Linking to a page that may not be there

from splent_framework.urls import has_endpoint

if has_endpoint("reviews.index"):
    links.append(url_for("reviews.index"))

has_endpoint(name) says whether this product serves the endpoint. It is a template global too.

{% if has_endpoint("reviews.index") %}
  <a href="{{ url_for('reviews.index') }}">{{ _("Reviews") }}</a>
{% endif %}

The contract scan reads such a link as an optional dependency.

Using another feature when it is there

from splent_framework.optional import optional_attr, optional_module

reviews = optional_module("splent_io.splent_feature_reviews.services")
if reviews is not None:
    reviews.ReviewsService().count_for(item)

register_badge = optional_attr("splent_io.splent_feature_reviews.badges", "register_badge")

optional_module answers the module, or None when its feature is not installed. optional_attr answers one name of it, or the default you pass, which also covers an older release that lacks the name.

A module that is installed but fails while importing still raises, as a broken feature should.

The contract scan does not see these calls. List the feature under requires.features_optional in your pyproject.toml yourself, and the scan keeps the entry.

Choosing who manages a screen

from splent_framework.decorators.decorators import role_required
from splent_framework.roles import PANEL_ROLES


@notes_bp.route("/admin/notes")
@role_required(*PANEL_ROLES)
def admin_index():
    ...
Constant Roles For
PANEL_ROLES ("admin", "staff") The site’s content, which the people who run it edit day to day
ADMIN_ROLES ("admin",) The site itself, its appearance, accounts, keys and settings

Access control explains gates, links and policies.


Reference

Call Answers
site_name(app=None) The site’s name, never empty
site_timezone(app=None) The product’s ZoneInfo
local_now(naive=False), local_today() Now and today on the site’s clock
site_root() The site’s address, without a trailing slash
absolute_url(path) path on that address
external_url_for(endpoint, **values) url_for on that address
safe_next_url(candidate) The path, or None
has_endpoint(name) Whether the product serves name
optional_module(name), optional_attr(name, attr, default=None) The module or name, or None
Setting Default Read from
SITE_NAME (none) The app’s config
TIMEZONE Europe/Madrid The product’s config or the environment
SERVER_NAME (none) The environment only, so it never binds Flask’s routing to a host
PREFERRED_URL_SCHEME https The environment
SPLENT_SITE_SERVER_NAME (none) The app’s config, when the environment has no SERVER_NAME

See also


Back to top

splent. Distributed by an LGPL license v3. Contact us: drorganvidez@us.es