Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
33 changes: 31 additions & 2 deletions src/backend/demo/defaults.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,36 @@
"""Parameters that define how the demo site will be built."""

NB_OBJECTS = {"users": 50, "files": 50, "max_users_per_document": 50}
NB_OBJECTS = {"users": 4, "files": 50, "max_users_per_document": 4}

USERS = [
{
"email": "page.turner@library.book",
"full_name": "Paige Turner",
"short_name": "Paige",
},
{
"email": "miles.ahead@roadmap.fwd",
"full_name": "Miles Ahead",
"short_name": "Miles",
},
{
"email": "archie.vist@vaulted.docs",
"full_name": "Archie Vist",
"short_name": "Archie",
},
{
"email": "wade.wilson@maximum.effort",
"full_name": "Wade Wilson",
"short_name": "Wade",
},
]

DEV_USERS = [
{"username": "drive", "email": "drive@drive.world", "language": "en-us"},
{
"username": "drive",
"email": "drive@drive.world",
"full_name": "Drive Developer",
"short_name": "Drive",
"language": "en-us",
},
]
167 changes: 133 additions & 34 deletions src/backend/demo/management/commands/create_demo.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
# ruff: noqa: S106
"""create_demo management command"""

import logging
Expand All @@ -19,6 +18,84 @@
fake = Faker()

logger = logging.getLogger(__file__)
DEFAULT_PARENT = object()


FILE_TYPE_ITEMS = (
{
"title": "Demo text document",
"filename": "demo-text-document.docx",
"mimetype": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
},
{
"title": "Demo spreadsheet",
"filename": "demo-spreadsheet.xlsx",
"mimetype": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
},
{
"title": "Demo presentation",
"filename": "demo-presentation.pptx",
"mimetype": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
},
{
"title": "Demo PDF",
"filename": "demo-pdf.pdf",
"mimetype": "application/pdf",
},
{
"title": "Demo image",
"filename": "demo-image.png",
"mimetype": "image/png",
},
{
"title": "Demo video",
"filename": "demo-video.mp4",
"mimetype": "video/mp4",
},
{
"title": "Demo archive",
"filename": "demo-archive.zip",
"mimetype": "application/zip",
},
{
"title": "Demo audio",
"filename": "demo-audio.mp3",
"mimetype": "audio/mpeg",
},
{
"title": "Demo other file",
"filename": "demo-other.bin",
"mimetype": "application/octet-stream",
},
)


def get_or_create_demo_user(user_data):
"""Get an existing demo user or create it when absent."""
email = user_data["email"]
user, _created = models.User.objects.get_or_create(
sub=email,
defaults={
"admin_email": email,
"email": email,
"full_name": user_data["full_name"],
"short_name": user_data["short_name"],
"password": "!",

Check warning on line 83 in src/backend/demo/management/commands/create_demo.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

"password" detected here, review this potentially hard-coded credential.

See more on https://sonarcloud.io/project/issues?id=suitenumerique_drive&issues=AZ4sVrhWI-vGf32cDAin&open=AZ4sVrhWI-vGf32cDAin&pullRequest=712
"is_superuser": False,
"is_active": True,
"is_staff": False,
},
)
update_fields = []
for field in ["full_name", "short_name"]:
if not getattr(user, field):
setattr(user, field, user_data[field])
update_fields.append(field)

if update_fields:
user.save(update_fields=update_fields)

return user


class Timeit:
Expand Down Expand Up @@ -65,58 +142,51 @@

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,
)
for user_data in defaults.USERS[: defaults.NB_OBJECTS["users"]]:
yield get_or_create_demo_user(user_data)


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,
)
user = get_or_create_demo_user(dev_user)

create_item(user)
yield user


