Skip to content

Commit 57046fb

Browse files
authored
Merge pull request #1 from 4teamwork/jch/TI-2887
Implement system messages
2 parents 46d2426 + c83e7b7 commit 57046fb

38 files changed

+1498
-9
lines changed

.flake8

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[flake8]
2+
# From https://github.com/4teamwork/ftw-buildouts/blob/master/pycodestyle.cfg
3+
ignore = E121,E122,E123,E125,E126,E127,E128,E203,E301,W503,W606
4+
max-line-length = 125
5+
exclude = .git,__pycache__,venv,.tox,manage.py,include,lib,javascript,lib,node_modules,scss,bin,.eggs,wsgi.py,wsgi.py,src,migrations,docs,gever,bootstrap.py

.github/workflows/tests.yml

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,19 @@ concurrency:
1010
jobs:
1111
test:
1212
runs-on: ubuntu-latest
13+
services:
14+
postgres:
15+
image: postgres
16+
env:
17+
POSTGRES_PASSWORD: postgres
18+
POSTGRES_DB: django_features_testing
19+
ports:
20+
- 5432:5432
21+
options: >-
22+
--health-cmd pg_isready
23+
--health-interval 10s
24+
--health-timeout 5s
25+
--health-retries 5
1326
steps:
1427
- name: Checkout
1528
uses: actions/checkout@v3
@@ -22,10 +35,12 @@ jobs:
2235
with:
2336
poetry-version: 2.1
2437
- name: Install dependencies
25-
run: poetry install --only=dev
38+
run: poetry install
2639
- name: isort
2740
run: poetry run isort --check-only --quiet --settings pyproject.toml .
2841
- name: flake8
2942
run: poetry run flake8
3043
- name: black
3144
run: poetry run black --check --config pyproject.toml .
45+
- name: test
46+
run: DJANGO_CONFIGURATION=Testing poetry run pytest

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,19 @@ A collection of fearures used in our Django-based web applications
33

44
[Changelog](CHANGELOG.md)
55

6+
## Installation
7+
8+
``` bash
9+
pip install ftw-django-features
10+
```
11+
12+
## Usage
13+
14+
Add desired app to `INSTALLED_APPS` in your Django project.
15+
16+
Available apps:
17+
- `django_features.system_message`
18+
619
## Development
720

821
Installing dependencies, assuming you have poetry installed:

app/__init__.py

Whitespace-only changes.

app/settings/__init__.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
__all__ = [
2+
"Development",
3+
"Testing",
4+
]
5+
6+
from .development import Development
7+
from .testing import Testing

app/settings/base.py

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import os
2+
from pathlib import Path
3+
4+
from configurations import Configuration
5+
from configurations import values
6+
from django.utils.translation import gettext_lazy as _
7+
8+
9+
class Base(Configuration):
10+
DEBUG = False
11+
12+
@property
13+
def INSTALLED_APPS(self) -> list[str]:
14+
installed_apps = [
15+
"modeltranslation",
16+
"django.contrib.admin",
17+
"django.contrib.auth",
18+
"django.contrib.contenttypes",
19+
"django.contrib.messages",
20+
"django.contrib.staticfiles",
21+
"django.contrib.sessions",
22+
"django_extensions",
23+
"django_linear_migrations",
24+
"constance",
25+
"rest_framework",
26+
"django_features.system_message",
27+
]
28+
return installed_apps
29+
30+
BASE_DIR = Path(__file__).parent.parent.parent
31+
DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
32+
33+
@property
34+
def MIDDLEWARE(self) -> list[str]:
35+
middlewares = [
36+
"django.middleware.security.SecurityMiddleware",
37+
"django.contrib.sessions.middleware.SessionMiddleware",
38+
"django.middleware.locale.LocaleMiddleware",
39+
"django.middleware.common.CommonMiddleware",
40+
"django.middleware.csrf.CsrfViewMiddleware",
41+
"django.contrib.auth.middleware.AuthenticationMiddleware",
42+
"django.contrib.messages.middleware.MessageMiddleware",
43+
"django.middleware.clickjacking.XFrameOptionsMiddleware",
44+
]
45+
return middlewares
46+
47+
AUTHENTICATION_BACKENDS = ["django.contrib.auth.backends.ModelBackend"]
48+
ROOT_URLCONF = "app.urls"
49+
50+
TIME_ZONE = "Europe/Zurich"
51+
USE_I18N = True
52+
USE_L10N = True
53+
USE_TZ = True
54+
55+
LANGUAGE_CODE = "de"
56+
LANGUAGES = [
57+
("de", _("Deutsch")),
58+
("en", _("Englisch")),
59+
("fr", _("Französisch")),
60+
]
61+
LOCALE_PATHS = [
62+
BASE_DIR / "django_features" / "locale",
63+
]
64+
65+
DATABASE_NAME = values.Value("django_features")
66+
DATABASE_ENGINE = values.Value("django.db.backends.postgresql")
67+
DATABASE_USER = values.Value("")
68+
DATABASE_PASSWORD = values.Value("")
69+
DATABASE_HOST = values.Value("")
70+
DATABASE_PORT = values.Value("")
71+
DATABASE_OPTIONS = values.DictValue({})
72+
73+
@property
74+
def DATABASES(self) -> dict:
75+
return {
76+
"default": {
77+
"ENGINE": self.DATABASE_ENGINE,
78+
"NAME": self.DATABASE_NAME,
79+
"USER": self.DATABASE_USER,
80+
"PASSWORD": self.DATABASE_PASSWORD,
81+
"HOST": self.DATABASE_HOST,
82+
"PORT": self.DATABASE_PORT,
83+
"OPTIONS": self.DATABASE_OPTIONS,
84+
"ATOMIC_REQUESTS": True,
85+
}
86+
}
87+
88+
TEMPLATES = [
89+
{
90+
"APP_DIRS": True,
91+
"BACKEND": "django.template.backends.django.DjangoTemplates",
92+
"DIRS": [],
93+
"OPTIONS": {
94+
"context_processors": [
95+
"django.template.context_processors.debug",
96+
"django.template.context_processors.request",
97+
"django.contrib.auth.context_processors.auth",
98+
"django.contrib.messages.context_processors.messages",
99+
]
100+
},
101+
}
102+
]
103+
104+
@property
105+
def STATIC_ROOT(self) -> str:
106+
default_value = os.path.join(self.BASE_DIR, "public/static")
107+
return values.Value(default=default_value, environ_name="STATIC_ROOT")
108+
109+
@property
110+
def STATIC_URL(self) -> str:
111+
value = values.Value("/static/", environ_name="STATIC_URL")
112+
value = value.strip("/")
113+
return "/" + value + "/"
114+
115+
REST_FRAMEWORK = {
116+
"DEFAULT_PERMISSION_CLASSES": ["rest_framework.permissions.IsAuthenticated"],
117+
"DEFAULT_AUTHENTICATION_CLASSES": [
118+
"rest_framework.authentication.SessionAuthentication"
119+
],
120+
"DEFAULT_RENDERER_CLASSES": ["rest_framework.renderers.JSONRenderer"],
121+
}
122+
123+
CONSTANCE_BACKEND = "constance.backends.database.DatabaseBackend"
124+
125+
@property
126+
def CONSTANCE_CONFIG(self) -> dict:
127+
return {
128+
"SYSTEM_MESSAGE_PERMISSION": (
129+
"",
130+
"Django permission to manage system messages.",
131+
str,
132+
),
133+
"ENABLE_SYSTEM_MESSAGE": (False, "Enables the system info feature.", bool),
134+
}
135+
136+
@property
137+
def CONSTANCE_CONFIG_FIELDSETS(self) -> dict:
138+
return {
139+
"Miscellaneous": {
140+
"fields": ("ENABLE_SYSTEM_MESSAGE", "SYSTEM_MESSAGE_PERMISSION"),
141+
"collapse": True,
142+
},
143+
}
144+
145+
SECRET_KEY = values.SecretValue()

app/settings/development.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from app.settings.base import Base
2+
3+
4+
class Development(Base):
5+
DEBUG = True
6+
SECRET_KEY = "secret"

app/settings/testing.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from configurations import values
2+
3+
from app.settings.base import Base
4+
5+
6+
class Testing(Base):
7+
SECRET_KEY = "secret"
8+
9+
DATABASE_NAME = values.Value("django_features_testing")
10+
DATABASE_ENGINE = values.Value("django.db.backends.postgresql")
11+
DATABASE_USER = values.Value("postgres")
12+
DATABASE_PASSWORD = values.Value("postgres")
13+
DATABASE_HOST = values.Value("127.0.0.1")
14+
DATABASE_PORT = values.Value("5432")
15+
DATABASE_OPTIONS = values.DictValue({})

app/tests/__init__.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
from typing import Any
2+
3+
from django.contrib.auth import get_user_model
4+
from django.test import TransactionTestCase
5+
from rest_framework.test import APIClient
6+
7+
8+
User = get_user_model()
9+
10+
11+
class APITestCase(TransactionTestCase):
12+
reset_sequences = True
13+
14+
def setUp(self) -> None:
15+
super().setUp()
16+
self.anonymous_client = APIClient()
17+
self.client = APIClient()
18+
self.login("kathi.barfuss")
19+
self.session = self.client.session
20+
21+
def get_or_create_user(self, username: str) -> tuple[Any, bool]:
22+
user, created = User.objects.get_or_create(username=username)
23+
if created:
24+
user.set_password("secret")
25+
user.save()
26+
return user, created
27+
28+
def login(self, username: str, password: str = "secret") -> None:
29+
self.user, _ = self.get_or_create_user(username=username)
30+
self.client.login(username=username, password=password)

app/tests/factories/__init__.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from typing import Any
2+
3+
from factory.django import DjangoModelFactory
4+
5+
6+
class BaseFactory(DjangoModelFactory):
7+
class Meta:
8+
abstract = True
9+
10+
def __new__(cls, *args: Any, **kwargs: Any) -> Any:
11+
return super().__new__(*args, **kwargs) # type: ignore

0 commit comments

Comments
 (0)