diff --git a/.github/workflows/test_startcmsproject.yml b/.github/workflows/test_startcmsproject.yml index 5652695..7a37325 100644 --- a/.github/workflows/test_startcmsproject.yml +++ b/.github/workflows/test_startcmsproject.yml @@ -7,6 +7,42 @@ concurrency: cancel-in-progress: true jobs: + validate-install-rules: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.13' + - name: Validate djangocms_install_rules.json against its schema + run: | + python -m pip install jsonschema + python << 'EOF' + import json + import os + import sys + import jsonschema + + with open("djangocms_install_rules.json") as f: + doc = json.load(f) + + schema_file = os.path.basename(doc.get("$schema", "")) + if not schema_file: + sys.exit("djangocms_install_rules.json has no $schema reference") + if not os.path.exists(schema_file): + sys.exit(f"Referenced schema {schema_file} not found in repository") + + with open(schema_file) as f: + schema = json.load(f) + + jsonschema.Draft7Validator.check_schema(schema) + errors = list(jsonschema.Draft7Validator(schema).iter_errors(doc)) + for error in errors: + print(f"ERROR at {list(error.absolute_path)}: {error.message}") + sys.exit(1 if errors else 0) + EOF + create-project: runs-on: ${{ matrix.os }} strategy: @@ -33,4 +69,4 @@ jobs: source ./.venv/bin/activate python -m pip install --upgrade pip python -m pip install Django~=${{ matrix.django-version }} django-cms - djangocms mysite --noinput --template https://github.com/${{ github.repository }}/archive/${{ github.head_ref || github.ref_name }}.zip + djangocms mysite --noinput --name requirements.in --template https://github.com/${{ github.repository }}/archive/${{ github.head_ref || github.ref_name }}.zip diff --git a/djangocms_install_rules.json b/djangocms_install_rules.json new file mode 100644 index 0000000..8863672 --- /dev/null +++ b/djangocms_install_rules.json @@ -0,0 +1,71 @@ +{ + "$schema": "https://raw.githubusercontent.com/django-cms/cms-template/main/djangocms_install_rules.schema.v1.json", + "comment": "Rules used by `djangocms .` to add django CMS to an existing project. Fetched from the cms-template repository (branch matching the installed django CMS major.minor), with this file as the bundled fallback. Each `when` condition may contain a `flag` (truthy command option) and/or a `mode` (list of matching --mode values). For installed_apps and middleware a rule may also carry a `before` or `after` anchor (an existing entry) for positional insertion; otherwise items are appended.", + "installed_apps": [ + {"items": ["djangocms_simple_admin_style"], "before": "django.contrib.admin"}, + { + "items": [ + "django.contrib.sites", + "cms", + "menus", + "treebeard", + "sekizai", + "filer", + "easy_thumbnails", + "djangocms_frontend", + "djangocms_text", + "djangocms_link" + ] + }, + {"items": ["djangocms_versioning"], "when": {"flag": "versioning"}}, + {"items": ["djangocms_moderation"], "when": {"flag": "moderation"}}, + {"items": ["djangocms_alias"], "when": {"flag": "alias"}}, + {"items": ["djangocms_stories", "taggit", "taggit_autosuggest"], "when": {"flag": "stories"}}, + {"items": ["rest_framework", "djangocms_rest"], "when": {"mode": ["headless", "hybrid"]}} + ], + "middleware": [ + { + "items": ["django.middleware.locale.LocaleMiddleware"], + "after": "django.contrib.sessions.middleware.SessionMiddleware" + }, + { + "items": [ + "cms.middleware.user.CurrentUserMiddleware", + "cms.middleware.page.CurrentPageMiddleware", + "cms.middleware.toolbar.ToolbarMiddleware", + "cms.middleware.language.LanguageCookieMiddleware" + ] + } + ], + "context_processors": [ + { + "items": [ + "django.template.context_processors.i18n", + "sekizai.context_processors.sekizai", + "cms.context_processors.cms_settings" + ] + } + ], + "settings": [ + {"name": "SITE_ID", "snippet": "SITE_ID = 1"}, + {"name": "X_FRAME_OPTIONS", "snippet": "X_FRAME_OPTIONS = \"SAMEORIGIN\""}, + {"name": "CMS_CONFIRM_VERSION4", "snippet": "CMS_CONFIRM_VERSION4 = True"}, + {"name": "LANGUAGES", "snippet": "LANGUAGES = [\n (\"{language_code}\", \"{language_name}\"),\n]"}, + {"name": "CMS_TEMPLATES", "snippet": "CMS_TEMPLATES = [\n (\"cms-base.html\", \"Default\"),\n]", "when": {"mode": ["traditional", "hybrid"]}}, + {"name": "CMS_PLACEHOLDERS", "snippet": "CMS_PLACEHOLDERS = [\n (\"cms-base.html\", (\"content\",), \"Content\"),\n]", "when": {"mode": ["headless"]}} + ], + "urls": [ + {"pattern": "path(\"api/\", include(\"djangocms_rest.urls\"))", "when": {"mode": ["headless", "hybrid"]}}, + {"pattern": "path(\"taggit_autosuggest/\", include(\"taggit_autosuggest.urls\"))", "when": {"flag": "stories"}}, + {"pattern": "path(\"\", include(\"cms.urls\"))", "when": {"mode": ["traditional", "hybrid"]}} + ], + "packages": { + "filer": "django-filer" + }, + "template_dir": { + "path": "templates", + "base_template": "cms-base.html", + "base_template_content": "{% extends \"bootstrap5/base.html\" %}{# Replace this with your CMS base template1 #}\n", + "when": {"mode": ["traditional", "hybrid"]} + } +} diff --git a/djangocms_install_rules.schema.v1.json b/djangocms_install_rules.schema.v1.json new file mode 100644 index 0000000..d5034a6 --- /dev/null +++ b/djangocms_install_rules.schema.v1.json @@ -0,0 +1,196 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://raw.githubusercontent.com/django-cms/cms-template/main/djangocms_install_rules.schema.v1.json", + "title": "django CMS install rules", + "description": "Rules used by `djangocms .` to add django CMS to an existing Django project. Fetched from the cms-template repository (branch matching the installed django CMS major.minor), with the bundled file as fallback.", + "type": "object", + "additionalProperties": false, + "properties": { + "$schema": { + "type": "string" + }, + "comment": { + "type": "string", + "description": "Free-form documentation; ignored by the installer." + }, + "installed_apps": { + "type": "array", + "description": "Apps to add to INSTALLED_APPS. Items are appended unless a before/after anchor is given.", + "items": { + "$ref": "#/definitions/anchoredRule" + } + }, + "middleware": { + "type": "array", + "description": "Middleware to add to MIDDLEWARE. Items are appended unless a before/after anchor is given.", + "items": { + "$ref": "#/definitions/anchoredRule" + } + }, + "context_processors": { + "type": "array", + "description": "Context processors to append to the template OPTIONS.", + "items": { + "$ref": "#/definitions/itemsRule" + } + }, + "settings": { + "type": "array", + "description": "Settings snippets to append to settings.py if the named setting is not already defined.", + "items": { + "$ref": "#/definitions/settingRule" + } + }, + "urls": { + "type": "array", + "description": "URL patterns to add to the project urlpatterns.", + "items": { + "$ref": "#/definitions/urlRule" + } + }, + "packages": { + "type": "object", + "description": "Maps an installed app or module name to its pip requirement, where the two differ.", + "additionalProperties": { + "type": "string" + } + }, + "template_dir": { + "type": "object", + "description": "Template directory to create, with an optional base template to seed it.", + "additionalProperties": false, + "required": ["path"], + "properties": { + "path": { + "type": "string", + "description": "Directory (relative to the project root) to create and register in TEMPLATES['DIRS']." + }, + "base_template": { + "type": "string", + "description": "Filename of the base template to create inside the template directory." + }, + "base_template_content": { + "type": "string", + "description": "Initial content of the base template." + }, + "when": { + "$ref": "#/definitions/when" + }, + "comment": { + "type": "string" + } + } + } + }, + "definitions": { + "when": { + "type": "object", + "description": "Condition for applying a rule. All given criteria must match.", + "additionalProperties": false, + "minProperties": 1, + "properties": { + "flag": { + "type": "string", + "description": "Name of a command option that must be truthy (e.g. \"versioning\" for --versioning)." + }, + "mode": { + "type": "array", + "description": "The rule applies if the --mode value is one of these.", + "minItems": 1, + "items": { + "enum": ["traditional", "headless", "hybrid"] + } + } + } + }, + "itemsRule": { + "type": "object", + "additionalProperties": false, + "required": ["items"], + "properties": { + "items": { + "type": "array", + "minItems": 1, + "items": { + "type": "string" + } + }, + "when": { + "$ref": "#/definitions/when" + }, + "comment": { + "type": "string" + } + } + }, + "anchoredRule": { + "type": "object", + "additionalProperties": false, + "required": ["items"], + "not": { + "required": ["before", "after"] + }, + "properties": { + "items": { + "type": "array", + "minItems": 1, + "items": { + "type": "string" + } + }, + "before": { + "type": "string", + "description": "Existing entry before which the items are inserted." + }, + "after": { + "type": "string", + "description": "Existing entry after which the items are inserted." + }, + "when": { + "$ref": "#/definitions/when" + }, + "comment": { + "type": "string" + } + } + }, + "settingRule": { + "type": "object", + "additionalProperties": false, + "required": ["name", "snippet"], + "properties": { + "name": { + "type": "string", + "description": "Setting name; the snippet is only added if this setting is not already defined." + }, + "snippet": { + "type": "string", + "description": "Python code appended to settings.py. May contain {language_code}/{language_name} placeholders." + }, + "when": { + "$ref": "#/definitions/when" + }, + "comment": { + "type": "string" + } + } + }, + "urlRule": { + "type": "object", + "additionalProperties": false, + "required": ["pattern"], + "properties": { + "pattern": { + "type": "string", + "description": "Python expression for the URL pattern, e.g. path(\"\", include(\"cms.urls\"))." + }, + "when": { + "$ref": "#/definitions/when" + }, + "comment": { + "type": "string" + } + } + } + } +} diff --git a/project_name/settings.py-tpl b/project_name/settings.py-tpl index 017ee5f..a65cb10 100644 --- a/project_name/settings.py-tpl +++ b/project_name/settings.py-tpl @@ -51,13 +51,22 @@ INSTALLED_APPS = [ 'menus', 'djangocms_text', - 'djangocms_link', - 'djangocms_alias', - 'djangocms_versioning', - + 'djangocms_link',{% if alias %} + 'djangocms_alias',{% endif %}{% if versioning %} + 'djangocms_versioning',{% endif %}{% if moderation %} + 'djangocms_moderation',{% endif %} +{% if stories %} + 'djangocms_stories', + 'taggit', + 'taggit-autosuggest', + 'parler', +{% endif %} +{% if mode == "headless" or mode == "hybrid" %} + 'djangocms_rest', + 'rest_framework', +{% endif %} 'sekizai', 'treebeard', - 'parler', 'filer', 'easy_thumbnails', @@ -198,12 +207,18 @@ CMS_CONFIRM_VERSION4 = True SITE_ID = 1 -# A base template is part of this setup +{% if mode == "headless" %}# A setting to define which placeholders are used + +CMS_PLACEHOLDERS = ( + ("base.html", ("content", "Content")) +) +{% else %}# A base template is part of this setup # https://docs.django-cms.org/en/{% if cms_docs_version %}release-{{ cms_docs_version }}.x{% else %}latest{% endif %}/reference/configuration.html#cms-templates CMS_TEMPLATES = ( ("base.html", _("Standard")), ) +{% endif %} # Enable permissions # https://docs.django-cms.org/en/{% if cms_docs_version %}release-{{ cms_docs_version }}.x{% else %}latest{% endif %}/topics/permissions.html diff --git a/project_name/urls.py-tpl b/project_name/urls.py-tpl index 76b4f7b..9d53af8 100644 --- a/project_name/urls.py-tpl +++ b/project_name/urls.py-tpl @@ -24,8 +24,10 @@ from django.views.i18n import JavaScriptCatalog urlpatterns = i18n_patterns( path('jsi18n/', JavaScriptCatalog.as_view(), name='javascript-catalog'), path('admin/', admin.site.urls), - path('filer/', include('filer.urls')), - path('', include('cms.urls')), + path('filer/', include('filer.urls')),{% if stories %} + path('taggit_autosuggest', include('taggit_autosuggest.urls')){% endif %}{% if mode == "hybrid" or mode == "headless" %} + path('api', include("djangocms_rest.urls"))),{% endif %}{% if mode == "hybrid" or mode == "traditional" %} + path('', include('cms.urls')),{% endif %} ) diff --git a/requirements.in b/requirements.in index 19560cd..f09582e 100644 --- a/requirements.in +++ b/requirements.in @@ -1,8 +1,10 @@ -djangocms-versioning -djangocms-alias -djangocms-frontend>=2.0.0a1 +djangocms-frontend>=2.0.0 django-filer djangocms-text -django-fsm<3 djangocms-simple-admin-style -django-treebeard!=5.0.3 +django-parler{% if versioning %}djangocms-versioning +{% endif %}{% if alias %}djangocms-alias +{% endif %}{% if moderation %}djangocms-moderation +{% endif %}{% if mode == "headless" or mode == "hybrid" %}djangocms-rest +{% endif %}{% if stories %}djangocms-stories +{% endif %}