Skip to content

paranoid-software/elemental-cms

Repository files navigation

elemental CMS

Elemental is a Flask and MongoDB web CMS intended for developers.

Our main goal is to allow developers to create and maintain web portals or web applications using their preferred programming IDE like VS Code, PyCharm, Visual Studio, etc.

The main interaction with the tool takes place through its CLI, a self documented command line tool called "elemental-cms" which helps us perform deployment tasks directly from the terminal.

It relies on MongoDB to store the metadata, pages' content, snippets' content, dependencies information, and user session data.

Version Compatibility

Elemental CMS 2.0.6 is compatible with:

  • Flask 2.2.5
  • Werkzeug 2.2.3
  • Flask-Babel 2.0.0
  • Python 3.6+

For version history and changes, see our CHANGELOG.

Work in progress

  • Official documentation construction
  • Media files management on GCS module test classes
  • Static files management on GCS module test classes
  • Samples review and update

To Do

  • Resources names validation for pages (similar to snippets)
  • Configurations schema review
  • Test coverage review
  • Support for sample settings file generation

Configuration Guide

Elemental CMS uses a JSON configuration file that defines how the CMS operates. There are two main contexts:

Core Context (cmsCoreContext)

Basic Settings

  • DEBUG: Enable/disable debug mode (default: false)
  • ENV: Environment name (e.g., "development", "production")
  • SECRET_KEY: Flask secret key for session encryption
  • SITE_NAME: Your site's name, used in templates
  • COMPANY: Your company name
  • CANONICAL_URL: Base URL for your site

Language Settings

  • LANGUAGES: List of supported language codes (e.g., ["en", "es"])
  • DEFAULT_LANGUAGE: Default language code (e.g., "en")
  • LANGUAGE_MODE: Either "single" or "multi" for language handling
    • "single": No language prefixes in URLs
    • "multi": URLs prefixed with language code (e.g., /en/about)

File Storage Settings

Cloud Storage (Optional)

  • STATIC_BUCKET: GCS bucket name for static files
  • MEDIA_BUCKET: GCS bucket name for media files
  • GOOGLE_SERVICE_ACCOUNT_INFO: GCS service account credentials
    • Required only when using Google Cloud Storage
    • Used by CLI for file operations

Workspace Settings

  • GLOBAL_DEPS_FOLDER: Folder for global dependencies (default: "workspace/global_deps")
  • PAGES_FOLDER: Folder for page files (default: "workspace/pages")
  • SNIPPETS_FOLDER: Folder for snippet files (default: "workspace/snippets")

Session Settings

  • USER_IDENTITY_SESSION_KEY: Session key for user identity (default: "userIdentity")
  • SESSION_STORAGE_ENABLED: Enable MongoDB session storage (default: true)
  • SESSION_TIMEOUT_IN_MINUTES: Session timeout in minutes (default: 360)

Development Settings

  • DESIGN_MODE_ENABLED: Enable local file-based content editing (default: false)
    • When true: Content is read from local files
    • When false: Content is read from MongoDB

Database Context (cmsDbContext)

  • id: Unique identifier for your MongoDB connection
  • connectionString: MongoDB connection string
    • Format: "mongodb://username:password@hostname:port/admin?directConnection=true"
  • databaseName: Name of the MongoDB database to use

Understanding CLI vs Web App Settings

Elemental CMS uses two separate configuration files:

  1. CLI Settings (local.cli.json):

    • Used by the elemental-cms command-line tool
    • Requires cloud storage configuration (STATIC_BUCKET, MEDIA_BUCKET, GOOGLE_SERVICE_ACCOUNT_INFO)
    • Used for content management operations (push, pull, publish)
    • Example operations that use CLI settings:
      elemental-cms init -c settings/local.cli.json
      elemental-cms pages push -p home en
      elemental-cms media push image.jpg
  2. Web App Settings (local.www.json):

    • Used by your Flask web application
    • Focuses on serving content and handling requests
    • Does not need cloud storage credentials
    • Used when running your web application
    • Example usage:
      CONFIG_FILEPATH = os.environ.get('CONFIG_FILEPATH', 'settings/local.www.json')

Key Differences:

