Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5bbe6d2360 | ||
|
|
1188216788 | ||
|
|
b603fb9dd2 | ||
|
|
05b9fbf20a | ||
|
|
7039fb3a63 | ||
|
|
4689e9fccb | ||
|
|
2e7cc026f6 | ||
|
|
14a7fe848f |
25
CHANGELOG.md
25
CHANGELOG.md
@@ -8,6 +8,27 @@ and this project does adhere to [Semantic Versioning](https://semver.org/spec/v2
|
||||
## [Unreleased]
|
||||
|
||||
|
||||
## [1.0.0] – 2023-03-04
|
||||
|
||||
### Added
|
||||
- `vgroups` filter now supports `unique=False` to return a list of all entries including duplicates (default: `True`)
|
||||
|
||||
### Fixed
|
||||
- Preserves original sort order in `vgroups` filter if `unique=True`
|
||||
- Remove duplicates from `GroupBySource` children
|
||||
|
||||
### Changed
|
||||
- `vgroups` filter uses `recursive=True` by default
|
||||
|
||||
|
||||
|
||||
## [0.9.9] – 2022-12-21
|
||||
|
||||
### Fixed
|
||||
- Keep original sorting order in `vgroups` filter if no `order_by` is set
|
||||
|
||||
|
||||
|
||||
## [0.9.8] – 2022-12-20
|
||||
|
||||
### Added
|
||||
@@ -142,7 +163,9 @@ and this project does adhere to [Semantic Versioning](https://semver.org/spec/v2
|
||||
Initial release
|
||||
|
||||
|
||||
[Unreleased]: https://github.com/relikd/lektor-groupby-plugin/compare/v0.9.8...HEAD
|
||||
[Unreleased]: https://github.com/relikd/lektor-groupby-plugin/compare/v1.0.0...HEAD
|
||||
[1.0.0]: https://github.com/relikd/lektor-groupby-plugin/compare/v0.9.9...v1.0.0
|
||||
[0.9.9]: https://github.com/relikd/lektor-groupby-plugin/compare/v0.9.8...v0.9.9
|
||||
[0.9.8]: https://github.com/relikd/lektor-groupby-plugin/compare/v0.9.7...v0.9.8
|
||||
[0.9.7]: https://github.com/relikd/lektor-groupby-plugin/compare/v0.9.6...v0.9.7
|
||||
[0.9.6]: https://github.com/relikd/lektor-groupby-plugin/compare/v0.9.5...v0.9.6
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
A generic grouping / clustering plugin.
|
||||
Can be used for tagging or similar tasks.
|
||||
The grouping algorithm is performed once.
|
||||
Contrary to, at least, cubic runtime if doing the same with Pad queries.
|
||||
|
||||
Install this plugin or modify your Lektor project file:
|
||||
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
# Usage
|
||||
|
||||
Overview:
|
||||
- [quick config example](#quick-config) shows how you can use the plugin config to setup a quick and easy tagging system.
|
||||
- [config example](#config-file) shows how you can use the plugin config to setup a quick and easy tagging system.
|
||||
- [simple example](#simple-example) goes into detail how to use it in your own plugin.
|
||||
- [advanced example](#advanced-example) touches on the potentials of the plugin.
|
||||
- [advanced example](#advanced-example) touches on the potentials of plugin development.
|
||||
- [Misc](#misc) shows other use-cases.
|
||||
|
||||
After reading this tutorial, have a look at other plugins that use `lektor-groupby`:
|
||||
- [lektor-inlinetags](https://github.com/relikd/lektor-inlinetags-plugin)
|
||||
|
||||
|
||||
|
||||
## About
|
||||
|
||||
To use the groupby plugin you have to add an attribute to your model file.
|
||||
@@ -35,15 +36,9 @@ The attribute name is later used for grouping.
|
||||
|
||||
|
||||
|
||||
## Quick config
|
||||
## Config File
|
||||
|
||||
Relevant files:
|
||||
|
||||
- [`configs/groupby.ini`](./configs/groupby.ini)
|
||||
- [`templates/example-config.html`](./templates/example-config.html)
|
||||
|
||||
|
||||
The easiest way to add tags to your site is by defining the `groupby.ini` config file.
|
||||
The easiest way to add tags to your site is by defining the [`configs/groupby.ini`](./configs/groupby.ini) file.
|
||||
|
||||
```ini
|
||||
[testA]
|
||||
@@ -67,9 +62,12 @@ url_suffix = .page.
|
||||
title = "Tagged: " ~ this.key_obj
|
||||
|
||||
[testA.key_map]
|
||||
Blog = News
|
||||
C# = c-sharp
|
||||
```
|
||||
|
||||
|
||||
### Config: Main Section
|
||||
|
||||
The configuration parameter are:
|
||||
|
||||
1. The section title (`testA`) must be one of the attribute variables we defined in our model.
|
||||
@@ -91,7 +89,7 @@ The configuration parameter are:
|
||||
These single-line fields are then expanded to lists as well.
|
||||
If you do not provide the `split` option, the whole field value will be used as tagname.
|
||||
6. The `enabled` parameter allows you to quickly disable the grouping.
|
||||
7. The `key_obj_fn` parameter (jinja2) accepts any function-like snippet or function call.
|
||||
7. The `key_obj_fn` parameter (jinja) accepts any function-like snippet or function call.
|
||||
The context provides two variables, `X` and `ARGS`.
|
||||
The former is the raw value of the grouping, this may be a text field, markdown, or whatever custom type you have provided.
|
||||
The latter is a named tuple with `record`, `key`, and `field` values (see [simple example](#simple-example)).
|
||||
@@ -101,13 +99,22 @@ The configuration parameter are:
|
||||
You can have multiple listeners, e.g., one for `/blog/` and another for `/projects/`.
|
||||
Just create as many custom attributes as you like, each having its own section (and subsections).
|
||||
|
||||
|
||||
### Config Subsection: .children
|
||||
|
||||
The `.children` subsection currently has a single config field: `order_by`.
|
||||
The usual [order-by](https://www.getlektor.com/docs/guides/page-order/) rules apply (comma separated list of keys with `-` for reversed order).
|
||||
The order-by key can be anything of the page attributes of the children.
|
||||
|
||||
The `.pagination` subsection accepts the same configuration options as the Lektor pagination [model](https://www.getlektor.com/docs/models/children/#pagination) and [guide](https://www.getlektor.com/docs/guides/pagination/).
|
||||
|
||||
### Config Subsection: .pagination
|
||||
|
||||
The `.pagination` subsection accepts the same configuration options as the default Lektor pagination ([model](https://www.getlektor.com/docs/models/children/#pagination), [guide](https://www.getlektor.com/docs/guides/pagination/)).
|
||||
Plus, an additional `url_suffix` parameter if you would like to customize the URL scheme.
|
||||
|
||||
|
||||
### Config Subsection: .fields
|
||||
|
||||
The `.fields` subsection is a list of key-value pairs which will be added as attributes to your grouping.
|
||||
You can access them in your template (e.g., `{{this.title}}`).
|
||||
All of the `.fields` values are evaluted in a jinja context, so be cautious when using plain strings.
|
||||
@@ -122,11 +129,6 @@ The built-in field attributes are:
|
||||
- `children`: the elements of the grouping (a `Query` of `Record` type)
|
||||
- `config`: configuration object (see below)
|
||||
|
||||
Without any changes, the `key` value will just be `slugify(key_obj)`.
|
||||
However, the `.key_map` subsection will replace `key_obj` with whatever replacement value is provided in the `.key_map` and then slugify.
|
||||
You could, for example, add a `C# = c-sharp` mapping, which would otherwise just be slugified to `c`.
|
||||
This is equivalent to `slugify(key_map.get(key_obj))`.
|
||||
|
||||
The `config` attribute contains the values that created the group:
|
||||
|
||||
- `key`: attribute key, e.g., `TestA`
|
||||
@@ -142,9 +144,21 @@ The `config` attribute contains the values that created the group:
|
||||
- `pagination`: raw values from `TestA.pagination`
|
||||
- `order_by`: list of key-strings from `TestA.children.order_by`
|
||||
|
||||
In your template file you have access to the config, attributes, fields, and children (Pages):
|
||||
|
||||
```jinja2
|
||||
### Config Subsection: .key_map
|
||||
|
||||
Without any changes, the `key` value will just be `slugify(key_obj)`.
|
||||
However, the `.key_map` subsection will replace `key_obj` with whatever replacement value is provided in the mapping and is then slugified.
|
||||
Take the given example, `C# = c-sharp`, which would otherwise be slugified to `c`.
|
||||
This is equivalent to `slugify(key_map.get(key_obj))`.
|
||||
|
||||
|
||||
### Config Template
|
||||
|
||||
In your template file ([`templates/example-config.html`](./templates/example-config.html)), you have access to the aforementioned attributes:
|
||||
|
||||
|
||||
```jinja
|
||||
<h2>{{ this.title }}</h2>
|
||||
<p>Key: {{ this.key }}, Attribute: {{ this.config.key }}</p>
|
||||
<ul>
|
||||
@@ -215,7 +229,7 @@ field = args.record[k.fieldKey].blocks[k.flowIndex].get(k.flowKey)
|
||||
|
||||
Again, you can use all properties in your template.
|
||||
|
||||
```jinja2
|
||||
```jinja
|
||||
<p>Custom field date: {{this.date}}</p>
|
||||
<ul>
|
||||
{%- for child in this.children %}
|
||||
@@ -300,8 +314,8 @@ template = example-advanced.html
|
||||
match = {{([^}]{1,32})}}
|
||||
```
|
||||
|
||||
The config file takes the same parameters as the [config example](#quick-config).
|
||||
We introduced a new config option `testC.pattern.match`.
|
||||
The config file takes the same parameters as the [config example](#config-file).
|
||||
We introduce a new config option `testC.pattern.match`.
|
||||
This regular expression matches `{{` + any string less than 32 characters + `}}`.
|
||||
Notice, the parenthesis (`()`) will match only the inner part, thus the replace function (`re.sub`) removes the `{{}}`.
|
||||
|
||||
@@ -318,7 +332,7 @@ You can combine this with the next use-case.
|
||||
|
||||
### Index pages & Group query + filter
|
||||
|
||||
```jinja2
|
||||
```jinja
|
||||
{%- for x in this|vgroups(keys=['TestA', 'TestB'], fields=[], flows=[], recursive=True, order_by='key_obj') %}
|
||||
<a href="{{ x|url }}">({{ x.key_obj }})</a>
|
||||
{%- endfor %}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from lektor.context import get_ctx
|
||||
from typing import TYPE_CHECKING, Set, Union, Iterable, Iterator
|
||||
from typing import TYPE_CHECKING, Set, List, Dict, Union, Iterable, Iterator
|
||||
import weakref
|
||||
from .util import split_strip
|
||||
if TYPE_CHECKING:
|
||||
@@ -49,8 +49,9 @@ class VGroups:
|
||||
*,
|
||||
fields: Union[str, Iterable[str], None] = None,
|
||||
flows: Union[str, Iterable[str], None] = None,
|
||||
recursive: bool = False,
|
||||
order_by: Union[str, Iterable[str], None] = None,
|
||||
recursive: bool = True,
|
||||
unique: bool = True,
|
||||
) -> Iterator['GroupBySource']:
|
||||
''' Extract all referencing groupby virtual objects from a page. '''
|
||||
# prepare filter
|
||||
@@ -68,7 +69,12 @@ class VGroups:
|
||||
GroupByRef.of(builder).make_once(keys) # ensure did cluster before use
|
||||
# find groups
|
||||
proc_list = [record]
|
||||
done_list = set() # type: Set[GroupBySource]
|
||||
|
||||
# Note: An ordered Set would be more approptiate but there is none.
|
||||
# So lets use the insert order of dict (guaranteed since Python 3.7)
|
||||
_only_uniques = {} # type: Dict[GroupBySource, None]
|
||||
_w_duplicates = [] # type: List[GroupBySource]
|
||||
|
||||
while proc_list:
|
||||
page = proc_list.pop(0)
|
||||
if recursive and hasattr(page, 'children'):
|
||||
@@ -80,7 +86,12 @@ class VGroups:
|
||||
continue
|
||||
if keys and vobj().config.key not in keys:
|
||||
continue
|
||||
done_list.add(vobj())
|
||||
if unique:
|
||||
_only_uniques[vobj()] = None # we only need the keys()
|
||||
else:
|
||||
_w_duplicates.append(vobj())
|
||||
|
||||
done_list = _only_uniques if unique else _w_duplicates
|
||||
|
||||
# manage config dependencies
|
||||
deps = set() # type: Set[str]
|
||||
|
||||
@@ -46,7 +46,7 @@ class GroupBySource(VirtualSourceObject):
|
||||
self.page_num = page_num
|
||||
|
||||
def append_child(self, child: 'Record', key_obj: Any) -> None:
|
||||
if child not in self.__children:
|
||||
if child.path not in self.__children:
|
||||
self.__children.append(child.path)
|
||||
# __key_obj_map is later used to find most used key_obj
|
||||
self.__key_obj_map.append(key_obj)
|
||||
|
||||
Reference in New Issue
Block a user