.. _custom_components: ####################################### Building Custom Components With Form Classes ####################################### .. index:: single: Custom frontend components .. versionadded:: 2.0 In this tutorial, we will explore how to create custom **Frontend Components**. These are more versatile than :ref:`template_components`, but require some minimal Python coding. You create a custom frontend component by subclassing the ``CMSFrontendComponent`` class to declare the form, plus providing a rendering template, and `djangocms-frontend` will take care integrating it automatically. Hero CMS component example ========================== In this tutorial we will create again a **Hero component** similar to the one in the :ref:`previous chapter `. Directory Structure ------------------- ``djangocms-frontend`` will look for custom frontend components in the **``cms_components`` module in any of your installed apps**. This way you can either keep components together in one theme app, or keep them with where they are used in different custom apps. Ensure your app has the following structure:: theme_app/ cms_components.py migrations/ models.py templates/ theme/ hero.html views.py admin.py In this example, `cms_components.py` will contain the component definition, and `hero.html` the presentation template. .. note:: Components will create migrations since they use proxy models of ``djangocms-frontend``'s ``FrontendUIItem`` model which are necessary, for example, to manage permissions. Those migrations will be added to the app containing the ``cms_component.py`` file. Creating two Custom Frontend Components --------------------------------------- Add a ``cms_components.py`` file to the ``theme`` app (see structure above): .. code-block:: python # theme/cms_components.py from djangocms_link.fields import LinkFormField from djangocms_frontend.component_base import CMSFrontendComponent from djangocms_frontend.component_pool import components from djangocms_frontend.contrib.image.fields import ImageFormField from django import forms @components.register class MyHeroComponent(CMSFrontendComponent): class Meta: # declare plugin properties name = "My Hero Component" # Name displayed in the CMS admin interface render_template = "theme/hero.html" # Template used to render the component allow_children = True # Allow child plugins inside this component mixins = ["Background"] # Add background styling options # for more complex components, you can add fieldsets # Declare fields for the component title = forms.CharField(required=True) slogan = forms.CharField(required=True, widget=forms.Textarea) hero_image = ImageFormField(required=True) def get_short_description(self): return self.title # Display the title in the structure board @components.register class MyButton(CMSFrontendComponent): class Meta: name = "Button" render_template = "components/button.html" allow_children = False text = forms.CharField(required=True) link = LinkFormField() def get_short_description(self): return self.text The templates could be, for example: .. code-block:: django {% load cms_tags frontend sekizai_tags %}

{{ instance.title }}

{{ instance.message }}