Setting CLI Config Web App Config Notes
STATIC_BUCKET Required Optional CLI needs it for push/pull operations
MEDIA_BUCKET Required Optional CLI needs it for media management
GOOGLE_SERVICE_ACCOUNT_INFO Required Not needed Only CLI performs cloud operations
STATIC_URL Cloud URL Can be local Web app can serve files locally
MEDIA_URL Cloud URL Can be local Web app can serve files locally
DESIGN_MODE_ENABLED Not used Optional Only affects web app behavior

Example Workflow:

  1. Use CLI config (local.cli.json) to manage content:

    # Push content to cloud storage
    elemental-cms push --all
  2. Use Web config (local.www.json) to serve content:

    # Web app serves content from local or cloud
    Elemental(www, elemental_context)

Configuration Examples

Local Development

CLI Config (local.cli.json):

{
  "cmsCoreContext": {
    "DEBUG": true,
    "ENV": "development",
    "STATIC_URL": "https://storage.googleapis.com/static-bucket",
    "MEDIA_URL": "https://storage.googleapis.com/media-bucket",
    "STATIC_BUCKET": "static-bucket",
    "MEDIA_BUCKET": "media-bucket",
    "GOOGLE_SERVICE_ACCOUNT_INFO": {
      "type": "service_account",
      "project_id": "your-project"
    },
    "DESIGN_MODE_ENABLED": false
  }
}

Web App Config (local.www.json):

{
  "cmsCoreContext": {
    "DEBUG": true,
    "ENV": "development",
    "STATIC_URL": "/static",
    "MEDIA_URL": "/media",
    "DESIGN_MODE_ENABLED": true
  }
}

Production Environment

CLI Config (production.cli.json):

{
  "cmsCoreContext": {
    "DEBUG": false,
    "ENV": "production",
    "STATIC_URL": "https://storage.googleapis.com/static-bucket",
    "MEDIA_URL": "https://storage.googleapis.com/media-bucket",
    "STATIC_BUCKET": "static-bucket",
    "MEDIA_BUCKET": "media-bucket",
    "GOOGLE_SERVICE_ACCOUNT_INFO": {
      "type": "service_account",
      "project_id": "your-project"
    }
  }
}

Web App Config (production.www.json):

{
  "cmsCoreContext": {
    "DEBUG": false,
    "ENV": "production",
    "STATIC_URL": "https://storage.googleapis.com/static-bucket",
    "MEDIA_URL": "https://storage.googleapis.com/media-bucket",
    "DESIGN_MODE_ENABLED": false
  }
}

Setup #

Once we have our project folder created and our virtual environment in place, we proceed to install Elemental CMS using pip.

pip install elemental-cms

The CLI includes an "init" command which will create a basic working directory structure.

Before we can issue the "init" command, we have to create a config file inside a "settings" folder with at least the following content:

{
  "cmsCoreContext": {
    "DEBUG": true,
    "ENV": "development",
    "SECRET_KEY": "the-secret",
    "SITE_NAME": "Elemental CMS",
    "COMPANY": "Your company name",
    "CANONICAL_URL": "https://elemental.cms",
    "LANGUAGES": [
      "en",
      "es"
    ],
    "DEFAULT_LANGUAGE": "en",
    "LANGUAGE_MODE": "multi",
    "STATIC_FOLDER": "static",
    "MEDIA_FOLDER": "media",
    "STATIC_URL": "https://storage.googleapis.com/static-files-bucket",
    "MEDIA_URL": "https://storage.googleapis.com/media-files-bucket",
    "STATIC_BUCKET": "static-files-bucket",
    "MEDIA_BUCKET": "media-files-bucket",
    "GLOBAL_DEPS_FOLDER": "workspace/global_deps",
    "PAGES_FOLDER": "workspace/pages",
    "SNIPPETS_FOLDER": "workspace/snippets",
    "GOOGLE_SERVICE_ACCOUNT_INFO": {
      "type": "service_account",
    },
    "USER_IDENTITY_SESSION_KEY": "userIdentity",
    "SESSION_STORAGE_ENABLED": true,
    "SESSION_TIMEOUT_IN_MINUTES": 360,
    "DESIGN_MODE_ENABLED": true
  },
  "cmsDbContext": {
    "id": "your-id",
    "connectionString": "mongodb://your-username:your-pwd@your-host-name:27017/admin?directConnection=true",
    "databaseName": "elemental_playground"
  }
}

