mirror of
https://github.com/suitenumerique/drive.git
synced 2026-09-28 20:45:13 +02:00
✨(backend) add demo populate command
Add a new Django app 'demo' that contains the command 'create_demo' Generate dummy users and files with existing factories. Signed-off-by: Fabre Florian <ffabre@hybird.org>
This commit is contained in:
@@ -177,6 +177,15 @@ stop: ## stop the development server using Docker
|
||||
|
||||
# -- Backend
|
||||
|
||||
demo: ## flush db then create a demo for load testing purpose
|
||||
@$(MAKE) resetdb
|
||||
@$(MANAGE) create_demo
|
||||
.PHONY: demo
|
||||
|
||||
index: ## index all files to remote search
|
||||
@$(MANAGE) index
|
||||
.PHONY: index
|
||||
|
||||
# Nota bene: Black should come after isort just in case they don't agree...
|
||||
lint: ## lint back-end python sources
|
||||
lint: \
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
"""Parameters that define how the demo site will be built."""
|
||||
|
||||
NB_OBJECTS = {"users": 50, "files": 50, "max_users_per_document": 50}
|
||||
|
||||
DEV_USERS = [
|
||||
{"username": "drive", "email": "drive@drive.world", "language": "en-us"},
|
||||
]
|
||||
@@ -0,0 +1,182 @@
|
||||
# ruff: noqa: S311, S106
|
||||
"""create_demo management command"""
|
||||
|
||||
import logging
|
||||
import random
|
||||
import time
|
||||
from io import BytesIO
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.files.storage import default_storage
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
|
||||
from faker import Faker
|
||||
|
||||
from core import factories, models
|
||||
|
||||
from demo import defaults
|
||||
|
||||
fake = Faker()
|
||||
|
||||
logger = logging.getLogger("impress.commands.demo.create_demo")
|
||||
|
||||
|
||||
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_users():
|
||||
"""Create random users"""
|
||||
for user_id in range(defaults.NB_OBJECTS["users"]):
|
||||
email = f"user.test{user_id:d}@example.com"
|
||||
yield factories.UserFactory(
|
||||
admin_email=email,
|
||||
email=email,
|
||||
sub=email,
|
||||
password="!",
|
||||
is_superuser=False,
|
||||
is_active=True,
|
||||
is_staff=False,
|
||||
)
|
||||
|
||||
|
||||
def create_dev_users():
|
||||
"""Create development users"""
|
||||
for dev_user in defaults.DEV_USERS:
|
||||
email = dev_user["email"]
|
||||
user = factories.UserFactory(
|
||||
admin_email=email,
|
||||
email=email,
|
||||
sub=email,
|
||||
password="!",
|
||||
is_superuser=False,
|
||||
is_active=True,
|
||||
is_staff=False,
|
||||
)
|
||||
|
||||
create_item(user)
|
||||
yield user
|
||||
|
||||
|
||||
def create_item(user):
|
||||
"""Create file item with the given user as creator"""
|
||||
workspace = user.get_main_workspace()
|
||||
item = factories.ItemFactory(
|
||||
type=models.ItemTypeChoices.FILE,
|
||||
update_upload_state=models.ItemUploadStateChoices.READY,
|
||||
link_reach=models.LinkReachChoices.AUTHENTICATED,
|
||||
link_role=models.LinkRoleChoices.READER,
|
||||
creator=user,
|
||||
parent=workspace,
|
||||
title=fake.sentence(nb_words=4),
|
||||
filename="content.txt",
|
||||
description=fake.sentence(nb_words=10),
|
||||
mimetype="text/plain",
|
||||
)
|
||||
|
||||
default_storage.connection.meta.client.put_object(
|
||||
Bucket=default_storage.bucket_name,
|
||||
Key=item.file_key,
|
||||
Body=BytesIO(fake.sentence(nb_words=50).encode()),
|
||||
ContentType="text/plain",
|
||||
)
|
||||
|
||||
return item
|
||||
|
||||
|
||||
def create_items(users):
|
||||
"""Create random items and file content for users"""
|
||||
for _id in range(defaults.NB_OBJECTS["files"]):
|
||||
user = random.choice(users)
|
||||
yield create_item(user)
|
||||
|
||||
|
||||
def create_demo(stdout):
|
||||
"""
|
||||
Create a database with demo data for developers to work in a realistic environment.
|
||||
"""
|
||||
with Timeit(stdout, "Creating users"):
|
||||
users = list(create_users())
|
||||
|
||||
with Timeit(stdout, "Creating items"):
|
||||
items = list(create_items(users))
|
||||
|
||||
with Timeit(stdout, "Creating development users"):
|
||||
dev_users = list(create_dev_users())
|
||||
|
||||
with Timeit(stdout, "Creating item accesses on development users"):
|
||||
for user in dev_users:
|
||||
create_item(user)
|
||||
|
||||
for item in items:
|
||||
factories.UserItemAccessFactory(
|
||||
item=item,
|
||||
user=user,
|
||||
role=models.RoleChoices.READER,
|
||||
)
|
||||
|
||||
|
||||
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)
|
||||
@@ -0,0 +1,34 @@
|
||||
"""Test the `create_demo` management command"""
|
||||
|
||||
from unittest import mock
|
||||
|
||||
from django.core.management import call_command
|
||||
from django.test import override_settings
|
||||
|
||||
import pytest
|
||||
|
||||
from core import models
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
@mock.patch(
|
||||
"demo.defaults.NB_OBJECTS",
|
||||
{
|
||||
"users": 10,
|
||||
"files": 10,
|
||||
"max_users_per_document": 5,
|
||||
},
|
||||
)
|
||||
@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() >= 10
|
||||
assert models.Item.objects.count() >= 10
|
||||
assert models.ItemAccess.objects.count() > 10
|
||||
|
||||
# assert dev users have doc accesses
|
||||
user = models.User.objects.get(email="drive@drive.world")
|
||||
assert models.ItemAccess.objects.filter(user=user).exists()
|
||||
@@ -998,6 +998,7 @@ class Development(Base):
|
||||
"drf_spectacular_sidecar",
|
||||
"debug_toolbar",
|
||||
"e2e",
|
||||
"demo",
|
||||
]
|
||||
|
||||
|
||||
@@ -1018,7 +1019,7 @@ class Test(Base):
|
||||
|
||||
def __init__(self):
|
||||
# pylint: disable=invalid-name
|
||||
self.INSTALLED_APPS += ["drf_spectacular_sidecar", "e2e"]
|
||||
self.INSTALLED_APPS += ["drf_spectacular_sidecar", "e2e", "demo"]
|
||||
|
||||
|
||||
class ContinuousIntegration(Test):
|
||||
|
||||
Reference in New Issue
Block a user