Metadata-Version: 2.4
Name: catalogue-client
Version: 0.2.0
Summary: Django client for showing products from a catalogue-server site
Author: Vasudeva Server
License-Expression: MIT
Project-URL: Homepage, https://vasudevaserver.org
Project-URL: Source, https://gitlab.com/vasudeva-server/catalogue-client
Classifier: Development Status :: 3 - Alpha
Classifier: Framework :: Django
Classifier: Framework :: Django :: 5.2
Classifier: Programming Language :: Python :: 3
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: Django<6.0,>=5.2
Requires-Dist: requests>=2.31
Dynamic: license-file

# catalogue-client

`catalogue-client` lets a Django site show products from another site that runs
[catalogue-server](https://gitlab.com/vasudeva-server/catalogue-server): titles,
descriptions, images, and the buying options for the visitor's country. Products
are never stored locally: every product is a Python value object built from the
server's read-only JSON API and cached. The site only stores which server it
uses, which works it offers, and which products each of its sites switched off.

## Setup

```python
INSTALLED_APPS += ["catalogue_client.apps.CatalogueClientConfig"]

CATALOGUE_SERVER_URL = "https://catalogue.example.org"
```

Mount the list page and the buying box's country picker, and run `migrate`:

```python
path("products/", include("catalogue_client.urls")),
```

## Choosing works

A catalogue-server *group* is a work; its products are the work's editions (one
per language and format). In the admin, **Catalogue client > Selected works**
chooses the server (its URL wins over `CATALOGUE_SERVER_URL`), narrows the
server's works by category (`CATALOGUE_CLIENT_CATEGORY_ATTRIBUTE`, e.g. a books
server's `genres`), language or a search, and selects the works whose editions
the sites get. With `CATALOGUE_CLIENT_SITE_FILTERS` limiting each site to its own
language, a site gets every edition of the selected works in that language.

Each site can then switch single products off (`SiteProduct`), for example from
a manage page built with:

```python
from catalogue_client.selection import set_product_enabled
from catalogue_client.utils import products_for_request

products = products_for_request(request, include_disabled=True)  # product.enabled
set_product_enabled(request.site, "a-product", False)
```

`selection.select_works([(slug, title), ...])` selects works from code, for
example in an import.

## The buying box

```django
{% load catalogue_client_tags %}
<link rel="stylesheet" href="{% static 'catalogue_client/css/purchase.css' %}">
{% product_purchase product %}
<script src="{% static 'catalogue_client/js/purchase.js' %}" defer></script>
```

It is the purchase box of catalogue-server's own product pages, with the same
`bk-*` markup:

- the best seller delivering to the visitor's country as the main button, and
  "View N other sellers", which opens every other seller in a side panel
  (sellers in the same region first);
- when no seller delivers to the country, a note saying so and a button to the
  nearest distributor that can be contacted (in the country, then in its
  region), else `CATALOGUE_CLIENT_CONTACT_URL`, with the sellers still one click
  away in the side panel;
- a country picker (the countries some seller delivers to), stored in a cookie;
- links to the work's other formats and languages: to this site's own page for
  the editions it offers, else to the server's page.

Colours and fonts follow the `--bk-*` custom properties when the site defines
them. Override any `catalogue_client/includes/*.html` template to change the
markup.

## Reading products

```python
from catalogue_client.utils import products_for_request, product_for_request

products = products_for_request(request, kinds=["book"], attributes={"language": "en"})
product = product_for_request(request, "trail-pack")
```

Filters: `slugs`, `kinds`, `groups`, `q`, and `attributes` (attribute name to one
value or a list of values). Calls made for a request pass the visitor's country
(`?country=`, the country picker's cookie, Cloudflare's `CF-IPCountry`, then the site's country), because
without one the server would locate the web server instead of the visitor.
`products_for_site(site)` does the same outside a request, for example in a
management command.

A `Product` has the server's generic fields (`slug`, `title`, `subtitle`,
`kind`, `identifier`, `group`, `short_description`, `description_html`,
`image_url`, `images`, `url`, `external_url`, `related_products`) and a
`purchase` (`PurchaseInfo`: price, `Offer`s, store links, note, distributors).
`editions_for_request(request, product)` returns the other editions of its work.