{% childplugins instance %} Get started Speak to Sales {% endchildplugins %}
{% addtoblock "js" %}{% endaddtoblock %} .. code-block:: django {% load djangocms_link_tags %} {{ instance.text }} As always, django CMS manages styling and JavaScript dependencies with **django-sekizai**. In this example, we add the Tailwind CSS CDN to the ``js`` block. .. note:: The component instance is available in the template as ``instance``. This is a proxy model of the ``FrontendUIItem`` model, which is a subclass of Django's ``Model`` class. The instance has all the fields declared in the component class. Additionally, if the component does not have a field called ``instance``, the fields themselves are available directly in the template. Both ways are equivalent:: {{ instance.title }} {{ title }} {{ instance.slogan }} {{ slogan }} Using slots for structured content =================================== Slots allow you to define specific regions within a component where content editors can add child plugins. This is useful when you want to control the structure while allowing flexibility in certain areas. To define slots, add a ``slots`` attribute to the component's ``Meta`` class: .. code-block:: python @components.register class MyHero(CMSFrontendComponent): class Meta: name = "My Hero Component" render_template = "hero.html" slots = ( ("title", "Title"), # (slot_name, verbose_name) ("slot", "Slot"), ) title = forms.CharField(required=True, initial="my title") slogan = forms.CharField(required=True, widget=forms.Textarea) image = ImageFormField(required=True) In your template, use the ``{% childplugins %}`` tag to render slot content with optional fallback: .. code-block:: django {% load cms_tags %}

{% childplugins instance "title" %} {{ instance.title }} {% endchildplugins %}

{% childplugins instance "slot" %} {% endchildplugins %}
When you define slots: - Child plugin classes are created automatically (e.g., ``MyHeroTitlePlugin``, ``MyHeroSlotPlugin``) - Slot plugins can only be added as children of the parent component - Empty slots display the fallback content between the template tags - All slots are automatically created when a component instance is saved For more details and advanced usage, see :ref:`components-with-slots`. Organizing fields with fieldsets ================================= For components with many fields, you can organize them into fieldsets to improve the editing experience. Fieldsets group related fields together and can be collapsed to keep the admin interface clean. To define fieldsets, add a ``fieldsets`` attribute to the component's ``Meta`` class: .. code-block:: python @components.register class MyCard(CMSFrontendComponent): class Meta: name = "Card" render_template = "card.html" fieldsets = [ (None, { "fields": ("title", "subtitle") }), ("Content", { "fields": ("body_text",), "classes": ("collapse",) }), ("Styling", { "fields": ("background_color", "text_color"), "classes": ("collapse",), "description": "Customize the appearance of the card" }), ] # Basic fields title = forms.CharField(required=True) subtitle = forms.CharField(required=False) # Content fields body_text = forms.CharField(required=False, widget=forms.Textarea) # Styling fields background_color = forms.CharField(required=False) text_color = forms.CharField(required=False) If you don't define ``fieldsets``, all fields will be shown in a single unnamed fieldset. Setting default field values ============================ When a new component instance is created, its fields are initialized with the ``initial`` values declared on each form field. You can override these defaults on a per-component basis by adding a ``default_config`` dictionary to the component's ``Meta`` class: .. code-block:: python @components.register class MyCard(CMSFrontendComponent): class Meta: name = "Card" render_template = "card.html" default_config = { "background_color": "#f8f9fa", "text_color": "#212529", } title = forms.CharField(required=True) background_color = forms.CharField(required=False, initial="#ffffff") text_color = forms.CharField(required=False, initial="#000000") In this example, new Card instances will start with ``background_color`` set to ``#f8f9fa`` instead of the form field's ``initial`` value of ``#ffffff``. Editors can still change the value in the plugin form. This is especially useful for fields that come from mixins, such as ``Background``, ``Spacing``, ``Responsive``, or ``Sizing``. Since mixin fields are defined elsewhere, you cannot change their ``initial`` values in your component class. ``default_config`` lets you set sensible defaults for these fields without having to redefine them: .. code-block:: python @components.register class MySection(CMSFrontendComponent): class Meta: name = "Section" render_template = "section.html" mixins = ["Background", "Spacing"] default_config = { "background_context": "light", "padding_y": "py-5", "margin_y": "my-3", } heading = forms.CharField(required=True) Here, the ``background_context``, ``padding_y``, and ``margin_y`` fields are all contributed by the ``Background`` and ``Spacing`` mixins. Without ``default_config``, they would start empty. Defaults are applied in the following priority order (highest wins): 1. **Form field** ``initial`` **values** — the built-in baseline. 2. **Meta** ``default_config`` — component-level overrides set by the developer. 3. **The** :py:attr:`~settings.DJANGOCMS_FRONTEND_PLUGIN_DEFAULTS` **setting** — project-level overrides set by the site administrator. This means a site administrator can always fine-tune defaults via the Django setting without modifying component code: .. code-block:: python # settings.py DJANGOCMS_FRONTEND_PLUGIN_DEFAULTS = { "MyCard": { "background_color": "#ffffff", }, } For more details on the setting, see :py:attr:`~settings.DJANGOCMS_FRONTEND_PLUGIN_DEFAULTS`. Limitations of custom frontend components ========================================= Custom frontend components are a powerful tool for developers, but they have a limitations: **Limited Python code**: Custom components are (indirect) subclasses of Django's ``AdminForm`` class and can contain Python code to modify the behavior of a form. You cannot directly add Python code to the resulting plugin class with the exception of ``get_render_template()``. Similarly, you cannot add Python code the model class, in this case with the exception of ``get_short_description()``. For maximun flexibility in your customized components, you can build a :ref:`custom Plugin`. Conclusion ========== In this tutorial, we explored how to create custom frontend components. These components empower developers to provide visually appealing components to content editors with minimal coding. By following the steps outlined above, you can: - Define components using the `CMSFrontendComponent` class. - Leverage templates to control the visual presentation of your components. - Register and manage your components seamlessly within django CMS.