✨(project) Django boilerplate
This commit introduces a boilerplate inspired by https://github.com/numerique-gouv/impress. The code has been cleaned to remove unnecessary Impress logic and dependencies. Changes made: - Removed Minio, WebRTC, and create bucket from the stack. - Removed the Next.js frontend (it will be replaced by Vite). - Cleaned up impress-specific backend logics. The whole stack remains functional: - All tests pass. - Linter checks pass. - Agent Connexion sources are already set-up. Why clear out the code? To adhere to the KISS principle, we aim to maintain a minimalist codebase. Cloning Impress allowed us to quickly inherit its code quality tools and deployment configurations for staging, pre-production, and production environments. What’s broken? - The tsclient is not functional anymore. - Some make commands need to be fixed. - Helm sources are outdated. - Naming across the project sources are inconsistent (impress, visio, etc.) - CI is not configured properly. This list might be incomplete. Let's grind it.
This commit is contained in:
committed by
lebaudantoine
parent
2d81979b0a
commit
5b1a2b20de
0
src/backend/demo/__init__.py
Normal file
0
src/backend/demo/__init__.py
Normal file
10
src/backend/demo/data/template/code.txt
Normal file
10
src/backend/demo/data/template/code.txt
Normal file
@@ -0,0 +1,10 @@
|
||||
<page size="A4">
|
||||
<div class="header">
|
||||
<img
|
||||
src="https://upload.wikimedia.org/wikipedia/fr/7/72/Logo_du_Gouvernement_de_la_R%C3%A9publique_fran%C3%A7aise_%282020%29.svg"
|
||||
/>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="body">{{ body }}</div>
|
||||
</div>
|
||||
</page>
|
||||
18
src/backend/demo/data/template/css.txt
Normal file
18
src/backend/demo/data/template/css.txt
Normal file
@@ -0,0 +1,18 @@
|
||||
body {
|
||||
background: white;
|
||||
font-family: arial
|
||||
}
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.header img {
|
||||
width: 5cm;
|
||||
margin-left: -0.4cm;
|
||||
}
|
||||
.body{
|
||||
margin-top: 1.5rem
|
||||
}
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
5
src/backend/demo/defaults.py
Normal file
5
src/backend/demo/defaults.py
Normal file
@@ -0,0 +1,5 @@
|
||||
"""Parameters that define how the demo site will be built."""
|
||||
|
||||
NB_OBJECTS = {
|
||||
"users": 100,
|
||||
}
|
||||
0
src/backend/demo/management/__init__.py
Normal file
0
src/backend/demo/management/__init__.py
Normal file
0
src/backend/demo/management/commands/__init__.py
Normal file
0
src/backend/demo/management/commands/__init__.py
Normal file
154
src/backend/demo/management/commands/create_demo.py
Normal file
154
src/backend/demo/management/commands/create_demo.py
Normal file
@@ -0,0 +1,154 @@
|
||||
# ruff: noqa: S311, S106
|
||||
"""create_demo management command"""
|
||||
|
||||
import logging
|
||||
import random
|
||||
import time
|
||||
from collections import defaultdict
|
||||
|
||||
from django import db
|
||||
from django.conf import settings
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
|
||||
from faker import Faker
|
||||
|
||||
from core import models
|
||||
|
||||
from demo import defaults
|
||||
|
||||
fake = Faker()
|
||||
|
||||
logger = logging.getLogger("impress.commands.demo.create_demo")
|
||||
|
||||
|
||||
def random_true_with_probability(probability):
|
||||
"""return True with the requested probability, False otherwise."""
|
||||
return random.random() < probability
|
||||
|
||||
|
||||
class BulkQueue:
|
||||
"""A utility class to create Django model instances in bulk by just pushing to a queue."""
|
||||
|
||||
BATCH_SIZE = 20000
|
||||
|
||||
def __init__(self, stdout, *args, **kwargs):
|
||||
"""Define the queue as a dict of lists."""
|
||||
self.queue = defaultdict(list)
|
||||
self.stdout = stdout
|
||||
|
||||
def _bulk_create(self, objects):
|
||||
"""Actually create instances in bulk in the database."""
|
||||
if not objects:
|
||||
return
|
||||
|
||||
objects[0]._meta.model.objects.bulk_create(objects, ignore_conflicts=False) # noqa: SLF001
|
||||
# In debug mode, Django keeps query cache which creates a memory leak in this case
|
||||
db.reset_queries()
|
||||
self.queue[objects[0]._meta.model.__name__] = [] # noqa: SLF001
|
||||
|
||||
def push(self, obj):
|
||||
"""Add a model instance to queue to that it gets created in bulk."""
|
||||
objects = self.queue[obj._meta.model.__name__] # noqa: SLF001
|
||||
objects.append(obj)
|
||||
if len(objects) > self.BATCH_SIZE:
|
||||
self._bulk_create(objects)
|
||||
self.stdout.write(".", ending="")
|
||||
|
||||
def flush(self):
|
||||
"""Flush the queue after creating the remaining model instances."""
|
||||
for objects in self.queue.values():
|
||||
self._bulk_create(objects)
|
||||
|
||||
|
||||
class Timeit:
|
||||
"""A utility context manager/method decorator to time execution."""
|
||||
|
||||
total_time = 0
|
||||
|
||||
def __init__(self, stdout, sentence=None):
|
||||
"""Set the sentence to be displayed for timing information."""
|
||||
self.sentence = sentence
|
||||
self.start = None
|
||||
self.stdout = stdout
|
||||
|
||||
def __call__(self, func):
|
||||
"""Behavior on call for use as a method decorator."""
|
||||
|
||||
def timeit_wrapper(*args, **kwargs):
|
||||
"""wrapper to trigger/stop the timer before/after function call."""
|
||||
self.__enter__()
|
||||
result = func(*args, **kwargs)
|
||||
self.__exit__(None, None, None)
|
||||
return result
|
||||
|
||||
return timeit_wrapper
|
||||
|
||||
def __enter__(self):
|
||||
"""Start timer upon entering context manager."""
|
||||
self.start = time.perf_counter()
|
||||
if self.sentence:
|
||||
self.stdout.write(self.sentence, ending=".")
|
||||
|
||||
def __exit__(self, exc_type, exc_value, exc_tb):
|
||||
"""Stop timer and display result upon leaving context manager."""
|
||||
if exc_type is not None:
|
||||
raise exc_type(exc_value)
|
||||
end = time.perf_counter()
|
||||
elapsed_time = end - self.start
|
||||
if self.sentence:
|
||||
self.stdout.write(f" Took {elapsed_time:g} seconds")
|
||||
|
||||
self.__class__.total_time += elapsed_time
|
||||
return elapsed_time
|
||||
|
||||
|
||||
def create_demo(stdout):
|
||||
"""
|
||||
Create a database with demo data for developers to work in a realistic environment.
|
||||
The code is engineered to create a huge number of objects fast.
|
||||
"""
|
||||
|
||||
queue = BulkQueue(stdout)
|
||||
|
||||
with Timeit(stdout, "Creating users"):
|
||||
for i in range(defaults.NB_OBJECTS["users"]):
|
||||
queue.push(
|
||||
models.User(
|
||||
admin_email=f"user{i:d}@example.com",
|
||||
email=f"user{i:d}@example.com",
|
||||
password="!",
|
||||
is_superuser=False,
|
||||
is_active=True,
|
||||
is_staff=False,
|
||||
language=random.choice(settings.LANGUAGES)[0],
|
||||
)
|
||||
)
|
||||
queue.flush()
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
"""A management command to create a demo database."""
|
||||
|
||||
help = __doc__
|
||||
|
||||
def add_arguments(self, parser):
|
||||
"""Add argument to require forcing execution when not in debug mode."""
|
||||
parser.add_argument(
|
||||
"-f",
|
||||
"--force",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Force command execution despite DEBUG is set to False",
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
"""Handling of the management command."""
|
||||
if not settings.DEBUG and not options["force"]:
|
||||
raise CommandError(
|
||||
(
|
||||
"This command is not meant to be used in production environment "
|
||||
"except you know what you are doing, if so use --force parameter"
|
||||
)
|
||||
)
|
||||
|
||||
create_demo(self.stdout)
|
||||
46
src/backend/demo/management/commands/createsuperuser.py
Normal file
46
src/backend/demo/management/commands/createsuperuser.py
Normal file
@@ -0,0 +1,46 @@
|
||||
"""Management user to create a superuser."""
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
UserModel = get_user_model()
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
"""Management command to create a superuser from and email and password."""
|
||||
|
||||
help = "Create a superuser with an email and a password"
|
||||
|
||||
def add_arguments(self, parser):
|
||||
"""Define required arguments "email" and "password"."""
|
||||
parser.add_argument(
|
||||
"--email",
|
||||
help=("Email for the user."),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--password",
|
||||
help="Password for the user.",
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
"""
|
||||
Given an email and a password, create a superuser or upgrade the existing
|
||||
user to superuser status.
|
||||
"""
|
||||
email = options.get("email")
|
||||
try:
|
||||
user = UserModel.objects.get(admin_email=email)
|
||||
except UserModel.DoesNotExist:
|
||||
user = UserModel(admin_email=email)
|
||||
message = "Superuser created successfully."
|
||||
else:
|
||||
if user.is_superuser and user.is_staff:
|
||||
message = "Superuser already exists."
|
||||
else:
|
||||
message = "User already existed and was upgraded to superuser."
|
||||
|
||||
user.is_superuser = True
|
||||
user.is_staff = True
|
||||
user.set_password(options["password"])
|
||||
user.save()
|
||||
|
||||
self.stdout.write(self.style.SUCCESS(message))
|
||||
0
src/backend/demo/tests/__init__.py
Normal file
0
src/backend/demo/tests/__init__.py
Normal file
18
src/backend/demo/tests/test_commands_create_demo.py
Normal file
18
src/backend/demo/tests/test_commands_create_demo.py
Normal file
@@ -0,0 +1,18 @@
|
||||
"""Test the `create_demo` management command"""
|
||||
|
||||
from django.core.management import call_command
|
||||
from django.test import override_settings
|
||||
|
||||
import pytest
|
||||
|
||||
from core import models
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
@override_settings(DEBUG=True)
|
||||
def test_commands_create_demo():
|
||||
"""The create_demo management command should create objects as expected."""
|
||||
call_command("create_demo")
|
||||
|
||||
assert models.User.objects.count() == 100
|
||||
Reference in New Issue
Block a user