Răsfoiți Sursa

Update project template (#604)

Basic template:
* Add `CoderedEventPage` and `CoderedLocationPage` (and associated
models) to the default template. This exposes those models to new
projects without having to write additional code.
* Add a pyproject.toml to prevent Django from trying to blacken
migrations when starting a new project. At some point Django started
doing this and it can potentially cause a huge slowdown.

Sass template (renamed to "pro"):
* Same changes from basic template
* This will be designated as the "professional" template for use in
real-world projects. Therefore, this template provides a full suite of
pre-configured developer tooling.
* Provide `ruff` with configuration.
* Provide `mypy` with configuration.
* Provide `pytest` with configuration.
* Provides custom User, Image, and Document models by default.

Related changes:
* To further avoid lag from the Django template renderer, we now
explicitly define files in project_template which are actually
templates.
* Add pipeline test matrix that checks both basic and pro templates.
Vince Salvino 1 an în urmă
părinte
comite
445e4c4a86
54 a modificat fișierele cu 49614 adăugiri și 694 ștergeri
  1. 18 10
      azure-pipelines.yml
  2. 10 2
      coderedcms/bin/coderedcms.py
  3. 18 16
      coderedcms/project_template/basic/.gitignore
  4. 3 1
      coderedcms/project_template/basic/manage.py
  5. 9 19
      coderedcms/project_template/basic/project_name/settings/base.py
  6. 1 1
      coderedcms/project_template/basic/project_name/settings/prod.py
  7. 10 7
      coderedcms/project_template/basic/project_name/urls.py
  8. 3 1
      coderedcms/project_template/basic/project_name/wsgi.py
  9. 33 0
      coderedcms/project_template/basic/pyproject.toml
  10. 24273 234
      coderedcms/project_template/basic/website/migrations/0001_initial.py
  11. 0 6
      coderedcms/project_template/basic/website/migrations/0002_initial_data.py
  12. 71 8
      coderedcms/project_template/basic/website/models.py
  13. 7 0
      coderedcms/project_template/pro/.cr.ini
  14. 0 0
      coderedcms/project_template/pro/.editorconfig
  15. 0 0
      coderedcms/project_template/pro/.gitattributes
  16. 18 16
      coderedcms/project_template/pro/.gitignore
  17. 18 0
      coderedcms/project_template/pro/README.md
  18. 0 0
      coderedcms/project_template/pro/custom_media/__init__.py
  19. 8 0
      coderedcms/project_template/pro/custom_media/admin.py
  20. 6 0
      coderedcms/project_template/pro/custom_media/apps.py
  21. 272 0
      coderedcms/project_template/pro/custom_media/migrations/0001_initial.py
  22. 0 0
      coderedcms/project_template/pro/custom_media/migrations/__init__.py
  23. 63 0
      coderedcms/project_template/pro/custom_media/models.py
  24. 0 0
      coderedcms/project_template/pro/custom_user/__init__.py
  25. 54 0
      coderedcms/project_template/pro/custom_user/admin.py
  26. 6 0
      coderedcms/project_template/pro/custom_user/apps.py
  27. 124 0
      coderedcms/project_template/pro/custom_user/migrations/0001_initial.py
  28. 0 0
      coderedcms/project_template/pro/custom_user/migrations/__init__.py
  29. 60 0
      coderedcms/project_template/pro/custom_user/models.py
  30. 3 1
      coderedcms/project_template/pro/manage.py
  31. 0 0
      coderedcms/project_template/pro/project_name/__init__.py
  32. 0 0
      coderedcms/project_template/pro/project_name/settings/__init__.py
  33. 14 14
      coderedcms/project_template/pro/project_name/settings/base.py
  34. 0 0
      coderedcms/project_template/pro/project_name/settings/dev.py
  35. 1 1
      coderedcms/project_template/pro/project_name/settings/prod.py
  36. 10 7
      coderedcms/project_template/pro/project_name/urls.py
  37. 3 1
      coderedcms/project_template/pro/project_name/wsgi.py
  38. 33 0
      coderedcms/project_template/pro/pyproject.toml
  39. 11 0
      coderedcms/project_template/pro/requirements-dev.txt
  40. 0 0
      coderedcms/project_template/pro/requirements.txt
  41. 0 0
      coderedcms/project_template/pro/website/__init__.py
  42. 0 0
      coderedcms/project_template/pro/website/apps.py
  43. 24274 235
      coderedcms/project_template/pro/website/migrations/0001_initial.py
  44. 0 6
      coderedcms/project_template/pro/website/migrations/0002_initial_data.py
  45. 0 0
      coderedcms/project_template/pro/website/migrations/__init__.py
  46. 149 0
      coderedcms/project_template/pro/website/models.py
  47. 0 0
      coderedcms/project_template/pro/website/static/website/js/custom.js
  48. 0 0
      coderedcms/project_template/pro/website/static/website/src/_variables.scss
  49. 0 0
      coderedcms/project_template/pro/website/static/website/src/custom.scss
  50. 0 0
      coderedcms/project_template/pro/website/templates/coderedcms/pages/base.html
  51. 0 2
      coderedcms/project_template/sass/requirements-dev.txt
  52. 0 86
      coderedcms/project_template/sass/website/models.py
  53. 2 2
      coderedcms/tests/test_bin.py
  54. 29 18
      docs/getting_started/install.rst

+ 18 - 10
azure-pipelines.yml

@@ -34,18 +34,27 @@ stages:
         py3.8_wag5.0:
           PYTHON_VERSION: '3.8'
           WAGTAIL_VERSION: '5.0.*'
+          TEMPLATE: 'basic'
         py3.9_wag5.0:
           PYTHON_VERSION: '3.9'
           WAGTAIL_VERSION: '5.0.*'
+          TEMPLATE: 'basic'
         py3.10_wag5.0:
           PYTHON_VERSION: '3.10'
           WAGTAIL_VERSION: '5.0.*'
+          TEMPLATE: 'basic'
         py3.11_wag5.1:
           PYTHON_VERSION: '3.11'
           WAGTAIL_VERSION: '5.1.*'
-        py3.12_wag5.2:
+          TEMPLATE: 'basic'
+        py3.12_wag5.2_basic:
           PYTHON_VERSION: '3.12'
           WAGTAIL_VERSION: '5.2.*'
+          TEMPLATE: 'basic'
+        py3.12_wag5.2_pro:
+          PYTHON_VERSION: '3.12'
+          WAGTAIL_VERSION: '5.2.*'
+          TEMPLATE: 'pro'
 
     steps:
     - task: UsePythonVersion@0
@@ -57,9 +66,16 @@ stages:
     - script: python -m pip install -r requirements-ci.txt wagtail==$(WAGTAIL_VERSION)
       displayName: 'CR-QC: Install coderedcms from local repo'
 
-    - script: coderedcms start testproject
+    - script: coderedcms start testproject --template $(TEMPLATE)
       displayName: 'CR-QC: Create starter project from template'
 
+    - script: |
+        cd testproject/
+        touch requirements-dev.txt
+        python -m pip install -r requirements-dev.txt
+        python manage.py makemigrations --check
+      displayName: 'CR-QC: Check migrations'
+
     - pwsh: ./ci/run-tests.ps1
       displayName: 'CR-QC: Run unit tests'
 
@@ -101,14 +117,6 @@ stages:
     - pwsh: ./ci/spellcheck.ps1
       displayName: 'CR-QC: Spelling'
 
-    - script: coderedcms start testproject
-      displayName: 'CR-QC: Generate a test project'
-
-    - script: |
-        cd testproject/
-        python manage.py makemigrations --check
-      displayName: 'CR-QC: Check migrations'
-
     - script: black --check .
       displayName: 'CR-QC: Black'
 

+ 10 - 2
coderedcms/bin/coderedcms.py

@@ -61,9 +61,17 @@ class CreateProject(TemplateCommand):
         if os.path.isdir(template_path):
             options["template"] = template_path
 
+        # Assume all files are NOT Django templates.
+        options["extensions"] = ["toml"]
         # Treat these files as Django templates to render the boilerplate.
-        options["extensions"] = ["py", "md", "txt"]
-        options["files"] = ["Dockerfile"]
+        options["files"] = [
+            "0002_initial_data.py",
+            "base.py",
+            "manage.py",
+            "README.md",
+            "requirements.txt",
+            "wsgi.py",
+        ]
 
         # Set options
         message = "Creating a Wagtail CRX project called %(project_name)s"

+ 18 - 16
coderedcms/project_template/basic/.gitignore

@@ -1,4 +1,4 @@
-# Created by https://www.gitignore.io, modified for use with CodeRed CMS.
+# Created by https://www.gitignore.io, modified for use with Wagtail CRX.
 
 #######################################
 ### Editors
@@ -197,7 +197,7 @@ tags
 *.pyc
 __pycache__/
 local_settings.py
-db.sqlite3
+*.sqlite3
 
 # If your build process includes running collectstatic, then you probably don't need or want to include staticfiles/
 # in your Git repository. Update and uncomment the following line accordingly.
@@ -315,6 +315,15 @@ dmypy.json
 # Pyre type checker
 .pyre/
 
+# Ruff
+.ruff_cache/
+
+
+
+#######################################
+### Operating Systems
+#######################################
+
 
 ### OSX ###
 
@@ -346,12 +355,6 @@ Temporary Items
 .apdisk
 
 
-
-#######################################
-### Operating Systems
-#######################################
-
-
 ### Windows ###
 
 # Windows thumbnail cache files
@@ -381,19 +384,18 @@ $RECYCLE.BIN/
 
 
 #######################################
-### CodeRed CMS
+### Wagtail CRX
 #######################################
 
 
-#### CodeRed CMS defaults ###
-
 # Cache
 cache/
 
 # File uploads from forms
-protected/
+/protected/
+
+# Media files
+/media/
 
-# if you want to store original uploaded media files in version control,
-# replace "media/" with "media/images/"
-media/
-#media/images/
+# Collected static files
+/static/

+ 3 - 1
coderedcms/project_template/basic/manage.py

@@ -2,9 +2,11 @@
 import os
 import sys
 
+
 if __name__ == "__main__":
     os.environ.setdefault(
-        "DJANGO_SETTINGS_MODULE", "{{ project_name }}.settings.dev"
+        "DJANGO_SETTINGS_MODULE",
+        "{{ project_name }}.settings.dev",
     )
 
     from django.core.management import execute_from_command_line

+ 9 - 19
coderedcms/project_template/basic/project_name/settings/base.py

@@ -10,12 +10,11 @@ For the full list of settings and their values, see
 https://docs.djangoproject.com/en/{{ docs_version }}/ref/settings/
 """
 
-# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
-import os
-from django.utils.translation import gettext_lazy as _
+from pathlib import Path
 
-PROJECT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
-BASE_DIR = os.path.dirname(PROJECT_DIR)
+
+# Build paths inside the project like this: BASE_DIR / "subdir".
+BASE_DIR = Path(__file__).resolve().parent.parent.parent
 
 
 # Quick-start development settings - unsuitable for production
@@ -70,8 +69,6 @@ MIDDLEWARE = [
     "django.contrib.auth.middleware.AuthenticationMiddleware",
     "django.middleware.clickjacking.XFrameOptionsMiddleware",
     "django.middleware.security.SecurityMiddleware",
-    #  Error reporting. Uncomment this to receive emails when a 404 is triggered.
-    # 'django.middleware.common.BrokenLinkEmailsMiddleware',
     # CMS functionality
     "wagtail.contrib.redirects.middleware.RedirectMiddleware",
     # Fetch from cache. Must be LAST.
@@ -105,7 +102,7 @@ WSGI_APPLICATION = "{{ project_name }}.wsgi.application"
 DATABASES = {
     "default": {
         "ENGINE": "django.db.backends.sqlite3",
-        "NAME": os.path.join(BASE_DIR, "db.sqlite3"),
+        "NAME": BASE_DIR / "db.sqlite3",
     }
 }
 
@@ -128,17 +125,15 @@ AUTH_PASSWORD_VALIDATORS = [
     },
 ]
 
+
 # Internationalization
 # https://docs.djangoproject.com/en/{{ docs_version }}/topics/i18n/
 
-# To add or change language of the project, modify the list below.
 LANGUAGE_CODE = "en-us"
 
-LANGUAGES = [("en-us", _("English"))]
-
 TIME_ZONE = "America/New_York"
 
-USE_I18N = True
+USE_I18N = False
 
 USE_TZ = True
 
@@ -151,10 +146,10 @@ STATICFILES_FINDERS = [
     "django.contrib.staticfiles.finders.AppDirectoriesFinder",
 ]
 
-STATIC_ROOT = os.path.join(BASE_DIR, "static")
+STATIC_ROOT = BASE_DIR / "static"
 STATIC_URL = "/static/"
 
-MEDIA_ROOT = os.path.join(BASE_DIR, "media")
+MEDIA_ROOT = BASE_DIR / "media"
 MEDIA_URL = "/media/"
 
 
@@ -170,11 +165,6 @@ WAGTAIL_SITE_NAME = "{{ sitename }}"
 
 WAGTAIL_ENABLE_UPDATE_CHECK = False
 
-WAGTAILSEARCH_BACKENDS = {
-    "default": {
-        "BACKEND": "wagtail.search.backends.database",
-    }
-}
 
 # Base URL to use when referring to full URLs within the Wagtail admin backend -
 # e.g. in notification emails. Don't include '/admin' or a trailing slash

+ 1 - 1
coderedcms/project_template/basic/project_name/settings/prod.py

@@ -32,7 +32,7 @@ SERVER_EMAIL = DEFAULT_FROM_EMAIL
 CACHES = {
     "default": {
         "BACKEND": "django.core.cache.backends.filebased.FileBasedCache",
-        "LOCATION": os.path.join(BASE_DIR, "cache"),  # noqa
+        "LOCATION": BASE_DIR / "cache",  # noqa
         "KEY_PREFIX": "coderedcms",
         "TIMEOUT": 14400,  # in seconds
     }

+ 10 - 7
coderedcms/project_template/basic/project_name/urls.py

@@ -1,10 +1,12 @@
-from django.conf import settings
-from django.urls import include, path
-from django.contrib import admin
-from wagtail.documents import urls as wagtaildocs_urls
 from coderedcms import admin_urls as crx_admin_urls
 from coderedcms import search_urls as crx_search_urls
 from coderedcms import urls as crx_urls
+from django.conf import settings
+from django.contrib import admin
+from django.urls import include
+from django.urls import path
+from wagtail.documents import urls as wagtaildocs_urls
+
 
 urlpatterns = [
     # Admin
@@ -24,10 +26,11 @@ urlpatterns = [
 ]
 
 
+# fmt: off
 if settings.DEBUG:
     from django.conf.urls.static import static
-    from django.contrib.staticfiles.urls import staticfiles_urlpatterns
 
     # Serve static and media files from development server
-    urlpatterns += staticfiles_urlpatterns()
-    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
+    urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)  # type: ignore
+    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)  # type: ignore
+# fmt: on

+ 3 - 1
coderedcms/project_template/basic/project_name/wsgi.py

@@ -11,8 +11,10 @@ import os
 
 from django.core.wsgi import get_wsgi_application
 
+
 os.environ.setdefault(
-    "DJANGO_SETTINGS_MODULE", "{{ project_name }}.settings.dev"
+    "DJANGO_SETTINGS_MODULE",
+    "{{ project_name }}.settings.dev",
 )
 
 application = get_wsgi_application()

+ 33 - 0
coderedcms/project_template/basic/pyproject.toml

@@ -0,0 +1,33 @@
+[tool.black]
+line-length = 80
+extend-exclude = ["migrations"]
+
+[tool.django-stubs]
+django_settings_module = "{{ project_name }}.settings.dev"
+
+[tool.mypy]
+ignore_missing_imports = true
+plugins = ["mypy_django_plugin.main"]
+exclude = [
+    '^\..*',
+    'migrations',
+    'node_modules',
+    'venv',
+]
+
+[tool.pytest.ini_options]
+DJANGO_SETTINGS_MODULE = "{{ project_name }}.settings.dev"
+addopts = "--cov --cov-report html"
+python_files = "tests.py test_*.py"
+
+[tool.ruff]
+extend-exclude = ["migrations"]
+line-length = 80
+
+[tool.ruff.lint]
+extend-select = ["I"]
+
+[tool.ruff.lint.isort]
+case-sensitive = false
+force-single-line = true
+lines-after-imports = 2

Fișier diff suprimat deoarece este prea mare
+ 24273 - 234
coderedcms/project_template/basic/website/migrations/0001_initial.py


+ 0 - 6
coderedcms/project_template/basic/website/migrations/0002_initial_data.py

@@ -1,10 +1,4 @@
-
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
-from django import VERSION as DJANGO_VERSION
 from django.db import migrations
-
 from wagtail.models import Locale
 
 

+ 71 - 8
coderedcms/project_template/basic/website/models.py

@@ -1,15 +1,18 @@
 """
 Create or customize your page models here.
 """
-from modelcluster.fields import ParentalKey
 from coderedcms.forms import CoderedFormField
-from coderedcms.models import (
-    CoderedArticlePage,
-    CoderedArticleIndexPage,
-    CoderedEmail,
-    CoderedFormPage,
-    CoderedWebPage,
-)
+from coderedcms.models import CoderedArticleIndexPage
+from coderedcms.models import CoderedArticlePage
+from coderedcms.models import CoderedEmail
+from coderedcms.models import CoderedEventIndexPage
+from coderedcms.models import CoderedEventOccurrence
+from coderedcms.models import CoderedEventPage
+from coderedcms.models import CoderedFormPage
+from coderedcms.models import CoderedLocationIndexPage
+from coderedcms.models import CoderedLocationPage
+from coderedcms.models import CoderedWebPage
+from modelcluster.fields import ParentalKey
 
 
 class ArticlePage(CoderedArticlePage):
@@ -45,6 +48,34 @@ class ArticleIndexPage(CoderedArticleIndexPage):
     template = "coderedcms/pages/article_index_page.html"
 
 
+class EventPage(CoderedEventPage):
+    class Meta:
+        verbose_name = "Event Page"
+
+    parent_page_types = ["website.EventIndexPage"]
+    template = "coderedcms/pages/event_page.html"
+
+
+class EventIndexPage(CoderedEventIndexPage):
+    """
+    Shows a list of event sub-pages.
+    """
+
+    class Meta:
+        verbose_name = "Events Landing Page"
+
+    index_query_pagemodel = "website.EventPage"
+
+    # Only allow EventPages beneath this page.
+    subpage_types = ["website.EventPage"]
+
+    template = "coderedcms/pages/event_index_page.html"
+
+
+class EventOccurrence(CoderedEventOccurrence):
+    event = ParentalKey(EventPage, related_name="occurrences")
+
+
 class FormPage(CoderedFormPage):
     """
     A page with an html <form>.
@@ -75,6 +106,38 @@ class FormConfirmEmail(CoderedEmail):
     page = ParentalKey("FormPage", related_name="confirmation_emails")
 
 
+class LocationPage(CoderedLocationPage):
+    """
+    A page that holds a location.  This could be a store, a restaurant, etc.
+    """
+
+    class Meta:
+        verbose_name = "Location Page"
+
+    template = "coderedcms/pages/location_page.html"
+
+    # Only allow LocationIndexPages above this page.
+    parent_page_types = ["website.LocationIndexPage"]
+
+
+class LocationIndexPage(CoderedLocationIndexPage):
+    """
+    A page that holds a list of locations and displays them with a Google Map.
+    This does require a Google Maps API Key in Settings > CRX Settings
+    """
+
+    class Meta:
+        verbose_name = "Location Landing Page"
+
+    # Override to specify custom index ordering choice/default.
+    index_query_pagemodel = "website.LocationPage"
+
+    # Only allow LocationPages beneath this page.
+    subpage_types = ["website.LocationPage"]
+
+    template = "coderedcms/pages/location_index_page.html"
+
+
 class WebPage(CoderedWebPage):
     """
     General use page with featureful streamfield and SEO attributes.

+ 7 - 0
coderedcms/project_template/pro/.cr.ini

@@ -0,0 +1,7 @@
+# This is a deployment file for CodeRed Cloud hosting.
+# If you are not using CodeRed Cloud, you can delete this file.
+#
+# https://www.codered.cloud/cli/
+#
+[cr]
+deploy_include = website/static/website/css/custom.css

+ 0 - 0
coderedcms/project_template/sass/.editorconfig → coderedcms/project_template/pro/.editorconfig


+ 0 - 0
coderedcms/project_template/sass/.gitattributes → coderedcms/project_template/pro/.gitattributes


+ 18 - 16
coderedcms/project_template/sass/.gitignore → coderedcms/project_template/pro/.gitignore

@@ -1,4 +1,4 @@
-# Created by https://www.gitignore.io, modified for use with CodeRed CMS.
+# Created by https://www.gitignore.io, modified for use with Wagtail CRX.
 
 #######################################
 ### Editors
@@ -197,7 +197,7 @@ tags
 *.pyc
 __pycache__/
 local_settings.py
-db.sqlite3
+*.sqlite3
 
 # If your build process includes running collectstatic, then you probably don't need or want to include staticfiles/
 # in your Git repository. Update and uncomment the following line accordingly.
@@ -315,6 +315,15 @@ dmypy.json
 # Pyre type checker
 .pyre/
 
+# Ruff
+.ruff_cache/
+
+
+
+#######################################
+### Operating Systems
+#######################################
+
 
 ### OSX ###
 
@@ -346,12 +355,6 @@ Temporary Items
 .apdisk
 
 
-
-#######################################
-### Operating Systems
-#######################################
-
-
 ### Windows ###
 
 # Windows thumbnail cache files
@@ -381,19 +384,18 @@ $RECYCLE.BIN/
 
 
 #######################################
-### CodeRed CMS
+### Wagtail CRX
 #######################################
 
 
-#### CodeRed CMS defaults ###
-
 # Cache
 cache/
 
 # File uploads from forms
-protected/
+/protected/
+
+# Media files
+/media/
 
-# if you want to store original uploaded media files in version control,
-# replace "media/" with "media/images/"
-media/
-#media/images/
+# Collected static files
+/static/

+ 18 - 0
coderedcms/project_template/sass/README.md → coderedcms/project_template/pro/README.md

@@ -32,6 +32,24 @@ Open this directory in a command prompt, then:
    to log in and get to work!
 
 
+## Linting / pre-deployment
+
+To check for errors, run the following commands:
+
+```
+ruff check --fix .
+ruff format .
+mypy .
+pytest .
+```
+
+Before deploying, be sure to build the sass:
+
+```
+python manage.py sass -t compressed website/static/website/src/custom.scss website/static/website/css/
+```
+
+
 ## Documentation links
 
 * To customize the content, design, and features of the site see

+ 0 - 0
coderedcms/project_template/sass/project_name/__init__.py → coderedcms/project_template/pro/custom_media/__init__.py


+ 8 - 0
coderedcms/project_template/pro/custom_media/admin.py

@@ -0,0 +1,8 @@
+from django.contrib import admin
+
+from .models import CustomDocument
+from .models import CustomImage
+
+
+admin.site.register(CustomDocument)
+admin.site.register(CustomImage)

+ 6 - 0
coderedcms/project_template/pro/custom_media/apps.py

@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class CustomMediaConfig(AppConfig):
+    default_auto_field = "django.db.models.BigAutoField"
+    name = "custom_media"

+ 272 - 0
coderedcms/project_template/pro/custom_media/migrations/0001_initial.py

@@ -0,0 +1,272 @@
+# Generated by Django 4.2.7 on 2023-11-03 22:24
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+import taggit.managers
+import wagtail.images.models
+import wagtail.models.collections
+import wagtail.search.index
+
+
+class Migration(migrations.Migration):
+    initial = True
+
+    dependencies = [
+        ("taggit", "0005_auto_20220424_2025"),
+        ("wagtailcore", "0083_workflowcontenttype"),
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name="CustomImage",
+            fields=[
+                (
+                    "id",
+                    models.BigAutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                (
+                    "title",
+                    models.CharField(max_length=255, verbose_name="title"),
+                ),
+                (
+                    "file",
+                    wagtail.images.models.WagtailImageField(
+                        height_field="height",
+                        upload_to=wagtail.images.models.get_upload_to,
+                        verbose_name="file",
+                        width_field="width",
+                    ),
+                ),
+                (
+                    "width",
+                    models.IntegerField(editable=False, verbose_name="width"),
+                ),
+                (
+                    "height",
+                    models.IntegerField(editable=False, verbose_name="height"),
+                ),
+                (
+                    "created_at",
+                    models.DateTimeField(
+                        auto_now_add=True,
+                        db_index=True,
+                        verbose_name="created at",
+                    ),
+                ),
+                (
+                    "focal_point_x",
+                    models.PositiveIntegerField(blank=True, null=True),
+                ),
+                (
+                    "focal_point_y",
+                    models.PositiveIntegerField(blank=True, null=True),
+                ),
+                (
+                    "focal_point_width",
+                    models.PositiveIntegerField(blank=True, null=True),
+                ),
+                (
+                    "focal_point_height",
+                    models.PositiveIntegerField(blank=True, null=True),
+                ),
+                (
+                    "file_size",
+                    models.PositiveIntegerField(editable=False, null=True),
+                ),
+                (
+                    "file_hash",
+                    models.CharField(
+                        blank=True, db_index=True, editable=False, max_length=40
+                    ),
+                ),
+                (
+                    "alt_text",
+                    models.CharField(
+                        blank=True,
+                        help_text="A description of this image used by search engines and screen readers.",
+                        max_length=255,
+                        verbose_name="Alt Text",
+                    ),
+                ),
+                (
+                    "credit",
+                    models.CharField(
+                        blank=True,
+                        help_text="Credit or attribute the source of the image. Properly attributing images taken from online sources can reduce your risk of copyright infringement.",
+                        max_length=255,
+                        verbose_name="Credit",
+                    ),
+                ),
+                (
+                    "collection",
+                    models.ForeignKey(
+                        default=wagtail.models.collections.get_root_collection_id,
+                        on_delete=django.db.models.deletion.CASCADE,
+                        related_name="+",
+                        to="wagtailcore.collection",
+                        verbose_name="collection",
+                    ),
+                ),
+                (
+                    "tags",
+                    taggit.managers.TaggableManager(
+                        blank=True,
+                        help_text=None,
+                        through="taggit.TaggedItem",
+                        to="taggit.Tag",
+                        verbose_name="tags",
+                    ),
+                ),
+                (
+                    "uploaded_by_user",
+                    models.ForeignKey(
+                        blank=True,
+                        editable=False,
+                        null=True,
+                        on_delete=django.db.models.deletion.SET_NULL,
+                        to=settings.AUTH_USER_MODEL,
+                        verbose_name="uploaded by user",
+                    ),
+                ),
+            ],
+            options={
+                "abstract": False,
+            },
+            bases=(
+                wagtail.images.models.ImageFileMixin,
+                wagtail.search.index.Indexed,
+                models.Model,
+            ),
+        ),
+        migrations.CreateModel(
+            name="CustomDocument",
+            fields=[
+                (
+                    "id",
+                    models.BigAutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                (
+                    "title",
+                    models.CharField(max_length=255, verbose_name="title"),
+                ),
+                (
+                    "file",
+                    models.FileField(
+                        upload_to="documents", verbose_name="file"
+                    ),
+                ),
+                (
+                    "created_at",
+                    models.DateTimeField(
+                        auto_now_add=True, verbose_name="created at"
+                    ),
+                ),
+                (
+                    "file_size",
+                    models.PositiveIntegerField(editable=False, null=True),
+                ),
+                (
+                    "file_hash",
+                    models.CharField(blank=True, editable=False, max_length=40),
+                ),
+                (
+                    "collection",
+                    models.ForeignKey(
+                        default=wagtail.models.collections.get_root_collection_id,
+                        on_delete=django.db.models.deletion.CASCADE,
+                        related_name="+",
+                        to="wagtailcore.collection",
+                        verbose_name="collection",
+                    ),
+                ),
+                (
+                    "tags",
+                    taggit.managers.TaggableManager(
+                        blank=True,
+                        help_text=None,
+                        through="taggit.TaggedItem",
+                        to="taggit.Tag",
+                        verbose_name="tags",
+                    ),
+                ),
+                (
+                    "uploaded_by_user",
+                    models.ForeignKey(
+                        blank=True,
+                        editable=False,
+                        null=True,
+                        on_delete=django.db.models.deletion.SET_NULL,
+                        to=settings.AUTH_USER_MODEL,
+                        verbose_name="uploaded by user",
+                    ),
+                ),
+            ],
+            options={
+                "verbose_name": "document",
+                "verbose_name_plural": "documents",
+                "abstract": False,
+            },
+            bases=(wagtail.search.index.Indexed, models.Model),
+        ),
+        migrations.CreateModel(
+            name="CustomRendition",
+            fields=[
+                (
+                    "id",
+                    models.BigAutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                (
+                    "filter_spec",
+                    models.CharField(db_index=True, max_length=255),
+                ),
+                (
+                    "file",
+                    wagtail.images.models.WagtailImageField(
+                        height_field="height",
+                        storage=wagtail.images.models.get_rendition_storage,
+                        upload_to=wagtail.images.models.get_rendition_upload_to,
+                        width_field="width",
+                    ),
+                ),
+                ("width", models.IntegerField(editable=False)),
+                ("height", models.IntegerField(editable=False)),
+                (
+                    "focal_point_key",
+                    models.CharField(
+                        blank=True, default="", editable=False, max_length=16
+                    ),
+                ),
+                (
+                    "image",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        related_name="renditions",
+                        to="custom_media.customimage",
+                    ),
+                ),
+            ],
+            options={
+                "unique_together": {
+                    ("image", "filter_spec", "focal_point_key")
+                },
+            },
+            bases=(wagtail.images.models.ImageFileMixin, models.Model),
+        ),
+    ]

+ 0 - 0
coderedcms/project_template/sass/project_name/settings/__init__.py → coderedcms/project_template/pro/custom_media/migrations/__init__.py


+ 63 - 0
coderedcms/project_template/pro/custom_media/models.py

@@ -0,0 +1,63 @@
+"""
+Custom overrides of Wagtail Document and Image models. All other
+models related to website content should most likely go in
+``website.models`` instead.
+"""
+from django.db import models
+from wagtail.documents.models import AbstractDocument
+from wagtail.documents.models import Document
+from wagtail.images.models import AbstractImage
+from wagtail.images.models import AbstractRendition
+from wagtail.images.models import Image
+
+
+class CustomDocument(AbstractDocument):
+    """
+    A custom Wagtail Document model. Right now it is the same as
+    the default, but can be easily extended by adding more fields here.
+    """
+
+    admin_form_fields = Document.admin_form_fields
+
+
+class CustomImage(AbstractImage):
+    """
+    A custom Wagtail Image model with fields for alt text and
+    credit/attribution.
+    """
+
+    alt_text = models.CharField(
+        max_length=255,
+        blank=True,
+        verbose_name="Alt Text",
+        help_text=(
+            "A description of this image used by search engines and screen readers."
+        ),
+    )
+    credit = models.CharField(
+        max_length=255,
+        blank=True,
+        verbose_name="Credit",
+        help_text=(
+            "Credit or attribute the source of the image. "
+            "Properly attributing images taken from online sources can "
+            "reduce your risk of copyright infringement."
+        ),
+    )
+    admin_form_fields = Image.admin_form_fields + (
+        "alt_text",
+        "credit",
+    )
+
+
+class CustomRendition(AbstractRendition):
+    """
+    Image rendition for our CustomImage model.
+    """
+
+    image = models.ForeignKey(
+        CustomImage, on_delete=models.CASCADE, related_name="renditions"
+    )
+
+    class Meta:
+        unique_together = (("image", "filter_spec", "focal_point_key"),)

+ 0 - 0
coderedcms/project_template/sass/website/__init__.py → coderedcms/project_template/pro/custom_user/__init__.py


+ 54 - 0
coderedcms/project_template/pro/custom_user/admin.py

@@ -0,0 +1,54 @@
+from django.contrib import admin
+from django.contrib.auth.admin import UserAdmin
+from django.utils.translation import gettext_lazy as _
+
+from .models import User
+
+
+class CustomUserAdmin(UserAdmin):
+    """
+    Override Django's default UserAdmin to use the email address
+    as the identifier, instead of username.
+    """
+
+    list_display = ["email", "first_name", "last_name", "is_staff"]
+    search_fields = ["email", "first_name", "last_name"]
+    ordering = ["email"]
+    filter_horizontal = ["groups", "user_permissions"]
+    fieldsets = (
+        (None, {"fields": ("email", "password")}),
+        (
+            _("Personal info"),
+            {
+                "fields": (
+                    "first_name",
+                    "last_name",
+                )
+            },
+        ),
+        (
+            _("Permissions"),
+            {
+                "fields": (
+                    "is_active",
+                    "is_staff",
+                    "is_superuser",
+                    "groups",
+                    "user_permissions",
+                ),
+            },
+        ),
+        (_("Important dates"), {"fields": ("last_login", "date_joined")}),
+    )
+    add_fieldsets = (
+        (
+            None,
+            {
+                "classes": ("wide",),
+                "fields": ("email", "password1", "password2"),
+            },
+        ),
+    )
+
+
+admin.site.register(User, CustomUserAdmin)

+ 6 - 0
coderedcms/project_template/pro/custom_user/apps.py

@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class CustomUserConfig(AppConfig):
+    default_auto_field = "django.db.models.BigAutoField"
+    name = "custom_user"

+ 124 - 0
coderedcms/project_template/pro/custom_user/migrations/0001_initial.py

@@ -0,0 +1,124 @@
+# Generated by Django 4.2.7 on 2023-11-03 22:24
+
+import custom_user.models
+from django.db import migrations, models
+import django.utils.timezone
+
+
+class Migration(migrations.Migration):
+    initial = True
+
+    dependencies = [
+        ("auth", "0012_alter_user_first_name_max_length"),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name="User",
+            fields=[
+                (
+                    "id",
+                    models.BigAutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                (
+                    "password",
+                    models.CharField(max_length=128, verbose_name="password"),
+                ),
+                (
+                    "last_login",
+                    models.DateTimeField(
+                        blank=True, null=True, verbose_name="last login"
+                    ),
+                ),
+                (
+                    "is_superuser",
+                    models.BooleanField(
+                        default=False,
+                        help_text="Designates that this user has all permissions without explicitly assigning them.",
+                        verbose_name="superuser status",
+                    ),
+                ),
+                (
+                    "first_name",
+                    models.CharField(
+                        blank=True, max_length=150, verbose_name="first name"
+                    ),
+                ),
+                (
+                    "last_name",
+                    models.CharField(
+                        blank=True, max_length=150, verbose_name="last name"
+                    ),
+                ),
+                (
+                    "is_staff",
+                    models.BooleanField(
+                        default=False,
+                        help_text="Designates whether the user can log into this admin site.",
+                        verbose_name="staff status",
+                    ),
+                ),
+                (
+                    "is_active",
+                    models.BooleanField(
+                        default=True,
+                        help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.",
+                        verbose_name="active",
+                    ),
+                ),
+                (
+                    "date_joined",
+                    models.DateTimeField(
+                        default=django.utils.timezone.now,
+                        verbose_name="date joined",
+                    ),
+                ),
+                (
+                    "email",
+                    models.EmailField(
+                        error_messages={
+                            "unique": "A user with that username already exists."
+                        },
+                        max_length=254,
+                        unique=True,
+                        verbose_name="email address",
+                    ),
+                ),
+                (
+                    "groups",
+                    models.ManyToManyField(
+                        blank=True,
+                        help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.",
+                        related_name="user_set",
+                        related_query_name="user",
+                        to="auth.group",
+                        verbose_name="groups",
+                    ),
+                ),
+                (
+                    "user_permissions",
+                    models.ManyToManyField(
+                        blank=True,
+                        help_text="Specific permissions for this user.",
+                        related_name="user_set",
+                        related_query_name="user",
+                        to="auth.permission",
+                        verbose_name="user permissions",
+                    ),
+                ),
+            ],
+            options={
+                "verbose_name": "user",
+                "verbose_name_plural": "users",
+                "abstract": False,
+            },
+            managers=[
+                ("objects", custom_user.models.UserManager()),
+            ],
+        ),
+    ]

+ 0 - 0
coderedcms/project_template/sass/website/migrations/__init__.py → coderedcms/project_template/pro/custom_user/migrations/__init__.py


+ 60 - 0
coderedcms/project_template/pro/custom_user/models.py

@@ -0,0 +1,60 @@
+from django.contrib.auth.base_user import BaseUserManager
+from django.contrib.auth.models import AbstractUser
+from django.db import models
+from django.utils.translation import gettext_lazy as _
+
+
+class UserManager(BaseUserManager):
+    """
+    Model manager for our custom User object, which uses email
+    addresses instead of usernames.
+    """
+
+    use_in_migrations = True
+
+    def _create_user(self, email, password, **extra_fields):
+        if not email:
+            raise ValueError("Email address must be provided.")
+        email = self.normalize_email(email)
+        user = self.model(email=email, **extra_fields)
+        user.set_password(password)
+        user.save(using=self._db)
+        return user
+
+    def create_user(self, email, password=None, **extra_fields):
+        extra_fields.setdefault("is_staff", False)
+        extra_fields.setdefault("is_superuser", False)
+        return self._create_user(email, password, **extra_fields)
+
+    def create_superuser(self, email, password=None, **extra_fields):
+        extra_fields.setdefault("is_staff", True)
+        extra_fields.setdefault("is_superuser", True)
+
+        if extra_fields.get("is_staff") is not True:
+            raise ValueError("Superuser must have is_staff=True.")
+        if extra_fields.get("is_superuser") is not True:
+            raise ValueError("Superuser must have is_superuser=True.")
+
+        return self._create_user(email, password, **extra_fields)
+
+
+class User(AbstractUser):  # type: ignore
+    """
+    A custom user model, which uses email instead of username as
+    the identifier.
+    """
+
+    username = None  # type: ignore
+
+    email = models.EmailField(
+        _("email address"),
+        unique=True,
+        error_messages={
+            "unique": _("A user with that username already exists."),
+        },
+    )
+
+    objects = UserManager()  # type: ignore
+
+    USERNAME_FIELD = "email"
+    REQUIRED_FIELDS = []

+ 3 - 1
coderedcms/project_template/sass/manage.py → coderedcms/project_template/pro/manage.py

@@ -2,9 +2,11 @@
 import os
 import sys
 
+
 if __name__ == "__main__":
     os.environ.setdefault(
-        "DJANGO_SETTINGS_MODULE", "{{ project_name }}.settings.dev"
+        "DJANGO_SETTINGS_MODULE",
+        "{{ project_name }}.settings.dev",
     )
 
     from django.core.management import execute_from_command_line

+ 0 - 0
coderedcms/project_template/sass/website/static/website/js/custom.js → coderedcms/project_template/pro/project_name/__init__.py


+ 0 - 0
coderedcms/project_template/pro/project_name/settings/__init__.py


+ 14 - 14
coderedcms/project_template/sass/project_name/settings/base.py → coderedcms/project_template/pro/project_name/settings/base.py

@@ -10,11 +10,11 @@ For the full list of settings and their values, see
 https://docs.djangoproject.com/en/{{ docs_version }}/ref/settings/
 """
 
-# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
-import os
+from pathlib import Path
 
-PROJECT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
-BASE_DIR = os.path.dirname(PROJECT_DIR)
+
+# Build paths inside the project like this: BASE_DIR / "subdir".
+BASE_DIR = Path(__file__).resolve().parent.parent.parent
 
 
 # Quick-start development settings - unsuitable for production
@@ -26,6 +26,8 @@ BASE_DIR = os.path.dirname(PROJECT_DIR)
 INSTALLED_APPS = [
     # This project
     "website",
+    "custom_media",
+    "custom_user",
     # Wagtail CRX (CodeRed Extensions)
     "coderedcms",
     "django_bootstrap5",
@@ -69,8 +71,6 @@ MIDDLEWARE = [
     "django.contrib.auth.middleware.AuthenticationMiddleware",
     "django.middleware.clickjacking.XFrameOptionsMiddleware",
     "django.middleware.security.SecurityMiddleware",
-    # Error reporting. Uncomment this to receive emails when a 404 is triggered.
-    # 'django.middleware.common.BrokenLinkEmailsMiddleware',
     # CMS functionality
     "wagtail.contrib.redirects.middleware.RedirectMiddleware",
     # Fetch from cache. Must be LAST.
@@ -104,7 +104,7 @@ WSGI_APPLICATION = "{{ project_name }}.wsgi.application"
 DATABASES = {
     "default": {
         "ENGINE": "django.db.backends.sqlite3",
-        "NAME": os.path.join(BASE_DIR, "db.sqlite3"),
+        "NAME": BASE_DIR / "db.sqlite3",
     }
 }
 
@@ -127,6 +127,8 @@ AUTH_PASSWORD_VALIDATORS = [
     },
 ]
 
+AUTH_USER_MODEL = "custom_user.User"
+
 
 # Internationalization
 # https://docs.djangoproject.com/en/{{ docs_version }}/topics/i18n/
@@ -148,10 +150,10 @@ STATICFILES_FINDERS = [
     "django.contrib.staticfiles.finders.AppDirectoriesFinder",
 ]
 
-STATIC_ROOT = os.path.join(BASE_DIR, "static")
+STATIC_ROOT = BASE_DIR / "static"
 STATIC_URL = "/static/"
 
-MEDIA_ROOT = os.path.join(BASE_DIR, "media")
+MEDIA_ROOT = BASE_DIR / "media"
 MEDIA_URL = "/media/"
 
 
@@ -167,11 +169,9 @@ WAGTAIL_SITE_NAME = "{{ sitename }}"
 
 WAGTAIL_ENABLE_UPDATE_CHECK = False
 
-WAGTAILSEARCH_BACKENDS = {
-    "default": {
-        "BACKEND": "wagtail.search.backends.database",
-    }
-}
+WAGTAILIMAGES_IMAGE_MODEL = "custom_media.CustomImage"
+
+WAGTAILDOCS_DOCUMENT_MODEL = "custom_media.CustomDocument"
 
 # Base URL to use when referring to full URLs within the Wagtail admin backend -
 # e.g. in notification emails. Don't include '/admin' or a trailing slash

+ 0 - 0
coderedcms/project_template/sass/project_name/settings/dev.py → coderedcms/project_template/pro/project_name/settings/dev.py


+ 1 - 1
coderedcms/project_template/sass/project_name/settings/prod.py → coderedcms/project_template/pro/project_name/settings/prod.py

@@ -32,7 +32,7 @@ SERVER_EMAIL = DEFAULT_FROM_EMAIL
 CACHES = {
     "default": {
         "BACKEND": "django.core.cache.backends.filebased.FileBasedCache",
-        "LOCATION": os.path.join(BASE_DIR, "cache"),  # noqa
+        "LOCATION": BASE_DIR / "cache",  # noqa
         "KEY_PREFIX": "coderedcms",
         "TIMEOUT": 14400,  # in seconds
     }

+ 10 - 7
coderedcms/project_template/sass/project_name/urls.py → coderedcms/project_template/pro/project_name/urls.py

@@ -1,10 +1,12 @@
-from django.conf import settings
-from django.urls import include, path
-from django.contrib import admin
-from wagtail.documents import urls as wagtaildocs_urls
 from coderedcms import admin_urls as crx_admin_urls
 from coderedcms import search_urls as crx_search_urls
 from coderedcms import urls as crx_urls
+from django.conf import settings
+from django.contrib import admin
+from django.urls import include
+from django.urls import path
+from wagtail.documents import urls as wagtaildocs_urls
+
 
 urlpatterns = [
     # Admin
@@ -24,10 +26,11 @@ urlpatterns = [
 ]
 
 
+# fmt: off
 if settings.DEBUG:
     from django.conf.urls.static import static
-    from django.contrib.staticfiles.urls import staticfiles_urlpatterns
 
     # Serve static and media files from development server
-    urlpatterns += staticfiles_urlpatterns()
-    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
+    urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)  # type: ignore
+    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)  # type: ignore
+# fmt: on

+ 3 - 1
coderedcms/project_template/sass/project_name/wsgi.py → coderedcms/project_template/pro/project_name/wsgi.py

@@ -11,8 +11,10 @@ import os
 
 from django.core.wsgi import get_wsgi_application
 
+
 os.environ.setdefault(
-    "DJANGO_SETTINGS_MODULE", "{{ project_name }}.settings.dev"
+    "DJANGO_SETTINGS_MODULE",
+    "{{ project_name }}.settings.dev",
 )
 
 application = get_wsgi_application()

+ 33 - 0
coderedcms/project_template/pro/pyproject.toml

@@ -0,0 +1,33 @@
+[tool.black]
+line-length = 80
+extend-exclude = ["migrations"]
+
+[tool.django-stubs]
+django_settings_module = "{{ project_name }}.settings.dev"
+
+[tool.mypy]
+ignore_missing_imports = true
+plugins = ["mypy_django_plugin.main"]
+exclude = [
+    '^\..*',
+    'migrations',
+    'node_modules',
+    'venv',
+]
+
+[tool.pytest.ini_options]
+DJANGO_SETTINGS_MODULE = "{{ project_name }}.settings.dev"
+addopts = "--cov --cov-report html"
+python_files = "tests.py test_*.py"
+
+[tool.ruff]
+extend-exclude = ["migrations"]
+line-length = 80
+
+[tool.ruff.lint]
+extend-select = ["I"]
+
+[tool.ruff.lint.isort]
+case-sensitive = false
+force-single-line = true
+lines-after-imports = 2

+ 11 - 0
coderedcms/project_template/pro/requirements-dev.txt

@@ -0,0 +1,11 @@
+# Install normal requirements
+-r requirements.txt
+
+# Tooling for local development
+django-sass
+django-stubs
+mypy
+pytest
+pytest-cov
+pytest-django
+ruff

+ 0 - 0
coderedcms/project_template/sass/requirements.txt → coderedcms/project_template/pro/requirements.txt


+ 0 - 0
coderedcms/project_template/pro/website/__init__.py


+ 0 - 0
coderedcms/project_template/sass/website/apps.py → coderedcms/project_template/pro/website/apps.py


Fișier diff suprimat deoarece este prea mare
+ 24274 - 235
coderedcms/project_template/pro/website/migrations/0001_initial.py


+ 0 - 6
coderedcms/project_template/sass/website/migrations/0002_initial_data.py → coderedcms/project_template/pro/website/migrations/0002_initial_data.py

@@ -1,10 +1,4 @@
-
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
-from django import VERSION as DJANGO_VERSION
 from django.db import migrations
-
 from wagtail.models import Locale
 
 

+ 0 - 0
coderedcms/project_template/pro/website/migrations/__init__.py


+ 149 - 0
coderedcms/project_template/pro/website/models.py

@@ -0,0 +1,149 @@
+"""
+Create or customize your page models here.
+"""
+from coderedcms.forms import CoderedFormField
+from coderedcms.models import CoderedArticleIndexPage
+from coderedcms.models import CoderedArticlePage
+from coderedcms.models import CoderedEmail
+from coderedcms.models import CoderedEventIndexPage
+from coderedcms.models import CoderedEventOccurrence
+from coderedcms.models import CoderedEventPage
+from coderedcms.models import CoderedFormPage
+from coderedcms.models import CoderedLocationIndexPage
+from coderedcms.models import CoderedLocationPage
+from coderedcms.models import CoderedWebPage
+from modelcluster.fields import ParentalKey
+
+
+class ArticlePage(CoderedArticlePage):
+    """
+    Article, suitable for news or blog content.
+    """
+
+    class Meta:
+        verbose_name = "Article"
+        ordering = ["-first_published_at"]
+
+    # Only allow this page to be created beneath an ArticleIndexPage.
+    parent_page_types = ["website.ArticleIndexPage"]
+
+    template = "coderedcms/pages/article_page.html"
+    search_template = "coderedcms/pages/article_page.search.html"
+
+
+class ArticleIndexPage(CoderedArticleIndexPage):
+    """
+    Shows a list of article sub-pages.
+    """
+
+    class Meta:
+        verbose_name = "Article Landing Page"
+
+    # Override to specify custom index ordering choice/default.
+    index_query_pagemodel = "website.ArticlePage"
+
+    # Only allow ArticlePages beneath this page.
+    subpage_types = ["website.ArticlePage"]
+
+    template = "coderedcms/pages/article_index_page.html"
+
+
+class EventPage(CoderedEventPage):
+    class Meta:
+        verbose_name = "Event Page"
+
+    parent_page_types = ["website.EventIndexPage"]
+    template = "coderedcms/pages/event_page.html"
+
+
+class EventIndexPage(CoderedEventIndexPage):
+    """
+    Shows a list of event sub-pages.
+    """
+
+    class Meta:
+        verbose_name = "Events Landing Page"
+
+    index_query_pagemodel = "website.EventPage"
+
+    # Only allow EventPages beneath this page.
+    subpage_types = ["website.EventPage"]
+
+    template = "coderedcms/pages/event_index_page.html"
+
+
+class EventOccurrence(CoderedEventOccurrence):
+    event = ParentalKey(EventPage, related_name="occurrences")
+
+
+class FormPage(CoderedFormPage):
+    """
+    A page with an html <form>.
+    """
+
+    class Meta:
+        verbose_name = "Form"
+
+    template = "coderedcms/pages/form_page.html"
+
+
+class FormPageField(CoderedFormField):
+    """
+    A field that links to a FormPage.
+    """
+
+    class Meta:
+        ordering = ["sort_order"]
+
+    page = ParentalKey("FormPage", related_name="form_fields")
+
+
+class FormConfirmEmail(CoderedEmail):
+    """
+    Sends a confirmation email after submitting a FormPage.
+    """
+
+    page = ParentalKey("FormPage", related_name="confirmation_emails")
+
+
+class LocationPage(CoderedLocationPage):
+    """
+    A page that holds a location.  This could be a store, a restaurant, etc.
+    """
+
+    class Meta:
+        verbose_name = "Location Page"
+
+    template = "coderedcms/pages/location_page.html"
+
+    # Only allow LocationIndexPages above this page.
+    parent_page_types = ["website.LocationIndexPage"]
+
+
+class LocationIndexPage(CoderedLocationIndexPage):
+    """
+    A page that holds a list of locations and displays them with a Google Map.
+    This does require a Google Maps API Key in Settings > CRX Settings
+    """
+
+    class Meta:
+        verbose_name = "Location Landing Page"
+
+    # Override to specify custom index ordering choice/default.
+    index_query_pagemodel = "website.LocationPage"
+
+    # Only allow LocationPages beneath this page.
+    subpage_types = ["website.LocationPage"]
+
+    template = "coderedcms/pages/location_index_page.html"
+
+
+class WebPage(CoderedWebPage):
+    """
+    General use page with featureful streamfield and SEO attributes.
+    """
+
+    class Meta:
+        verbose_name = "Web Page"
+
+    template = "coderedcms/pages/web_page.html"

+ 0 - 0
coderedcms/project_template/pro/website/static/website/js/custom.js


+ 0 - 0
coderedcms/project_template/sass/website/static/website/src/_variables.scss → coderedcms/project_template/pro/website/static/website/src/_variables.scss


+ 0 - 0
coderedcms/project_template/sass/website/static/website/src/custom.scss → coderedcms/project_template/pro/website/static/website/src/custom.scss


+ 0 - 0
coderedcms/project_template/sass/website/templates/coderedcms/pages/base.html → coderedcms/project_template/pro/website/templates/coderedcms/pages/base.html


+ 0 - 2
coderedcms/project_template/sass/requirements-dev.txt

@@ -1,2 +0,0 @@
-# Tooling for local development
-django-sass

+ 0 - 86
coderedcms/project_template/sass/website/models.py

@@ -1,86 +0,0 @@
-"""
-Create or customize your page models here.
-"""
-from modelcluster.fields import ParentalKey
-from coderedcms.forms import CoderedFormField
-from coderedcms.models import (
-    CoderedArticlePage,
-    CoderedArticleIndexPage,
-    CoderedEmail,
-    CoderedFormPage,
-    CoderedWebPage,
-)
-
-
-class ArticlePage(CoderedArticlePage):
-    """
-    Article, suitable for news or blog content.
-    """
-
-    class Meta:
-        verbose_name = "Article"
-        ordering = ["-first_published_at"]
-
-    # Only allow this page to be created beneath an ArticleIndexPage.
-    parent_page_types = ["website.ArticleIndexPage"]
-
-    template = "coderedcms/pages/article_page.html"
-    search_template = "coderedcms/pages/article_page.search.html"
-
-
-class ArticleIndexPage(CoderedArticleIndexPage):
-    """
-    Shows a list of article sub-pages.
-    """
-
-    class Meta:
-        verbose_name = "Article Landing Page"
-
-    # Override to specify custom index ordering choice/default.
-    index_query_pagemodel = "website.ArticlePage"
-
-    # Only allow ArticlePages beneath this page.
-    subpage_types = ["website.ArticlePage"]
-
-    template = "coderedcms/pages/article_index_page.html"
-
-
-class FormPage(CoderedFormPage):
-    """
-    A page with an html <form>.
-    """
-
-    class Meta:
-        verbose_name = "Form"
-
-    template = "coderedcms/pages/form_page.html"
-
-
-class FormPageField(CoderedFormField):
-    """
-    A field that links to a FormPage.
-    """
-
-    class Meta:
-        ordering = ["sort_order"]
-
-    page = ParentalKey("FormPage", related_name="form_fields")
-
-
-class FormConfirmEmail(CoderedEmail):
-    """
-    Sends a confirmation email after submitting a FormPage.
-    """
-
-    page = ParentalKey("FormPage", related_name="confirmation_emails")
-
-
-class WebPage(CoderedWebPage):
-    """
-    General use page with featureful streamfield and SEO attributes.
-    """
-
-    class Meta:
-        verbose_name = "Web Page"
-
-    template = "coderedcms/pages/web_page.html"

+ 2 - 2
coderedcms/tests/test_bin.py

@@ -88,7 +88,7 @@ class TestCoderedcmsStart(unittest.TestCase):
         )
         self.cleanup()
 
-    def test_template_sass(self):
+    def test_template_pro(self):
         self.setup()
         # Set args
         sys.argv = [
@@ -97,7 +97,7 @@ class TestCoderedcmsStart(unittest.TestCase):
             "myproject",
             self.TEST_DIR,
             "--template",
-            "sass",
+            "pro",
         ]
         # Run
         coderedcms_main()

+ 29 - 18
docs/getting_started/install.rst

@@ -7,23 +7,20 @@ Basic Installation
 
 #. Make a directory (folder) for your project.
 #. Create a virtual environment.
-    **Not sure how to create a virtual environment?**
-    Creating a virtual environment for your project only involves a few commands.
-    See below:
 
     **Windows (PowerShell):**
 
     .. code-block:: ps1con
 
-        PS> python -m venv .\venv\
-        PS> .\venv\Scripts\Activate.ps1
+       PS> python -m venv .\venv\
+       PS> .\venv\Scripts\Activate.ps1
 
     **macOS, Linux:**
 
     .. code-block:: console
 
-        $ python -m venv ./venv/
-        $ source ./venv/bin/activate
+       $ python -m venv ./venv/
+       $ source ./venv/bin/activate
 
     You can name your virtual environment anything you like. It is just for your use
     on your computer.
@@ -32,8 +29,9 @@ Basic Installation
     environments here <https://docs.python.org/3/tutorial/venv.html>`_.
 
     .. note::
-        You will need to be in the directory (folder) of your Wagtail project and have your
-        virtual environment activated to install dependencies and run your site.
+
+       You will need to be in the directory (folder) of your Wagtail project and have your
+       virtual environment activated to install dependencies and run your site.
 
 #. Run ``pip install coderedcms``
 #. Run ``coderedcms start mysite --sitename "My Company Inc." --domain www.example.com``
@@ -55,20 +53,28 @@ Follow the tutorial to build: :doc:`tutorial01`.
 You can also play around with our tutorial database. Learn more: :ref:`load-data`.
 
 
-Installing with Sass Support
-----------------------------
+Professional Installation (includes Sass/SCSS)
+----------------------------------------------
+
+The professional boilerplate includes additional features pre-configured, such as:
+
+* Custom Image and Document models
+* Custom User model (using email address as username)
+* SCSS compilation (using Python, not Node.js)
+* Ruff, MyPy, Pytest tooling pre-configured
 
-To create a project that is pre-configured to use Sass for CSS compilation:
+To use the professional boilerplate, add ``--template pro`` to the start command:
 
 #. Run ``pip install coderedcms``
 #. Run
 
    .. code-block:: console
 
-       $ coderedcms start mysite --template sass --sitename "My Company Inc." --domain www.example.com
+      $ coderedcms start mysite --template pro --sitename "My Company Inc." --domain www.example.com
 
    .. note::
-       ``--sitename`` and ``--domain`` are optional to pre-populate settings of your website.
+
+      ``--sitename`` and ``--domain`` are optional to pre-populate settings of your website.
 
 #. Enter the ``mysite`` project with ``cd mysite/``.
 #. Install the development tooling with:
@@ -109,7 +115,7 @@ the project and play around with it. The database is located in ``website > fixt
 
 Follow these steps to upload it:
 
-1. Navigate to the tutorial project in the Command Line by going to ``coderedcms > tutorial > mysite``. 
+1. Navigate to the tutorial project in the Command Line by going to ``coderedcms > tutorial > mysite``.
 
 2. In a fresh virtual environment, type ``pip install -r requirements.txt`` to set up the requirements for the project.
 
@@ -117,7 +123,7 @@ Follow these steps to upload it:
 
 4. Do the initial migration for the tutorial site with ``python manage.py migrate``.
 
-5. Navigate to the ``database.json`` file in the Fixtures folder and copy the path to the file. 
+5. Navigate to the ``database.json`` file in the Fixtures folder and copy the path to the file.
 
 6. From the Command Line, type ``python manage.py loaddata "path/to/database.json"``, replacing that last part with the correct path to the file.
 
@@ -136,6 +142,11 @@ or at a URL using the ``--template`` option. Additionally, we provide some built
 | ``basic``  | The default starter project. The simplest option, good for most |
 |            | sites.                                                          |
 +------------+-----------------------------------------------------------------+
-| ``sass``   | Similar to basic, but with extra tooling to support SCSS to CSS |
-|            | compilation.                                                    |
+| ``pro``    | Custom Image, Document, User models. Extra tooling to support   |
+|            | SCSS to CSS compilation. Developer tooling such as ruff, mypy,  |
+|            | and pytest.                                                     |
 +------------+-----------------------------------------------------------------+
+
+.. versionchanged:: 3.0
+
+   The "pro" template was added in version 3.0. Previously it was named "sass" and had fewer features.

Unele fișiere nu au fost afișate deoarece prea multe fișiere au fost modificate în acest diff