After we create the config file under the name for example local.cli.json, we can issue the "init" command as shown below:

elemental-cms init -c settings/local.cli.json

Executing this command will create and update our .elemental metadata file setting the "configFilePath" property to "settings/local.cli.json", and it will update the folder structure which will ends looking like this:

workdir
└───media    
└───settings
    └───local.cli.json
└───static
└───templates
    └───base.html
└───translations
└───workspace
    └───global_deps
    └───pages
    └───snippets
└───.elemental

Be aware of that in Windows OS using Visual Studio 2019 after running the init command (and any other command that modify the folders and files structure), the created files and folders will not be added to the project automaticaly.

Shell Autocomplete (Optional)

Elemental CMS supports shell autocomplete for commands and arguments, making it faster to work with snippets, pages, and global dependencies.

The completion scripts are included in the package. To enable autocomplete, run the setup script once:

# If you cloned the repo
./enable-completion.sh

# If you installed via pip, find and run the script
bash $(python -c "import elementalcms, os; print(os.path.join(os.path.dirname(os.path.dirname(elementalcms.__file__)), 'enable-completion.sh'))")

# Then reload your shell
source ~/.zshrc  # for Zsh
# or
source ~/.bashrc  # for Bash

Once enabled, you can use TAB completion:

elemental-cms snippets diff -s <TAB>    # Shows available snippet names
elemental-cms pages push -p home <TAB>  # Shows available languages
elemental-cms global-deps push -d bootstrap <TAB>  # Shows valid types

For detailed instructions and troubleshooting, see COMPLETION.md.

To disable autocomplete:

./disable-completion.sh

Working with Snippets

Snippets are reusable HTML components that can be included in your pages. They are managed through the snippets command.

Snippet Naming Rules

  • Must be lowercase (e.g., header, nav-bar)
  • Must start with a letter
  • Can only contain letters, numbers, and hyphens
  • Examples of valid names: nav-bar, footer-2, main-menu
  • Examples of invalid names: Header (uppercase), 1nav (starts with number), nav_bar (underscore)

Creating a Snippet

elemental-cms snippets create -s nav-bar

This will create two files in your SNIPPETS_FOLDER:

  • nav-bar.json: Contains snippet metadata and dependencies
  • nav-bar.html: Contains the HTML content

Managing Snippets

# List all snippets (shows * for snippets that: have local changes, are missing local files, or exist locally but not in the database)
elemental-cms snippets list

# Compare local and database versions of a snippet
elemental-cms snippets diff -s nav-bar

# Push a snippet to CMS
elemental-cms snippets push -s nav-bar

# Push all snippets
elemental-cms snippets push --all

# Pull a snippet from CMS
elemental-cms snippets pull -s nav-bar

# Remove a snippet
elemental-cms snippets remove -s nav-bar

Working with Pages

Pages are the main content units in Elemental CMS. Each page consists of a spec file (metadata) and a content file (HTML).

Creating a Page

elemental-cms pages create -p home en

This creates two files in your PAGES_FOLDER/en directory:

  • home.json: Contains page metadata and dependencies
  • home.html: Contains the HTML content

Managing Pages

# List all pages (shows * for pages that: have local changes, are missing local files, or exist locally but not in the database)
elemental-cms pages list

# Compare local and database versions of a page
elemental-cms pages diff -p home en

# Compare draft version
elemental-cms pages diff -p home en --drafts

# Push a page to CMS
elemental-cms pages push -p home en

# Push all pages
elemental-cms pages push --all

# Pull a page from CMS
elemental-cms pages pull -p home en

# Pull all pages
elemental-cms pages pull --all

# Publish a page
elemental-cms pages publish -p home en

# Publish all pages that have draft versions
elemental-cms pages publish --all

# Unpublish a page
elemental-cms pages unpublish -p home en

# Remove a page
elemental-cms pages remove -p home en

Page Structure

Spec File

The spec file (home.json) contains the page metadata:

{
    "_id": {
        "$oid": "619b8f70f065731d43fb11fc"
    },
    "name": "home",
    "language": "en",
    "title": "home page",
    "description": "",
    "isHome": false,
    "cssDeps": [],
    "jsDeps": [],
    "createdAt": {
        "$date": 1637584752066
    },
    "lastModifiedAt": {
        "$date": 1637584752066
    }
}