def create_item(user):
def create_item(
user,
title=None,
file_data=None,
parent=DEFAULT_PARENT,
):
"""Create file item with the given user as creator"""
parent = factories.ItemFactory(
creator=user,
users=[(user, models.RoleChoices.OWNER)],
type=models.ItemTypeChoices.FOLDER,
)
file_data = file_data or {}
content = file_data.get("content") or fake.sentence(nb_words=50).encode()
if parent is DEFAULT_PARENT:
parent = factories.ItemFactory(
creator=user,
users=[(user, models.RoleChoices.OWNER)],
type=models.ItemTypeChoices.FOLDER,
)

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,
users=[(user, models.RoleChoices.OWNER)] if parent is None else None,
parent=parent,
title=fake.sentence(nb_words=4),
filename="content.txt",
title=title or fake.sentence(nb_words=4),
filename=file_data.get("filename", "content.txt"),
description=fake.sentence(nb_words=10),
mimetype="text/plain",
mimetype=file_data.get("mimetype", "text/plain"),
size=len(content),
)

default_storage.save(item.file_key, BytesIO(fake.sentence(nb_words=50).encode()))
default_storage.save(item.file_key, BytesIO(content))

return item

Expand All @@ -128,7 +198,22 @@
yield create_item(user)


def create_demo(stdout):
def create_file_type_items(user):
"""Create one ready file for each file type category described in issue #597."""
for file_type_item in FILE_TYPE_ITEMS:
yield create_item(
user,
title=file_type_item["title"],
file_data={
"filename": file_type_item["filename"],
"mimetype": file_type_item["mimetype"],
"content": f"{file_type_item['title']} fixture".encode(),
},
parent=None,
)


def create_demo(stdout, *, file_types=False):
"""
Create a database with demo data for developers to work in a realistic environment.
"""
Expand All @@ -152,6 +237,10 @@
role=models.RoleChoices.READER,
)

if file_types:
with Timeit(stdout, "Creating file type items"):
list(create_file_type_items(dev_users[0]))


class Command(BaseCommand):
"""A management command to create a demo database."""
Expand All @@ -167,6 +256,13 @@
default=False,
help="Force command execution despite DEBUG is set to False",
)
parser.add_argument(
"--file_types",
"--file-types",
action="store_true",
default=False,
help="Create files covering the file type categories from issue #597",
)

def handle(self, *args, **options):
"""Handling of the management command."""
Expand All @@ -178,4 +274,7 @@
)
)

create_demo(self.stdout)
create_demo(
self.stdout,
file_types=options["file_types"],
)
45 changes: 34 additions & 11 deletions src/backend/demo/tests/test_commands_create_demo.py
Original file line number Diff line number Diff line change
@@ -1,34 +1,57 @@
"""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

from demo import defaults
from demo.management.commands.create_demo import FILE_TYPE_ITEMS

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.User.objects.count() == 5
assert set(models.User.objects.values_list("email", flat=True)) == {
user["email"] for user in defaults.USERS
} | {"drive@drive.world"}
assert not models.User.objects.filter(full_name__isnull=True).exists()
assert not models.User.objects.filter(full_name="").exists()
assert not models.User.objects.filter(short_name__isnull=True).exists()
assert not models.User.objects.filter(short_name="").exists()
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()


@override_settings(DEBUG=True)
def test_commands_create_demo_with_file_types():
"""The create_demo command should optionally add files for filter development."""
call_command("create_demo", "--file_types")

expected_filenames = {item["filename"] for item in FILE_TYPE_ITEMS}
expected_mimetypes = {item["mimetype"] for item in FILE_TYPE_ITEMS}
file_type_items = models.Item.objects.filter(filename__in=expected_filenames)

assert file_type_items.count() == len(FILE_TYPE_ITEMS)
assert set(file_type_items.values_list("mimetype", flat=True)) == expected_mimetypes
assert all(item.is_root for item in file_type_items)


@override_settings(DEBUG=True)
def test_commands_create_demo_can_be_run_twice_without_resetting_database():
"""The create_demo command should reuse deterministic demo users."""
call_command("create_demo", "--file_types")
call_command("create_demo", "--file_types")

assert models.User.objects.filter(email="drive@drive.world").count() == 1
Loading