Content helpers
Table of contents
- Introduction
- Giving an item a slug
- Previewing a draft
- Paging a list
- Adding pages to the sitemap
- Writing a summary
- Reading a number from a form
- Reference
- Advanced
- See also
Introduction
A feature that publishes things, such as posts, events or pages, needs the same small pieces as every other one. A slug for each item, a preview of drafts, a paged list, a place in the sitemap and a short summary. Each helper here gives you one of them in a line or two.
from splent_framework.content import unique_slug
post.slug = unique_slug(Post, form.title.data, exclude_id=post.id)
Giving an item a slug
unique_slug(Post, "Jesús Moreno León") # "jesus-moreno-leon"
unique_slug(Post, "Welcome") # "welcome-2" while another post is "welcome"
unique_slug makes a slug of the title, accents folded, and adds -2, -3 and so on while another row has it.
Pass exclude_id when you rename an item, so it may keep its own slug. Any other keyword narrows where the slug must be unique.
page.slug = unique_slug(Page, title, exclude_id=page.id, course_id=course.id)
If you look items up through a repository instead of a model, pass taken, a function that says whether a slug is in use.
slug = unique_slug(value=title, taken=posts_repository.slug_exists)
| Option | Default | Means |
|---|---|---|
field |
"slug" |
The column that holds the slug |
fallback |
"item" |
The slug of a title with nothing to keep, such as "¡¿?!" |
Previewing a draft
from splent_framework.content import public_or_preview
@events_bp.route("/events/<slug>")
def show(slug):
event = events_service.by_slug(slug)
event = public_or_preview(
event,
event is not None and event.published,
endpoint="events.admin_index",
notice=_("This event is a draft. Only the people who manage events see it."),
)
return render_template("events/show.html", event=event)
A visitor gets a 404 for a draft. An account that may open the panel screen endpoint sees the page, with the notice flashed as a warning. An access policy on that screen counts too.
A missing item, None, is a 404 for everyone. Pass view_args when the panel screen takes arguments, and roles to ask other roles than PANEL_ROLES.
Paging a list
from splent_framework.pages import page_of
@notes_bp.route("/admin/notes")
def admin_index():
page = page_of(Note.query.order_by(Note.created_at.desc(), Note.id.desc()))
return render_template("notes/admin/list.html", page=page)
{% for note in page.items %}
<tr><td>{{ note.title }}</td></tr>
{% endfor %}
{{ pager(page) }}
page_of reads page and per_page from the request, leniently. A page that is not a number is the first, one past the end is the last, and per_page is 25 unless the request asks for another size from 1 to 100.
pager draws Bootstrap’s pagination under the list, in the page’s language, and keeps the request’s other arguments such as a search. A list of one page draws nothing.
Order by columns that end in a unique one, as above, or rows can move between pages. For an API, page by cursor with paginate instead.
page attribute |
Holds |
|---|---|
items |
The rows of this page |
page, pages |
This page’s number and how many there are |
per_page, total |
The size of a page and the rows in all |
first, last |
The positions of the first and last row shown, from 1 |
has_prev, has_next |
Whether there is a page before or after |
url(number) |
This address on another page |
Adding pages to the sitemap
from splent_framework.sitemap import register_sitemap
from splent_framework.urls import external_url_for
def init_feature(app):
register_sitemap(app, "notes", public_entries)
def public_entries():
yield external_url_for("notes.index"), None
for note in notes_service.public_notes():
yield external_url_for("notes.show", slug=note.slug), note.updated_at
The framework answers /sitemap.xml with the entries of every feature, and /sitemap/notes.xml with yours alone. Each entry is an absolute address and when it last changed, a date, a datetime or None.
List only what an anonymous visitor can open. Crawlers keep a sitemap for days, so an address that answers 404 must never be in it.
A provider that fails is left out and logged, and the rest of the sitemap still answers. has_sitemap() tells a robots.txt whether there is a sitemap to point at.
Writing a summary
<meta name="description" content="{{ post.content | summary }}">
<p class="card-text">{{ post.content | summary(80) }}</p>
summary turns HTML or Markdown into plain text and cuts it at a word within 160 characters, with an ellipsis. Pass a length for a shorter one. In Python it is summary from splent_framework.utils.text.
Reading a number from a form
from splent_framework.utils.form_helpers import form_int
position = form_int(request.form, "position", default=0, low=0, high=100)
A missing value, or one that is not a whole number, gives the default. The result always stays between low and high, so a typo never answers a 500.
Reference
| Call | Signature |
|---|---|
unique_slug |
unique_slug(model=None, value="", exclude_id=None, *, field="slug", fallback="item", taken=None, **scope) |
public_or_preview |
public_or_preview(item, published, *, endpoint, notice, view_args=None, roles=PANEL_ROLES) |
page_of |
page_of(query, per_page=None, page=None, most=100) |
pager |
pager(page, label=None), a template global. label names the navigation for screen readers |
register_sitemap |
register_sitemap(app, name, entries) |
has_sitemap |
has_sitemap(app=None) |
summary |
summary(value, length=160), a template filter |
form_int |
form_int(form, key, default=0, low=None, high=None) |
Advanced
A route of your own at
/sitemap.xmlkeeps answering it, since the features register their routes before the framework adds its own. A sitemap holds at most 50,000 addresses and the rest are left out with a warning.