## Fields the server site added

A catalogue-server site keeps its own fields in each product's `attributes`
(a books site stores the author, language, publisher and so on there), and can
add computed keys to the API with its `CATALOGUE_API_PRODUCT_DATA` setting. Both
reach the client unchanged: `product.attributes` and `product.attr("author")`
for the first, `product.extra` for the second.

To give them names, subclass `Product` in your own app and point
`CATALOGUE_CLIENT_PRODUCT_CLASS` at it. Every product the client returns is then
built as your class:

```python
# my_site/catalogue.py
from catalogue_client.models import Product


class Book(Product):
    @property
    def author(self):
        return self.attr("author")

    @property
    def choice_label(self):  # how CatalogueProductChoiceField lists it
        return f"{self.title} ({self.attr('language').upper()})"
```

```python
CATALOGUE_CLIENT_PRODUCT_CLASS = "my_site.catalogue.Book"
```

Filters that always apply to a site, such as showing a Czech site only Czech
editions, come from `CATALOGUE_CLIENT_SITE_FILTERS`, a function
`(site) -> dict` of the filters above.

## Settings

| Setting | Default | |
|---|---|---|
| `CATALOGUE_SERVER_URL` | `""` | catalogue-server origin, no path; used until a server is chosen in the admin |
| `CATALOGUE_CLIENT_API_PATH` | `/api/v1/` | |
| `CATALOGUE_CLIENT_TIMEOUT` | `10` | seconds |
| `CATALOGUE_CLIENT_CACHE_SECONDS` | `3600` | |
| `CATALOGUE_CLIENT_DEFAULT_COUNTRY` | `""` | when neither the request nor the site gives one |
| `CATALOGUE_CLIENT_TEMPLATE` | `catalogue_client/product_list.html` | template of the list page (context: `products`, `selected_product`, `catalogue_error`) |
| `CATALOGUE_CLIENT_PRODUCT_CLASS` | `""` | dotted path to your `Product` subclass |
| `CATALOGUE_CLIENT_SITE_FILTERS` | `""` | dotted path to `(site) -> dict` of filters |
| `CATALOGUE_CLIENT_SELECTED_WORKS_ONLY` | `True` | sites only get the editions of the selected works |
| `CATALOGUE_CLIENT_CATEGORY_ATTRIBUTE` | `""` | product attribute that puts works into categories in the works admin |
| `CATALOGUE_CLIENT_CATEGORY_LABELS` | `{}` | category value to label |
| `CATALOGUE_CLIENT_LANGUAGE_ATTRIBUTE` | `"language"` | product attribute with an ISO 639-1 code |
| `CATALOGUE_CLIENT_DEFAULT_LANGUAGE` | `""` | language of products without one |
| `CATALOGUE_CLIENT_KIND_LABELS` | `{}` | product kind to label in the edition links |
| `CATALOGUE_CLIENT_DEFAULT_KIND` | `""` | kind of products without one |
| `CATALOGUE_CLIENT_PRODUCT_NOUN` | `"product"` | "… deliver this product to …" |
| `CATALOGUE_CLIENT_CONTACT_URL` | `""` | contact for visitors no seller or distributor serves |
| `CATALOGUE_CLIENT_CONTACT_LABEL` | `"Contact us"` | its button |
| `CATALOGUE_CLIENT_CONTACT_HELP` | `"We can help you find a local option."` | the sentence after the "no seller" note |
| `CATALOGUE_CLIENT_COUNTRY_COOKIE` | `"catalogue_country"` | the country picker's cookie |

The site's country is read from `site.domain_settings.country` when the site
uses [servicetrees](https://gitlab.com/vasudeva-server/servicetrees); otherwise
set `CATALOGUE_CLIENT_DEFAULT_COUNTRY`.

## Status

Unmaintained and provided as-is. There is no commitment to updates, security fixes, or support.

Pull requests are welcome and may be looked at. There is no commitment to review or merge them. Contributions are licensed under the same license as this project.

Issues are not tracked. Please do not open bug reports or feature requests.

This repository is [MIT licensed](LICENSE).

[Vasudeva Server](https://vasudevaserver.org)
