7.6 KiB
Usage
Overview:
- the quick config example shows how you can use the plugin config to setup a quick and easy tagging system.
- the simple example goes into detail how this plugin works.
- the advanced example touches on the potentials of the plugin.
About
To use the groupby plugin you have to add an attribute to your model file.
In our case you can refer to the models/page.ini model:
[fields.tags]
label = Tags
type = strings
testA = true
testB = true
[fields.body]
label = Body
type = markdown
testC = true
We did define three custom attributes testA, testB, and testC.
You may add custom attributes to all of the fields.
It is crucial that the value of the custom attribute is set to true.
The attribute name is later used for grouping.
Quick config
Relevant files:
configs/groupby.ini
templates/example-config.html
The easiest way to add tags to your site is by defining the groupby.ini config file.
[testA]
root = /
slug = config/{group}.html
template = example-config.html
split = ' '
The configuration parameter are:
- The section title (
testA) must be one of the attribute variables we defined in our model. - The
rootparameter (/) is the root page of the groupby. All results will be placed under this directory, e.g.,/tags/tagname/. If you useroot = /blog, the results path will be/blog/tags/tagname/. The groupby plugin will traverse all sub-pages wich contain the attributetestA. - The
slugparameter (config/{group}.html) is where the results are placed. In our case, the path resolves toconfig/tagname.html. The default value is{attrib}/{group}/index.htmlwhich would resolve totestA/tagname/index.html. - The
templateparameter (example-config.html) is used to render the results page. If no explicit template is set, the default templategroupby-testA.htmlwill be used. WheretestAis replaced with whatever attribute you chose. - The
splitparameter (' ') will be used as string delimiter. Fields of typestringsandcheckboxesare already lists and don't need splitting. The split is only relevant for fields of typestringortext. These single-line fields are then expanded to lists as well. If you do not provide thesplitoption, the whole field value will be used as tagname.
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.
In your template file you have access to the children (pages) and their tags.
The emitted extras for the child is a list of original tagnames.
{%- for child, extras in this.children.items() %}
<li>Page: {{ child.path }}, Tags: {{ extras }}</li>
{%- endfor %}
Simple example
Relevant files:
packages/simple-example/lektor_simple.py
templates/example-simple.html
def on_groupby_after_build_all(self, groupby, builder, **extra):
@groupby.watch('/blog', 'testB', slug='simple/{group}/index.html',
template='example-simple.html', flatten=True)
def convert_simple_example(args):
value = args.field # list, since model is 'strings' type
for tag in value:
yield slugify(tag), {'val': tag, 'tags_in_page': len(value)}
# page = args.record # extract additional info from source
# fieldKey, flowIndex, flowKey = args.key # or get field index
# if flowIndex is None:
# obj = page[fieldKey]
# else:
# obj = page[fieldKey].blocks[flowIndex].get(flowKey)
This example is roughly equivalent to the config file example.
The parameters of the @groupby.watch function (root, attribute, slug, template) correspond to the same config parameters described above.
There is a new flatten parameter:
- Flatten determines how Flow elements are processed.
If
False, the callback function is called once per Flow element. IfTrue(default), the callback is called for all Flow-Blocks of the Flow individually. The attributetestBcan be attached to either the Flow or a Flow-Block regardless.
The args parameter of the convert_simple_example() function is a named tuple, it has three attributes:
- The
recordpoints to thePagesource which contains the tag. - The
keytuple(field-key, flow-index, flow-key)tells which field is processed. For Flow types,flow-indexandflow-keyare set, otherwise they areNone. - The
fieldvalue is the content of the processed field. The field value is reoughly equivalent to the following:
args.page[fieldKey].blocks[flowIndex].get(flowKey)
The callback body can produce groupings but does not have to.
If you choose to produce an entry, you have to yield a tuple pair of (groupkey, extra-info).
groupkey is used to combine & cluster pages and must be URL-safe.
The extra-info is passed through to your template file.
You can yield more than one entry per source or filter / ignore pages if you don't yield anything.
The template file can access and display the extra-info:
{%- for child, extras in this.children.items() %}
<b>Page: {{ child.title }}<b>
<ul>
{%- for extra in extras %}
<li>Name: {{ extra.val }}, Tag count: {{ extra.tags_in_page }}</li>
{%- endfor %}
</ul>
{%- endfor %}
Advanced example
Relevant files:
configs/advanced.ini
packages/advanced-example/lektor_advanced.py
templates/example-advanced.html
The following example is similar to the previous one.
Except that it loads a config file and replaces in-text occurrences of {{Tagname}} with <a href="/tag/">Tagname</a>.
def on_groupby_before_build_all(self, groupby, builder, **extra):
# load config
regex = re.compile(self.get_config().get('match'))
# since we load and use a config file, we need to track the dependency
@groupby.depends_on(self.config_filename)
@groupby.watch('/', 'testC', slug='advanced/{group}/',
template='example-advanced.html')
def convert_replace_example(args):
# args.field assumed to be Markdown
obj = args.field.source
for match in regex.finditer(obj):
tag = match.group(1)
yield slugify(tag), tag
def _fn(match: re.Match) -> str:
tag = match.group(1)
return f'<a href="/advanced/{slugify(tag)}/">{tag}</a>'
args.field.source = regex.sub(_fn, obj)
One important thing to notice is, we use on_groupby_before_build_all to register our callback function.
This is required because we would like to modify the source before it is written to disk.
If you look back to the simple example, we used on_groupby_after_build_all because we did not care when it is executed.
Generally, it makes little difference which one you use (on-after is likely less busy).
Just know that you can process the source before or after it is build.
For Markdown fields, we can modify the source attribute directly.
All other field typed need to be accessed via args.record key indirection.
match = {{([^}]{1,32})}}
Lastly, the config file contains a regular expression which matches {{ + any string less than 32 characters + }}.
Notice, the parenthesis (()) will match the inner part but the replace function (re.sub) will remove the {{}} too.
If the user changes the regex pattern in the config file, we need to rebuild all tags. For this purpose we need to track changes to the config file. This is done by calling:
@groupby.depends_on(file1, file2, ...)