Content file

The content file will have the HTML for the page.

<div></div>

Notes on Page Management

  1. Draft vs Published: When pushing a page, it creates a "draft" version in the database. Use publish to make it accessible through the web application.

  2. Repository Difference Indicators: The list command shows an asterisk (*) next to pages that:

    • Have differences between local and database versions
    • Are missing their local files
    • Exist locally but not in the database
  3. Batch Operations: Some commands support --all flag:

    • push --all: Push all pages
    • pull --all: Pull all pages
    • publish --all: Publish all pages that have draft versions

Running the app

We have created a multilanguage page and successfully published it, but we are missing our application entry point.

Since this framework is based on Flask we can create an entry point just like we will do it for any other Flask application; a simple boilerplate can be found down below:

import json
import os

from elementalcms import Elemental, ElementalContext
from elementalcms.core import FlaskContext, MongoDbContext
from flask import Flask

www = Flask(__name__, template_folder='templates', static_folder='static')

CONFIG_FILEPATH = os.environ.get('CONFIG_FILEPATH', 'settings/local.www.json')

with open(CONFIG_FILEPATH) as config_file:
    settings = json.load(config_file)
    cms_core_context = FlaskContext(settings['cmsCoreContext'])
    cms_db_context = MongoDbContext(settings['cmsDbContext'])
    elemental_context = ElementalContext(cms_core_context, cms_db_context)

Elemental(www, elemental_context)


if __name__ == '__main__':
    www.run(host='0.0.0.0', port=8000)

Note that in order to run the app locally we need another setting file (local.www.sjon) with some minor modifications. Like the one shown below:

{
  "cmsCoreContext": {
    "DEBUG": true,
    "ENV": "development",
    "SECRET_KEY": "the-secret",
    "SITE_NAME": "Elemental CMS",
    "COMPANY": "Your company name",
    "CANONICAL_URL": "https://elemental.cms",
    "LANGUAGES": [
      "en",
      "es"
    ],
    "DEFAULT_LANGUAGE": "en",
    "LANGUAGE_MODE": "multi",
    "STATIC_FOLDER": "static",
    "MEDIA_FOLDER": "media",
    "STATIC_URL": "/static",
    "MEDIA_URL": "/media",
    "GLOBAL_DEPS_FOLDER": "workspace/global_deps",
    "PAGES_FOLDER": "workspace/pages",
    "SNIPPETS_FOLDER": "workspace/snippets",
    "USER_IDENTITY_SESSION_KEY": "userIdentity",
    "SESSION_STORAGE_ENABLED": true,
    "SESSION_TIMEOUT_IN_MINUTES": 360,
    "DESIGN_MODE_ENABLED": true
  },
  "cmsDbContext": {
    "id": "your-id",
    "connectionString": "mongodb://your-username:your-pwd@your-host-name:27017/admin?directConnection=true",
    "databaseName": "elemental_playground"
  }
}

In this file we do not need buckets information, sinces static and media resoruces will be served locally. We do not need a Google Service Account niether because that info is needed only by the cli tool to send local files to GCS.

Windows OS + Visual Studio 2019

In Visual Studio 2019 we have some minor challenges to get started due to "problems" related with the operative system security policies more than with the tool.

  • We start by creating a Python project and adding a virtual environment
  • Then visual studio offers developer terminals in at least 2 flavors:
    • Developer PowerShell where the environment do not get activated by default, so we must activate it by running the command:
.\.venv\Scripts\Activate.ps1

Depending on your security policies this command will or will not ork. When it does not work it will show an error telling you something like: "Activate.ps1 cannot be loaded because running scripts is disabled on this system. For more information, see about_Execution_Policies at https:/go.microsoft.com/fwlink/?LinkID=135170."

To overcome this situation we must enable running scripts at least to the current user, following the official Microsoft documentation.

  • Developer Command Promt were the environment do not get activated by default, so we must activate it by running the command:
.venv\Scripts\activate

In both cases we assume you create the virtual environment under the name .venv

Resolving this minor setbacks we can go back to the Setup step and follow the getting started guide normaly.

Sponsor this project

Languages