==================================
Tutorial: Creating a List Plugin
==================================
.. admonition:: What you'll learn
In this tutorial, you'll learn how to create a complex list-based custom plugin for django-resume
in your own Django project. We'll build a "Certifications" plugin that displays
professional certifications on a resume.
.. admonition:: Prerequisites
- Basic understanding of Django forms and templates
- Familiarity with Python classes and inheritance
- A Django project with django-resume installed as a third-party package
- Completion of :doc:`creating_simple_plugins` tutorial (recommended)
.. admonition:: Working Example Available
A complete certifications plugin implementation is already included in the django-resume
example project. To see it in action, enable example plugins in your settings:
.. code-block:: python
# settings.py
DJANGO_RESUME_ENABLE_EXAMPLE_PLUGINS = True
Then restart your server to see the certifications and motto plugins.
Overview
========
List plugins in django-resume are Python classes that manage collections of related items.
Each plugin handles multiple data entries, provides forms for editing individual items
and the overall section, and renders content using multiple templates.
Custom plugins are defined as Python modules in your project and use template
files stored on disk. django-resume no longer supports creating plugins from
database rows.
In this tutorial, we'll create a plugin to display professional certifications,
including the certification name, issuing organization, issue date, and
expiration date. Users will be able to add, edit, and delete multiple certifications.
Step 1: Understanding List vs Simple Plugins
============================================
django-resume provides two base plugin types:
- **SimplePlugin**: For plugins with a single form and straightforward data structure (like "About", "Contact Info", or personal mottos)
- **ListPlugin**: For plugins that manage lists of items (like multiple projects, certifications, or jobs)
Since certifications are typically multiple items that users want to manage individually,
we'll use ``ListPlugin`` as our base.
List plugins are more complex because they need to handle:
- Multiple items with individual forms
- Position/ordering of items
- Adding and deleting items
- Both section-level and item-level editing
Step 2: Create the Plugin Module
================================
Create a new Django app or module in your project for custom plugins:
.. code-block:: bash
# If you don't have a plugins app yet, create one
python manage.py startapp plugins
# Or create a simple module directory
mkdir myproject/plugins
touch myproject/plugins/__init__.py
touch myproject/plugins/certifications.py
For this tutorial, we'll assume you have a ``plugins`` app or module in your project.
The plugin code lives in your repository, not in the database.
Step 3: Define the Data Model Forms
===================================
List plugins require two form classes: one for individual items and one for the section header.
.. code-block:: python
# myproject/plugins/certifications.py (or plugins/certifications.py)
from typing import Type
from django import forms
from django.http import HttpRequest
from django_resume.plugins.base import ListPlugin, ListItemFormMixin, ContextDict
class CertificationForm(ListItemFormMixin, forms.Form):
"""Form for individual certification entries."""
name = forms.CharField(
label="Certification Name",
max_length=200,
help_text="e.g., AWS Certified Solutions Architect"
)
organization = forms.CharField(
label="Issuing Organization",
max_length=200,
help_text="e.g., Amazon Web Services"
)
# IMPORTANT: Use CharField with DateInput widget, not DateField!
# This is because plugin data is stored as JSON, which doesn't support date objects
issue_date = forms.CharField(
label="Issue Date",
widget=forms.DateInput(attrs={'type': 'date'})
)
expiration_date = forms.CharField(
label="Expiration Date",
widget=forms.DateInput(attrs={'type': 'date'}),
required=False,
help_text="Leave blank if certification doesn't expire"
)
credential_id = forms.CharField(
label="Credential ID",
max_length=100,
required=False,
help_text="Certificate number or ID (optional)"
)
position = forms.IntegerField(widget=forms.NumberInput(), required=False)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.set_initial_position()
@staticmethod
def get_max_position(items: list[dict]) -> int:
"""Return the maximum position value from the existing items."""
positions = [item.get("position", 0) for item in items]
return max(positions) if positions else -1
def set_initial_position(self) -> None:
"""Set the position to the next available position."""
if "position" not in self.initial:
self.initial["position"] = self.get_max_position(self.existing_items) + 1
def set_context(self, item: dict, context: ContextDict) -> ContextDict:
"""Set up context for rendering individual certification items."""
context["cert"] = {
"id": item["id"],
"name": item["name"],
"organization": item["organization"],
"issue_date": item["issue_date"],
"expiration_date": item.get("expiration_date", ""),
"credential_id": item.get("credential_id", ""),
"edit_url": context["edit_url"],
"delete_url": context["delete_url"],
}
return context
@staticmethod
def get_initial() -> ContextDict:
"""Default values for new certification entries."""
return {
"name": "Certification Name",
"organization": "Issuing Organization",
"issue_date": "2024-01-01",
"expiration_date": "",
"credential_id": "",
}
class CertificationFlatForm(forms.Form):
"""Form for the overall certifications section."""
title = forms.CharField(
label="Section Title",
max_length=100,
initial="Certifications"
)
@staticmethod
def set_context(item: dict, context: ContextDict) -> ContextDict:
"""Set up context for rendering the section title."""
context["certifications"] = {"title": item.get("title", "Certifications")}
context["certifications"]["edit_flat_url"] = context["edit_flat_url"]
return context
Step 4: Implement the Plugin Class
==================================
Now let's create the main plugin class:
.. code-block:: python
class CertificationsPlugin(ListPlugin):
name: str = "certifications"
verbose_name: str = "Certifications"
# AI prompt for LLM-based content generation
prompt = """
Create a django-resume plugin to display professional certifications.
Each certification should include the name, issuing organization,
issue date, and optional expiration date and credential ID.
The plugin should allow users to add multiple certifications and
display them in a clean, organized format. Include functionality
for both admin and inline editing.
"""
@staticmethod
def get_form_classes() -> dict[str, Type[forms.Form]]:
"""Return the form classes used by this plugin."""
return {"item": CertificationForm, "flat": CertificationFlatForm}
Step 5: Create Templates
========================
List plugins require multiple templates for different parts of the interface.
Create the template directory structure:
.. code-block:: bash
# Create templates in your project's template directory
mkdir -p templates/django_resume/plugins/certifications/plain
**Main Content Template**
.. code-block:: html
No certifications added yet.
{{ cert.organization }}
Credential ID: {{ cert.credential_id }}
{% endif %}