瀏覽代碼

Use ruff instead of black and flake8 (#643)

We switched the project templates to ruff in 3.0, and it has been
working out well. Time to switch the main project over to it.

* All configs are now in pyproject.toml.
* setup.cfg removed.
* Azure pipelines and contributing guide are both updated accordingly.
* Project has been formatted with ruff.
Vince Salvino 9 月之前
父節點
當前提交
526939e750
共有 41 個文件被更改,包括 412 次插入372 次删除
  1. 4 9
      azure-pipelines.yml
  2. 1 4
      ci/spellcheck.ps1
  3. 5 2
      coderedcms/admin_urls.py
  4. 2 2
      coderedcms/api/mailchimp.py
  5. 2 1
      coderedcms/bin/coderedcms.py
  6. 49 55
      coderedcms/blocks/__init__.py
  7. 1 1
      coderedcms/blocks/base_blocks.py
  8. 4 6
      coderedcms/blocks/content_blocks.py
  9. 8 9
      coderedcms/blocks/html_blocks.py
  10. 2 1
      coderedcms/blocks/layout_blocks.py
  11. 9 10
      coderedcms/blocks/stream_form_blocks.py
  12. 4 2
      coderedcms/forms.py
  13. 3 2
      coderedcms/importexport.py
  14. 11 11
      coderedcms/models/integration_models.py
  15. 68 60
      coderedcms/models/page_models.py
  16. 8 11
      coderedcms/models/snippet_models.py
  17. 6 7
      coderedcms/models/tests/test_navbars_and_footers.py
  18. 25 27
      coderedcms/models/tests/test_page_models.py
  19. 5 6
      coderedcms/models/tests/test_wagtailsettings_models.py
  20. 19 18
      coderedcms/models/wagtailsettings_models.py
  21. 2 0
      coderedcms/search_urls.py
  22. 1 0
      coderedcms/settings.py
  23. 8 5
      coderedcms/templatetags/coderedcms_tags.py
  24. 1 0
      coderedcms/tests/test_bin.py
  25. 2 1
      coderedcms/tests/test_templates.py
  26. 9 11
      coderedcms/tests/test_urls.py
  27. 13 14
      coderedcms/tests/testapp/models.py
  28. 5 1
      coderedcms/tests/urls.py
  29. 12 11
      coderedcms/urls.py
  30. 1 1
      coderedcms/utils.py
  31. 23 22
      coderedcms/views.py
  32. 12 14
      coderedcms/wagtail_flexible_forms/blocks.py
  33. 0 1
      coderedcms/wagtail_flexible_forms/edit_handlers.py
  34. 31 26
      coderedcms/wagtail_flexible_forms/models.py
  35. 4 3
      coderedcms/wagtail_hooks.py
  36. 4 5
      docs/contributing/index.rst
  37. 6 0
      docs/releases/v4.0.0.rst
  38. 37 1
      pyproject.toml
  39. 2 2
      requirements-ci.txt
  40. 0 10
      setup.cfg
  41. 3 0
      setup.py

+ 4 - 9
azure-pipelines.yml

@@ -1,4 +1,3 @@
-# Python Django
 # Test a Django project on multiple versions of Python.
 # Add steps that analyze code, save build artifacts, deploy, and more:
 # https://docs.microsoft.com/azure/devops/pipelines/languages/python
@@ -13,13 +12,10 @@
 #
 # Use PowerShell Core for any utility scripts so they are re-usable across
 # Windows, macOS, and Linux.
-#
-
 
 trigger:
   - main
 
-
 stages:
 - stage: Unit_Tests
   displayName: Unit Tests
@@ -71,7 +67,6 @@ stages:
 
     - 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'
@@ -117,11 +112,11 @@ stages:
     - pwsh: ./ci/spellcheck.ps1
       displayName: 'CR-QC: Spelling'
 
-    - script: black --check .
-      displayName: 'CR-QC: Black'
+    - script: ruff format --check .
+      displayName: 'CR-QC: Ruff Format'
 
-    - script: flake8 .
-      displayName: 'CR-QC: Flake8'
+    - script: ruff check .
+      displayName: 'CR-QC: Ruff Check'
 
   - job: codecov
     displayName: Code Coverage

+ 1 - 4
ci/spellcheck.ps1

@@ -15,10 +15,7 @@ Push-Location $projectDir
 $ExitCode = 0
 
 # Run spell checker.
-codespell `
-    --skip="migrations,vendor,_build,*.css.map,*.jpg,*.png,*.pyc" `
-    --ignore-words-list="assertIn" `
-    coderedcms docs
+codespell coderedcms docs
 $ExitCode = $LastExitCode
 
 # Print output.

+ 5 - 2
coderedcms/admin_urls.py

@@ -1,6 +1,9 @@
-from django.urls import include, path
+from django.urls import include
+from django.urls import path
 from wagtail.admin import urls as wagtailadmin_urls
-from coderedcms.views import import_index, import_pages_from_csv_file
+
+from coderedcms.views import import_index
+from coderedcms.views import import_pages_from_csv_file
 
 
 urlpatterns = [

+ 2 - 2
coderedcms/api/mailchimp.py

@@ -1,7 +1,7 @@
+import requests
 from wagtail.models import Site
-from coderedcms.models.wagtailsettings_models import LayoutSettings
 
-import requests
+from coderedcms.models.wagtailsettings_models import LayoutSettings
 
 
 class MailchimpApi:

+ 2 - 1
coderedcms/bin/coderedcms.py

@@ -46,9 +46,10 @@ class CreateProject(TemplateCommand):
         options["secret_key"] = get_random_secret_key()
 
         # Handle custom template logic
-        import coderedcms
         import wagtail
 
+        import coderedcms
+
         crx_path = os.path.dirname(coderedcms.__file__)
         if not options["template"]:
             options["template"] = "basic"

+ 49 - 55
coderedcms/blocks/__init__.py

@@ -5,63 +5,57 @@ single `blocks` module.
 """
 
 from django.utils.translation import gettext_lazy as _
-
 from wagtail import blocks
 
-from .stream_form_blocks import (
-    CoderedStreamFormCharFieldBlock,
-    CoderedStreamFormCheckboxesFieldBlock,
-    CoderedStreamFormCheckboxFieldBlock,
-    CoderedStreamFormDateFieldBlock,
-    CoderedStreamFormDateTimeFieldBlock,
-    CoderedStreamFormDropdownFieldBlock,
-    CoderedStreamFormFileFieldBlock,
-    CoderedStreamFormImageFieldBlock,
-    CoderedStreamFormNumberFieldBlock,
-    CoderedStreamFormRadioButtonsFieldBlock,
-    CoderedStreamFormStepBlock,
-    CoderedStreamFormTextFieldBlock,
-    CoderedStreamFormTimeFieldBlock,
-)
-from .html_blocks import (
-    ButtonBlock,
-    EmbedGoogleMapBlock,
-    ImageBlock,
-    ImageLinkBlock,
-    DownloadBlock,
-    EmbedVideoBlock,
-    PageListBlock,
-    PagePreviewBlock,
-    QuoteBlock,
-    RichTextBlock,
-    TableBlock,
-)
-from .content_blocks import (  # noqa
-    AccordionBlock,
-    CardBlock,
-    CarouselBlock,
-    ContentWallBlock,
-    FilmStripBlock,
-    ImageGalleryBlock,
-    ModalBlock,
-    NavDocumentLinkWithSubLinkBlock,
-    NavExternalLinkWithSubLinkBlock,
-    NavPageLinkWithSubLinkBlock,
-    PriceListBlock,
-    ReusableContentBlock,
-)
-from .layout_blocks import CardGridBlock, GridBlock, HeroBlock
-from .base_blocks import (  # noqa
-    BaseBlock,
-    BaseLayoutBlock,
-    BaseLinkBlock,
-    ClassifierTermChooserBlock,
-    CoderedAdvColumnSettings,
-    CoderedAdvSettings,
-    CoderedAdvTrackingSettings,
-    CollectionChooserBlock,
-    LinkStructValue,
-)
+from .base_blocks import BaseBlock  # noqa
+from .base_blocks import BaseLayoutBlock  # noqa
+from .base_blocks import BaseLinkBlock  # noqa
+from .base_blocks import ClassifierTermChooserBlock  # noqa
+from .base_blocks import CoderedAdvColumnSettings  # noqa
+from .base_blocks import CoderedAdvSettings  # noqa
+from .base_blocks import CoderedAdvTrackingSettings  # noqa
+from .base_blocks import CollectionChooserBlock  # noqa
+from .base_blocks import LinkStructValue  # noqa
+from .content_blocks import AccordionBlock
+from .content_blocks import CardBlock
+from .content_blocks import CarouselBlock
+from .content_blocks import ContentWallBlock  # noqa
+from .content_blocks import FilmStripBlock
+from .content_blocks import ImageGalleryBlock
+from .content_blocks import ModalBlock
+from .content_blocks import NavDocumentLinkWithSubLinkBlock
+from .content_blocks import NavExternalLinkWithSubLinkBlock
+from .content_blocks import NavPageLinkWithSubLinkBlock
+from .content_blocks import PriceListBlock
+from .content_blocks import ReusableContentBlock
+from .html_blocks import ButtonBlock
+from .html_blocks import DownloadBlock
+from .html_blocks import EmbedGoogleMapBlock
+from .html_blocks import EmbedVideoBlock
+from .html_blocks import ImageBlock
+from .html_blocks import ImageLinkBlock
+from .html_blocks import PageListBlock
+from .html_blocks import PagePreviewBlock
+from .html_blocks import QuoteBlock
+from .html_blocks import RichTextBlock
+from .html_blocks import TableBlock
+from .layout_blocks import CardGridBlock
+from .layout_blocks import GridBlock
+from .layout_blocks import HeroBlock
+from .stream_form_blocks import CoderedStreamFormCharFieldBlock
+from .stream_form_blocks import CoderedStreamFormCheckboxesFieldBlock
+from .stream_form_blocks import CoderedStreamFormCheckboxFieldBlock
+from .stream_form_blocks import CoderedStreamFormDateFieldBlock
+from .stream_form_blocks import CoderedStreamFormDateTimeFieldBlock
+from .stream_form_blocks import CoderedStreamFormDropdownFieldBlock
+from .stream_form_blocks import CoderedStreamFormFileFieldBlock
+from .stream_form_blocks import CoderedStreamFormImageFieldBlock
+from .stream_form_blocks import CoderedStreamFormNumberFieldBlock
+from .stream_form_blocks import CoderedStreamFormRadioButtonsFieldBlock
+from .stream_form_blocks import CoderedStreamFormStepBlock
+from .stream_form_blocks import CoderedStreamFormTextFieldBlock
+from .stream_form_blocks import CoderedStreamFormTimeFieldBlock
+
 
 # Collections of blocks commonly used together.
 

+ 1 - 1
coderedcms/blocks/base_blocks.py

@@ -8,9 +8,9 @@ from django.utils.functional import cached_property
 from django.utils.safestring import mark_safe
 from django.utils.translation import gettext_lazy as _
 from wagtail import blocks
-from wagtail.models import Collection
 from wagtail.coreutils import resolve_model_string
 from wagtail.documents.blocks import DocumentChooserBlock
+from wagtail.models import Collection
 
 from coderedcms.settings import crx_settings
 

+ 4 - 6
coderedcms/blocks/content_blocks.py

@@ -9,12 +9,10 @@ from wagtail.documents.blocks import DocumentChooserBlock
 from wagtail.images.blocks import ImageChooserBlock
 from wagtail.snippets.blocks import SnippetChooserBlock
 
-from .base_blocks import (
-    BaseBlock,
-    BaseLayoutBlock,
-    ButtonMixin,
-    CollectionChooserBlock,
-)
+from .base_blocks import BaseBlock
+from .base_blocks import BaseLayoutBlock
+from .base_blocks import ButtonMixin
+from .base_blocks import CollectionChooserBlock
 from .html_blocks import ButtonBlock
 
 

+ 8 - 9
coderedcms/blocks/html_blocks.py

@@ -8,21 +8,20 @@ creating recursion.
 """
 
 import logging
+
 from django.utils.translation import gettext_lazy as _
-from wagtail.contrib.table_block.blocks import TableBlock as WagtailTableBlock
 from wagtail import blocks
+from wagtail.contrib.table_block.blocks import TableBlock as WagtailTableBlock
 from wagtail.documents.blocks import DocumentChooserBlock
 from wagtail.embeds.blocks import EmbedBlock
 from wagtail.images.blocks import ImageChooserBlock
 
-from .base_blocks import (
-    BaseBlock,
-    BaseLinkBlock,
-    ButtonMixin,
-    ClassifierTermChooserBlock,
-    CoderedAdvTrackingSettings,
-    LinkStructValue,
-)
+from .base_blocks import BaseBlock
+from .base_blocks import BaseLinkBlock
+from .base_blocks import ButtonMixin
+from .base_blocks import ClassifierTermChooserBlock
+from .base_blocks import CoderedAdvTrackingSettings
+from .base_blocks import LinkStructValue
 
 
 logger = logging.getLogger("coderedcms")

+ 2 - 1
coderedcms/blocks/layout_blocks.py

@@ -9,7 +9,8 @@ from wagtail.images.blocks import ImageChooserBlock
 
 from coderedcms.settings import crx_settings
 
-from .base_blocks import BaseLayoutBlock, CoderedAdvColumnSettings
+from .base_blocks import BaseLayoutBlock
+from .base_blocks import CoderedAdvColumnSettings
 
 
 # Level 1 layout blocks

+ 9 - 10
coderedcms/blocks/stream_form_blocks.py

@@ -1,17 +1,16 @@
 from django.utils.translation import gettext_lazy as _
 from wagtail import blocks
 
+from coderedcms.blocks.base_blocks import BaseBlock
+from coderedcms.blocks.base_blocks import CoderedAdvSettings
+from coderedcms.forms import CoderedDateField
+from coderedcms.forms import CoderedDateInput
+from coderedcms.forms import CoderedDateTimeField
+from coderedcms.forms import CoderedDateTimeInput
+from coderedcms.forms import CoderedTimeField
+from coderedcms.forms import CoderedTimeInput
+from coderedcms.forms import SecureFileField
 from coderedcms.wagtail_flexible_forms import blocks as form_blocks
-from coderedcms.blocks.base_blocks import BaseBlock, CoderedAdvSettings
-from coderedcms.forms import (
-    CoderedDateField,
-    CoderedDateInput,
-    CoderedDateTimeField,
-    CoderedDateTimeInput,
-    CoderedTimeField,
-    CoderedTimeInput,
-    SecureFileField,
-)
 
 
 class CoderedFormAdvSettings(CoderedAdvSettings):

+ 4 - 2
coderedcms/forms.py

@@ -4,21 +4,23 @@ Enhancements to wagtail.contrib.forms.
 
 import csv
 import os
+
 from django import forms
 from django.contrib.contenttypes.models import ContentType
 from django.core.exceptions import ValidationError
 from django.db import models
 from django.http import HttpResponse
 from django.utils.translation import gettext_lazy as _
+from wagtail.contrib.forms.forms import FormBuilder
+from wagtail.contrib.forms.models import AbstractFormField
 from wagtail.contrib.forms.views import (
     SubmissionsListView as WagtailSubmissionsListView,
 )
-from wagtail.contrib.forms.forms import FormBuilder
-from wagtail.contrib.forms.models import AbstractFormField
 
 from coderedcms.settings import crx_settings
 from coderedcms.utils import attempt_protected_media_value_conversion
 
+
 FORM_FIELD_CHOICES = (
     (
         _("Text"),

+ 3 - 2
coderedcms/importexport.py

@@ -8,13 +8,14 @@ or simply deprecate all of this functionality.
 See: https://github.com/torchbox/wagtail-import-export/
 """
 
-import csv
 import copy
+import csv
 
 from django import forms
 from django.apps import apps
 from django.contrib.contenttypes.models import ContentType
-from django.db import models, transaction
+from django.db import models
+from django.db import transaction
 from django.utils.translation import gettext as _
 from modelcluster.models import get_all_child_relations
 from wagtail.admin.widgets import AdminPageChooser

+ 11 - 11
coderedcms/models/integration_models.py

@@ -1,16 +1,16 @@
+import json
+
 from django.db import models
 from django.forms.widgets import Input
-from django.template import Context, Template
+from django.template import Context
+from django.template import Template
 from django.template.loader import render_to_string
 from django.utils.translation import gettext_lazy as _
-
-from wagtail.admin.panels import FieldPanel
 from wagtail import hooks
+from wagtail.admin.panels import FieldPanel
 
 from coderedcms.api.mailchimp import MailchimpApi
 
-import json
-
 
 class MailchimpSubscriberIntegrationWidget(Input):
     template_name = (
@@ -95,14 +95,14 @@ class MailchimpSubscriberIntegrationWidget(Input):
                 }
 
                 list_library[mlist["id"]]["merge_fields"] = (
-                    mailchimp.get_merge_fields_for_list(mlist["id"])[
-                        "merge_fields"
-                    ]
+                    mailchimp.get_merge_fields_for_list(
+                        mlist["id"]
+                    )["merge_fields"]
                 )
                 list_library[mlist["id"]]["interest_categories"] = (
-                    mailchimp.get_interest_categories_for_list(mlist["id"])[
-                        "categories"
-                    ]
+                    mailchimp.get_interest_categories_for_list(
+                        mlist["id"]
+                    )["categories"]
                 )
 
                 for category in list_library[mlist["id"]][

+ 68 - 60
coderedcms/models/page_models.py

@@ -6,94 +6,102 @@ import json
 import logging
 import os
 import warnings
-from datetime import date, datetime
-from typing import Dict, List, Optional, TYPE_CHECKING, Union, Tuple
+from datetime import date
+from datetime import datetime
+from pathlib import Path
+from typing import TYPE_CHECKING
+from typing import Dict
+from typing import List
+from typing import Optional
+from typing import Tuple
+from typing import Union
+
+import geocoder
 
 # This is a requirement for icalendar, even if django doesn't require it
 import pytz
-
-import geocoder
 from django import forms
 from django.conf import settings
 from django.contrib import messages
-from django.core.files.uploadedfile import (
-    InMemoryUploadedFile,
-    TemporaryUploadedFile,
-)
 from django.core.files.storage import FileSystemStorage
+from django.core.files.uploadedfile import InMemoryUploadedFile
+from django.core.files.uploadedfile import TemporaryUploadedFile
 from django.core.mail import EmailMessage
-from django.core.paginator import (
-    Paginator,
-    InvalidPage,
-    EmptyPage,
-    PageNotAnInteger,
-)
+from django.core.paginator import EmptyPage
+from django.core.paginator import InvalidPage
+from django.core.paginator import PageNotAnInteger
+from django.core.paginator import Paginator
 from django.core.serializers.json import DjangoJSONEncoder
-from django.core.validators import MaxValueValidator, MinValueValidator
+from django.core.validators import MaxValueValidator
+from django.core.validators import MinValueValidator
 from django.db import models
-from django.db.models.signals import post_delete, post_save
+from django.db.models.signals import post_delete
+from django.db.models.signals import post_save
 from django.dispatch import receiver
-from django.http import JsonResponse, HttpResponseRedirect
-from django.shortcuts import render, redirect
-from django.template import Context, Template
+from django.http import HttpResponseRedirect
+from django.http import JsonResponse
+from django.shortcuts import redirect
+from django.shortcuts import render
+from django.template import Context
+from django.template import Template
 from django.template.loader import render_to_string
 from django.utils import timezone
 from django.utils.html import strip_tags
 from django.utils.safestring import mark_safe
 from django.utils.translation import gettext_lazy as _
-from eventtools.models import BaseEvent, BaseOccurrence
+from eventtools.models import BaseEvent
+from eventtools.models import BaseOccurrence
 from icalendar import Alarm
 from icalendar import Event as ICalEvent
-from modelcluster.fields import ParentalKey, ParentalManyToManyField
 from modelcluster.contrib.taggit import ClusterTaggableManager
-from pathlib import Path
+from modelcluster.fields import ParentalKey
+from modelcluster.fields import ParentalManyToManyField
 from taggit.models import TaggedItemBase
-from wagtail.admin.panels import (
-    FieldPanel,
-    FieldRowPanel,
-    InlinePanel,
-    MultiFieldPanel,
-    ObjectList,
-    TabbedInterface,
-)
 from wagtail import hooks
-from wagtail.fields import StreamField
-from wagtail.models import Orderable, PageBase, Page, Site
-from wagtail.coreutils import resolve_model_string
-from wagtail.contrib.forms.panels import FormSubmissionsPanel
+from wagtail.admin.panels import FieldPanel
+from wagtail.admin.panels import FieldRowPanel
+from wagtail.admin.panels import InlinePanel
+from wagtail.admin.panels import MultiFieldPanel
+from wagtail.admin.panels import ObjectList
+from wagtail.admin.panels import TabbedInterface
 from wagtail.contrib.forms.forms import WagtailAdminFormPageForm
-from wagtail.images import get_image_model_string
 from wagtail.contrib.forms.models import FormSubmission
+from wagtail.contrib.forms.panels import FormSubmissionsPanel
+from wagtail.coreutils import resolve_model_string
+from wagtail.fields import StreamField
+from wagtail.images import get_image_model_string
+from wagtail.models import Orderable
+from wagtail.models import Page
+from wagtail.models import PageBase
+from wagtail.models import Site
 from wagtail.search import index
 from wagtail.utils.decorators import cached_classmethod
 from wagtailcache.cache import WagtailCacheMixin
-from wagtailseo.models import SeoMixin, TwitterCard
-from wagtailseo.utils import get_struct_data_images, StructDataEncoder
+from wagtailseo.models import SeoMixin
+from wagtailseo.models import TwitterCard
+from wagtailseo.utils import StructDataEncoder
+from wagtailseo.utils import get_struct_data_images
 
 from coderedcms import utils
-from coderedcms.blocks import (
-    CONTENT_STREAMBLOCKS,
-    LAYOUT_STREAMBLOCKS,
-    STREAMFORM_BLOCKS,
-    ContentWallBlock,
-)
-from coderedcms.fields import CoderedStreamField, ColorField
-from coderedcms.forms import CoderedFormBuilder, CoderedSubmissionsListView
+from coderedcms.blocks import CONTENT_STREAMBLOCKS
+from coderedcms.blocks import LAYOUT_STREAMBLOCKS
+from coderedcms.blocks import STREAMFORM_BLOCKS
+from coderedcms.blocks import ContentWallBlock
+from coderedcms.fields import CoderedStreamField
+from coderedcms.fields import ColorField
+from coderedcms.forms import CoderedFormBuilder
+from coderedcms.forms import CoderedSubmissionsListView
 from coderedcms.models.snippet_models import ClassifierTerm
 from coderedcms.models.wagtailsettings_models import LayoutSettings
-from coderedcms.wagtail_flexible_forms.blocks import (
-    FormFieldBlock,
-    FormStepBlock,
-)
-from coderedcms.wagtail_flexible_forms.models import (
-    Step,
-    Steps,
-    StreamFormMixin,
-    StreamFormJSONEncoder,
-    SessionFormSubmission,
-    SubmissionRevision,
-)
 from coderedcms.settings import crx_settings
+from coderedcms.wagtail_flexible_forms.blocks import FormFieldBlock
+from coderedcms.wagtail_flexible_forms.blocks import FormStepBlock
+from coderedcms.wagtail_flexible_forms.models import SessionFormSubmission
+from coderedcms.wagtail_flexible_forms.models import Step
+from coderedcms.wagtail_flexible_forms.models import Steps
+from coderedcms.wagtail_flexible_forms.models import StreamFormJSONEncoder
+from coderedcms.wagtail_flexible_forms.models import StreamFormMixin
+from coderedcms.wagtail_flexible_forms.models import SubmissionRevision
 from coderedcms.widgets import ClassifierSelectWidget
 
 
@@ -381,9 +389,9 @@ class CoderedPage(WagtailCacheMixin, SeoMixin, Page, metaclass=CoderedPageMeta):
             "*", []
         ) + crx_settings.CRX_FRONTEND_TEMPLATES_PAGES.get(klassname, [])
 
-        self._meta.get_field("index_order_by").choices = (
-            self.index_order_by_choices
-        )
+        self._meta.get_field(
+            "index_order_by"
+        ).choices = self.index_order_by_choices
         self._meta.get_field("custom_template").choices = template_choices
         if not self.id:
             self.index_order_by = self.index_order_by_default

+ 8 - 11
coderedcms/models/snippet_models.py

@@ -7,19 +7,16 @@ from django.utils.text import slugify
 from django.utils.translation import gettext_lazy as _
 from modelcluster.fields import ParentalKey
 from modelcluster.models import ClusterableModel
-from wagtail.admin.panels import (
-    FieldPanel,
-    InlinePanel,
-    MultiFieldPanel,
-)
+from wagtail.admin.panels import FieldPanel
+from wagtail.admin.panels import InlinePanel
+from wagtail.admin.panels import MultiFieldPanel
+from wagtail.images import get_image_model_string
 from wagtail.models import Orderable
 from wagtail.snippets.models import register_snippet
-from wagtail.images import get_image_model_string
-from coderedcms.blocks import (
-    HTML_STREAMBLOCKS,
-    LAYOUT_STREAMBLOCKS,
-    NAVIGATION_STREAMBLOCKS,
-)
+
+from coderedcms.blocks import HTML_STREAMBLOCKS
+from coderedcms.blocks import LAYOUT_STREAMBLOCKS
+from coderedcms.blocks import NAVIGATION_STREAMBLOCKS
 from coderedcms.fields import CoderedStreamField
 from coderedcms.settings import crx_settings
 

+ 6 - 7
coderedcms/models/tests/test_navbars_and_footers.py

@@ -1,14 +1,13 @@
 from django.test import Client
-from wagtail.test.utils import WagtailPageTests
 from wagtail.models import Site
+from wagtail.test.utils import WagtailPageTests
 
+from coderedcms.models.snippet_models import Footer
+from coderedcms.models.snippet_models import Navbar
+from coderedcms.models.wagtailsettings_models import FooterOrderable
+from coderedcms.models.wagtailsettings_models import LayoutSettings
+from coderedcms.models.wagtailsettings_models import NavbarOrderable
 from coderedcms.tests.testapp.models import WebPage
-from coderedcms.models.snippet_models import Footer, Navbar
-from coderedcms.models.wagtailsettings_models import (
-    LayoutSettings,
-    NavbarOrderable,
-    FooterOrderable,
-)
 
 
 class NavbarFooterTestCase(WagtailPageTests):

+ 25 - 27
coderedcms/models/tests/test_page_models.py

@@ -1,35 +1,33 @@
 from datetime import timedelta
+
 from django.test import Client
 from django.utils import timezone
 from wagtail.test.utils import WagtailPageTests
 
-from coderedcms.models.page_models import (
-    CoderedArticleIndexPage,
-    CoderedArticlePage,
-    CoderedEventIndexPage,
-    CoderedEventPage,
-    CoderedFormPage,
-    CoderedLocationIndexPage,
-    CoderedLocationPage,
-    CoderedPage,
-    CoderedStreamFormPage,
-    CoderedWebPage,
-    get_page_models,
-)
-from coderedcms.models.snippet_models import Classifier, ClassifierTerm
-from coderedcms.tests.testapp.models import (
-    ArticleIndexPage,
-    ArticlePage,
-    EventIndexPage,
-    EventPage,
-    EventOccurrence,
-    FormPage,
-    IndexTestPage,
-    LocationIndexPage,
-    LocationPage,
-    StreamFormPage,
-    WebPage,
-)
+from coderedcms.models.page_models import CoderedArticleIndexPage
+from coderedcms.models.page_models import CoderedArticlePage
+from coderedcms.models.page_models import CoderedEventIndexPage
+from coderedcms.models.page_models import CoderedEventPage
+from coderedcms.models.page_models import CoderedFormPage
+from coderedcms.models.page_models import CoderedLocationIndexPage
+from coderedcms.models.page_models import CoderedLocationPage
+from coderedcms.models.page_models import CoderedPage
+from coderedcms.models.page_models import CoderedStreamFormPage
+from coderedcms.models.page_models import CoderedWebPage
+from coderedcms.models.page_models import get_page_models
+from coderedcms.models.snippet_models import Classifier
+from coderedcms.models.snippet_models import ClassifierTerm
+from coderedcms.tests.testapp.models import ArticleIndexPage
+from coderedcms.tests.testapp.models import ArticlePage
+from coderedcms.tests.testapp.models import EventIndexPage
+from coderedcms.tests.testapp.models import EventOccurrence
+from coderedcms.tests.testapp.models import EventPage
+from coderedcms.tests.testapp.models import FormPage
+from coderedcms.tests.testapp.models import IndexTestPage
+from coderedcms.tests.testapp.models import LocationIndexPage
+from coderedcms.tests.testapp.models import LocationPage
+from coderedcms.tests.testapp.models import StreamFormPage
+from coderedcms.tests.testapp.models import WebPage
 
 
 class BasicPageTestCase:

+ 5 - 6
coderedcms/models/tests/test_wagtailsettings_models.py

@@ -1,14 +1,13 @@
 from unittest.mock import patch
 
-from django.test import Client, override_settings
-from wagtail.test.utils import WagtailPageTests
+from django.test import Client
+from django.test import override_settings
 from wagtail.models import Site
+from wagtail.test.utils import WagtailPageTests
 
+from coderedcms.models.wagtailsettings_models import AnalyticsSettings
+from coderedcms.models.wagtailsettings_models import maybe_register_setting
 from coderedcms.tests.testapp.models import WebPage
-from coderedcms.models.wagtailsettings_models import (
-    AnalyticsSettings,
-    maybe_register_setting,
-)
 
 
 class AnalyticsSettingsTestCase(WagtailPageTests):

+ 19 - 18
coderedcms/models/wagtailsettings_models.py

@@ -8,18 +8,19 @@ from django.db import models
 from django.utils.translation import gettext_lazy as _
 from modelcluster.fields import ParentalKey
 from modelcluster.models import ClusterableModel
-from wagtail.admin.panels import (
-    FieldPanel,
-    InlinePanel,
-    HelpPanel,
-    MultiFieldPanel,
-)
-from wagtail.models import Orderable
-from wagtail.contrib.settings.models import BaseSiteSetting, register_setting
+from wagtail.admin.panels import FieldPanel
+from wagtail.admin.panels import HelpPanel
+from wagtail.admin.panels import InlinePanel
+from wagtail.admin.panels import MultiFieldPanel
+from wagtail.contrib.settings.models import BaseSiteSetting
+from wagtail.contrib.settings.models import register_setting
 from wagtail.images import get_image_model_string
+from wagtail.models import Orderable
+
 from coderedcms.fields import MonospaceField
+from coderedcms.models.snippet_models import Footer
+from coderedcms.models.snippet_models import Navbar
 from coderedcms.settings import crx_settings
-from coderedcms.models.snippet_models import Navbar, Footer
 
 
 def maybe_register_setting(disable: bool, **kwargs):
@@ -208,15 +209,15 @@ class LayoutSettings(ClusterableModel, BaseSiteSetting):
         """
         super().__init__(*args, **kwargs)
         # Set choices dynamically.
-        self._meta.get_field("navbar_collapse_mode").choices = (
-            crx_settings.CRX_FRONTEND_NAVBAR_COLLAPSE_MODE_CHOICES
-        )
-        self._meta.get_field("navbar_color_scheme").choices = (
-            crx_settings.CRX_FRONTEND_NAVBAR_COLOR_SCHEME_CHOICES
-        )
-        self._meta.get_field("navbar_format").choices = (
-            crx_settings.CRX_FRONTEND_NAVBAR_FORMAT_CHOICES
-        )
+        self._meta.get_field(
+            "navbar_collapse_mode"
+        ).choices = crx_settings.CRX_FRONTEND_NAVBAR_COLLAPSE_MODE_CHOICES
+        self._meta.get_field(
+            "navbar_color_scheme"
+        ).choices = crx_settings.CRX_FRONTEND_NAVBAR_COLOR_SCHEME_CHOICES
+        self._meta.get_field(
+            "navbar_format"
+        ).choices = crx_settings.CRX_FRONTEND_NAVBAR_FORMAT_CHOICES
         # Set default dynamically.
         if not self.id:
             self.navbar_class = crx_settings.CRX_FRONTEND_NAVBAR_CLASS_DEFAULT

+ 2 - 0
coderedcms/search_urls.py

@@ -1,6 +1,8 @@
 from django.urls import path
+
 from coderedcms.views import search
 
+
 urlpatterns = [
     path("", search, name="crx_search"),
 ]

+ 1 - 0
coderedcms/settings.py

@@ -1,4 +1,5 @@
 import os
+
 from django.apps import apps
 from django.conf import settings
 

+ 8 - 5
coderedcms/templatetags/coderedcms_tags.py

@@ -1,21 +1,24 @@
-import string
 import random
+import string
 
 from bs4 import BeautifulSoup
 from django import template
 from django.db.models.query import QuerySet
 from django.forms import ClearableFileInput
 from django.utils.html import mark_safe
-from wagtail.models import Collection
 from wagtail.images import get_image_model
+from wagtail.models import Collection
 
-from coderedcms import utils, __version__
+from coderedcms import __version__
+from coderedcms import utils
 from coderedcms.blocks import CoderedAdvSettings
 from coderedcms.forms import SearchForm
-from coderedcms.models.snippet_models import Navbar, Footer
+from coderedcms.models.snippet_models import Footer
+from coderedcms.models.snippet_models import Navbar
+from coderedcms.models.wagtailsettings_models import LayoutSettings
 from coderedcms.settings import crx_settings as crx_settings_obj
 from coderedcms.settings import get_bootstrap_setting
-from coderedcms.models.wagtailsettings_models import LayoutSettings
+
 
 register = template.Library()
 

+ 1 - 0
coderedcms/tests/test_bin.py

@@ -2,6 +2,7 @@ import os
 import shutil
 import sys
 import unittest
+
 from coderedcms.bin.coderedcms import main as coderedcms_main
 
 

+ 2 - 1
coderedcms/tests/test_templates.py

@@ -1,6 +1,7 @@
 import pytest
 from django.contrib.auth import get_user_model
-from django.test import override_settings, TestCase
+from django.test import TestCase
+from django.test import override_settings
 
 
 EXPECTED_BANNER_HTML = """

+ 9 - 11
coderedcms/tests/test_urls.py

@@ -1,23 +1,21 @@
-import pytest
 import unittest
-
 from ast import literal_eval
 from datetime import timedelta
 
-from django.urls import reverse
+import pytest
 from django.test import Client
 from django.test.utils import override_settings
+from django.urls import reverse
 from django.utils import timezone
-
-from wagtail.models import Site, Page
-from wagtail.images.tests.utils import Image, get_test_image_file
+from wagtail.images.tests.utils import Image
+from wagtail.images.tests.utils import get_test_image_file
+from wagtail.models import Page
+from wagtail.models import Site
 
 from coderedcms.models import LayoutSettings
-from coderedcms.tests.testapp.models import (
-    EventPage,
-    EventIndexPage,
-    EventOccurrence,
-)
+from coderedcms.tests.testapp.models import EventIndexPage
+from coderedcms.tests.testapp.models import EventOccurrence
+from coderedcms.tests.testapp.models import EventPage
 
 
 @pytest.mark.django_db

+ 13 - 14
coderedcms/tests/testapp/models.py

@@ -1,19 +1,18 @@
-from coderedcms.models.page_models import CoderedPage
 from modelcluster.fields import ParentalKey
+
 from coderedcms.forms import CoderedFormField
-from coderedcms.models import (
-    CoderedArticlePage,
-    CoderedArticleIndexPage,
-    CoderedEventIndexPage,
-    CoderedEventPage,
-    CoderedEventOccurrence,
-    CoderedEmail,
-    CoderedFormPage,
-    CoderedLocationIndexPage,
-    CoderedLocationPage,
-    CoderedStreamFormPage,
-    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 CoderedStreamFormPage
+from coderedcms.models import CoderedWebPage
+from coderedcms.models.page_models import CoderedPage
 
 
 class ArticlePage(CoderedArticlePage):

+ 5 - 1
coderedcms/tests/urls.py

@@ -1,10 +1,14 @@
-from django.urls import include, path, re_path
 from django.contrib import admin
+from django.urls import include
+from django.urls import path
+from django.urls import re_path
 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
 
+
 urlpatterns = [
     path("django-admin/", admin.site.urls),
     path("admin/", include(crx_admin_urls)),

+ 12 - 11
coderedcms/urls.py

@@ -1,16 +1,17 @@
-from django.urls import include, path, re_path
-from wagtail.contrib.sitemaps.views import sitemap
+from django.urls import include
+from django.urls import path
+from django.urls import re_path
 from wagtail import urls as wagtailcore_urls
+from wagtail.contrib.sitemaps.views import sitemap
+
 from coderedcms.settings import crx_settings
-from coderedcms.views import (
-    event_generate_ical_for_calendar,
-    event_generate_recurring_ical_for_event,
-    event_generate_single_ical_for_event,
-    event_get_calendar_events,
-    favicon,
-    robots,
-    serve_protected_file,
-)
+from coderedcms.views import event_generate_ical_for_calendar
+from coderedcms.views import event_generate_recurring_ical_for_event
+from coderedcms.views import event_generate_single_ical_for_event
+from coderedcms.views import event_get_calendar_events
+from coderedcms.views import favicon
+from coderedcms.views import robots
+from coderedcms.views import serve_protected_file
 
 
 urlpatterns = [

+ 1 - 1
coderedcms/utils.py

@@ -1,5 +1,5 @@
-from django.core.validators import URLValidator
 from django.core.exceptions import ValidationError
+from django.core.validators import URLValidator
 from django.utils.html import mark_safe
 
 from coderedcms.settings import crx_settings

+ 23 - 22
coderedcms/views.py

@@ -1,37 +1,38 @@
 import mimetypes
 import os
 from datetime import datetime
-from django.http import (
-    Http404,
-    HttpResponse,
-    HttpResponsePermanentRedirect,
-    JsonResponse,
-)
-from django.contrib.auth.decorators import login_required, permission_required
+
+from django.contrib.auth.decorators import login_required
+from django.contrib.auth.decorators import permission_required
 from django.contrib.contenttypes.models import ContentType
-from django.core.paginator import (
-    Paginator,
-    InvalidPage,
-    EmptyPage,
-    PageNotAnInteger,
-)
-from django.shortcuts import redirect, render
+from django.core.paginator import EmptyPage
+from django.core.paginator import InvalidPage
+from django.core.paginator import PageNotAnInteger
+from django.core.paginator import Paginator
+from django.http import Http404
+from django.http import HttpResponse
+from django.http import HttpResponsePermanentRedirect
+from django.http import JsonResponse
+from django.shortcuts import redirect
+from django.shortcuts import render
 from django.utils import timezone
-from django.utils.translation import ngettext, gettext_lazy as _
+from django.utils.translation import gettext_lazy as _
+from django.utils.translation import ngettext
 from django.views.decorators.http import require_POST
 from icalendar import Calendar
 from wagtail.admin import messages
-from wagtail.models import Page, get_page_models
+from wagtail.models import Page
+from wagtail.models import get_page_models
 from wagtail.search.backends import get_search_backend
 from wagtail.search.backends.database.mysql.mysql import MySQLSearchBackend
+
 from coderedcms import utils
 from coderedcms.forms import SearchForm
-from coderedcms.models import CoderedPage, LayoutSettings
-from coderedcms.importexport import (
-    convert_csv_to_json,
-    import_pages,
-    ImportPagesFromCSVFileForm,
-)
+from coderedcms.importexport import ImportPagesFromCSVFileForm
+from coderedcms.importexport import convert_csv_to_json
+from coderedcms.importexport import import_pages
+from coderedcms.models import CoderedPage
+from coderedcms.models import LayoutSettings
 from coderedcms.settings import crx_settings
 from coderedcms.templatetags.coderedcms_tags import get_name_of_class
 

+ 12 - 14
coderedcms/wagtail_flexible_forms/blocks.py

@@ -1,22 +1,20 @@
+from anyascii import anyascii
 from django import forms
 from django.db.models import BLANK_CHOICE_DASH
 from django.utils.dateparse import parse_datetime
 from django.utils.text import slugify
 from django.utils.translation import gettext_lazy as _
-from anyascii import anyascii
-from wagtail.blocks import (
-    StructBlock,
-    TextBlock,
-    CharBlock,
-    BooleanBlock,
-    ListBlock,
-    StreamBlock,
-    DateBlock,
-    TimeBlock,
-    DateTimeBlock,
-    ChoiceBlock,
-    RichTextBlock,
-)
+from wagtail.blocks import BooleanBlock
+from wagtail.blocks import CharBlock
+from wagtail.blocks import ChoiceBlock
+from wagtail.blocks import DateBlock
+from wagtail.blocks import DateTimeBlock
+from wagtail.blocks import ListBlock
+from wagtail.blocks import RichTextBlock
+from wagtail.blocks import StreamBlock
+from wagtail.blocks import StructBlock
+from wagtail.blocks import TextBlock
+from wagtail.blocks import TimeBlock
 
 
 class FormFieldBlock(StructBlock):

+ 0 - 1
coderedcms/wagtail_flexible_forms/edit_handlers.py

@@ -1,7 +1,6 @@
 from django.template.loader import render_to_string
 from django.utils.safestring import mark_safe
 from django.utils.translation import gettext as _
-
 from wagtail.admin.panels import EditHandler
 
 

+ 31 - 26
coderedcms/wagtail_flexible_forms/models.py

@@ -1,12 +1,11 @@
+import datetime
+import json
+import os
 from collections import OrderedDict
 from importlib import import_module
 from itertools import zip_longest
-import json
-import os
 from pathlib import Path
 
-from PIL import Image
-import datetime
 from django.apps import apps
 from django.conf import settings
 from django.contrib import messages
@@ -14,32 +13,35 @@ from django.contrib.contenttypes.fields import GenericForeignKey
 from django.contrib.contenttypes.models import ContentType
 from django.core.files.storage import default_storage
 from django.core.serializers.json import DjangoJSONEncoder
-from django.db.models import (
-    CharField,
-    TextField,
-    DateTimeField,
-    Model,
-    ForeignKey,
-    PROTECT,
-    CASCADE,
-    QuerySet,
-)
+from django.db.models import CASCADE
+from django.db.models import PROTECT
+from django.db.models import CharField
+from django.db.models import DateTimeField
+from django.db.models import ForeignKey
+from django.db.models import Model
+from django.db.models import QuerySet
+from django.db.models import TextField
 from django.db.models.fields.files import FieldFile
 from django.db.models.signals import post_delete
 from django.dispatch import receiver
-from django.forms import Form, ImageField, FileField, URLField, EmailField
+from django.forms import EmailField
+from django.forms import FileField
+from django.forms import Form
+from django.forms import ImageField
+from django.forms import URLField
 from django.http import HttpResponseRedirect
 from django.template.response import TemplateResponse
-from django.utils.safestring import SafeData, mark_safe
+from django.utils.safestring import SafeData
+from django.utils.safestring import mark_safe
 from django.utils.timezone import now
 from django.utils.translation import gettext_lazy as _
-from wagtail.contrib.forms.models import (
-    AbstractForm,
-    AbstractEmailForm,
-    AbstractFormSubmission,
-)
+from PIL import Image
+from wagtail.contrib.forms.models import AbstractEmailForm
+from wagtail.contrib.forms.models import AbstractForm
+from wagtail.contrib.forms.models import AbstractFormSubmission
 
-from .blocks import FormStepBlock, FormFieldBlock
+from .blocks import FormFieldBlock
+from .blocks import FormStepBlock
 
 
 class Step:
@@ -513,10 +515,13 @@ class SessionFormSubmission(AbstractFormSubmission):
             self.form_page.get_data_fields(by_step=True),
             self.get_steps_data(raw=raw),
         ):
-            yield step, [
-                (field_name, field_label, step_data[field_name])
-                for field_name, field_label in step_data_fields
-            ]
+            yield (
+                step,
+                [
+                    (field_name, field_label, step_data[field_name])
+                    for field_name, field_label in step_data_fields
+                ],
+            )
 
 
 @receiver(post_delete, sender=SessionFormSubmission)

+ 4 - 3
coderedcms/wagtail_hooks.py

@@ -1,13 +1,13 @@
 import mimetypes
 
 from django.contrib.contenttypes.models import ContentType
-from django.templatetags.static import static
 from django.http.response import HttpResponse
+from django.templatetags.static import static
 from django.urls import reverse
 from django.utils.html import format_html
 from django.utils.translation import gettext_lazy as _
-from wagtail.admin.menu import MenuItem
 from wagtail import hooks
+from wagtail.admin.menu import MenuItem
 from wagtail.models import get_page_models
 from wagtail.permission_policies.pages import PagePermissionPolicy
 from wagtailcache.cache import clear_cache
@@ -87,9 +87,10 @@ def crx_forms(user, editable_forms):
     of its existence. Essentially this is a fork of wagtail.contrib.forms.get_forms_for_user()
     and wagtail.contrib.forms.get_form_types()
     """
-    from coderedcms.models import CoderedFormMixin
     from wagtail.contrib.forms.models import FormMixin
 
+    from coderedcms.models import CoderedFormMixin
+
     form_models = [
         model
         for model in get_page_models()

+ 4 - 5
docs/contributing/index.rst

@@ -206,18 +206,17 @@ For example, here is how you would add tests for a new abstract page type,
 Static Analysis
 ---------------
 
-All code should be formatted with ``black`` before committing:
+All code should be formatted with ``ruff`` before committing:
 
 .. code-block:: console
 
-    $ black .
+    $ ruff format .
 
-Flake8 is used to check for syntax and style errors. To analyze the entire
-codebase, run:
+Ruff is also used to check for syntax and style errors. To analyze the entire codebase, run:
 
 .. code-block:: console
 
-    $ flake8 .
+    $ ruff check --fix .
 
 
 Contributor Guidelines

+ 6 - 0
docs/releases/v4.0.0.rst

@@ -38,6 +38,12 @@ Bug fixes
 * Fix template error (Server 500 error) when a page is deleted which is referenced in a Page Preview block.
 
 
+Maintenance
+-----------
+
+* Ruff is now used for formatting and linting in place of black and flake8.
+
+
 Upgrade considerations
 ----------------------
 

+ 37 - 1
pyproject.toml

@@ -1,6 +1,5 @@
 [tool.black]
 line-length = 80
-target-version = ['py37', 'py38', 'py39', 'py310']
 exclude = '''
 (
   /(
@@ -21,3 +20,40 @@ exclude = '''
   )/
 )
 '''
+
+[tool.codespell]
+skip = 'migrations,vendor,_build,*.css.map,*.jpg,*.png,*.pyc'
+ignore-words-list = 'assertIn'
+
+[tool.django-stubs]
+django_settings_module = "coderedcms.tests.settings"
+
+[tool.mypy]
+ignore_missing_imports = true
+plugins = ["mypy_django_plugin.main"]
+exclude = [
+    '^\..*',
+    'migrations',
+    'node_modules',
+    'venv',
+    'testproject',
+]
+
+[tool.pytest.ini_options]
+DJANGO_SETTINGS_MODULE = "coderedcms.tests.settings"
+addopts = "--cov --cov-report html --cov-report xml --junitxml junit/test-results.xml"
+python_files = "tests.py test_*.py"
+junit_family = "xunit2"
+junit_suite_name = "coderedcms"
+
+[tool.ruff]
+extend-exclude = ["migrations", "testproject"]
+line-length = 80
+
+[tool.ruff.lint]
+extend-select = ["I"]
+
+[tool.ruff.lint.isort]
+case-sensitive = false
+force-single-line = true
+lines-after-imports = 2

+ 2 - 2
requirements-ci.txt

@@ -2,10 +2,10 @@
 -e .
 
 # Requirements, in addition to coderedcms, needed for CI runner.
-black==24.2.0
 codespell
-flake8
+pytest
 pytest-cov
 pytest-django
+ruff
 sphinx
 sphinx-wagtail-theme

+ 0 - 10
setup.cfg

@@ -1,10 +0,0 @@
-[flake8]
-exclude = .venv/*,build/*,*/migrations/*,schema.py
-max-line-length = 100
-
-[tool:pytest]
-DJANGO_SETTINGS_MODULE = coderedcms.tests.settings
-addopts = --cov coderedcms --cov-report html --cov-report xml --junitxml junit/test-results.xml
-python_files = tests.py test_*.py
-junit_family = xunit2
-junit_suite_name = coderedcms

+ 3 - 0
setup.py

@@ -1,7 +1,10 @@
 import os
+
 from setuptools import setup
+
 from coderedcms import __version__
 
+
 with open(
     os.path.join(os.path.dirname(__file__), "README.md"), encoding="utf8"
 ) as readme: