Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Django Migration Linter Integration #43

Merged
merged 59 commits into from
Jan 14, 2025
Merged
Show file tree
Hide file tree
Changes from 45 commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
653bdd9
Added django-migration-linter to project dependencies and updated Mak…
nehakerung Nov 22, 2024
5b72cdf
Poetry.lock fixes
nehakerung Nov 25, 2024
47d73e6
Excluded apps added for linter
nehakerung Nov 26, 2024
3deb099
CI pipeline added
nehakerung Nov 26, 2024
ece5e3c
Merge branch 'main' into feature/django-migration-linter-CMS-201
nehakerung Nov 26, 2024
f068fa7
Recent changes
nehakerung Nov 26, 2024
f9260e0
CI file updated for the lint migration job.
sanjeevz3009 Nov 26, 2024
d83786b
Updated per review suggestions
nehakerung Nov 27, 2024
b37c7b7
Pipeline test
nehakerung Nov 28, 2024
c0bfbc7
Custom django lint migrations script added for cleaner output
sanjeevz3009 Nov 29, 2024
ce6f2af
Poetry lock file updated
sanjeevz3009 Nov 29, 2024
6a0eb6b
Merge branch 'main' into feature/django-migration-linter-CMS-201
sanjeevz3009 Nov 29, 2024
eaabfc6
Lock file updated
sanjeevz3009 Nov 29, 2024
06f6b12
Fix lint-py errors
nehakerung Nov 29, 2024
d9c256c
Update README.md
nehakerung Nov 29, 2024
b451c99
Fixed linting issues
sanjeevz3009 Dec 3, 2024
a47584f
Merge branch 'main' into feature/django-migration-linter-CMS-201
sanjeevz3009 Dec 3, 2024
2284554
Lock file updated
sanjeevz3009 Dec 3, 2024
89fd58f
Outer scope variable redefining lint issue fixed
sanjeevz3009 Dec 3, 2024
7d75d1e
Removing lintmigration step
sanjeevz3009 Dec 3, 2024
a5aef7a
Django migration linter moved out of the dev dependency
sanjeevz3009 Dec 3, 2024
0efbb59
README updated and make file commands added for lintmigrations and li…
sanjeevz3009 Dec 3, 2024
de260bb
Migration linter step added back in
sanjeevz3009 Dec 3, 2024
ce493fa
Retry to execute make command for migration linter
sanjeevz3009 Dec 3, 2024
3ede596
Retry
sanjeevz3009 Dec 3, 2024
e772c21
Exit codes added to pass or fail the custom migration script run
sanjeevz3009 Dec 5, 2024
fc366b8
Merge branch 'main' into feature/django-migration-linter-CMS-201
nehakerung Dec 5, 2024
f190f51
Changes from reviews
nehakerung Dec 6, 2024
aa5ef55
Code formatted
sanjeevz3009 Dec 9, 2024
02f24f8
django migration linter removed from the dev dependencies
sanjeevz3009 Dec 9, 2024
62c348a
Added django-migration-linter to dev.py
sanjeevz3009 Dec 9, 2024
c5bc5e6
Migration linter being added only in dev
sanjeevz3009 Dec 9, 2024
ac5d2bb
Lint errors fixed
sanjeevz3009 Dec 9, 2024
ab9519e
CI updated
sanjeevz3009 Dec 9, 2024
3ac93a3
CI lint migration test
sanjeevz3009 Dec 9, 2024
29066b1
env changed
sanjeevz3009 Dec 9, 2024
5d7538b
Test command removed
sanjeevz3009 Dec 9, 2024
ebf0155
CI changes reverted and MIGRATION_LINTER_OPTIONS moved to dev.py
sanjeevz3009 Dec 9, 2024
541569e
Update ci.yml
nehakerung Dec 9, 2024
fb8bd73
Rename script
nehakerung Dec 10, 2024
9b8cd3b
Change lint-migrations command, delete script, fix documents error
nehakerung Dec 11, 2024
c1083d1
Document migration fixes and readme update
nehakerung Dec 11, 2024
80daaf9
Move CODEOWNER file location (#53)
MebinAbraham Dec 5, 2024
81727f2
UAT fixes (#56)
zerolab Dec 10, 2024
7085463
Documents migrations re-add
nehakerung Dec 11, 2024
bdd72af
Add noop for reverse migrations (create homepage/release calendar index)
zerolab Dec 13, 2024
739c51e
Merge main
nehakerung Dec 13, 2024
b22b9c0
ci updated to run the migration linter as part of the test python job
nehakerung Dec 13, 2024
dab2db0
Fix documents
nehakerung Dec 13, 2024
162ade0
merge main
nehakerung Dec 13, 2024
ce114c7
Migrations files ignored
sanjeevz3009 Dec 16, 2024
4ed61f8
Merge branch 'main' into feature/django-migration-linter-CMS-201
nehakerung Dec 16, 2024
57b18a6
Merge branch 'main' into feature/django-migration-linter-CMS-201
nehakerung Jan 13, 2025
19a848d
Merge branch 'main' into feature/django-migration-linter-CMS-201
nehakerung Jan 13, 2025
b7d79d4
Update poetry.lock
nehakerung Jan 13, 2025
f0b82ad
Merge branch 'main' into feature/django-migration-linter-CMS-201
nehakerung Jan 13, 2025
15750df
Delete .docker/bashrc.sh
nehakerung Jan 14, 2025
92d2350
Add .docker/bashrc.sh
nehakerung Jan 14, 2025
145c672
File permision change
nehakerung Jan 14, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
File renamed without changes.
45 changes: 45 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,51 @@ jobs:
- name: Test
run: make test

test-lint-migrations:
runs-on: ubuntu-latest
needs:
- lint

env:
DJANGO_SETTINGS_MODULE: cms.settings.dev
DATABASE_URL: postgres://postgres:postgres@localhost/postgres # pragma: allowlist secret
ENABLE_DJANGO_DEFENDER: 'false'
POSTGRES_HOST_AUTH_METHOD: trust
SECRET_KEY: fake_secret_key_to_run_tests # pragma: allowlist secret

services:
postgres:
image: postgres:16
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres # pragma: allowlist secret
POSTGRES_DB: postgres
ports:
- 5432:5432
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5

steps:
- uses: actions/checkout@v4
- name: Install Poetry
run: pipx install poetry==${{ env.POETRY_VERSION }}

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version-file: .python-version
cache: poetry

- name: Install dependencies
run: make install-dev

- uses: actions/download-artifact@v4
with:
name: static
path: cms/static_compiled/

- name: Lint Migrations
run: make lint-migrations

docker:
runs-on: ubuntu-latest
steps:
Expand Down
6 changes: 5 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ format-frontend: ## Format front-end files (CSS, JS, YAML, MD)
npm run format

.PHONY: lint
lint: lint-py lint-html lint-frontend ## Run all linters (python, html, front-end)
lint: lint-py lint-html lint-frontend lint-migrations ## Run all linters (python, html, front-end, migrations)

.PHONY: lint-py
lint-py: ## Run all Python linters (ruff/pylint/mypy).
Expand All @@ -50,6 +50,10 @@ lint-html: ## Run HTML Linters
lint-frontend: ## Run front-end linters
npm run lint

.PHONY: lint-migrations
lint-migrations: ## Run django-migration-linter
poetry run python manage.py lintmigrations --quiet ignore ok

.PHONY: test
test: ## Run the tests and check coverage.
poetry run coverage erase
Expand Down
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ Follow these steps to set up and run the project using Docker.

Once the containers are running, you need to manually start Django from within the web container. This allows for running both the Django server and any additional background services (e.g., schedulers).

> ⚠️ WARNING
> ⚠️ WARNING
> The `honcho` command will pick up your local mounted `.env` file when running via `docker-compose`. Ensure that you comment out any variables in the `.env` file which might cause clashes in the container context as they will take precedence when running `honcho start`.

```bash
Expand Down Expand Up @@ -244,6 +244,18 @@ To lint the Python code, run:
make lint
```

#### Django Migration Linter

[Django Migration Linter](https://github.com/3YOURMIND/django-migration-linter) for linting migrations files in the project.

To lint the django migration files:

```bash
make lint-migrations
```

#### Format

To auto-format the Python code, and correct fixable linting issues, run:

```bash
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Generated by Django 5.1.4 on 2024-12-10 11:08
import django
from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("core", "0003_delete_tracking"),
]

operations = [
migrations.AddConstraint(
model_name="contactdetails",
constraint=models.UniqueConstraint(
django.db.models.functions.text.Lower("name"),
django.db.models.functions.text.Lower("email"),
name="core_contactdetails_name_unique",
violation_error_message="Contact details with this name and email combination already exists.",
),
),
]
21 changes: 19 additions & 2 deletions cms/core/models/snippets.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from typing import ClassVar
from typing import Any, ClassVar

from django.db import models
from django.db.models.functions import Lower
from django.utils.translation import gettext_lazy as _
from wagtail.admin.panels import FieldPanel
from wagtail.search import index

Expand All @@ -26,11 +28,26 @@ class ContactDetails(index.Indexed, models.Model):
index.SearchField("name"),
index.AutocompleteField("name"),
index.SearchField("email"),
index.AutocompleteField("email"),
index.SearchField("phone"),
]

class Meta:
verbose_name_plural = "contact details"
verbose_name = _("contact details")
verbose_name_plural = _("contact details")
constraints: ClassVar[list[models.BaseConstraint]] = [
models.UniqueConstraint(
Lower("name"),
Lower("email"),
name="core_contactdetails_name_unique",
violation_error_message=_("Contact details with this name and email combination already exists."),
),
]

def save(self, *args: Any, **kwargs: Any) -> None:
if self.name:
self.name = self.name.strip()
super().save(*args, **kwargs)

def __str__(self) -> str:
return str(self.name)
43 changes: 38 additions & 5 deletions cms/core/tests/test_models.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,45 @@
from django.db import IntegrityError
from django.test import TestCase
from django.urls import reverse
from wagtail.test.utils.wagtail_tests import WagtailTestUtils

from cms.core.models import ContactDetails


class ContactDetailsTestCase(TestCase):
"""Tests for the ContactDetails model."""
class ContactDetailsTestCase(WagtailTestUtils, TestCase):
@classmethod
def setUpTestData(cls):
cls.contact = ContactDetails.objects.create(name="PSF", email="[email protected]")

def test_contactdetails__str(self):
"""Checks the contact details string representation."""
contact = ContactDetails(name="PSF", email="[email protected]")
self.assertEqual(str(contact), "PSF")
self.assertEqual(str(self.contact), "PSF")

def test_contactdetails_trims_trailing_whitespace_on_save(self):
details = ContactDetails(name=" Retail ", email="[email protected]")
details.save()

self.assertEqual(details.name, "Retail")

def test_contactdetails_uniqueness_validation__with_name_case_variation(self):
with self.assertRaisesMessage(IntegrityError, "core_contactdetails_name_unique"):
ContactDetails.objects.create(name="Psf", email="[email protected]")

def test_contactdetails_uniqueness_validation__with_email_case_variation(self):
with self.assertRaisesMessage(IntegrityError, "core_contactdetails_name_unique"):
ContactDetails.objects.create(name="PSF", email="[email protected]")

def test_contactdetails_creation(self):
self.assertEqual(ContactDetails.objects.count(), 1)
ContactDetails.objects.create(name="PSF", email="[email protected]")

self.assertEqual(ContactDetails.objects.count(), 2)

def test_contactdetails_add_via_ui(self):
self.login()
response = self.client.post(
reverse(ContactDetails.snippet_viewset.get_url_name("add")), # pylint: disable=no-member
data={"name": self.contact.name, "email": self.contact.email},
)

self.assertContains(response, "Contact details with this name and email combination already exists.")
self.assertEqual(ContactDetails.objects.count(), 1)
49 changes: 49 additions & 0 deletions cms/core/viewsets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
from typing import ClassVar

from django.utils.translation import gettext_lazy as _
from wagtail.admin.ui.tables import Column, UpdatedAtColumn
from wagtail.snippets.views.chooser import ChooseResultsView as SnippetChooseResultsView
from wagtail.snippets.views.chooser import ChooseView as SnippetChooseView
from wagtail.snippets.views.chooser import SnippetChooserViewSet
from wagtail.snippets.views.snippets import IndexView as SnippetIndexView
from wagtail.snippets.views.snippets import SnippetViewSet

from cms.core.models import ContactDetails


class ContactDetailsIndex(SnippetIndexView):
list_display: ClassVar[list[str | Column]] = ["name", "email", "phone", UpdatedAtColumn()]


class ContactDetailsChooseColumnsMixin:
@property
def columns(self) -> list[Column]:
title_column = self.title_column # type: ignore[attr-defined]
title_column.label = _("Name")
return [title_column, Column("email"), Column("phone")]


class ContactDetailsChooseView(ContactDetailsChooseColumnsMixin, SnippetChooseView): ...


class ContactDetailsChooseResultsView(ContactDetailsChooseColumnsMixin, SnippetChooseResultsView): ...


class ContactDetailsChooserViewset(SnippetChooserViewSet):
choose_view_class = ContactDetailsChooseView
choose_results_view_class = ContactDetailsChooseResultsView


class ContactDetailsViewSet(SnippetViewSet):
"""A snippet viewset for ContactDetails.

See:
- https://docs.wagtail.org/en/stable/topics/snippets/registering.html
- https://docs.wagtail.org/en/stable/topics/snippets/customizing.html#icon
"""

model = ContactDetails
icon = "identity"

index_view_class = ContactDetailsIndex
chooser_viewset_class = ContactDetailsChooserViewset
15 changes: 1 addition & 14 deletions cms/core/wagtail_hooks.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
from wagtail import hooks
from wagtail.snippets.models import register_snippet
from wagtail.snippets.views.snippets import SnippetViewSet

from cms.core.models import ContactDetails
from cms.core.viewsets import ContactDetailsViewSet


@hooks.register("register_icons")
Expand All @@ -21,16 +20,4 @@ def register_icons(icons: list[str]) -> list[str]:
]


class ContactDetailsViewSet(SnippetViewSet):
"""A snippet viewset for ContactDetails.

See:
- https://docs.wagtail.org/en/stable/topics/snippets/registering.html
- https://docs.wagtail.org/en/stable/topics/snippets/customizing.html#icon
"""

model = ContactDetails
icon = "identity"


register_snippet(ContactDetailsViewSet)
2 changes: 1 addition & 1 deletion cms/documents/migrations/0001_initial.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class Migration(migrations.Migration):
("title", models.CharField(max_length=255)),
("file", models.FileField()),
("created_at", models.DateTimeField(auto_now_add=True)),
("file_size", models.PositiveIntegerField(editable=False, null=True)),
("file_size", models.PositiveBigIntegerField(editable=False, null=True)),
(
"file_hash",
models.CharField(blank=True, editable=False, max_length=40),
Expand Down
2 changes: 1 addition & 1 deletion cms/jinja2/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,7 @@
onsCookiesBanner({
'lang': "cy" if LANGUAGE_CODE == "cy" else "en",
'serviceName': COOKIE_BANNER_SERVICE_NAME,
'settingsLinkURL': MANAGE_COOKIE_SETTINGS_URL,
'settingsLinkUrl': MANAGE_COOKIE_SETTINGS_URL,
})
}}
{# fmt:on #}
Expand Down
16 changes: 15 additions & 1 deletion cms/settings/dev.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
# Enable Wagtail's style guide in Wagtail's settings menu.
# http://docs.wagtail.io/en/stable/contributing/styleguide.html
INSTALLED_APPS += ["wagtail.contrib.styleguide"] # noqa: F405

INSTALLED_APPS += ["django_migration_linter"]

# Disable forcing HTTPS locally since development server supports HTTP only.
SECURE_SSL_REDIRECT = False
Expand Down Expand Up @@ -57,3 +57,17 @@
except ImportError:
pass
# pylint: enable=unused-wildcard-import,useless-suppression

MIGRATION_LINTER_OPTIONS = {
"exclude_apps": [
"taggit",
"wagtailcore",
"wagtailembeds",
"wagtailimages",
"wagtailadmin",
"wagtailsearch",
"wagtaildocs",
"wagtailredirects",
"wagtailusers",
]
}
Loading