# Lektor Plugin: groupby A generic grouping / clustering plugin. Can be used for tagging or similar tasks. Overview: - the [basic example](#usage-basic-example) goes into detail how this plugin works. - the [quick config](#usage-quick-config) example show how you can use the plugin config to setup a quick and easy tagging system. - the [complex example](#usage-a-slightly-more-complex-example) touches on the potential of what is possible. ## Usage: Basic example Lets start with a simple example: adding a tags field to your model. Assuming you have a `blog-entry.ini` that is used for all children of `/blog` path. #### `models/blog-entry.ini` ```ini [fields.tags] label = Tags type = strings myvar = true [fields.body] label = Content type = markdown ``` Notice we introduce a new attribute variable: `myvar = true`. The name can be anything here, we will come to that later. The only thing that matters is that the value is a boolean and set to true. Edit your blog entry and add these two new tags: ``` Awesome Latest News ``` Next, we need a plugin to add the groupby event listener. #### `packages/test/lektor_my_tags_plugin.py` ```python def on_groupby_init(self, groupby, **extra): @groupby.watch('/blog', 'myvar', flatten=True, template='myvar.html', slug='tag/{group}/index.html') def do_myvar(args): page = args.record # extract additional info from source fieldKey, flowIndex, flowKey = args.key # or get field index directly # val = page.get(fieldKey).blocks[flowIndex].get(flowKey) value = args.field # list type since model is 'strings' type for tag in value: yield slugify(tag), {'val': tag, 'tags_in_page': len(value)} ``` There are a few important things here: 1. The first parameter (`'/blog'`) is the root page of the groupby. All results will be placed under this directory, e.g., `/blog/tags/clean/`. You can also just use `/`, in which case the same path would be `/tags/clean/`. Or create multiple listeners, one for `/blog/` and another for `/projects/`, etc. 2. The second parameter (`'myvar'`) must be the same attribute variable we used in our `blog-entry.ini` model. The groupby plugin will traverse all models and search for this attribute name. 3. Flatten determines how Flow elements are processed. If `False`, the callback function `convert_myvar()` is called once per Flow element (if the Flow element has the `myvar` attribute attached). If `True` (default), the callback is called for all Flow blocks individually. 4. The template `myvar.html` is used to render the grouping page. This parameter is optional. If no explicit template is set, the default template `groupby-myvar.html` would be used. Where `myvar` is replaced with whatever attribute you chose. 5. Finally, the slug `tag/{group}/index.html` is where the result is placed. The default value for this parameter is `{attrib}/{group}/index.html`. In our case, the default path would resolve to `myvar/awesome/index.html`. We explicitly chose to replace the default slug with our own, which ignores the attrib path component and instead puts the result pages inside the `/tag` directory. (PS: you could also use for example `t/{group}.html`, etc.) So much for the `args` parameter. 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. Our simple example will generate the output files `tag/awesome/index.html` and `tag/latest-news/index.html`. Lets take a look at the html next. #### `templates/myvar.html` ```html