Metadata-Version: 2.4
Name: splent_feature_team
Version: 0.7.0
License-Expression: LGPL-3.0-or-later
Requires-Python: >=3.13
Description-Content-Type: text/markdown
License-File: COPYING
License-File: COPYING.LESSER
Requires-Dist: splent_framework>=1.21.0
Dynamic: license-file

# splent_feature_team

The `team` feature of a [SPLENT](https://splent.io) 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

A directory of people. `/team` lists the published members grouped by
role, in the order of `Role.order`, and `/team/<slug>` shows one profile.
A member can hold several roles and then appears under each of them. A
member's bio is stored HTML, and the profile draws it through the
framework's `rich_text` filter, which keeps the formatting and drops
scripts, event handlers and `javascript:` links.

A member who is not published stays off both pages, and `/team/<slug>`
answers 404 for them. The panel's accounts still open the profile to check
it, with a notice that the public does not see it.

The panel manages it at `/admin/team` (members) and `/admin/team/roles`
(roles). Only the panel's accounts reach those screens.

The public pages extend `shell('public')` and the panel's screens
`shell('app')`, so a product that points a shell at another layout
(`SPLENT_SHELL_PUBLIC`, `SPLENT_SHELL_APP`) moves them with it.

## Menus

team registers two entries in the framework's menu registry.

| Key | Section | Order | Icon | Opens | Shown to |
|-----|---------|-------|------|-------|----------|
| `team` | `site` | 50 | `users` | `/team`, and is current on a profile too | Everyone |
| `team_admin` | `admin` | 70 | `user-check` | `/admin/team`, current on every panel screen | The panel's accounts |

`team_admin` names no roles: it takes the ones `role_required` asks for on
the panel's screens. A product hides or reorders either by key in its
`[tool.splent.layout.menu]`.

For one minor the panel link is still drawn in
`layout.authenticated_sidebar` as well, for the shells that fill their
sidebar from hooks. A shell draws the registry or those slots, never both,
or the link shows twice.

## Addresses

The member's photo, website and profile links and the role's logo and
website end up in a `src` or an `href` of the public pages, so the forms
check them before they store them.

| Address | Allowed |
|---------|---------|
| A member's photo, a role's logo | An `http://` or `https://` address, or a path on the site such as `/static/uploads/logo.png` |
| A member's website and profile links, a role's website | An `http://` or `https://` address |

A value that breaks a rule is refused with a message, nothing is saved,
and the form comes back with what was typed. A photo, a logo or a website
longer than its 512 character column is refused rather than cut, since a
cut address points somewhere else. The member form has no website field,
so saving it keeps the website a seeder set.

A seeder writes the columns directly and is not checked. Every template
draws a stored address through the framework's `safe_url` filter, so one
that is not `http`, `https`, `mailto` or a path on the site renders as an
empty `href` or `src` instead of running.

## Roles that stand for an organisation

A role is a section of the page. When a product groups its people by
organisation (a university, a company, a partner lab), each organisation is
a role, and the role can carry three optional details.

| Column        | What the public page shows                              |
|---------------|---------------------------------------------------------|
| `description` | a paragraph under the section heading                   |
| `logo`        | an image beside the heading, an image URL like `TeamMember.photo` |
| `link`        | the website, linked from the logo and shown as an address |

A role without any of them renders as the bare heading it always was, so
products that group by job (Faculty, Students, Staff) see no difference.

In the panel, the Details button of each role on `/admin/team/roles` opens
the full form, where the logo is picked from the media library.

The form checks the logo and the website as described in
[Addresses](#addresses), and a role whose details are refused keeps what it
had.

A product seeds its organisations with its own seeder, replacing this
feature's sample data.

```python
import os

from splent_framework.seeders.BaseSeeder import BaseSeeder
from splent_io.splent_feature_media.services import MediaService
from splent_io.splent_feature_team.models import Role, TeamMember

LOGOS = os.path.join(os.path.dirname(__file__), "static", "logos")


class MyTeamSeeder(BaseSeeder):
    replaces = ("splent_io.splent_feature_team.seeders",)

    def run(self):
        logo = MediaService().import_from_file(
            os.path.join(LOGOS, "somewhere.svg"), source_key="seed://my/somewhere"
        )
        [university] = self.seed(
            [
                Role(
                    name="University of Somewhere",
                    slug="university-of-somewhere",
                    order=1,
                    description="Develops and runs the platform.",
                    logo=logo.url,
                    link="https://www.somewhere.example",
                )
            ]
        )
        person = TeamMember(name="Ada Lovelace", slug="ada-lovelace")
        person.roles = [university]
        self.seed([person])
```

A product that wants other markup for the page overrides
`team/list.html` in its own templates. Each group is a `Role` with
`description`, `logo` and `link` next to its `name`.

## Configuration

Declared in `[tool.splent.config]`. A product overrides it in its own
`[tool.splent.config]`.

| Key | Default | What it does |
|-----|---------|--------------|
| `TEAM_SHOW_EMPTY_ROLES` | `false` | Lists on `/team` the roles with no published member too, each with its heading, logo and description and no people. For a product whose roles stand for organisations. |

```toml
# the product's pyproject.toml
[tool.splent.config]
TEAM_SHOW_EMPTY_ROLES = true
```

`team/list.html` gets those roles in `groups` with an empty list of
members, so a product that overrides it finds them there as well.

## Use it in a product

```bash
splent feature:add splent_feature_team
```

## Develop

```bash
splent feature:test splent_feature_team
splent feature:contract splent_feature_team
```

## Release

```bash
splent feature:release splent_feature_team --dry-run --bump patch
splent feature:release splent_feature_team
```

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.
