How to Use ========== This section outlines the basic steps to use Django-headless-cms. Define Models ------------- Models are the core of Django-headless-cms, representing your system or content. It is crucial to invest time in practicing and designing your system's database. Before proceeding, note the following regarding relationships: - To reference a single row, use `ForeignKey`. - To reference multiple rows, use `ManyToManyField`, with support for `M2MSortedOrderThrough` (explained later). - To define an abstract class that can be referenced by multiple other classes, use `GenericRelation` from `Django content types `_. Normal Model ~~~~~~~~~~~~ - Create models inheriting from :ref:`LocalizedPublicationModel`. - This base model supports auto import-export UI, versioning, publishing/drafting content, and auto-translation for any model that inherits it. - Don't forget to register the model with `@reversion.register(exclude=("published_version",))`. - For multi-language fields with content varying across different languages, use the following fields: :: # Fields from django-localized-fields localized_fields.fields.LocalizedCharField localized_fields.fields.LocalizedTextField localized_fields.fields.LocalizedIntegerField localized_fields.fields.LocalizedFloatField localized_fields.fields.LocalizedFileField # Fields implemented by django-headless-cms headless_cms.fields.LocalizedUniqueNormalizedSlugField headless_cms.fields.LocalizedBooleanField headless_cms.fields.AutoLanguageUrlField headless_cms.fields.LocalizedMartorField Example: :: @reversion.register(exclude=("published_version",)) class Action(LocalizedPublicationModel): text = LocalizedCharField(blank=True, null=True, required=False) icon = CharField(default="", blank=True) Singleton Model ~~~~~~~~~~~~~~~ - For singleton models, such as a model representing your `Landing Page` or `Contact Page`, inherit from :ref:`LocalizedSingletonModel`. Example: :: @reversion.register(exclude=("published_version",)) class IndexPage(LocalizedSingletonModel): title = LocalizedTextField(default=dict, blank=True, null=True) description = LocalizedTextField(default=dict, blank=True, null=True) Title Slug Model ~~~~~~~~~~~~~~~~ - For models requiring a `title` field and a `slug` field auto-generated from the title (e.g., `Post` model), inherit from :ref:`LocalizedTitleSlugModel`. Example: :: @reversion.register(exclude=("published_version",)) class Post(LocalizedTitleSlugModel): excerpt = LocalizedTextField(blank=True, null=True, required=False) image = models.ForeignKey( PostImage, blank=True, null=True, on_delete=models.SET_NULL, related_name="posts", ) draft = models.BooleanField(default=False) author = LocalizedCharField(blank=True, null=True, required=False) content = LocalizedMartorField(default=dict, blank=True, null=True, required=False) publish_date = DateTimeField(blank=True, null=True) updated_date = DateTimeField() Dynamic File Model ~~~~~~~~~~~~~~~~~~ - For models that allow either file uploads or external links and return the source based on either field (e.g., an image model), use :ref:`LocalizedDynamicFileModel`. Example: :: class Image(LocalizedDynamicFileModel): author = LocalizedCharField(blank=True, null=True, required=False) Sorted M2M Through Model ~~~~~~~~~~~~~~~~~~~~~~~~ - For a M2M through model that can be sorted via the admin interface, inherit from :ref:`M2MSortedOrderThrough`. Example: :: @reversion.register(exclude=("published_version",)) class PriceItem(LocalizedPublicationModel): title = LocalizedCharField(blank=True, null=True, required=False) subtitle = LocalizedCharField(blank=True, null=True, required=False) @reversion.register(exclude=("published_version",)) class Pricing(LocalizedPublicationModel): prices = models.ManyToManyField( PriceItem, related_name="pricing", blank=True, through="PriceItemThrough", ) class PriceItemThrough(M2MSortedOrderThrough): pricing = models.ForeignKey(Pricing, on_delete=models.CASCADE) price_item = models.ForeignKey(PriceItem, on_delete=models.CASCADE) .. note:: For self-referencing models, ensure you add `fk_name`, which is the field name of the parent model that the through model points to. Example: :: @reversion.register(exclude=("published_version",)) class Post(LocalizedTitleSlugModel): excerpt = LocalizedTextField(blank=True, null=True, required=False) related_posts = models.ManyToManyField( "self", blank=True, through="RelatedPost", symmetrical=False, ) class RelatedPost(M2MSortedOrderThrough): fk_name = "source_post" source_post = models.ForeignKey( Post, on_delete=models.CASCADE, related_name="source_through" ) related_post = models.ForeignKey( Post, on_delete=models.CASCADE, related_name="related_through" ) Define Admin ------------ Auto admin ~~~~~~~~~~ To quickly set up the admin interface for your models, you can use the :ref:`auto_admins` utility provided by `headless_cms.admin`. This utility automatically registers your models with the Django admin site. Example: :: from headless_cms.admin import auto_admins from your_app.models import Article, Post auto_admins([Article, Post]) In this example, the :ref:`auto_admins` function is used to register the `Article` and `Post` models with the Django admin site. This setup allows you to manage these models through the admin interface without manually registering each one. Manual Admin ~~~~~~~~~~~~ If you want to extend or modify the admin interface, inherit from :ref:`EnhancedLocalizedVersionAdmin`. There are several utilities available to assist you in creating the admin interface, so be sure to check :ref:`Admin` for more information. Define Serializers & Views -------------------------- Auto Serializers ~~~~~~~~~~~~~~~~ Django-headless-cms provides utilities to automatically generate serializers for your models, simplifying the setup of API views. Below is a simple example demonstrating how to use auto serializers along with custom pagination and viewsets. For more information, refer to :ref:`auto_serializer`. Example: :: from rest_framework.pagination import PageNumberPagination from rest_framework.viewsets import ReadOnlyModelViewSet from headless_cms.mixins import CMSSchemaMixin from headless_cms.serializers import auto_serializer from your_app.models import Post from your_app.serializers import RelatedPostSerializer class PostPaginator(PageNumberPagination): page_size = 10 page_size_query_param = "size" class PostViewSet(CMSSchemaMixin, ReadOnlyModelViewSet): queryset = Post.published_objects.published(auto_prefetch=True) serializer_class = auto_serializer(Post) pagination_class = PostPaginator In this example, the `auto_serializer` function is used to generate a serializer for the `Post` model. Custom pagination is provided by the `PostPaginator` class. The `PostViewSet` class inherits from `CMSSchemaMixin` and `ReadOnlyModelViewSet` to provide read-only access to the published `Post` objects, with the related posts being serialized using `RelatedPostSerializer`. .. note:: Remember to use `.published_objects.published(auto_prefetch=True)` for the queryset in your viewsets to ensure that only published objects are fetched, and to enable automatic prefetching of related data. Manual Serializers ~~~~~~~~~~~~~~~~~~ If you need to extend or customize your serializers beyond what is provided by the auto serializers, you can manually define your serializers. Inherit from :ref:`LocalizedModelSerializer` or other appropriate base serializers and customize as needed. Example: :: from headless_cms.serializers import LocalizedModelSerializer from your_app.models import Post, RelatedPost class RelatedPostSerializer(LocalizedModelSerializer): class Meta: model = RelatedPost fields = ['id', 'title'] class PostSerializer(LocalizedModelSerializer): related_posts = RelatedPostSerializer(read_only=True, many=True) class Meta: model = Post fields = ['id', 'title', 'content', 'related_posts'] In this example, `RelatedPostSerializer` and `PostSerializer` are manually defined to provide custom serialization logic. The `PostSerializer` includes a nested `RelatedPostSerializer` to handle related posts. Refer to :ref:`Serializers` for more information. Admin Dashboard --------------- Detail Page Features ~~~~~~~~~~~~~~~~~~~~ Once you register your model with the Django admin, you will have access to the following features when viewing the detail page (update form) for individual objects or content: - **Published status**: Unpublished | Published (Outdated) | Published (Latest) - **History**: View your object's version history, including which version is currently published. - **Publish actions**: Publish, Unpublish, or Recursively Publish content. - **Multi-language fields**: Use tabs to switch between languages. - **Sortable inline items**: Drag and drop to reorder. - **Preview related models**: View related models in modals. - **Translation tools**: Translate missing fields, force re-translate all fields, translate children, and force re-translate children. - **Export**: Export individual rows. List Page Features ~~~~~~~~~~~~~~~~~~ When you visit the admin list page, you will have access to the following actions: - Recover deleted items - Import data - Publish/Unpublish items - Translate/Force re-translate items - Export data Admin Extra Features ~~~~~~~~~~~~~~~~~~~~ For additional admin features, such as updating the interface language via the admin panel, refer to `django-admin-interface `_. This package provides a range of enhancements to the Django admin interface, offering more customization and flexibility. API Documentation & Playground ------------------------------ To enable API documentation and the playground, add the following line to your Django `urlpatterns`. This will automatically document all of your Views/Viewsets that inherit from :ref:`CMSSchemaMixin`: :: urlpatterns = [ # other urls path("", include("headless_cms.schema.urls")), ] API Documentation ~~~~~~~~~~~~~~~~~ Visit the Redoc API Documentation at http://localhost:8000/api/cms-schema/redoc/ (replace `localhost` with your backend's deployed URL in the production environment) to view your API documentation. API Playground ~~~~~~~~~~~~~~ .. note:: To change the **accept-language** header when testing the API, open the URL below in incognito mode. Otherwise, your session language might override your header language. This issue may occur if you have integrated the language selection feature of the `Django admin interface`. Visit the Swagger API Playground at http://localhost:8000/api/cms-schema/swg/ (replace `localhost` with your backend's deployed URL in the production environment) to interact with your API. Remember to set the **accept-language** header with the desired language code or leave it blank to use your default language. Django Management Commands -------------------------- Django-headless-cms provides several useful management commands to assist you: - :ref:`Clean Outdated Drafts`: Cleans up outdated drafts. - :ref:`Export CMS Data`: Exports your entire CMS data, supporting multiple formats. - :ref:`Import CMS Data`: Imports your exported CMS data from a local file or remote URL. For more information and detailed usage instructions, refer to the respective documentation for each command. How to Use Admin Panel ---------------------- The Django-headless-cms admin panel offers several features to help you manage your content efficiently: Publish/Draft & Versioning Content ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Publish/Draft content - Versioning content: Revert to any previously saved version (click on the `History` button on the detail page) - Recursive publish .. image:: images/how-to-use/publish.png :alt: Publish actions .. image:: images/how-to-use/history.png :alt: Versioning content Translation ~~~~~~~~~~~ To set up the languages for your content, you need to configure the following settings in `DJANGO_SETTINGS`: :: LANGUAGE_CODE = "en" # your primary content language LANGUAGES = [ ("en", "English"), ("af", "Afrikaans"), ("ar", "العربية"), # list your languages, with the primary one at the top for convenience ] - Auto-translate objects (if using OpenAI) and recursively translate. .. image:: images/how-to-use/translation.png :alt: Translation Reset Translation ~~~~~~~~~~~~~~~~~ .. note:: To reset translations, follow these steps: - Clear the content of the primary language. - Click on `Translation Missing`. - All your content will be flushed. You can now add your primary language content again. Markdown Editor ~~~~~~~~~~~~~~~ - Use :ref:`LocalizedMartorField` to add a markdown editor field with multi-language support. .. image:: images/how-to-use/markdown-1.png :alt: Markdown editor - Preview the markdown content and its utilities inside the editor. .. image:: images/how-to-use/markdown-preview.png :alt: Markdown preview - Preview the content in full screen to focus more on editing markdown content. .. image:: images/how-to-use/markdown-full-screen.png :alt: Markdown full screen For more customization options for the markdown editor, refer to the Martor original documentation at `django-markdown-editor (Martor) `_. Import/Export Items ~~~~~~~~~~~~~~~~~~~ - **Single Item Export**: Use the *export* button at the end of the admin page to export a single item (you may need to update the `django-import-export` package to have this feature). .. image:: images/how-to-use/export-single.png :alt: Export single - **Multiple Item Export**: Use admin actions in the list view to export multiple items at the same time (see the `Admin List Actions` section). - **Import Items**: Use the *import* button on the admin list page to import exported content into your system. Note that imported items will be `unpublished` by default. .. image:: images/how-to-use/import-button.png :alt: Import button Admin List Actions ~~~~~~~~~~~~~~~~~~ On the admin list page, you can perform actions on multiple items: - Publish/Unpublish items - Translate/Force re-translate items - Export items .. image:: images/how-to-use/admin-actions.png :alt: Admin actions