Content helpers

Table of contents

  1. Introduction
  2. Giving an item a slug
  3. Previewing a draft
  4. Paging a list
  5. Adding pages to the sitemap
  6. Writing a summary
  7. Reading a number from a form
  8. Reference
  9. Advanced
  10. 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.xml keeps 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.

See also


Back to top

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