From 9ca543877c908d7f8c2c6dce6db46cd34b1b09c3 Mon Sep 17 00:00:00 2001 From: CoprDistGit Date: Wed, 31 May 2023 05:53:22 +0000 Subject: automatic import of python-flask-taxonomies --- .gitignore | 1 + python-flask-taxonomies.spec | 4625 ++++++++++++++++++++++++++++++++++++++++++ sources | 1 + 3 files changed, 4627 insertions(+) create mode 100644 python-flask-taxonomies.spec create mode 100644 sources diff --git a/.gitignore b/.gitignore index e69de29..1a9efaf 100644 --- a/.gitignore +++ b/.gitignore @@ -0,0 +1 @@ +/flask-taxonomies-7.0.3.tar.gz diff --git a/python-flask-taxonomies.spec b/python-flask-taxonomies.spec new file mode 100644 index 0000000..9596a2b --- /dev/null +++ b/python-flask-taxonomies.spec @@ -0,0 +1,4625 @@ +%global _empty_manifest_terminate_build 0 +Name: python-flask-taxonomies +Version: 7.0.3 +Release: 1 +Summary: please add a summary manually as the author left a blank one +License: Creative Commons Attribution-Noncommercial-Share Alike license +URL: https://pypi.org/project/flask-taxonomies/ +Source0: https://mirrors.nju.edu.cn/pypi/web/packages/90/97/7f40fc23f026daed1afe657e2ea092dd87e868a25b0dcd722cdf2e4fb820/flask-taxonomies-7.0.3.tar.gz +BuildArch: noarch + +Requires: python3-flask +Requires: python3-flask-sqlalchemy +Requires: python3-blinker +Requires: python3-sqlalchemy-utils +Requires: python3-slugify +Requires: python3-jsonpatch +Requires: python3-webargs +Requires: python3-jsonpointer +Requires: python3-LinkHeader +Requires: python3-flask-principal +Requires: python3-luqum +Requires: python3-flask-migrate +Requires: python3-psycopg2 +Requires: python3-pytest +Requires: python3-pytest-flask-sqlalchemy +Requires: python3-md-toc +Requires: python3-isort +Requires: python3-check-manifest +Requires: python3-pytest-coverage +Requires: python3-pytest-pep8 + +%description +# Flask Taxonomies + +[![](https://img.shields.io/github/license/oarepo/flask-taxonomies.svg)](https://github.com/oarepo/flask-taxonomies/blob/master/LICENSE) +[![](https://img.shields.io/travis/oarepo/flask-taxonomies.svg)](https://travis-ci.org/oarepo/flask-taxonomies) +[![](https://img.shields.io/coveralls/oarepo/flask-taxonomies.svg)](https://coveralls.io/r/oarepo/flask-taxonomies) +[![](https://img.shields.io/pypi/v/flask-taxonomies.svg)](https://pypi.org/pypi/flask-taxonomies) + + + +- [Flask Taxonomies](#flask-taxonomies) + - [Installation](#installation) + - [Terminology](#terminology) + - [REST API](#rest-api) + - [Retrieving resources](#retrieving-resources) + - [``Prefer`` HTTP header](#prefer-http-header) + - [Returned representation](#returned-representation) + - [Includes and excludes](#includes-and-excludes) + - [Including extra data](#including-extra-data) + - [Excluding data](#excluding-data) + - [Query parameters](#query-parameters) + - [Selecting subset of term data](#selecting-subset-of-term-data) + - [Pagination](#pagination) + - [Taxonomy](#taxonomy) + - [Creating](#creating) + - [Updating](#updating) + - [Replacing via HTTP PUT](#replacing-via-http-put) + - [Patching with HTTP POST](#patching-with-http-post) + - [Deleting](#deleting) + - [Taxonomy Term](#taxonomy-term) + - [Creating](#creating-1) + - [Updating](#updating-1) + - [Deleting](#deleting-1) + - [Un-Deleting](#un-deleting) + - [Moving](#moving) + - [Renaming](#renaming) + - [Configuration](#configuration) + - [Configuration Variables](#configuration-variables) + - [Security](#security) + - [Recommended initial settings](#recommended-initial-settings) + - [Python API](#python-api) + - [Signals](#signals) + + + +## Installation + +```bash +pip install flask-taxonomies +``` + +```python +from flask_taxonomies.ext import FlaskTaxonomies +from flask_taxonomies.views import blueprint +from flask import Flask +from flask_principal import Principal + +app = Flask('__test__') + +FlaskTaxonomies(app) +Principal(app) +app.register_blueprint(blueprint, url_prefix=app.config['FLASK_TAXONOMIES_URL_PREFIX']) + +db = ... +from flask_taxonomies.models import Base +Base.metadata.create_all(db.engine) +``` + +## Terminology + +**Taxonomy** is a tree of taxonomy terms. It is represented as a database object identified by +*code*. A taxonomy may contain its original url (in case the taxonomy is defined elsewhere) +and additional metadata as a json object (containing, for example, taxonomy title). It may also +contain a default set of selectors for filtering metadata. + +**TaxonomyTerm** represents a single node in a taxonomy. It is identified by its *slug* +and may contain additional metadata as json object. A term can contain children to represent +hierarchy of taxonomy terms. Term does not define ordering within children, it is up to +application logic to define any ordering. + +## REST API + +The rest API sits on the ``app.config['FLASK_TAXONOMIES_URL_PREFIX']`` url, implicitly +``/api/2.0/taxonomies/``. It follows the REST API principles with pagination inspired +by GitHub API. + +### Retrieving resources + +#### ``Prefer`` HTTP header + +Implicitly, the API returns rather minimal representation. The amount of the returned metadata +can be changed via HTTP ``prefer`` header or alternatively by query parameters. + +##### Returned representation + +The ``prefer`` header is a standard way of telling what you expect to get +as a response to the request. It is defined in [rfc7240](https://tools.ietf.org/html/rfc7240). + +If the header is not present, ``return=representation`` is assumed. One can specify ``return=minimal`` +to obtain minimal dataset, or other return types (even your own) defined in ``FLASK_TAXONOMIES_REPRESENTATION`` +config. + +**return=minimal** + +returns the minimal representation. Mostly not usable directly as it does not return any metadata, +just the code/slug. + +*Listing:* +```console +$ curl -i -H "Prefer: return=minimal" http://127.0.0.1:5000/api/2.0/taxonomies/ + +HTTP/1.0 200 OK +Link: ; rel=self + +[ + { + "code": "country" + } +] +``` + +*Get taxonomy:* +```console +$ curl -i -H "Prefer: return=minimal" http://127.0.0.1:5000/api/2.0/taxonomies/country + +HTTP/1.0 200 OK +Content-Type: application/json + +{ + "code": "country" +} +``` + +*Get term:* +```console +$ curl -i -H "Prefer: return=minimal" http://127.0.0.1:5000/api/2.0/taxonomies/country/europe + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +{ + "slug": "europe" +} +``` + +**return=representation** + +this is the default return type. Returns all user data declared on taxonomy/term together with +ancestor and urls. For example: + +*Listing:* +```console +$ curl -i http://127.0.0.1:5000/api/2.0/taxonomies/ +HTTP/1.0 200 OK +Link: ; rel=self + +[ + { + "code": "country", + "links": { + "custom": "https://www.kaggle.com/nikitagrec/world-capitals-gps/data", + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/country/" + }, + "title": "List of countries" + } +] +``` + +*Get term:* +```console +$ curl -i http://127.0.0.1:5000/api/2.0/taxonomies/country/europe/cz +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +{ + "CapitalLatitude": "50.083333333333336", + "CapitalLongitude": "14.466667", + "CapitalName": "Prague", + "ContinentName": "Europe", + "CountryCode": "CZ", + "CountryName": "Czech Republic", + "ancestors": [ + { + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/country/europe" + } + } + ], + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/country/europe/cz" + } +} +``` + +``Europe`` has no user data, so it contains only the ``links`` section. + +##### Includes and excludes + +The returned representation can be modified by specifying which metadata should be included/excluded. +Currently supported includes/excludes are: + +```python +INCLUDE_URL = 'url' +INCLUDE_DESCENDANTS_URL = 'drl' +INCLUDE_DESCENDANTS_COUNT = 'dcn' +INCLUDE_ANCESTORS_HIERARCHY = 'anh' +INCLUDE_ANCESTORS = 'anc' +INCLUDE_ANCESTOR_LIST = 'anl' +INCLUDE_DATA = 'data' +INCLUDE_ID = 'id' +INCLUDE_DESCENDANTS = 'dsc' +INCLUDE_ENVELOPE='env' +INCLUDE_DELETED = 'del' +INCLUDE_SLUG = 'slug' +INCLUDE_LEVEL = 'lvl' +INCLUDE_STATUS = 'sta' +``` + +###### Including extra data + +Examples: + +**Include record url in response** + +```console +$ curl -i -H "Prefer: return=minimal; include=url" \ + http://127.0.0.1:5000/api/2.0/taxonomies/country/europe + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +{ + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/country/europe" + }, + "slug": "europe" +} +``` + +Adds a ``links`` section to payload with record url (``"self":``) + +**Include descendants url in response** + +```console +$ curl -i -H "Prefer: return=minimal; include=url drl" \ + http://127.0.0.1:5000/api/2.0/taxonomies/country/europe + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +{ + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/country/europe", + "tree": "http://127.0.0.1:5000/api/2.0/taxonomies/country/europe?representation:include=dsc" + }, + "slug": "europe" +} +``` + +Adds a ``links`` section to payload with recoord url with descendants (``"tree":``) + +**Include descendants count in response** + +```console +$ curl -i -H "Prefer: return=minimal; include=dcn" \ + http://127.0.0.1:5000/api/2.0/taxonomies/country/europe + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +{ + "slug": "europe", + "descendants_count": 58 +} +``` + +Adds a ``descendant_count`` with a number of descendant terms under the term. The value is 0 +if the term is a leaf term. + +On taxonomy, returns the total number of terms in taxonomy: + +```console +$ curl -i -H "Prefer: return=minimal; include=dcn" \ + http://127.0.0.1:5000/api/2.0/taxonomies/country + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +{ + "code": "country", + "descendants_count": 253 +} +``` + +**Include ancestors with hierarchy in response** + +```console +$ curl -i -H "Prefer: return=minimal; include=anh url" \ + http://127.0.0.1:5000/api/2.0/taxonomies/country/europe/cz + +{ + "children": [ + { + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/country/europe/cz" + }, + "slug": "europe/cz" + } + ], + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/country/europe" + }, + "slug": "europe", + "ancestor": true +} +``` + +If the term has ancestors, they are serialized and the term is included as their +child. This is useful for example when showing the taxonomy in tree form - the +rendering mechanism for the tree will stay the same. All ancestor terms are marked +with ``ancestor=true`` flag to help with ui rendering (for example to gray ancestors). + +Adding url as well is recommended to get urls of ancestors. + +**Include ancestors without hierarchy in response** + +```console +$ curl -i -H "Prefer: return=minimal; include=anc url" \ + http://127.0.0.1:5000/api/2.0/taxonomies/country/europe/cz + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +{ + "ancestors": [ + { + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/country/europe" + }, + "slug": "europe" + } + ], + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/country/europe/cz" + }, + "slug": "europe/cz" +} +``` +The ancestors are rendered inside the ``ancestors`` element. Adding url as well +is recommended to get urls of ancestors. + +**Include ancestor list in response** + +```console +$ curl -i -H "Prefer: return=representation; include=anl ant par" \ + http://127.0.0.1:5000/api/2.0/taxonomies/country/europe/cz + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +[ + { + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/country/europe" + }, + "is_ancestor": true + }, + { + "CapitalLatitude": "50.083333333333336", + "CapitalLongitude": "14.466667", + "CapitalName": "Prague", + "ContinentName": "Europe", + "CountryCode": "CZ", + "CountryName": "Czech Republic", + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/country/europe/cz", + "parent": "http://127.0.0.1:5000/api/2.0/taxonomies/country/europe" + }, + "is_ancestor": false + } +] +``` +The ancestors are rendered on the same level as the term. This rendering might be used +for example when serializing the taxonomy term to elasticsearch - this way all the +ancestors are serialized within one array and indexed into one object in ES. + +Note also the: + * ``ant`` - adds ``ancestor`` field (``false`` if this is the term that has been queried), + ``true`` for ancestor term + * ``parent`` - adds link to the parent within ``links`` section + +Even one-term result is rendered as array: + +```console +$ curl -i -H "Prefer: return=representation; include=anl ant par" \ + http://127.0.0.1:5000/api/2.0/taxonomies/country/europe + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +[ + { + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/country/europe" + }, + "is_ancestor": false + } +] +``` + +**Include data in response** + +This is the default setting unless ``minimal`` representation is selected. In this case, +pass ``include=data`` to have data included. + +```console +$ curl -i -H "Prefer: return=minimal; include=data" \ + http://127.0.0.1:5000/api/2.0/taxonomies/country/europe/cz + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +{ + "CapitalLatitude": "50.083333333333336", + "CapitalLongitude": "14.466667", + "CapitalName": "Prague", + "ContinentName": "Europe", + "CountryCode": "CZ", + "CountryName": "Czech Republic", + "slug": "europe/cz" +} +``` + +**Include id in response** + +Use ``include=id`` to get the internal id included. This is rarely needed as API does not accept +this id at all. + +**Include descendant terms in response** + +To serialize descendants into the response, use ``include=dsc``: + +```console +$ curl -i -H "Prefer: return=minimal; include=dsc" \ + http://127.0.0.1:5000/api/2.0/taxonomies/country/europe + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +{ + "children": [ + { + "slug": "europe/ad" + }, + { + "slug": "europe/al" + }, + ... + { + "slug": "europe/va" + } + ], + "slug": "europe" +} +``` + +**Include slug in response** + +Adds ``slug`` to response. In the ``minimal`` mode the slug is added automatically, use this tag to +add it in ``return=representation``: + +```console +$ curl -i -H "Prefer: return=representation; include=slug" \ + http://127.0.0.1:5000/api/2.0/taxonomies/country/europe/cz + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +{ + "CapitalLatitude": "50.083333333333336", + "CapitalLongitude": "14.466667", + "CapitalName": "Prague", + "ContinentName": "Europe", + "CountryCode": "CZ", + "CountryName": "Czech Republic", + "ancestors": [ + { + "links":{"self":"http://127.0.0.1:5000/api/2.0/taxonomies/country/europe"}, + "slug": "europe" + } + ], + "links":{"self":"http://127.0.0.1:5000/api/2.0/taxonomies/country/europe/cz"}, + "slug": "europe/cz" +} +``` + +**Include hierarchy level in response** + +Adds hierarchy level to taxonomy term. Top-level terms have ``level=1``, taxonomy ``0``. + +```console +$ curl -i -H "Prefer: return=minimal; include=lvl" \ + http://127.0.0.1:5000/api/2.0/taxonomies/country/europe/cz + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +{ + "level": 2, + "slug": "europe/cz" +} +``` + +**Include deleted terms in response** + +Let's delete a country from Europe: + +```console +$ curl -X DELETE -i http://127.0.0.1:5000/api/2.0/taxonomies/country/europe/gb + +HTTP/1.0 200 OK +Content-Type: application/json + +{ + "CapitalLatitude": "51.5", + "CapitalLongitude": "-0.083333", + "CapitalName": "London", + "ContinentName": "Europe", + "CountryCode": "GB", + "CountryName": "United Kingdom", + "links":{"self":"http://127.0.0.1:5000/api/2.0/taxonomies/country/europe/gb"} +} +``` + +United Kingdom has indeed been removed from Europe: + +```console +$ curl -i http://127.0.0.1:5000/api/2.0/taxonomies/country/europe/gb +HTTP/1.0 410 GONE + +{ + "message": "http://127.0.0.1:5000/api/2.0/taxonomies/country/europe/gb was not found on the server", + "reason": "deleted" +} +``` + +Now run the GET again with removed terms included: + +```console +$ curl -i -H "Prefer: return=minimal; include=del sta" \ + http://127.0.0.1:5000/api/2.0/taxonomies/country/europe/gb + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +{ + "slug": "europe/gb", + "status": "deleted", + "busy_count":0, + "descendants_busy_count":0 +} +``` + +**Include term status** + +Including the status will add the following metadata: + * ``status`` of the term (alive, delete_pending, deleted, moved) + * ``busy_count`` - an integer saying how "busy" the term is. Being busy means + that a potentially destructive operation (such as deleting, moving or renaming slug) + is in progress. + * ``descendants_busy_count`` - a number of descendants that are busy + +```console +$ curl -i -H "Prefer: return=minimal; include=del sta dsc" \ + http://127.0.0.1:5000/api/2.0/taxonomies/country/europe + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +{ + "busy_count":0, + "children": [ + { + "busy_count":0, + "descendants_busy_count":0, + "slug": "europe/ad", + "status": "alive" + }, + ... + { + "busy_count":0, + "descendants_busy_count":0, + "slug": "europe/gb", + "status": "deleted" + }, + ... + { + "busy_count":0, + "descendants_busy_count":0, + "slug": "europe/va", + "status": "alive" + } + ], + "descendants_busy_count":0, + "slug": "europe", + "status": "alive" +} +``` + +###### Excluding data + +To exclude data from the representation, use ``exclude=...`` in prefer header. + +#### Query parameters + +Values from the ``prefer`` header can be used as query parameters: + +``` +curl -i -H "Prefer: return=minimal;" \ + "http://127.0.0.1:5000/api/2.0/taxonomies/country/europe? + representation:include=sta,url&representation:exclude=slug" + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +{ + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/country/europe" + }, + "status": "alive" +} +``` + +#### Selecting subset of term data + +Use ``select= ...`` in ``prefer`` header or ``representation:select=`` query parameter +to select just part of user data: + +```console +$ curl -i -H "Prefer: return=representation;select=/CapitalName /CountryCode" \ + "http://127.0.0.1:5000/api/2.0/taxonomies/country/europe/cz" + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +{ + "CapitalName": "Prague", + "CountryCode": "CZ", + "ancestors": [ + { + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/country/europe" + } + } + ], + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/country/europe/cz" + } +} +``` + +#### Maximum levels + +When descendants are selected, a maximum level of descendants can be specified via +``levels=`` part of ``prefer`` header (or representation:levels=n in the query). + +Example: + + ```console +$ curl -i -H "Prefer: return=representation; levels=1" \ + "http://127.0.0.1:5000/api/2.0/taxonomies/country" + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +{ + 'children': [ + {'slug': 'africa'}, + {'slug': 'antarctica'}, + {'slug': 'asia'}, + {'slug': 'australia'}, + {'slug': 'central-america'}, + {'slug': 'europe'}, + {'slug': 'north-america'}, + {'slug': 'south-america'} + ], + 'code': 'country', + 'title': 'List of countries' +} +``` + +#### Pagination + +If descendants are requested without further arguments the whole tree is returned (well, in fact +at most FLASK_TAXONOMIES_MAX_RESULTS_RETURNED terms to prevent server crash). This leads to high amount +of data transferred and possibly a client crash. To prevent this, pagination should be used on larger +taxonomies. + +Specify ``size`` argument to have at most this number of taxonomy terms returned. For example: + +```console +$ curl -i -H "Prefer: return=minimal;" \ + "http://127.0.0.1:5000/api/2.0/taxonomies/country/europe?representation:include=dsc&size=5" + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree +X-Page: 1 +X-PageSize: 5 +X-Total: 58 + +{ + "children": [ + { + "slug": "europe/ad" + }, + { + "slug": "europe/al" + }, + { + "slug": "europe/am" + }, + { + "slug": "europe/at" + } + ], + "slug": "europe" +} +``` + +This returns 5 terms - europe and 4 children. To return the next page, add ``page=2`` argument: + +```console +$ curl -i -H "Prefer: return=minimal;" \ + "http://127.0.0.1:5000/api/2.0/taxonomies/country/europe?representation:include=dsc&size=5&page=4" + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree +X-Page: 4 +X-PageSize: 5 +X-Total: 58 + +[ + { + "slug": "europe/dk" + }, + { + "slug": "europe/ee" + }, + { + "slug": "europe/es" + }, + { + "slug": "europe/fi" + }, + { + "slug": "europe/fo" + } +] +``` + +Note that the first page contains the root element and the second does not. To fix it, either use ``include=anh`` +to always get hierarchical representation: + +```console +$ curl -i -H "Prefer: return=minimal;" \ + "http://127.0.0.1:5000/api/2.0/taxonomies/country/europe?representation:include=dsc,anh&size=5&page=2" + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree +X-Page: 2 +X-PageSize: 5 +X-Total: 58 + +{ + "ancestor": true, + "children": [ + { + "slug": "europe/ax" + }, + { + "slug": "europe/az" + }, + { + "slug": "europe/ba" + }, + { + "slug": "europe/be" + } + ], + "slug": "europe" +} +``` + +or, if interested only in descendants and not the node itself, ``exclude=self`` + +```console +$ curl -i -H "Prefer: return=minimal;" \ + "http://127.0.0.1:5000/api/2.0/taxonomies/country/europe? + representation:include=dsc&representation:exclude=self&size=5&page=1" + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree +X-Page: 1 +X-PageSize: 5 +X-Total: 58 + +[ + { + "slug": "europe/ad" + }, + { + "slug": "europe/al" + }, + { + "slug": "europe/am" + }, + { + "slug": "europe/at" + }, + { + "slug": "europe/ax" + } +] +``` + +#### Searching + +Use ``q=`` parameter to search within terms. Returns all the resources +whose ``metadata`` contain the expression in q. + +##### Simple query + +If ``q`` is a simple string not containing ':' or string in quotes, +it is interpreted as a string that must be present in any of +values inside the json. + +The current implementation is dependent on the database backend +and might perform sub-optimal ``ilike %x%`` query on textified json. + +```console +$ curl -i -H "Prefer: return=minimal; include=data dsc; exclude=self" \ + http://127.0.0.1:5000/api/2.0/taxonomies/country/europe?q=Prague + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +[ + { + "CapitalLatitude": "50.083333333333336", + "CapitalLongitude": "14.466667", + "CapitalName": "Prague", + "ContinentName": "Europe", + "CountryCode": "CZ", + "CountryName": "Czech Republic", + "slug": "europe/cz" + } +] +``` + +##### Lucene-like query + +If the ``q`` contains a ':' character not enclosed in quotes, +it is parsed as a query in lucene syntax, with the following +allowed constructs: + + * path:value for matching value at the given path + * a.b.c:value for representing nested paths + * AND, OR, NOT, brackets + +The query will be executed if the database or search backend support +it. If not supported, HTTP 501 will be returned. + +### Taxonomy +#### Creating + +To create a taxonomy, either use HTTP PUT: + +```console +$ curl -i -X PUT 'http://127.0.0.1:5000/api/2.0/taxonomies/test' \ +-H 'Content-Type: application/json' \ +--data-raw '{ + "title": "Test taxonomy" +}' + +HTTP/1.0 201 CREATED +Link: ; rel=self +Link: ; rel=tree +Location: http://127.0.0.1:5000/api/2.0/taxonomies/test/ + +{ + "code": "test", + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/test/" + }, + "title": "Test taxonomy" +} +``` + +Or HTTP POST with ``code`` in the payload + +```console +$ curl -i -X POST 'http://127.0.0.1:5000/api/2.0/taxonomies/' \ + -H 'Content-Type: application/json' --data-raw '{ + "title": "Test taxonomy 1", "code": "test1" +}' + +HTTP/1.0 201 CREATED +Link: ; rel=self +Link: ; rel=tree +Location: http://127.0.0.1:5000/api/2.0/taxonomies/test1/ + +{ + "code": "test1", + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/test1/" + }, + "title": "Test taxonomy 1" +} +``` + + +#### Updating + +##### Replacing via HTTP PUT + +```console +$ curl -i -X PUT 'http://127.0.0.1:5000/api/2.0/taxonomies/test' \ + --header 'Content-Type: application/json' --data-raw '{ + "title": "Test taxonomy updated" +}' + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +{ + "code": "test", + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/test/" + }, + "title": "Test taxonomy updated" +} +``` + +Note that terms are not updated nor removed when taxonomy metadata are updated. + +##### Patching with HTTP PATCH + +```console +$ curl -i -X PATCH 'http://127.0.0.1:5000/api/2.0/taxonomies/test' \ + --header 'Content-Type: application/json' --data-raw '[{ + "op": "replace", "path": "/title", "value": "Test taxonomy updated via patch" +}]' + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +{ + "code": "test", + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/test/" + }, + "title": "Test taxonomy updated via patch" +} +``` + +#### Deleting + +```console +$ curl -i -X DELETE 'http://127.0.0.1:5000/api/2.0/taxonomies/test1' + +HTTP/1.0 204 NO CONTENT +Content-Type: text/html; charset=utf-8 +``` + +### Taxonomy Term +#### Creating + +As in creating taxonomy, term can be created either via HTTP PUT: + +```console +$ curl -i -X PUT 'http://127.0.0.1:5000/api/2.0/taxonomies/test/term' \ + --header 'Content-Type: application/json' --data-raw '{ + "title": "Test Term" +}' + +HTTP/1.0 201 CREATED +Link: ; rel=self +Link: ; rel=tree +Location: http://127.0.0.1:5000/api/2.0/taxonomies/test/term + +{ + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/test/term" + }, + "title": "Test Term" +} +``` + +or POST: + +```console +$ curl -i -X POST 'http://127.0.0.1:5000/api/2.0/taxonomies/test' \ + --header 'Content-Type: application/json' --data-raw '{ + "title": "Test term 1", "slug": "term1" +}' + +HTTP/1.0 201 CREATED +Link: ; rel=self +Link: ; rel=tree +Location: http://127.0.0.1:5000/api/2.0/taxonomies/test/term1 + +{ + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/test/term1" + }, + "title": "Test term 1" +} +``` + +As PUT/POST operation also mean updating term if slug exists, to be sure +that you are creating a new one use ``If-None-Match: '*'`` header: + +```console +$ curl -i -X PUT 'http://127.0.0.1:5000/api/2.0/taxonomies/test/term' \ + --header 'Content-Type: application/json' \ + --header 'If-None-Match: '*'' \ + --data-raw '{ + "title": "Test Term" +}' + +HTTP/1.0 412 Precondition Failed + +{ + "message": "The taxonomy already contains a term on this slug. As If-None-Match: '*' has been requested, not modifying the term", + "reason": "term-exists" +} +``` + +Terms can be created within terms via HTTP PUT: + +```console +$ curl -i -X PUT 'http://127.0.0.1:5000/api/2.0/taxonomies/test/term/nested' \ + --header 'Content-Type: application/json' --data-raw '{ + "title": "Nested Term" +}' + +HTTP/1.0 201 CREATED +Link: ; rel=self +Link: ; rel=tree +Location: http://127.0.0.1:5000/api/2.0/taxonomies/test/term/nested + +{ + "ancestors":[ + { + "links":{ + "self":"http://127.0.0.1:5000/api/2.0/taxonomies/test/term" + }, + "title":"Test Term" + } + ], + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/test/term/nested" + }, + "title": "Nested Term" +} +``` + +or POST: + +```console +$ curl -i -X POST 'http://127.0.0.1:5000/api/2.0/taxonomies/test/term1' \ + --header 'Content-Type: application/json' --data-raw '{ + "title": "Test nested term 1", "slug": "nested1" +}' + +HTTP/1.0 201 CREATED +Link: ; rel=self +Link: ; rel=tree +Location: http://127.0.0.1:5000/api/2.0/taxonomies/test/term1/nested1 + +{ + "ancestors":[ + { + "links":{ + "self":"http://127.0.0.1:5000/api/2.0/taxonomies/test/term1" + }, + "title":"Test term 1" + } + ], + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/test/term1/nested1" + }, + "title": "Test nested term 1" +} +``` + +#### Updating + +As for taxonomy, use HTTP ``PUT``: + +```console +$ curl -i -X PUT 'http://127.0.0.1:5000/api/2.0/taxonomies/test/term' \ + --header 'Content-Type: application/json' --data-raw '{ + "title": "Test Term updated" +}' + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +{ + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/test/term" + }, + "title": "Test Term updated" +} +``` + +or ``PATCH``: + +```console +$ curl -i -X PATCH 'http://127.0.0.1:5000/api/2.0/taxonomies/test/term' \ + --header 'Content-Type: application/json' --data-raw '[{ + "op": "replace", "path": "/title", "value": "Test taxonomy term updated via patch" +}]' + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +{ + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/test/term" + }, + "title": "Test taxonomy term updated via patch" +} +``` + +As PUT operation also means creating term if slug does not exist, to be sure +that you are just updating use ``If-Match: '*'`` header: + +```console +$ curl -i -X PUT 'http://127.0.0.1:5000/api/2.0/taxonomies/test/unknown' \ + --header 'Content-Type: application/json' \ + --header 'If-Match: '*'' \ + --data-raw '{ + "title": "Test Term" +}' + +HTTP/1.0 412 Precondition Failed + +{ + "message": "The taxonomy does not contain a term on this slug. As If-Match: '*' has been requested, not creating a new term", + "reason": "term-does-not-exist" +} +``` + + +#### Deleting + +Use HTTP delete to remove a term. The removed term will be returned in the response: + +```console +$ curl -i -X DELETE 'http://127.0.0.1:5000/api/2.0/taxonomies/test/term1' + +HTTP/1.0 200 OK +Content-Type: application/json + +{ + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/test/term1" + }, + "title": "Test term 1" +} +``` + +Subsequent GET returns 410: + +```console +$ curl -i 'http://127.0.0.1:5000/api/2.0/taxonomies/test/term1' + +HTTP/1.0 410 GONE + +{ + "message": "http://127.0.0.1:5000/api/2.0/taxonomies/test/term1 was not found on the server", + "reason": "deleted" +} +``` + +But the term stays on the server: + +```console +$ curl -i 'http://127.0.0.1:5000/api/2.0/taxonomies/test/term?representation:include=del' +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +{ + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/test/term" + }, + "title": "Test taxonomy term updated via patch" +} +``` + +#### Un-Deleting + +To salvage a deleted term, update it via PATCH, with an empty set of operations to keep it unmodified: + +```console +$ curl -i -X PATCH -H "Prefer: return=minimal; include=del" \ + 'http://127.0.0.1:5000/api/2.0/taxonomies/test/term' \ + --header 'Content-Type: application/json' --data-raw '[]' + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +{ + "slug": "term" +} +``` + +#### Moving + +Use HTTP post with content type ``application/vnd.move`` and ``Destination`` header: + +```console +$ curl -i -X POST \ + -H 'Content-Type: application/vnd.move' \ + -H "Destination: /" \ + 'http://127.0.0.1:5000/api/2.0/taxonomies/test/term/nested' + +HTTP/1.0 200 OK + +{ + "links":{ + "self":"http://127.0.0.1:5000/api/2.0/taxonomies/test/nested" + }, + "title":"Nested Term" +} +``` + +The original url returns 301: + +```console +$ curl -i 'http://127.0.0.1:5000/api/2.0/taxonomies/test/term/nested' + +HTTP/1.0 301 MOVED PERMANENTLY +Location: http://127.0.0.1:5000/api/2.0/taxonomies/test/nested +Link: ; rel=self +Link: ; rel=obsoleted_by + +{ + "links": { + "self": "http://localhost/api/2.0/taxonomies/test/term/nested", + "obsoleted_by": "http://localhost/api/2.0/taxonomies/test/nested" + }, + "status": "moved" +} +``` + +#### Renaming + +Use HTTP post with content type ``application/vnd.move`` and ``Rename`` header: + +```console +$ curl -i -X POST \ + -H 'Content-Type: application/vnd.move' \ + -H "Rename: renamed-nested" \ + 'http://127.0.0.1:5000/api/2.0/taxonomies/test/nested' + +HTTP/1.0 200 OK + +{ + "links":{ + "self":"http://127.0.0.1:5000/api/2.0/taxonomies/test/renamed-nested" + }, + "title":"Nested Term" +} +``` + + +The original url returns 301: + +```console +$ curl -i 'http://127.0.0.1:5000/api/2.0/taxonomies/test/nested' + +HTTP/1.0 301 MOVED PERMANENTLY +Location: http://127.0.0.1:5000/api/2.0/taxonomies/test/renamed-nested +Link: ; rel=self +Link: ; rel=obsoleted_by + +{ + "links": { + "self": "http://localhost/api/2.0/taxonomies/test/nested", + "obsoleted_by": "http://localhost/api/2.0/taxonomies/test/renamed-nested" + }, + "status": "moved" +} +``` + + +## Configuration + +### Configuration Variables + +``FLASK_TAXONOMIES_SERVER_NAME`` + +Server name hosting the taxonomies. If not set, SERVER_NAME is used. + +``FLASK_TAXONOMIES_SERVER_SCHEME`` + +Scheme to use in generated urls, defaults to ``https`` + +``FLASK_TAXONOMIES_URL_PREFIX`` + +A prefix on which taxonomies are served, defaults to ``/api/2.0/taxonomies/`` + +``FLASK_TAXONOMIES_REPRESENTATION`` + +Values for ``Prefer: return=`` header. ``minimal`` and ``representation`` are obligatory, +you are free to add other return representations. + +```python +from flask_taxonomies.constants import * + +FLASK_TAXONOMIES_REPRESENTATION = { + 'minimal': { + 'include': [INCLUDE_SLUG, INCLUDE_SELF], + 'exclude': [], + 'select': None, + 'options': {} + }, + 'representation': { + 'include': [INCLUDE_DATA, INCLUDE_ANCESTORS, + INCLUDE_URL, INCLUDE_SELF], + 'exclude': [], + 'select': None, + 'options': {} + }, + 'full': { + 'include': [INCLUDE_DATA, INCLUDE_ANCESTORS, INCLUDE_URL, + INCLUDE_DESCENDANTS_URL, INCLUDE_SELF], + 'exclude': [], + 'select': None, + 'options': {} + } +} +``` + +``FLASK_TAXONOMIES_MAX_RESULTS_RETURNED`` + +Specifies max results returned when pagination is not used. Defaults to ``10000``. + +### Security + +Flask taxonomies uses ``flask-principal`` to handle security. The default permissions are +that everyone is allowed to read/create/update/delete/move all taxonomies and terms. + +To restrict the access, specify permission factories (a function that returns a list of permission) +for each operation. + +``FLASK_TAXONOMIES_PERMISSION_FACTORIES`` + +A dictionary of operation to a list of permissions. + +``` +FLASK_TAXONOMIES_PERMISSION_FACTORIES = { + 'taxonomy_list': request -> List[Permission] + 'taxonomy_read': request, taxonomy -> List[Permission] + 'taxonomy_create': request, code -> List[Permission] + 'taxonomy_update': request, taxonomy -> List[Permission] + 'taxonomy_delete': request, taxonomy -> List[Permission], + + 'taxonomy_term_read': request, taxonomy, slug -> List[Permission] + 'taxonomy_term_create': request, taxonomy, slug -> List[Permission] + 'taxonomy_term_update': request, taxonomy, term -> List[Permission] + 'taxonomy_term_delete': request, taxonomy, term -> List[Permission], + 'taxonomy_term_move': request, taxonomy, term, destination, rename -> List[Permission], +} +``` +The right-hand side can be either a list/tuple of permissions, function with the above-mentioned +signatures or a string pointing to the implementation. The string form is resolved on +the first request. + +If ``.can`` on any of the permissions returns True or the list is empty, access is granted. + +#### Recommended initial settings + +The recommended initial settings are read-only for everyone except admin role: + +```python +from flask_principal import RoleNeed + +FLASK_TAXONOMIES_PERMISSION_FACTORIES = { + 'taxonomy_create': [RoleNeed('admin')], + 'taxonomy_update': [RoleNeed('admin')], + 'taxonomy_delete': [RoleNeed('admin')], + + 'taxonomy_term_create': [RoleNeed('admin')], + 'taxonomy_term_update': [RoleNeed('admin')], + 'taxonomy_term_delete': [RoleNeed('admin')], + 'taxonomy_term_move': [RoleNeed('admin')] +} +``` + +## Python API + +The calls below use ``session`` as an optional parameter. If not supplied, session from +current_app is used. + +``TermIdentification`` is a class to identify taxonomy term, binding taxonomy (or its code), +slug or a term instance. See [flask_taxonomies/term_identification.py](./flask_taxonomies/term_identification.py) +for details. + +```python +from flask_taxonomies.proxies import current_flask_taxonomies + +# returns a taxonomy list +current_flask_taxonomies.list_taxonomies(session=None) + +# returns a taxonomy with the given code. Fails by default if not found +current_flask_taxonomies.get_taxonomy(code, fail=True, session=None) + +# creates a new taxonomy +current_flask_taxonomies.create_taxonomy(code, extra_data=None, url=None, + select=None, session=None) + +# updates a taxonomy +current_flask_taxonomies.update_taxonomy( + taxonomy: [Taxonomy, str], extra_data, + url=MISSING, select=MISSING, + session=None) + +# deletes a taxonomy +current_flask_taxonomies.delete_taxonomy(taxonomy: Taxonomy, session=None) + +# lists terms within the taxonomy. +current_flask_taxonomies.list_taxonomy(taxonomy: [Taxonomy, str], levels=None, + status_cond=TaxonomyTerm.status == TermStatusEnum.alive, + order=True, session=None) + +# creates a new term inside a taxonomy +current_flask_taxonomies.create_term(ti: TermIdentification, + extra_data=None, session=None) + +# updates a term, setting or patching extra_data +current_flask_taxonomies.update_term(ti: [TaxonomyTerm, TermIdentification], + status_cond=TaxonomyTerm.status == TermStatusEnum.alive, + extra_data=None, patch=False, status=MISSING, session=None) + +# returns all descendants of a term +current_flask_taxonomies.descendants(ti: TermIdentification, levels=None, + status_cond=TaxonomyTerm.status == TermStatusEnum.alive, + order=True, session=None) + +# returns a term and its descendants +current_flask_taxonomies.descendants_or_self(ti: TermIdentification, levels=None, + status_cond=TaxonomyTerm.status == TermStatusEnum.alive, + order=True, session=None) + +# returns all ancestors of a term +current_flask_taxonomies.ancestors(ti: TermIdentification, + status_cond=TaxonomyTerm.status == TermStatusEnum.alive, session=None) + +# returns term and its ancestors +current_flask_taxonomies.ancestors_or_self(ti: TermIdentification, + status_cond=TaxonomyTerm.status == TermStatusEnum.alive, session=None) + +# removes a term +current_flask_taxonomies.delete_term(ti: TermIdentification, + remove_after_delete=True, session=None) + +# renames term's slug +current_flask_taxonomies.rename_term(ti: TermIdentification, new_slug=None, + remove_after_delete=True, session=None) + +# moves term into a new parent within the same taxonomy +current_flask_taxonomies.move_term(ti: TermIdentification, new_parent=None, + remove_after_delete=True, session=None) +``` + +### Signals + +See [flask_taxonomies/signals.py](flask_taxonomies/signals.py) for details + + + +%package -n python3-flask-taxonomies +Summary: please add a summary manually as the author left a blank one +Provides: python-flask-taxonomies +BuildRequires: python3-devel +BuildRequires: python3-setuptools +BuildRequires: python3-pip +%description -n python3-flask-taxonomies +# Flask Taxonomies + +[![](https://img.shields.io/github/license/oarepo/flask-taxonomies.svg)](https://github.com/oarepo/flask-taxonomies/blob/master/LICENSE) +[![](https://img.shields.io/travis/oarepo/flask-taxonomies.svg)](https://travis-ci.org/oarepo/flask-taxonomies) +[![](https://img.shields.io/coveralls/oarepo/flask-taxonomies.svg)](https://coveralls.io/r/oarepo/flask-taxonomies) +[![](https://img.shields.io/pypi/v/flask-taxonomies.svg)](https://pypi.org/pypi/flask-taxonomies) + + + +- [Flask Taxonomies](#flask-taxonomies) + - [Installation](#installation) + - [Terminology](#terminology) + - [REST API](#rest-api) + - [Retrieving resources](#retrieving-resources) + - [``Prefer`` HTTP header](#prefer-http-header) + - [Returned representation](#returned-representation) + - [Includes and excludes](#includes-and-excludes) + - [Including extra data](#including-extra-data) + - [Excluding data](#excluding-data) + - [Query parameters](#query-parameters) + - [Selecting subset of term data](#selecting-subset-of-term-data) + - [Pagination](#pagination) + - [Taxonomy](#taxonomy) + - [Creating](#creating) + - [Updating](#updating) + - [Replacing via HTTP PUT](#replacing-via-http-put) + - [Patching with HTTP POST](#patching-with-http-post) + - [Deleting](#deleting) + - [Taxonomy Term](#taxonomy-term) + - [Creating](#creating-1) + - [Updating](#updating-1) + - [Deleting](#deleting-1) + - [Un-Deleting](#un-deleting) + - [Moving](#moving) + - [Renaming](#renaming) + - [Configuration](#configuration) + - [Configuration Variables](#configuration-variables) + - [Security](#security) + - [Recommended initial settings](#recommended-initial-settings) + - [Python API](#python-api) + - [Signals](#signals) + + + +## Installation + +```bash +pip install flask-taxonomies +``` + +```python +from flask_taxonomies.ext import FlaskTaxonomies +from flask_taxonomies.views import blueprint +from flask import Flask +from flask_principal import Principal + +app = Flask('__test__') + +FlaskTaxonomies(app) +Principal(app) +app.register_blueprint(blueprint, url_prefix=app.config['FLASK_TAXONOMIES_URL_PREFIX']) + +db = ... +from flask_taxonomies.models import Base +Base.metadata.create_all(db.engine) +``` + +## Terminology + +**Taxonomy** is a tree of taxonomy terms. It is represented as a database object identified by +*code*. A taxonomy may contain its original url (in case the taxonomy is defined elsewhere) +and additional metadata as a json object (containing, for example, taxonomy title). It may also +contain a default set of selectors for filtering metadata. + +**TaxonomyTerm** represents a single node in a taxonomy. It is identified by its *slug* +and may contain additional metadata as json object. A term can contain children to represent +hierarchy of taxonomy terms. Term does not define ordering within children, it is up to +application logic to define any ordering. + +## REST API + +The rest API sits on the ``app.config['FLASK_TAXONOMIES_URL_PREFIX']`` url, implicitly +``/api/2.0/taxonomies/``. It follows the REST API principles with pagination inspired +by GitHub API. + +### Retrieving resources + +#### ``Prefer`` HTTP header + +Implicitly, the API returns rather minimal representation. The amount of the returned metadata +can be changed via HTTP ``prefer`` header or alternatively by query parameters. + +##### Returned representation + +The ``prefer`` header is a standard way of telling what you expect to get +as a response to the request. It is defined in [rfc7240](https://tools.ietf.org/html/rfc7240). + +If the header is not present, ``return=representation`` is assumed. One can specify ``return=minimal`` +to obtain minimal dataset, or other return types (even your own) defined in ``FLASK_TAXONOMIES_REPRESENTATION`` +config. + +**return=minimal** + +returns the minimal representation. Mostly not usable directly as it does not return any metadata, +just the code/slug. + +*Listing:* +```console +$ curl -i -H "Prefer: return=minimal" http://127.0.0.1:5000/api/2.0/taxonomies/ + +HTTP/1.0 200 OK +Link: ; rel=self + +[ + { + "code": "country" + } +] +``` + +*Get taxonomy:* +```console +$ curl -i -H "Prefer: return=minimal" http://127.0.0.1:5000/api/2.0/taxonomies/country + +HTTP/1.0 200 OK +Content-Type: application/json + +{ + "code": "country" +} +``` + +*Get term:* +```console +$ curl -i -H "Prefer: return=minimal" http://127.0.0.1:5000/api/2.0/taxonomies/country/europe + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +{ + "slug": "europe" +} +``` + +**return=representation** + +this is the default return type. Returns all user data declared on taxonomy/term together with +ancestor and urls. For example: + +*Listing:* +```console +$ curl -i http://127.0.0.1:5000/api/2.0/taxonomies/ +HTTP/1.0 200 OK +Link: ; rel=self + +[ + { + "code": "country", + "links": { + "custom": "https://www.kaggle.com/nikitagrec/world-capitals-gps/data", + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/country/" + }, + "title": "List of countries" + } +] +``` + +*Get term:* +```console +$ curl -i http://127.0.0.1:5000/api/2.0/taxonomies/country/europe/cz +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +{ + "CapitalLatitude": "50.083333333333336", + "CapitalLongitude": "14.466667", + "CapitalName": "Prague", + "ContinentName": "Europe", + "CountryCode": "CZ", + "CountryName": "Czech Republic", + "ancestors": [ + { + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/country/europe" + } + } + ], + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/country/europe/cz" + } +} +``` + +``Europe`` has no user data, so it contains only the ``links`` section. + +##### Includes and excludes + +The returned representation can be modified by specifying which metadata should be included/excluded. +Currently supported includes/excludes are: + +```python +INCLUDE_URL = 'url' +INCLUDE_DESCENDANTS_URL = 'drl' +INCLUDE_DESCENDANTS_COUNT = 'dcn' +INCLUDE_ANCESTORS_HIERARCHY = 'anh' +INCLUDE_ANCESTORS = 'anc' +INCLUDE_ANCESTOR_LIST = 'anl' +INCLUDE_DATA = 'data' +INCLUDE_ID = 'id' +INCLUDE_DESCENDANTS = 'dsc' +INCLUDE_ENVELOPE='env' +INCLUDE_DELETED = 'del' +INCLUDE_SLUG = 'slug' +INCLUDE_LEVEL = 'lvl' +INCLUDE_STATUS = 'sta' +``` + +###### Including extra data + +Examples: + +**Include record url in response** + +```console +$ curl -i -H "Prefer: return=minimal; include=url" \ + http://127.0.0.1:5000/api/2.0/taxonomies/country/europe + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +{ + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/country/europe" + }, + "slug": "europe" +} +``` + +Adds a ``links`` section to payload with record url (``"self":``) + +**Include descendants url in response** + +```console +$ curl -i -H "Prefer: return=minimal; include=url drl" \ + http://127.0.0.1:5000/api/2.0/taxonomies/country/europe + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +{ + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/country/europe", + "tree": "http://127.0.0.1:5000/api/2.0/taxonomies/country/europe?representation:include=dsc" + }, + "slug": "europe" +} +``` + +Adds a ``links`` section to payload with recoord url with descendants (``"tree":``) + +**Include descendants count in response** + +```console +$ curl -i -H "Prefer: return=minimal; include=dcn" \ + http://127.0.0.1:5000/api/2.0/taxonomies/country/europe + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +{ + "slug": "europe", + "descendants_count": 58 +} +``` + +Adds a ``descendant_count`` with a number of descendant terms under the term. The value is 0 +if the term is a leaf term. + +On taxonomy, returns the total number of terms in taxonomy: + +```console +$ curl -i -H "Prefer: return=minimal; include=dcn" \ + http://127.0.0.1:5000/api/2.0/taxonomies/country + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +{ + "code": "country", + "descendants_count": 253 +} +``` + +**Include ancestors with hierarchy in response** + +```console +$ curl -i -H "Prefer: return=minimal; include=anh url" \ + http://127.0.0.1:5000/api/2.0/taxonomies/country/europe/cz + +{ + "children": [ + { + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/country/europe/cz" + }, + "slug": "europe/cz" + } + ], + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/country/europe" + }, + "slug": "europe", + "ancestor": true +} +``` + +If the term has ancestors, they are serialized and the term is included as their +child. This is useful for example when showing the taxonomy in tree form - the +rendering mechanism for the tree will stay the same. All ancestor terms are marked +with ``ancestor=true`` flag to help with ui rendering (for example to gray ancestors). + +Adding url as well is recommended to get urls of ancestors. + +**Include ancestors without hierarchy in response** + +```console +$ curl -i -H "Prefer: return=minimal; include=anc url" \ + http://127.0.0.1:5000/api/2.0/taxonomies/country/europe/cz + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +{ + "ancestors": [ + { + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/country/europe" + }, + "slug": "europe" + } + ], + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/country/europe/cz" + }, + "slug": "europe/cz" +} +``` +The ancestors are rendered inside the ``ancestors`` element. Adding url as well +is recommended to get urls of ancestors. + +**Include ancestor list in response** + +```console +$ curl -i -H "Prefer: return=representation; include=anl ant par" \ + http://127.0.0.1:5000/api/2.0/taxonomies/country/europe/cz + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +[ + { + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/country/europe" + }, + "is_ancestor": true + }, + { + "CapitalLatitude": "50.083333333333336", + "CapitalLongitude": "14.466667", + "CapitalName": "Prague", + "ContinentName": "Europe", + "CountryCode": "CZ", + "CountryName": "Czech Republic", + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/country/europe/cz", + "parent": "http://127.0.0.1:5000/api/2.0/taxonomies/country/europe" + }, + "is_ancestor": false + } +] +``` +The ancestors are rendered on the same level as the term. This rendering might be used +for example when serializing the taxonomy term to elasticsearch - this way all the +ancestors are serialized within one array and indexed into one object in ES. + +Note also the: + * ``ant`` - adds ``ancestor`` field (``false`` if this is the term that has been queried), + ``true`` for ancestor term + * ``parent`` - adds link to the parent within ``links`` section + +Even one-term result is rendered as array: + +```console +$ curl -i -H "Prefer: return=representation; include=anl ant par" \ + http://127.0.0.1:5000/api/2.0/taxonomies/country/europe + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +[ + { + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/country/europe" + }, + "is_ancestor": false + } +] +``` + +**Include data in response** + +This is the default setting unless ``minimal`` representation is selected. In this case, +pass ``include=data`` to have data included. + +```console +$ curl -i -H "Prefer: return=minimal; include=data" \ + http://127.0.0.1:5000/api/2.0/taxonomies/country/europe/cz + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +{ + "CapitalLatitude": "50.083333333333336", + "CapitalLongitude": "14.466667", + "CapitalName": "Prague", + "ContinentName": "Europe", + "CountryCode": "CZ", + "CountryName": "Czech Republic", + "slug": "europe/cz" +} +``` + +**Include id in response** + +Use ``include=id`` to get the internal id included. This is rarely needed as API does not accept +this id at all. + +**Include descendant terms in response** + +To serialize descendants into the response, use ``include=dsc``: + +```console +$ curl -i -H "Prefer: return=minimal; include=dsc" \ + http://127.0.0.1:5000/api/2.0/taxonomies/country/europe + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +{ + "children": [ + { + "slug": "europe/ad" + }, + { + "slug": "europe/al" + }, + ... + { + "slug": "europe/va" + } + ], + "slug": "europe" +} +``` + +**Include slug in response** + +Adds ``slug`` to response. In the ``minimal`` mode the slug is added automatically, use this tag to +add it in ``return=representation``: + +```console +$ curl -i -H "Prefer: return=representation; include=slug" \ + http://127.0.0.1:5000/api/2.0/taxonomies/country/europe/cz + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +{ + "CapitalLatitude": "50.083333333333336", + "CapitalLongitude": "14.466667", + "CapitalName": "Prague", + "ContinentName": "Europe", + "CountryCode": "CZ", + "CountryName": "Czech Republic", + "ancestors": [ + { + "links":{"self":"http://127.0.0.1:5000/api/2.0/taxonomies/country/europe"}, + "slug": "europe" + } + ], + "links":{"self":"http://127.0.0.1:5000/api/2.0/taxonomies/country/europe/cz"}, + "slug": "europe/cz" +} +``` + +**Include hierarchy level in response** + +Adds hierarchy level to taxonomy term. Top-level terms have ``level=1``, taxonomy ``0``. + +```console +$ curl -i -H "Prefer: return=minimal; include=lvl" \ + http://127.0.0.1:5000/api/2.0/taxonomies/country/europe/cz + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +{ + "level": 2, + "slug": "europe/cz" +} +``` + +**Include deleted terms in response** + +Let's delete a country from Europe: + +```console +$ curl -X DELETE -i http://127.0.0.1:5000/api/2.0/taxonomies/country/europe/gb + +HTTP/1.0 200 OK +Content-Type: application/json + +{ + "CapitalLatitude": "51.5", + "CapitalLongitude": "-0.083333", + "CapitalName": "London", + "ContinentName": "Europe", + "CountryCode": "GB", + "CountryName": "United Kingdom", + "links":{"self":"http://127.0.0.1:5000/api/2.0/taxonomies/country/europe/gb"} +} +``` + +United Kingdom has indeed been removed from Europe: + +```console +$ curl -i http://127.0.0.1:5000/api/2.0/taxonomies/country/europe/gb +HTTP/1.0 410 GONE + +{ + "message": "http://127.0.0.1:5000/api/2.0/taxonomies/country/europe/gb was not found on the server", + "reason": "deleted" +} +``` + +Now run the GET again with removed terms included: + +```console +$ curl -i -H "Prefer: return=minimal; include=del sta" \ + http://127.0.0.1:5000/api/2.0/taxonomies/country/europe/gb + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +{ + "slug": "europe/gb", + "status": "deleted", + "busy_count":0, + "descendants_busy_count":0 +} +``` + +**Include term status** + +Including the status will add the following metadata: + * ``status`` of the term (alive, delete_pending, deleted, moved) + * ``busy_count`` - an integer saying how "busy" the term is. Being busy means + that a potentially destructive operation (such as deleting, moving or renaming slug) + is in progress. + * ``descendants_busy_count`` - a number of descendants that are busy + +```console +$ curl -i -H "Prefer: return=minimal; include=del sta dsc" \ + http://127.0.0.1:5000/api/2.0/taxonomies/country/europe + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +{ + "busy_count":0, + "children": [ + { + "busy_count":0, + "descendants_busy_count":0, + "slug": "europe/ad", + "status": "alive" + }, + ... + { + "busy_count":0, + "descendants_busy_count":0, + "slug": "europe/gb", + "status": "deleted" + }, + ... + { + "busy_count":0, + "descendants_busy_count":0, + "slug": "europe/va", + "status": "alive" + } + ], + "descendants_busy_count":0, + "slug": "europe", + "status": "alive" +} +``` + +###### Excluding data + +To exclude data from the representation, use ``exclude=...`` in prefer header. + +#### Query parameters + +Values from the ``prefer`` header can be used as query parameters: + +``` +curl -i -H "Prefer: return=minimal;" \ + "http://127.0.0.1:5000/api/2.0/taxonomies/country/europe? + representation:include=sta,url&representation:exclude=slug" + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +{ + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/country/europe" + }, + "status": "alive" +} +``` + +#### Selecting subset of term data + +Use ``select= ...`` in ``prefer`` header or ``representation:select=`` query parameter +to select just part of user data: + +```console +$ curl -i -H "Prefer: return=representation;select=/CapitalName /CountryCode" \ + "http://127.0.0.1:5000/api/2.0/taxonomies/country/europe/cz" + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +{ + "CapitalName": "Prague", + "CountryCode": "CZ", + "ancestors": [ + { + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/country/europe" + } + } + ], + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/country/europe/cz" + } +} +``` + +#### Maximum levels + +When descendants are selected, a maximum level of descendants can be specified via +``levels=`` part of ``prefer`` header (or representation:levels=n in the query). + +Example: + + ```console +$ curl -i -H "Prefer: return=representation; levels=1" \ + "http://127.0.0.1:5000/api/2.0/taxonomies/country" + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +{ + 'children': [ + {'slug': 'africa'}, + {'slug': 'antarctica'}, + {'slug': 'asia'}, + {'slug': 'australia'}, + {'slug': 'central-america'}, + {'slug': 'europe'}, + {'slug': 'north-america'}, + {'slug': 'south-america'} + ], + 'code': 'country', + 'title': 'List of countries' +} +``` + +#### Pagination + +If descendants are requested without further arguments the whole tree is returned (well, in fact +at most FLASK_TAXONOMIES_MAX_RESULTS_RETURNED terms to prevent server crash). This leads to high amount +of data transferred and possibly a client crash. To prevent this, pagination should be used on larger +taxonomies. + +Specify ``size`` argument to have at most this number of taxonomy terms returned. For example: + +```console +$ curl -i -H "Prefer: return=minimal;" \ + "http://127.0.0.1:5000/api/2.0/taxonomies/country/europe?representation:include=dsc&size=5" + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree +X-Page: 1 +X-PageSize: 5 +X-Total: 58 + +{ + "children": [ + { + "slug": "europe/ad" + }, + { + "slug": "europe/al" + }, + { + "slug": "europe/am" + }, + { + "slug": "europe/at" + } + ], + "slug": "europe" +} +``` + +This returns 5 terms - europe and 4 children. To return the next page, add ``page=2`` argument: + +```console +$ curl -i -H "Prefer: return=minimal;" \ + "http://127.0.0.1:5000/api/2.0/taxonomies/country/europe?representation:include=dsc&size=5&page=4" + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree +X-Page: 4 +X-PageSize: 5 +X-Total: 58 + +[ + { + "slug": "europe/dk" + }, + { + "slug": "europe/ee" + }, + { + "slug": "europe/es" + }, + { + "slug": "europe/fi" + }, + { + "slug": "europe/fo" + } +] +``` + +Note that the first page contains the root element and the second does not. To fix it, either use ``include=anh`` +to always get hierarchical representation: + +```console +$ curl -i -H "Prefer: return=minimal;" \ + "http://127.0.0.1:5000/api/2.0/taxonomies/country/europe?representation:include=dsc,anh&size=5&page=2" + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree +X-Page: 2 +X-PageSize: 5 +X-Total: 58 + +{ + "ancestor": true, + "children": [ + { + "slug": "europe/ax" + }, + { + "slug": "europe/az" + }, + { + "slug": "europe/ba" + }, + { + "slug": "europe/be" + } + ], + "slug": "europe" +} +``` + +or, if interested only in descendants and not the node itself, ``exclude=self`` + +```console +$ curl -i -H "Prefer: return=minimal;" \ + "http://127.0.0.1:5000/api/2.0/taxonomies/country/europe? + representation:include=dsc&representation:exclude=self&size=5&page=1" + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree +X-Page: 1 +X-PageSize: 5 +X-Total: 58 + +[ + { + "slug": "europe/ad" + }, + { + "slug": "europe/al" + }, + { + "slug": "europe/am" + }, + { + "slug": "europe/at" + }, + { + "slug": "europe/ax" + } +] +``` + +#### Searching + +Use ``q=`` parameter to search within terms. Returns all the resources +whose ``metadata`` contain the expression in q. + +##### Simple query + +If ``q`` is a simple string not containing ':' or string in quotes, +it is interpreted as a string that must be present in any of +values inside the json. + +The current implementation is dependent on the database backend +and might perform sub-optimal ``ilike %x%`` query on textified json. + +```console +$ curl -i -H "Prefer: return=minimal; include=data dsc; exclude=self" \ + http://127.0.0.1:5000/api/2.0/taxonomies/country/europe?q=Prague + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +[ + { + "CapitalLatitude": "50.083333333333336", + "CapitalLongitude": "14.466667", + "CapitalName": "Prague", + "ContinentName": "Europe", + "CountryCode": "CZ", + "CountryName": "Czech Republic", + "slug": "europe/cz" + } +] +``` + +##### Lucene-like query + +If the ``q`` contains a ':' character not enclosed in quotes, +it is parsed as a query in lucene syntax, with the following +allowed constructs: + + * path:value for matching value at the given path + * a.b.c:value for representing nested paths + * AND, OR, NOT, brackets + +The query will be executed if the database or search backend support +it. If not supported, HTTP 501 will be returned. + +### Taxonomy +#### Creating + +To create a taxonomy, either use HTTP PUT: + +```console +$ curl -i -X PUT 'http://127.0.0.1:5000/api/2.0/taxonomies/test' \ +-H 'Content-Type: application/json' \ +--data-raw '{ + "title": "Test taxonomy" +}' + +HTTP/1.0 201 CREATED +Link: ; rel=self +Link: ; rel=tree +Location: http://127.0.0.1:5000/api/2.0/taxonomies/test/ + +{ + "code": "test", + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/test/" + }, + "title": "Test taxonomy" +} +``` + +Or HTTP POST with ``code`` in the payload + +```console +$ curl -i -X POST 'http://127.0.0.1:5000/api/2.0/taxonomies/' \ + -H 'Content-Type: application/json' --data-raw '{ + "title": "Test taxonomy 1", "code": "test1" +}' + +HTTP/1.0 201 CREATED +Link: ; rel=self +Link: ; rel=tree +Location: http://127.0.0.1:5000/api/2.0/taxonomies/test1/ + +{ + "code": "test1", + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/test1/" + }, + "title": "Test taxonomy 1" +} +``` + + +#### Updating + +##### Replacing via HTTP PUT + +```console +$ curl -i -X PUT 'http://127.0.0.1:5000/api/2.0/taxonomies/test' \ + --header 'Content-Type: application/json' --data-raw '{ + "title": "Test taxonomy updated" +}' + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +{ + "code": "test", + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/test/" + }, + "title": "Test taxonomy updated" +} +``` + +Note that terms are not updated nor removed when taxonomy metadata are updated. + +##### Patching with HTTP PATCH + +```console +$ curl -i -X PATCH 'http://127.0.0.1:5000/api/2.0/taxonomies/test' \ + --header 'Content-Type: application/json' --data-raw '[{ + "op": "replace", "path": "/title", "value": "Test taxonomy updated via patch" +}]' + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +{ + "code": "test", + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/test/" + }, + "title": "Test taxonomy updated via patch" +} +``` + +#### Deleting + +```console +$ curl -i -X DELETE 'http://127.0.0.1:5000/api/2.0/taxonomies/test1' + +HTTP/1.0 204 NO CONTENT +Content-Type: text/html; charset=utf-8 +``` + +### Taxonomy Term +#### Creating + +As in creating taxonomy, term can be created either via HTTP PUT: + +```console +$ curl -i -X PUT 'http://127.0.0.1:5000/api/2.0/taxonomies/test/term' \ + --header 'Content-Type: application/json' --data-raw '{ + "title": "Test Term" +}' + +HTTP/1.0 201 CREATED +Link: ; rel=self +Link: ; rel=tree +Location: http://127.0.0.1:5000/api/2.0/taxonomies/test/term + +{ + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/test/term" + }, + "title": "Test Term" +} +``` + +or POST: + +```console +$ curl -i -X POST 'http://127.0.0.1:5000/api/2.0/taxonomies/test' \ + --header 'Content-Type: application/json' --data-raw '{ + "title": "Test term 1", "slug": "term1" +}' + +HTTP/1.0 201 CREATED +Link: ; rel=self +Link: ; rel=tree +Location: http://127.0.0.1:5000/api/2.0/taxonomies/test/term1 + +{ + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/test/term1" + }, + "title": "Test term 1" +} +``` + +As PUT/POST operation also mean updating term if slug exists, to be sure +that you are creating a new one use ``If-None-Match: '*'`` header: + +```console +$ curl -i -X PUT 'http://127.0.0.1:5000/api/2.0/taxonomies/test/term' \ + --header 'Content-Type: application/json' \ + --header 'If-None-Match: '*'' \ + --data-raw '{ + "title": "Test Term" +}' + +HTTP/1.0 412 Precondition Failed + +{ + "message": "The taxonomy already contains a term on this slug. As If-None-Match: '*' has been requested, not modifying the term", + "reason": "term-exists" +} +``` + +Terms can be created within terms via HTTP PUT: + +```console +$ curl -i -X PUT 'http://127.0.0.1:5000/api/2.0/taxonomies/test/term/nested' \ + --header 'Content-Type: application/json' --data-raw '{ + "title": "Nested Term" +}' + +HTTP/1.0 201 CREATED +Link: ; rel=self +Link: ; rel=tree +Location: http://127.0.0.1:5000/api/2.0/taxonomies/test/term/nested + +{ + "ancestors":[ + { + "links":{ + "self":"http://127.0.0.1:5000/api/2.0/taxonomies/test/term" + }, + "title":"Test Term" + } + ], + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/test/term/nested" + }, + "title": "Nested Term" +} +``` + +or POST: + +```console +$ curl -i -X POST 'http://127.0.0.1:5000/api/2.0/taxonomies/test/term1' \ + --header 'Content-Type: application/json' --data-raw '{ + "title": "Test nested term 1", "slug": "nested1" +}' + +HTTP/1.0 201 CREATED +Link: ; rel=self +Link: ; rel=tree +Location: http://127.0.0.1:5000/api/2.0/taxonomies/test/term1/nested1 + +{ + "ancestors":[ + { + "links":{ + "self":"http://127.0.0.1:5000/api/2.0/taxonomies/test/term1" + }, + "title":"Test term 1" + } + ], + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/test/term1/nested1" + }, + "title": "Test nested term 1" +} +``` + +#### Updating + +As for taxonomy, use HTTP ``PUT``: + +```console +$ curl -i -X PUT 'http://127.0.0.1:5000/api/2.0/taxonomies/test/term' \ + --header 'Content-Type: application/json' --data-raw '{ + "title": "Test Term updated" +}' + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +{ + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/test/term" + }, + "title": "Test Term updated" +} +``` + +or ``PATCH``: + +```console +$ curl -i -X PATCH 'http://127.0.0.1:5000/api/2.0/taxonomies/test/term' \ + --header 'Content-Type: application/json' --data-raw '[{ + "op": "replace", "path": "/title", "value": "Test taxonomy term updated via patch" +}]' + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +{ + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/test/term" + }, + "title": "Test taxonomy term updated via patch" +} +``` + +As PUT operation also means creating term if slug does not exist, to be sure +that you are just updating use ``If-Match: '*'`` header: + +```console +$ curl -i -X PUT 'http://127.0.0.1:5000/api/2.0/taxonomies/test/unknown' \ + --header 'Content-Type: application/json' \ + --header 'If-Match: '*'' \ + --data-raw '{ + "title": "Test Term" +}' + +HTTP/1.0 412 Precondition Failed + +{ + "message": "The taxonomy does not contain a term on this slug. As If-Match: '*' has been requested, not creating a new term", + "reason": "term-does-not-exist" +} +``` + + +#### Deleting + +Use HTTP delete to remove a term. The removed term will be returned in the response: + +```console +$ curl -i -X DELETE 'http://127.0.0.1:5000/api/2.0/taxonomies/test/term1' + +HTTP/1.0 200 OK +Content-Type: application/json + +{ + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/test/term1" + }, + "title": "Test term 1" +} +``` + +Subsequent GET returns 410: + +```console +$ curl -i 'http://127.0.0.1:5000/api/2.0/taxonomies/test/term1' + +HTTP/1.0 410 GONE + +{ + "message": "http://127.0.0.1:5000/api/2.0/taxonomies/test/term1 was not found on the server", + "reason": "deleted" +} +``` + +But the term stays on the server: + +```console +$ curl -i 'http://127.0.0.1:5000/api/2.0/taxonomies/test/term?representation:include=del' +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +{ + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/test/term" + }, + "title": "Test taxonomy term updated via patch" +} +``` + +#### Un-Deleting + +To salvage a deleted term, update it via PATCH, with an empty set of operations to keep it unmodified: + +```console +$ curl -i -X PATCH -H "Prefer: return=minimal; include=del" \ + 'http://127.0.0.1:5000/api/2.0/taxonomies/test/term' \ + --header 'Content-Type: application/json' --data-raw '[]' + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +{ + "slug": "term" +} +``` + +#### Moving + +Use HTTP post with content type ``application/vnd.move`` and ``Destination`` header: + +```console +$ curl -i -X POST \ + -H 'Content-Type: application/vnd.move' \ + -H "Destination: /" \ + 'http://127.0.0.1:5000/api/2.0/taxonomies/test/term/nested' + +HTTP/1.0 200 OK + +{ + "links":{ + "self":"http://127.0.0.1:5000/api/2.0/taxonomies/test/nested" + }, + "title":"Nested Term" +} +``` + +The original url returns 301: + +```console +$ curl -i 'http://127.0.0.1:5000/api/2.0/taxonomies/test/term/nested' + +HTTP/1.0 301 MOVED PERMANENTLY +Location: http://127.0.0.1:5000/api/2.0/taxonomies/test/nested +Link: ; rel=self +Link: ; rel=obsoleted_by + +{ + "links": { + "self": "http://localhost/api/2.0/taxonomies/test/term/nested", + "obsoleted_by": "http://localhost/api/2.0/taxonomies/test/nested" + }, + "status": "moved" +} +``` + +#### Renaming + +Use HTTP post with content type ``application/vnd.move`` and ``Rename`` header: + +```console +$ curl -i -X POST \ + -H 'Content-Type: application/vnd.move' \ + -H "Rename: renamed-nested" \ + 'http://127.0.0.1:5000/api/2.0/taxonomies/test/nested' + +HTTP/1.0 200 OK + +{ + "links":{ + "self":"http://127.0.0.1:5000/api/2.0/taxonomies/test/renamed-nested" + }, + "title":"Nested Term" +} +``` + + +The original url returns 301: + +```console +$ curl -i 'http://127.0.0.1:5000/api/2.0/taxonomies/test/nested' + +HTTP/1.0 301 MOVED PERMANENTLY +Location: http://127.0.0.1:5000/api/2.0/taxonomies/test/renamed-nested +Link: ; rel=self +Link: ; rel=obsoleted_by + +{ + "links": { + "self": "http://localhost/api/2.0/taxonomies/test/nested", + "obsoleted_by": "http://localhost/api/2.0/taxonomies/test/renamed-nested" + }, + "status": "moved" +} +``` + + +## Configuration + +### Configuration Variables + +``FLASK_TAXONOMIES_SERVER_NAME`` + +Server name hosting the taxonomies. If not set, SERVER_NAME is used. + +``FLASK_TAXONOMIES_SERVER_SCHEME`` + +Scheme to use in generated urls, defaults to ``https`` + +``FLASK_TAXONOMIES_URL_PREFIX`` + +A prefix on which taxonomies are served, defaults to ``/api/2.0/taxonomies/`` + +``FLASK_TAXONOMIES_REPRESENTATION`` + +Values for ``Prefer: return=`` header. ``minimal`` and ``representation`` are obligatory, +you are free to add other return representations. + +```python +from flask_taxonomies.constants import * + +FLASK_TAXONOMIES_REPRESENTATION = { + 'minimal': { + 'include': [INCLUDE_SLUG, INCLUDE_SELF], + 'exclude': [], + 'select': None, + 'options': {} + }, + 'representation': { + 'include': [INCLUDE_DATA, INCLUDE_ANCESTORS, + INCLUDE_URL, INCLUDE_SELF], + 'exclude': [], + 'select': None, + 'options': {} + }, + 'full': { + 'include': [INCLUDE_DATA, INCLUDE_ANCESTORS, INCLUDE_URL, + INCLUDE_DESCENDANTS_URL, INCLUDE_SELF], + 'exclude': [], + 'select': None, + 'options': {} + } +} +``` + +``FLASK_TAXONOMIES_MAX_RESULTS_RETURNED`` + +Specifies max results returned when pagination is not used. Defaults to ``10000``. + +### Security + +Flask taxonomies uses ``flask-principal`` to handle security. The default permissions are +that everyone is allowed to read/create/update/delete/move all taxonomies and terms. + +To restrict the access, specify permission factories (a function that returns a list of permission) +for each operation. + +``FLASK_TAXONOMIES_PERMISSION_FACTORIES`` + +A dictionary of operation to a list of permissions. + +``` +FLASK_TAXONOMIES_PERMISSION_FACTORIES = { + 'taxonomy_list': request -> List[Permission] + 'taxonomy_read': request, taxonomy -> List[Permission] + 'taxonomy_create': request, code -> List[Permission] + 'taxonomy_update': request, taxonomy -> List[Permission] + 'taxonomy_delete': request, taxonomy -> List[Permission], + + 'taxonomy_term_read': request, taxonomy, slug -> List[Permission] + 'taxonomy_term_create': request, taxonomy, slug -> List[Permission] + 'taxonomy_term_update': request, taxonomy, term -> List[Permission] + 'taxonomy_term_delete': request, taxonomy, term -> List[Permission], + 'taxonomy_term_move': request, taxonomy, term, destination, rename -> List[Permission], +} +``` +The right-hand side can be either a list/tuple of permissions, function with the above-mentioned +signatures or a string pointing to the implementation. The string form is resolved on +the first request. + +If ``.can`` on any of the permissions returns True or the list is empty, access is granted. + +#### Recommended initial settings + +The recommended initial settings are read-only for everyone except admin role: + +```python +from flask_principal import RoleNeed + +FLASK_TAXONOMIES_PERMISSION_FACTORIES = { + 'taxonomy_create': [RoleNeed('admin')], + 'taxonomy_update': [RoleNeed('admin')], + 'taxonomy_delete': [RoleNeed('admin')], + + 'taxonomy_term_create': [RoleNeed('admin')], + 'taxonomy_term_update': [RoleNeed('admin')], + 'taxonomy_term_delete': [RoleNeed('admin')], + 'taxonomy_term_move': [RoleNeed('admin')] +} +``` + +## Python API + +The calls below use ``session`` as an optional parameter. If not supplied, session from +current_app is used. + +``TermIdentification`` is a class to identify taxonomy term, binding taxonomy (or its code), +slug or a term instance. See [flask_taxonomies/term_identification.py](./flask_taxonomies/term_identification.py) +for details. + +```python +from flask_taxonomies.proxies import current_flask_taxonomies + +# returns a taxonomy list +current_flask_taxonomies.list_taxonomies(session=None) + +# returns a taxonomy with the given code. Fails by default if not found +current_flask_taxonomies.get_taxonomy(code, fail=True, session=None) + +# creates a new taxonomy +current_flask_taxonomies.create_taxonomy(code, extra_data=None, url=None, + select=None, session=None) + +# updates a taxonomy +current_flask_taxonomies.update_taxonomy( + taxonomy: [Taxonomy, str], extra_data, + url=MISSING, select=MISSING, + session=None) + +# deletes a taxonomy +current_flask_taxonomies.delete_taxonomy(taxonomy: Taxonomy, session=None) + +# lists terms within the taxonomy. +current_flask_taxonomies.list_taxonomy(taxonomy: [Taxonomy, str], levels=None, + status_cond=TaxonomyTerm.status == TermStatusEnum.alive, + order=True, session=None) + +# creates a new term inside a taxonomy +current_flask_taxonomies.create_term(ti: TermIdentification, + extra_data=None, session=None) + +# updates a term, setting or patching extra_data +current_flask_taxonomies.update_term(ti: [TaxonomyTerm, TermIdentification], + status_cond=TaxonomyTerm.status == TermStatusEnum.alive, + extra_data=None, patch=False, status=MISSING, session=None) + +# returns all descendants of a term +current_flask_taxonomies.descendants(ti: TermIdentification, levels=None, + status_cond=TaxonomyTerm.status == TermStatusEnum.alive, + order=True, session=None) + +# returns a term and its descendants +current_flask_taxonomies.descendants_or_self(ti: TermIdentification, levels=None, + status_cond=TaxonomyTerm.status == TermStatusEnum.alive, + order=True, session=None) + +# returns all ancestors of a term +current_flask_taxonomies.ancestors(ti: TermIdentification, + status_cond=TaxonomyTerm.status == TermStatusEnum.alive, session=None) + +# returns term and its ancestors +current_flask_taxonomies.ancestors_or_self(ti: TermIdentification, + status_cond=TaxonomyTerm.status == TermStatusEnum.alive, session=None) + +# removes a term +current_flask_taxonomies.delete_term(ti: TermIdentification, + remove_after_delete=True, session=None) + +# renames term's slug +current_flask_taxonomies.rename_term(ti: TermIdentification, new_slug=None, + remove_after_delete=True, session=None) + +# moves term into a new parent within the same taxonomy +current_flask_taxonomies.move_term(ti: TermIdentification, new_parent=None, + remove_after_delete=True, session=None) +``` + +### Signals + +See [flask_taxonomies/signals.py](flask_taxonomies/signals.py) for details + + + +%package help +Summary: Development documents and examples for flask-taxonomies +Provides: python3-flask-taxonomies-doc +%description help +# Flask Taxonomies + +[![](https://img.shields.io/github/license/oarepo/flask-taxonomies.svg)](https://github.com/oarepo/flask-taxonomies/blob/master/LICENSE) +[![](https://img.shields.io/travis/oarepo/flask-taxonomies.svg)](https://travis-ci.org/oarepo/flask-taxonomies) +[![](https://img.shields.io/coveralls/oarepo/flask-taxonomies.svg)](https://coveralls.io/r/oarepo/flask-taxonomies) +[![](https://img.shields.io/pypi/v/flask-taxonomies.svg)](https://pypi.org/pypi/flask-taxonomies) + + + +- [Flask Taxonomies](#flask-taxonomies) + - [Installation](#installation) + - [Terminology](#terminology) + - [REST API](#rest-api) + - [Retrieving resources](#retrieving-resources) + - [``Prefer`` HTTP header](#prefer-http-header) + - [Returned representation](#returned-representation) + - [Includes and excludes](#includes-and-excludes) + - [Including extra data](#including-extra-data) + - [Excluding data](#excluding-data) + - [Query parameters](#query-parameters) + - [Selecting subset of term data](#selecting-subset-of-term-data) + - [Pagination](#pagination) + - [Taxonomy](#taxonomy) + - [Creating](#creating) + - [Updating](#updating) + - [Replacing via HTTP PUT](#replacing-via-http-put) + - [Patching with HTTP POST](#patching-with-http-post) + - [Deleting](#deleting) + - [Taxonomy Term](#taxonomy-term) + - [Creating](#creating-1) + - [Updating](#updating-1) + - [Deleting](#deleting-1) + - [Un-Deleting](#un-deleting) + - [Moving](#moving) + - [Renaming](#renaming) + - [Configuration](#configuration) + - [Configuration Variables](#configuration-variables) + - [Security](#security) + - [Recommended initial settings](#recommended-initial-settings) + - [Python API](#python-api) + - [Signals](#signals) + + + +## Installation + +```bash +pip install flask-taxonomies +``` + +```python +from flask_taxonomies.ext import FlaskTaxonomies +from flask_taxonomies.views import blueprint +from flask import Flask +from flask_principal import Principal + +app = Flask('__test__') + +FlaskTaxonomies(app) +Principal(app) +app.register_blueprint(blueprint, url_prefix=app.config['FLASK_TAXONOMIES_URL_PREFIX']) + +db = ... +from flask_taxonomies.models import Base +Base.metadata.create_all(db.engine) +``` + +## Terminology + +**Taxonomy** is a tree of taxonomy terms. It is represented as a database object identified by +*code*. A taxonomy may contain its original url (in case the taxonomy is defined elsewhere) +and additional metadata as a json object (containing, for example, taxonomy title). It may also +contain a default set of selectors for filtering metadata. + +**TaxonomyTerm** represents a single node in a taxonomy. It is identified by its *slug* +and may contain additional metadata as json object. A term can contain children to represent +hierarchy of taxonomy terms. Term does not define ordering within children, it is up to +application logic to define any ordering. + +## REST API + +The rest API sits on the ``app.config['FLASK_TAXONOMIES_URL_PREFIX']`` url, implicitly +``/api/2.0/taxonomies/``. It follows the REST API principles with pagination inspired +by GitHub API. + +### Retrieving resources + +#### ``Prefer`` HTTP header + +Implicitly, the API returns rather minimal representation. The amount of the returned metadata +can be changed via HTTP ``prefer`` header or alternatively by query parameters. + +##### Returned representation + +The ``prefer`` header is a standard way of telling what you expect to get +as a response to the request. It is defined in [rfc7240](https://tools.ietf.org/html/rfc7240). + +If the header is not present, ``return=representation`` is assumed. One can specify ``return=minimal`` +to obtain minimal dataset, or other return types (even your own) defined in ``FLASK_TAXONOMIES_REPRESENTATION`` +config. + +**return=minimal** + +returns the minimal representation. Mostly not usable directly as it does not return any metadata, +just the code/slug. + +*Listing:* +```console +$ curl -i -H "Prefer: return=minimal" http://127.0.0.1:5000/api/2.0/taxonomies/ + +HTTP/1.0 200 OK +Link: ; rel=self + +[ + { + "code": "country" + } +] +``` + +*Get taxonomy:* +```console +$ curl -i -H "Prefer: return=minimal" http://127.0.0.1:5000/api/2.0/taxonomies/country + +HTTP/1.0 200 OK +Content-Type: application/json + +{ + "code": "country" +} +``` + +*Get term:* +```console +$ curl -i -H "Prefer: return=minimal" http://127.0.0.1:5000/api/2.0/taxonomies/country/europe + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +{ + "slug": "europe" +} +``` + +**return=representation** + +this is the default return type. Returns all user data declared on taxonomy/term together with +ancestor and urls. For example: + +*Listing:* +```console +$ curl -i http://127.0.0.1:5000/api/2.0/taxonomies/ +HTTP/1.0 200 OK +Link: ; rel=self + +[ + { + "code": "country", + "links": { + "custom": "https://www.kaggle.com/nikitagrec/world-capitals-gps/data", + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/country/" + }, + "title": "List of countries" + } +] +``` + +*Get term:* +```console +$ curl -i http://127.0.0.1:5000/api/2.0/taxonomies/country/europe/cz +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +{ + "CapitalLatitude": "50.083333333333336", + "CapitalLongitude": "14.466667", + "CapitalName": "Prague", + "ContinentName": "Europe", + "CountryCode": "CZ", + "CountryName": "Czech Republic", + "ancestors": [ + { + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/country/europe" + } + } + ], + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/country/europe/cz" + } +} +``` + +``Europe`` has no user data, so it contains only the ``links`` section. + +##### Includes and excludes + +The returned representation can be modified by specifying which metadata should be included/excluded. +Currently supported includes/excludes are: + +```python +INCLUDE_URL = 'url' +INCLUDE_DESCENDANTS_URL = 'drl' +INCLUDE_DESCENDANTS_COUNT = 'dcn' +INCLUDE_ANCESTORS_HIERARCHY = 'anh' +INCLUDE_ANCESTORS = 'anc' +INCLUDE_ANCESTOR_LIST = 'anl' +INCLUDE_DATA = 'data' +INCLUDE_ID = 'id' +INCLUDE_DESCENDANTS = 'dsc' +INCLUDE_ENVELOPE='env' +INCLUDE_DELETED = 'del' +INCLUDE_SLUG = 'slug' +INCLUDE_LEVEL = 'lvl' +INCLUDE_STATUS = 'sta' +``` + +###### Including extra data + +Examples: + +**Include record url in response** + +```console +$ curl -i -H "Prefer: return=minimal; include=url" \ + http://127.0.0.1:5000/api/2.0/taxonomies/country/europe + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +{ + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/country/europe" + }, + "slug": "europe" +} +``` + +Adds a ``links`` section to payload with record url (``"self":``) + +**Include descendants url in response** + +```console +$ curl -i -H "Prefer: return=minimal; include=url drl" \ + http://127.0.0.1:5000/api/2.0/taxonomies/country/europe + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +{ + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/country/europe", + "tree": "http://127.0.0.1:5000/api/2.0/taxonomies/country/europe?representation:include=dsc" + }, + "slug": "europe" +} +``` + +Adds a ``links`` section to payload with recoord url with descendants (``"tree":``) + +**Include descendants count in response** + +```console +$ curl -i -H "Prefer: return=minimal; include=dcn" \ + http://127.0.0.1:5000/api/2.0/taxonomies/country/europe + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +{ + "slug": "europe", + "descendants_count": 58 +} +``` + +Adds a ``descendant_count`` with a number of descendant terms under the term. The value is 0 +if the term is a leaf term. + +On taxonomy, returns the total number of terms in taxonomy: + +```console +$ curl -i -H "Prefer: return=minimal; include=dcn" \ + http://127.0.0.1:5000/api/2.0/taxonomies/country + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +{ + "code": "country", + "descendants_count": 253 +} +``` + +**Include ancestors with hierarchy in response** + +```console +$ curl -i -H "Prefer: return=minimal; include=anh url" \ + http://127.0.0.1:5000/api/2.0/taxonomies/country/europe/cz + +{ + "children": [ + { + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/country/europe/cz" + }, + "slug": "europe/cz" + } + ], + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/country/europe" + }, + "slug": "europe", + "ancestor": true +} +``` + +If the term has ancestors, they are serialized and the term is included as their +child. This is useful for example when showing the taxonomy in tree form - the +rendering mechanism for the tree will stay the same. All ancestor terms are marked +with ``ancestor=true`` flag to help with ui rendering (for example to gray ancestors). + +Adding url as well is recommended to get urls of ancestors. + +**Include ancestors without hierarchy in response** + +```console +$ curl -i -H "Prefer: return=minimal; include=anc url" \ + http://127.0.0.1:5000/api/2.0/taxonomies/country/europe/cz + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +{ + "ancestors": [ + { + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/country/europe" + }, + "slug": "europe" + } + ], + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/country/europe/cz" + }, + "slug": "europe/cz" +} +``` +The ancestors are rendered inside the ``ancestors`` element. Adding url as well +is recommended to get urls of ancestors. + +**Include ancestor list in response** + +```console +$ curl -i -H "Prefer: return=representation; include=anl ant par" \ + http://127.0.0.1:5000/api/2.0/taxonomies/country/europe/cz + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +[ + { + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/country/europe" + }, + "is_ancestor": true + }, + { + "CapitalLatitude": "50.083333333333336", + "CapitalLongitude": "14.466667", + "CapitalName": "Prague", + "ContinentName": "Europe", + "CountryCode": "CZ", + "CountryName": "Czech Republic", + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/country/europe/cz", + "parent": "http://127.0.0.1:5000/api/2.0/taxonomies/country/europe" + }, + "is_ancestor": false + } +] +``` +The ancestors are rendered on the same level as the term. This rendering might be used +for example when serializing the taxonomy term to elasticsearch - this way all the +ancestors are serialized within one array and indexed into one object in ES. + +Note also the: + * ``ant`` - adds ``ancestor`` field (``false`` if this is the term that has been queried), + ``true`` for ancestor term + * ``parent`` - adds link to the parent within ``links`` section + +Even one-term result is rendered as array: + +```console +$ curl -i -H "Prefer: return=representation; include=anl ant par" \ + http://127.0.0.1:5000/api/2.0/taxonomies/country/europe + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +[ + { + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/country/europe" + }, + "is_ancestor": false + } +] +``` + +**Include data in response** + +This is the default setting unless ``minimal`` representation is selected. In this case, +pass ``include=data`` to have data included. + +```console +$ curl -i -H "Prefer: return=minimal; include=data" \ + http://127.0.0.1:5000/api/2.0/taxonomies/country/europe/cz + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +{ + "CapitalLatitude": "50.083333333333336", + "CapitalLongitude": "14.466667", + "CapitalName": "Prague", + "ContinentName": "Europe", + "CountryCode": "CZ", + "CountryName": "Czech Republic", + "slug": "europe/cz" +} +``` + +**Include id in response** + +Use ``include=id`` to get the internal id included. This is rarely needed as API does not accept +this id at all. + +**Include descendant terms in response** + +To serialize descendants into the response, use ``include=dsc``: + +```console +$ curl -i -H "Prefer: return=minimal; include=dsc" \ + http://127.0.0.1:5000/api/2.0/taxonomies/country/europe + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +{ + "children": [ + { + "slug": "europe/ad" + }, + { + "slug": "europe/al" + }, + ... + { + "slug": "europe/va" + } + ], + "slug": "europe" +} +``` + +**Include slug in response** + +Adds ``slug`` to response. In the ``minimal`` mode the slug is added automatically, use this tag to +add it in ``return=representation``: + +```console +$ curl -i -H "Prefer: return=representation; include=slug" \ + http://127.0.0.1:5000/api/2.0/taxonomies/country/europe/cz + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +{ + "CapitalLatitude": "50.083333333333336", + "CapitalLongitude": "14.466667", + "CapitalName": "Prague", + "ContinentName": "Europe", + "CountryCode": "CZ", + "CountryName": "Czech Republic", + "ancestors": [ + { + "links":{"self":"http://127.0.0.1:5000/api/2.0/taxonomies/country/europe"}, + "slug": "europe" + } + ], + "links":{"self":"http://127.0.0.1:5000/api/2.0/taxonomies/country/europe/cz"}, + "slug": "europe/cz" +} +``` + +**Include hierarchy level in response** + +Adds hierarchy level to taxonomy term. Top-level terms have ``level=1``, taxonomy ``0``. + +```console +$ curl -i -H "Prefer: return=minimal; include=lvl" \ + http://127.0.0.1:5000/api/2.0/taxonomies/country/europe/cz + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +{ + "level": 2, + "slug": "europe/cz" +} +``` + +**Include deleted terms in response** + +Let's delete a country from Europe: + +```console +$ curl -X DELETE -i http://127.0.0.1:5000/api/2.0/taxonomies/country/europe/gb + +HTTP/1.0 200 OK +Content-Type: application/json + +{ + "CapitalLatitude": "51.5", + "CapitalLongitude": "-0.083333", + "CapitalName": "London", + "ContinentName": "Europe", + "CountryCode": "GB", + "CountryName": "United Kingdom", + "links":{"self":"http://127.0.0.1:5000/api/2.0/taxonomies/country/europe/gb"} +} +``` + +United Kingdom has indeed been removed from Europe: + +```console +$ curl -i http://127.0.0.1:5000/api/2.0/taxonomies/country/europe/gb +HTTP/1.0 410 GONE + +{ + "message": "http://127.0.0.1:5000/api/2.0/taxonomies/country/europe/gb was not found on the server", + "reason": "deleted" +} +``` + +Now run the GET again with removed terms included: + +```console +$ curl -i -H "Prefer: return=minimal; include=del sta" \ + http://127.0.0.1:5000/api/2.0/taxonomies/country/europe/gb + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +{ + "slug": "europe/gb", + "status": "deleted", + "busy_count":0, + "descendants_busy_count":0 +} +``` + +**Include term status** + +Including the status will add the following metadata: + * ``status`` of the term (alive, delete_pending, deleted, moved) + * ``busy_count`` - an integer saying how "busy" the term is. Being busy means + that a potentially destructive operation (such as deleting, moving or renaming slug) + is in progress. + * ``descendants_busy_count`` - a number of descendants that are busy + +```console +$ curl -i -H "Prefer: return=minimal; include=del sta dsc" \ + http://127.0.0.1:5000/api/2.0/taxonomies/country/europe + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +{ + "busy_count":0, + "children": [ + { + "busy_count":0, + "descendants_busy_count":0, + "slug": "europe/ad", + "status": "alive" + }, + ... + { + "busy_count":0, + "descendants_busy_count":0, + "slug": "europe/gb", + "status": "deleted" + }, + ... + { + "busy_count":0, + "descendants_busy_count":0, + "slug": "europe/va", + "status": "alive" + } + ], + "descendants_busy_count":0, + "slug": "europe", + "status": "alive" +} +``` + +###### Excluding data + +To exclude data from the representation, use ``exclude=...`` in prefer header. + +#### Query parameters + +Values from the ``prefer`` header can be used as query parameters: + +``` +curl -i -H "Prefer: return=minimal;" \ + "http://127.0.0.1:5000/api/2.0/taxonomies/country/europe? + representation:include=sta,url&representation:exclude=slug" + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +{ + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/country/europe" + }, + "status": "alive" +} +``` + +#### Selecting subset of term data + +Use ``select= ...`` in ``prefer`` header or ``representation:select=`` query parameter +to select just part of user data: + +```console +$ curl -i -H "Prefer: return=representation;select=/CapitalName /CountryCode" \ + "http://127.0.0.1:5000/api/2.0/taxonomies/country/europe/cz" + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +{ + "CapitalName": "Prague", + "CountryCode": "CZ", + "ancestors": [ + { + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/country/europe" + } + } + ], + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/country/europe/cz" + } +} +``` + +#### Maximum levels + +When descendants are selected, a maximum level of descendants can be specified via +``levels=`` part of ``prefer`` header (or representation:levels=n in the query). + +Example: + + ```console +$ curl -i -H "Prefer: return=representation; levels=1" \ + "http://127.0.0.1:5000/api/2.0/taxonomies/country" + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +{ + 'children': [ + {'slug': 'africa'}, + {'slug': 'antarctica'}, + {'slug': 'asia'}, + {'slug': 'australia'}, + {'slug': 'central-america'}, + {'slug': 'europe'}, + {'slug': 'north-america'}, + {'slug': 'south-america'} + ], + 'code': 'country', + 'title': 'List of countries' +} +``` + +#### Pagination + +If descendants are requested without further arguments the whole tree is returned (well, in fact +at most FLASK_TAXONOMIES_MAX_RESULTS_RETURNED terms to prevent server crash). This leads to high amount +of data transferred and possibly a client crash. To prevent this, pagination should be used on larger +taxonomies. + +Specify ``size`` argument to have at most this number of taxonomy terms returned. For example: + +```console +$ curl -i -H "Prefer: return=minimal;" \ + "http://127.0.0.1:5000/api/2.0/taxonomies/country/europe?representation:include=dsc&size=5" + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree +X-Page: 1 +X-PageSize: 5 +X-Total: 58 + +{ + "children": [ + { + "slug": "europe/ad" + }, + { + "slug": "europe/al" + }, + { + "slug": "europe/am" + }, + { + "slug": "europe/at" + } + ], + "slug": "europe" +} +``` + +This returns 5 terms - europe and 4 children. To return the next page, add ``page=2`` argument: + +```console +$ curl -i -H "Prefer: return=minimal;" \ + "http://127.0.0.1:5000/api/2.0/taxonomies/country/europe?representation:include=dsc&size=5&page=4" + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree +X-Page: 4 +X-PageSize: 5 +X-Total: 58 + +[ + { + "slug": "europe/dk" + }, + { + "slug": "europe/ee" + }, + { + "slug": "europe/es" + }, + { + "slug": "europe/fi" + }, + { + "slug": "europe/fo" + } +] +``` + +Note that the first page contains the root element and the second does not. To fix it, either use ``include=anh`` +to always get hierarchical representation: + +```console +$ curl -i -H "Prefer: return=minimal;" \ + "http://127.0.0.1:5000/api/2.0/taxonomies/country/europe?representation:include=dsc,anh&size=5&page=2" + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree +X-Page: 2 +X-PageSize: 5 +X-Total: 58 + +{ + "ancestor": true, + "children": [ + { + "slug": "europe/ax" + }, + { + "slug": "europe/az" + }, + { + "slug": "europe/ba" + }, + { + "slug": "europe/be" + } + ], + "slug": "europe" +} +``` + +or, if interested only in descendants and not the node itself, ``exclude=self`` + +```console +$ curl -i -H "Prefer: return=minimal;" \ + "http://127.0.0.1:5000/api/2.0/taxonomies/country/europe? + representation:include=dsc&representation:exclude=self&size=5&page=1" + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree +X-Page: 1 +X-PageSize: 5 +X-Total: 58 + +[ + { + "slug": "europe/ad" + }, + { + "slug": "europe/al" + }, + { + "slug": "europe/am" + }, + { + "slug": "europe/at" + }, + { + "slug": "europe/ax" + } +] +``` + +#### Searching + +Use ``q=`` parameter to search within terms. Returns all the resources +whose ``metadata`` contain the expression in q. + +##### Simple query + +If ``q`` is a simple string not containing ':' or string in quotes, +it is interpreted as a string that must be present in any of +values inside the json. + +The current implementation is dependent on the database backend +and might perform sub-optimal ``ilike %x%`` query on textified json. + +```console +$ curl -i -H "Prefer: return=minimal; include=data dsc; exclude=self" \ + http://127.0.0.1:5000/api/2.0/taxonomies/country/europe?q=Prague + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +[ + { + "CapitalLatitude": "50.083333333333336", + "CapitalLongitude": "14.466667", + "CapitalName": "Prague", + "ContinentName": "Europe", + "CountryCode": "CZ", + "CountryName": "Czech Republic", + "slug": "europe/cz" + } +] +``` + +##### Lucene-like query + +If the ``q`` contains a ':' character not enclosed in quotes, +it is parsed as a query in lucene syntax, with the following +allowed constructs: + + * path:value for matching value at the given path + * a.b.c:value for representing nested paths + * AND, OR, NOT, brackets + +The query will be executed if the database or search backend support +it. If not supported, HTTP 501 will be returned. + +### Taxonomy +#### Creating + +To create a taxonomy, either use HTTP PUT: + +```console +$ curl -i -X PUT 'http://127.0.0.1:5000/api/2.0/taxonomies/test' \ +-H 'Content-Type: application/json' \ +--data-raw '{ + "title": "Test taxonomy" +}' + +HTTP/1.0 201 CREATED +Link: ; rel=self +Link: ; rel=tree +Location: http://127.0.0.1:5000/api/2.0/taxonomies/test/ + +{ + "code": "test", + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/test/" + }, + "title": "Test taxonomy" +} +``` + +Or HTTP POST with ``code`` in the payload + +```console +$ curl -i -X POST 'http://127.0.0.1:5000/api/2.0/taxonomies/' \ + -H 'Content-Type: application/json' --data-raw '{ + "title": "Test taxonomy 1", "code": "test1" +}' + +HTTP/1.0 201 CREATED +Link: ; rel=self +Link: ; rel=tree +Location: http://127.0.0.1:5000/api/2.0/taxonomies/test1/ + +{ + "code": "test1", + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/test1/" + }, + "title": "Test taxonomy 1" +} +``` + + +#### Updating + +##### Replacing via HTTP PUT + +```console +$ curl -i -X PUT 'http://127.0.0.1:5000/api/2.0/taxonomies/test' \ + --header 'Content-Type: application/json' --data-raw '{ + "title": "Test taxonomy updated" +}' + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +{ + "code": "test", + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/test/" + }, + "title": "Test taxonomy updated" +} +``` + +Note that terms are not updated nor removed when taxonomy metadata are updated. + +##### Patching with HTTP PATCH + +```console +$ curl -i -X PATCH 'http://127.0.0.1:5000/api/2.0/taxonomies/test' \ + --header 'Content-Type: application/json' --data-raw '[{ + "op": "replace", "path": "/title", "value": "Test taxonomy updated via patch" +}]' + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +{ + "code": "test", + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/test/" + }, + "title": "Test taxonomy updated via patch" +} +``` + +#### Deleting + +```console +$ curl -i -X DELETE 'http://127.0.0.1:5000/api/2.0/taxonomies/test1' + +HTTP/1.0 204 NO CONTENT +Content-Type: text/html; charset=utf-8 +``` + +### Taxonomy Term +#### Creating + +As in creating taxonomy, term can be created either via HTTP PUT: + +```console +$ curl -i -X PUT 'http://127.0.0.1:5000/api/2.0/taxonomies/test/term' \ + --header 'Content-Type: application/json' --data-raw '{ + "title": "Test Term" +}' + +HTTP/1.0 201 CREATED +Link: ; rel=self +Link: ; rel=tree +Location: http://127.0.0.1:5000/api/2.0/taxonomies/test/term + +{ + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/test/term" + }, + "title": "Test Term" +} +``` + +or POST: + +```console +$ curl -i -X POST 'http://127.0.0.1:5000/api/2.0/taxonomies/test' \ + --header 'Content-Type: application/json' --data-raw '{ + "title": "Test term 1", "slug": "term1" +}' + +HTTP/1.0 201 CREATED +Link: ; rel=self +Link: ; rel=tree +Location: http://127.0.0.1:5000/api/2.0/taxonomies/test/term1 + +{ + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/test/term1" + }, + "title": "Test term 1" +} +``` + +As PUT/POST operation also mean updating term if slug exists, to be sure +that you are creating a new one use ``If-None-Match: '*'`` header: + +```console +$ curl -i -X PUT 'http://127.0.0.1:5000/api/2.0/taxonomies/test/term' \ + --header 'Content-Type: application/json' \ + --header 'If-None-Match: '*'' \ + --data-raw '{ + "title": "Test Term" +}' + +HTTP/1.0 412 Precondition Failed + +{ + "message": "The taxonomy already contains a term on this slug. As If-None-Match: '*' has been requested, not modifying the term", + "reason": "term-exists" +} +``` + +Terms can be created within terms via HTTP PUT: + +```console +$ curl -i -X PUT 'http://127.0.0.1:5000/api/2.0/taxonomies/test/term/nested' \ + --header 'Content-Type: application/json' --data-raw '{ + "title": "Nested Term" +}' + +HTTP/1.0 201 CREATED +Link: ; rel=self +Link: ; rel=tree +Location: http://127.0.0.1:5000/api/2.0/taxonomies/test/term/nested + +{ + "ancestors":[ + { + "links":{ + "self":"http://127.0.0.1:5000/api/2.0/taxonomies/test/term" + }, + "title":"Test Term" + } + ], + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/test/term/nested" + }, + "title": "Nested Term" +} +``` + +or POST: + +```console +$ curl -i -X POST 'http://127.0.0.1:5000/api/2.0/taxonomies/test/term1' \ + --header 'Content-Type: application/json' --data-raw '{ + "title": "Test nested term 1", "slug": "nested1" +}' + +HTTP/1.0 201 CREATED +Link: ; rel=self +Link: ; rel=tree +Location: http://127.0.0.1:5000/api/2.0/taxonomies/test/term1/nested1 + +{ + "ancestors":[ + { + "links":{ + "self":"http://127.0.0.1:5000/api/2.0/taxonomies/test/term1" + }, + "title":"Test term 1" + } + ], + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/test/term1/nested1" + }, + "title": "Test nested term 1" +} +``` + +#### Updating + +As for taxonomy, use HTTP ``PUT``: + +```console +$ curl -i -X PUT 'http://127.0.0.1:5000/api/2.0/taxonomies/test/term' \ + --header 'Content-Type: application/json' --data-raw '{ + "title": "Test Term updated" +}' + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +{ + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/test/term" + }, + "title": "Test Term updated" +} +``` + +or ``PATCH``: + +```console +$ curl -i -X PATCH 'http://127.0.0.1:5000/api/2.0/taxonomies/test/term' \ + --header 'Content-Type: application/json' --data-raw '[{ + "op": "replace", "path": "/title", "value": "Test taxonomy term updated via patch" +}]' + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +{ + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/test/term" + }, + "title": "Test taxonomy term updated via patch" +} +``` + +As PUT operation also means creating term if slug does not exist, to be sure +that you are just updating use ``If-Match: '*'`` header: + +```console +$ curl -i -X PUT 'http://127.0.0.1:5000/api/2.0/taxonomies/test/unknown' \ + --header 'Content-Type: application/json' \ + --header 'If-Match: '*'' \ + --data-raw '{ + "title": "Test Term" +}' + +HTTP/1.0 412 Precondition Failed + +{ + "message": "The taxonomy does not contain a term on this slug. As If-Match: '*' has been requested, not creating a new term", + "reason": "term-does-not-exist" +} +``` + + +#### Deleting + +Use HTTP delete to remove a term. The removed term will be returned in the response: + +```console +$ curl -i -X DELETE 'http://127.0.0.1:5000/api/2.0/taxonomies/test/term1' + +HTTP/1.0 200 OK +Content-Type: application/json + +{ + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/test/term1" + }, + "title": "Test term 1" +} +``` + +Subsequent GET returns 410: + +```console +$ curl -i 'http://127.0.0.1:5000/api/2.0/taxonomies/test/term1' + +HTTP/1.0 410 GONE + +{ + "message": "http://127.0.0.1:5000/api/2.0/taxonomies/test/term1 was not found on the server", + "reason": "deleted" +} +``` + +But the term stays on the server: + +```console +$ curl -i 'http://127.0.0.1:5000/api/2.0/taxonomies/test/term?representation:include=del' +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +{ + "links": { + "self": "http://127.0.0.1:5000/api/2.0/taxonomies/test/term" + }, + "title": "Test taxonomy term updated via patch" +} +``` + +#### Un-Deleting + +To salvage a deleted term, update it via PATCH, with an empty set of operations to keep it unmodified: + +```console +$ curl -i -X PATCH -H "Prefer: return=minimal; include=del" \ + 'http://127.0.0.1:5000/api/2.0/taxonomies/test/term' \ + --header 'Content-Type: application/json' --data-raw '[]' + +HTTP/1.0 200 OK +Link: ; rel=self +Link: ; rel=tree + +{ + "slug": "term" +} +``` + +#### Moving + +Use HTTP post with content type ``application/vnd.move`` and ``Destination`` header: + +```console +$ curl -i -X POST \ + -H 'Content-Type: application/vnd.move' \ + -H "Destination: /" \ + 'http://127.0.0.1:5000/api/2.0/taxonomies/test/term/nested' + +HTTP/1.0 200 OK + +{ + "links":{ + "self":"http://127.0.0.1:5000/api/2.0/taxonomies/test/nested" + }, + "title":"Nested Term" +} +``` + +The original url returns 301: + +```console +$ curl -i 'http://127.0.0.1:5000/api/2.0/taxonomies/test/term/nested' + +HTTP/1.0 301 MOVED PERMANENTLY +Location: http://127.0.0.1:5000/api/2.0/taxonomies/test/nested +Link: ; rel=self +Link: ; rel=obsoleted_by + +{ + "links": { + "self": "http://localhost/api/2.0/taxonomies/test/term/nested", + "obsoleted_by": "http://localhost/api/2.0/taxonomies/test/nested" + }, + "status": "moved" +} +``` + +#### Renaming + +Use HTTP post with content type ``application/vnd.move`` and ``Rename`` header: + +```console +$ curl -i -X POST \ + -H 'Content-Type: application/vnd.move' \ + -H "Rename: renamed-nested" \ + 'http://127.0.0.1:5000/api/2.0/taxonomies/test/nested' + +HTTP/1.0 200 OK + +{ + "links":{ + "self":"http://127.0.0.1:5000/api/2.0/taxonomies/test/renamed-nested" + }, + "title":"Nested Term" +} +``` + + +The original url returns 301: + +```console +$ curl -i 'http://127.0.0.1:5000/api/2.0/taxonomies/test/nested' + +HTTP/1.0 301 MOVED PERMANENTLY +Location: http://127.0.0.1:5000/api/2.0/taxonomies/test/renamed-nested +Link: ; rel=self +Link: ; rel=obsoleted_by + +{ + "links": { + "self": "http://localhost/api/2.0/taxonomies/test/nested", + "obsoleted_by": "http://localhost/api/2.0/taxonomies/test/renamed-nested" + }, + "status": "moved" +} +``` + + +## Configuration + +### Configuration Variables + +``FLASK_TAXONOMIES_SERVER_NAME`` + +Server name hosting the taxonomies. If not set, SERVER_NAME is used. + +``FLASK_TAXONOMIES_SERVER_SCHEME`` + +Scheme to use in generated urls, defaults to ``https`` + +``FLASK_TAXONOMIES_URL_PREFIX`` + +A prefix on which taxonomies are served, defaults to ``/api/2.0/taxonomies/`` + +``FLASK_TAXONOMIES_REPRESENTATION`` + +Values for ``Prefer: return=`` header. ``minimal`` and ``representation`` are obligatory, +you are free to add other return representations. + +```python +from flask_taxonomies.constants import * + +FLASK_TAXONOMIES_REPRESENTATION = { + 'minimal': { + 'include': [INCLUDE_SLUG, INCLUDE_SELF], + 'exclude': [], + 'select': None, + 'options': {} + }, + 'representation': { + 'include': [INCLUDE_DATA, INCLUDE_ANCESTORS, + INCLUDE_URL, INCLUDE_SELF], + 'exclude': [], + 'select': None, + 'options': {} + }, + 'full': { + 'include': [INCLUDE_DATA, INCLUDE_ANCESTORS, INCLUDE_URL, + INCLUDE_DESCENDANTS_URL, INCLUDE_SELF], + 'exclude': [], + 'select': None, + 'options': {} + } +} +``` + +``FLASK_TAXONOMIES_MAX_RESULTS_RETURNED`` + +Specifies max results returned when pagination is not used. Defaults to ``10000``. + +### Security + +Flask taxonomies uses ``flask-principal`` to handle security. The default permissions are +that everyone is allowed to read/create/update/delete/move all taxonomies and terms. + +To restrict the access, specify permission factories (a function that returns a list of permission) +for each operation. + +``FLASK_TAXONOMIES_PERMISSION_FACTORIES`` + +A dictionary of operation to a list of permissions. + +``` +FLASK_TAXONOMIES_PERMISSION_FACTORIES = { + 'taxonomy_list': request -> List[Permission] + 'taxonomy_read': request, taxonomy -> List[Permission] + 'taxonomy_create': request, code -> List[Permission] + 'taxonomy_update': request, taxonomy -> List[Permission] + 'taxonomy_delete': request, taxonomy -> List[Permission], + + 'taxonomy_term_read': request, taxonomy, slug -> List[Permission] + 'taxonomy_term_create': request, taxonomy, slug -> List[Permission] + 'taxonomy_term_update': request, taxonomy, term -> List[Permission] + 'taxonomy_term_delete': request, taxonomy, term -> List[Permission], + 'taxonomy_term_move': request, taxonomy, term, destination, rename -> List[Permission], +} +``` +The right-hand side can be either a list/tuple of permissions, function with the above-mentioned +signatures or a string pointing to the implementation. The string form is resolved on +the first request. + +If ``.can`` on any of the permissions returns True or the list is empty, access is granted. + +#### Recommended initial settings + +The recommended initial settings are read-only for everyone except admin role: + +```python +from flask_principal import RoleNeed + +FLASK_TAXONOMIES_PERMISSION_FACTORIES = { + 'taxonomy_create': [RoleNeed('admin')], + 'taxonomy_update': [RoleNeed('admin')], + 'taxonomy_delete': [RoleNeed('admin')], + + 'taxonomy_term_create': [RoleNeed('admin')], + 'taxonomy_term_update': [RoleNeed('admin')], + 'taxonomy_term_delete': [RoleNeed('admin')], + 'taxonomy_term_move': [RoleNeed('admin')] +} +``` + +## Python API + +The calls below use ``session`` as an optional parameter. If not supplied, session from +current_app is used. + +``TermIdentification`` is a class to identify taxonomy term, binding taxonomy (or its code), +slug or a term instance. See [flask_taxonomies/term_identification.py](./flask_taxonomies/term_identification.py) +for details. + +```python +from flask_taxonomies.proxies import current_flask_taxonomies + +# returns a taxonomy list +current_flask_taxonomies.list_taxonomies(session=None) + +# returns a taxonomy with the given code. Fails by default if not found +current_flask_taxonomies.get_taxonomy(code, fail=True, session=None) + +# creates a new taxonomy +current_flask_taxonomies.create_taxonomy(code, extra_data=None, url=None, + select=None, session=None) + +# updates a taxonomy +current_flask_taxonomies.update_taxonomy( + taxonomy: [Taxonomy, str], extra_data, + url=MISSING, select=MISSING, + session=None) + +# deletes a taxonomy +current_flask_taxonomies.delete_taxonomy(taxonomy: Taxonomy, session=None) + +# lists terms within the taxonomy. +current_flask_taxonomies.list_taxonomy(taxonomy: [Taxonomy, str], levels=None, + status_cond=TaxonomyTerm.status == TermStatusEnum.alive, + order=True, session=None) + +# creates a new term inside a taxonomy +current_flask_taxonomies.create_term(ti: TermIdentification, + extra_data=None, session=None) + +# updates a term, setting or patching extra_data +current_flask_taxonomies.update_term(ti: [TaxonomyTerm, TermIdentification], + status_cond=TaxonomyTerm.status == TermStatusEnum.alive, + extra_data=None, patch=False, status=MISSING, session=None) + +# returns all descendants of a term +current_flask_taxonomies.descendants(ti: TermIdentification, levels=None, + status_cond=TaxonomyTerm.status == TermStatusEnum.alive, + order=True, session=None) + +# returns a term and its descendants +current_flask_taxonomies.descendants_or_self(ti: TermIdentification, levels=None, + status_cond=TaxonomyTerm.status == TermStatusEnum.alive, + order=True, session=None) + +# returns all ancestors of a term +current_flask_taxonomies.ancestors(ti: TermIdentification, + status_cond=TaxonomyTerm.status == TermStatusEnum.alive, session=None) + +# returns term and its ancestors +current_flask_taxonomies.ancestors_or_self(ti: TermIdentification, + status_cond=TaxonomyTerm.status == TermStatusEnum.alive, session=None) + +# removes a term +current_flask_taxonomies.delete_term(ti: TermIdentification, + remove_after_delete=True, session=None) + +# renames term's slug +current_flask_taxonomies.rename_term(ti: TermIdentification, new_slug=None, + remove_after_delete=True, session=None) + +# moves term into a new parent within the same taxonomy +current_flask_taxonomies.move_term(ti: TermIdentification, new_parent=None, + remove_after_delete=True, session=None) +``` + +### Signals + +See [flask_taxonomies/signals.py](flask_taxonomies/signals.py) for details + + + +%prep +%autosetup -n flask-taxonomies-7.0.3 + +%build +%py3_build + +%install +%py3_install +install -d -m755 %{buildroot}/%{_pkgdocdir} +if [ -d doc ]; then cp -arf doc %{buildroot}/%{_pkgdocdir}; fi +if [ -d docs ]; then cp -arf docs %{buildroot}/%{_pkgdocdir}; fi +if [ -d example ]; then cp -arf example %{buildroot}/%{_pkgdocdir}; fi +if [ -d examples ]; then cp -arf examples %{buildroot}/%{_pkgdocdir}; fi +pushd %{buildroot} +if [ -d usr/lib ]; then + find usr/lib -type f -printf "/%h/%f\n" >> filelist.lst +fi +if [ -d usr/lib64 ]; then + find usr/lib64 -type f -printf "/%h/%f\n" >> filelist.lst +fi +if [ -d usr/bin ]; then + find usr/bin -type f -printf "/%h/%f\n" >> filelist.lst +fi +if [ -d usr/sbin ]; then + find usr/sbin -type f -printf "/%h/%f\n" >> filelist.lst +fi +touch doclist.lst +if [ -d usr/share/man ]; then + find usr/share/man -type f -printf "/%h/%f.gz\n" >> doclist.lst +fi +popd +mv %{buildroot}/filelist.lst . +mv %{buildroot}/doclist.lst . + +%files -n python3-flask-taxonomies -f filelist.lst +%dir %{python3_sitelib}/* + +%files help -f doclist.lst +%{_docdir}/* + +%changelog +* Wed May 31 2023 Python_Bot - 7.0.3-1 +- Package Spec generated diff --git a/sources b/sources new file mode 100644 index 0000000..a4ccb55 --- /dev/null +++ b/sources @@ -0,0 +1 @@ +8c871985e0009c4965e5d84dc82e3a2d flask-taxonomies-7.0.3.tar.gz -- cgit v1.2.3