#### This Page

# Categories

Because Lektor is based on the idea of mirroring the tree structure from the file system to the final website it sometimes might not be quite clear how one could achieve any form of filtering (for instance categories). However this is actually very easily achieved through the concept of “child replacement”. This will give you a quick introduction to how this feature can be used to implement categories.

## Basic Setup

In this case we assume you have a bunch of projects that can exist in different categories and it should be possible to see which projects belong to which category.

## The Models

So we will end up with four models: `projects.ini`, `project.ini`, `project-categories.ini` and `project-category.ini`. Here is how they look:

### `projects.ini`

    [model]
    name = Projects
    label = Projects
    hidden = yes
    protected = yes

    [children]
    model = project
    order_by = -date, name

### `project.ini`

    [model]
    name = Project
    label = {{ this.name }}
    hidden = yes

    [fields.name]
    label = Name
    type = string

    [fields.date]
    label = Date
    type = date

    [fields.description]
    label = Description
    type = markdown

    [fields.categories]
    label = Categories
    type = checkboxes
    source = site.query('/project-categories')

The above models should be mostly clear. What is probably not entirely clear is the `source` parameter for the categories. Essentially we instruct the admin panel to fill the selection for the checkboxes from the child pages below the `project-categories` folder. This is where we will maintain the categories.

### `project-categories.ini`

    [model]
    name = Project Categories
    label = Project Categories
    hidden = yes
    protected = yes

    [children]
    model = project-category
    order_by = name

### `project-category.ini`

    [model]
    name = Project Category
    label = {{ this.name }}
    hidden = yes

    [children]
    replaced_with = site.query('/projects').filter(F.categories.contains(this))

    [fields.name]
    label = Name
    type = string

So this is where the magic lives. the `replaced_with` key in the `[children]` section tells Lektor to ignore the child pages that would normally exist below the path but to substitute them with another query. In this case we replace them with all the projects where the category (`this`) is in the checked set of categories (`F.categories`).

## Initializing The Folders

Now that we have the models, we need to initialize the folders. This is necessary because most models are hidden which means they cannot be selected from the admin interface. We need the following things:

### `projects/contents.lr`

    _model: projects

### `project-categories/contents.lr`

    _model: project-categories
    ---
    _slug: projects/categories

Here we also tell the system that we want to move the page from `project-categories/` to `projects/categories/` which looks nicer. This means a category named `Foo` will then end up as `projects/categories/foo/`.

## Creating Categories

Now we can head to the admin panel to create some categories. Each category that is created will become available for new projects in the category selection.

## The Templates

To render out all the pages we probably want to use some macros to reuse markup that appears on multiple pages.

### `projects.html`

    {% extends "layout.html" %}
    {% from "macros/projects.html" import
      render_project_list, render_category_nav %}
    {% block title %}Projects{% endblock %}
    {% block body %}
      <h1>Projects</h1>
      {{ render_category_nav(active=none) }}
      {{ render_project_list(this.children) }}
    {% endblock %}

### `project.html`

    {% extends "layout.html" %}
    {% block title %}{{ this.name }}{% endblock %}
    {% block body %}
      <h1>{{ this.name }}</h1>
      <div class="description">{{ this.description }}</div>
    {% endblock %}

### `project-categories.html`

    {% extends "layout.html" %}
    {% from "macros/projects.html" import render_category_nav %}
    {% block title %}Project Categories{% endblock %}
    {% block body %}
      <h1>Project Categories</h1>
      {{ render_category_nav(active=none) }}
    {% endblock %}

### `project-category.html`

    {% extends "layout.html" %}
    {% from "macros/projects.html" import render_category_nav,
       render_project_list %}
    {% block title %}Project Category {{ this.name }}{% endblock %}
    {% block body %}
      <h1>Project Category {{ this.name }}</h1>
      {{ render_category_nav(active=this._id) }}
      {{ render_project_list(this.children) }}
    {% endblock %}

### `macros/projects.html`

    {% macro render_category_nav(active=none) %}
      <ul>
      {% for category in site.query('/project-categories') %}
        <li{% if category._id == active %} class="active"{% endif
          %}><a href="{{ category|url }}">{{ category.name }}</a></li>
      {% endfor %}
      </ul>
    {% endmacro %}

    {% macro render_project_list(projects) %}
      <ul>
      {% for project in projects %}
        <li><a href="{{ project|url }}">{{ project.name }}</a></li>
      {% endfor %}
      </ul>
    {% endmacro %}

## Comments