Merge pull request 'Added custom command to create example data' (#92) from example-data into release
Reviewed-on: https://gitea.pangea.org/trustchain-oc1-orchestral/IdHub/pulls/92
This commit is contained in:
commit
eb547367cb
|
@ -0,0 +1,293 @@
|
||||||
|
import random
|
||||||
|
from typing import Type, Callable, List
|
||||||
|
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from django.db import models, IntegrityError
|
||||||
|
|
||||||
|
from idhub.models import Event, Rol, Service, UserRol
|
||||||
|
from idhub_auth.models import User
|
||||||
|
|
||||||
|
from faker import Faker
|
||||||
|
|
||||||
|
DEFAULT_OBJECTS_CREATED: int = 30
|
||||||
|
RANDOM_STRING_LENGTH: int = 30
|
||||||
|
EMAIL_RANDOM_STRING_LENGTH: int = 10
|
||||||
|
EXISTING_EVENTS: int = 30
|
||||||
|
|
||||||
|
fake = Faker()
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
"""
|
||||||
|
Populate the database with dummy values.
|
||||||
|
You can either specify which model to create objects for,
|
||||||
|
or create objects for all models.
|
||||||
|
If no data is specified it will create 30 events, users,
|
||||||
|
services, roles, and user roles.
|
||||||
|
"""
|
||||||
|
|
||||||
|
help = 'Populate the database with dummy values for testing.'
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
In the context of a Django management command,
|
||||||
|
initializing lists like created_users in the constructor ensures
|
||||||
|
that each run of the command has its own set of users, services,
|
||||||
|
roles, etc., and does not unintentionally share or retain state
|
||||||
|
across different invocations of the command.
|
||||||
|
"""
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.created_users = []
|
||||||
|
self.created_services = []
|
||||||
|
self.created_roles = []
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
any_option_used = self.process_options(options)
|
||||||
|
|
||||||
|
if not any_option_used:
|
||||||
|
self.create_all()
|
||||||
|
|
||||||
|
def process_options(self, input_options):
|
||||||
|
option_methods = {
|
||||||
|
'events': self.create_events,
|
||||||
|
'users': self.create_users,
|
||||||
|
'superusers': self.create_superusers,
|
||||||
|
'services': self.create_services,
|
||||||
|
'roles': self.create_roles,
|
||||||
|
'userroles': self.create_user_roles,
|
||||||
|
'userrole': self.create_specific_user_role,
|
||||||
|
'register': self.register_user,
|
||||||
|
'register_superuser': self.register_superuser,
|
||||||
|
'amount': self.create_all
|
||||||
|
}
|
||||||
|
|
||||||
|
any_option_used = self.match_input_to_options(input_options,
|
||||||
|
option_methods)
|
||||||
|
|
||||||
|
return any_option_used
|
||||||
|
|
||||||
|
def match_input_to_options(self, input_options, option_methods):
|
||||||
|
any_option_used = False
|
||||||
|
|
||||||
|
for option, method in option_methods.items():
|
||||||
|
is_valid_option = option in input_options and input_options[
|
||||||
|
option] is not None
|
||||||
|
if is_valid_option:
|
||||||
|
self.call_selected_method(input_options, method, option)
|
||||||
|
any_option_used = True
|
||||||
|
|
||||||
|
return any_option_used
|
||||||
|
|
||||||
|
def call_selected_method(self, input_options, method, option):
|
||||||
|
args = self.create_argument_list(input_options, option)
|
||||||
|
method(*args)
|
||||||
|
|
||||||
|
def create_argument_list(self, input_options, option):
|
||||||
|
if isinstance(input_options[option], list):
|
||||||
|
args = input_options[option]
|
||||||
|
else:
|
||||||
|
args = [input_options[option]]
|
||||||
|
return args
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument(
|
||||||
|
'--amount', type=int, action='store',
|
||||||
|
help='Create N objects (includes events, users, services, roles, and user roles)'
|
||||||
|
)
|
||||||
|
parser.add_argument('--events', type=int,
|
||||||
|
help='Create the specified number of events')
|
||||||
|
parser.add_argument('--users', type=int,
|
||||||
|
help='Create the specified number of users')
|
||||||
|
parser.add_argument('--superusers', type=int,
|
||||||
|
help='Create the specified number of superusers')
|
||||||
|
parser.add_argument('--services', type=int,
|
||||||
|
help='Create the specified number of services')
|
||||||
|
parser.add_argument('--roles', type=int,
|
||||||
|
help='Create the specified number of roles')
|
||||||
|
parser.add_argument('--userroles', type=int,
|
||||||
|
help='Create the specified number of user roles')
|
||||||
|
parser.add_argument(
|
||||||
|
'--userrole', nargs=2, type=str,
|
||||||
|
help='Create a user role for user U and service S',
|
||||||
|
metavar=('U', 'S'),
|
||||||
|
)
|
||||||
|
parser.add_argument('--register', nargs=2, type=str,
|
||||||
|
help='Create a user with email EMAIL and password PW',
|
||||||
|
metavar=('EMAIL', 'PW'))
|
||||||
|
parser.add_argument('--register-superuser', nargs=2, type=str,
|
||||||
|
help='Create a superuser with email EMAIL and '
|
||||||
|
'password PW',
|
||||||
|
metavar=('EMAIL', 'PW'))
|
||||||
|
|
||||||
|
def register_user(self, email: str, password: str):
|
||||||
|
"""
|
||||||
|
Register a new user with the given email and password from the command line.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
self.create_user(email, password)
|
||||||
|
self.stdout.write(f"Successfully registered user: {email}")
|
||||||
|
except IntegrityError:
|
||||||
|
self.stdout.write(f"Failed to register user: {email}")
|
||||||
|
|
||||||
|
def register_superuser(self, email: str, password: str):
|
||||||
|
"""
|
||||||
|
Register a new superuser with the given email and password from the command line.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
self.create_superuser(email, password)
|
||||||
|
self.stdout.write(f"Successfully registered superuser: {email}")
|
||||||
|
except IntegrityError:
|
||||||
|
self.stdout.write(f"Failed to register superuser: {email}")
|
||||||
|
|
||||||
|
def create_all(self, amount=DEFAULT_OBJECTS_CREATED):
|
||||||
|
self.create_events(amount)
|
||||||
|
self.create_users(amount)
|
||||||
|
self.create_roles(amount)
|
||||||
|
self.create_services(amount)
|
||||||
|
self.create_user_roles(amount)
|
||||||
|
|
||||||
|
def create_objects(self, model: Type[models.Model],
|
||||||
|
data_generator: Callable, amount: int) -> List[
|
||||||
|
models.Model]:
|
||||||
|
"""
|
||||||
|
Generic method to create objects for a given model and keep track of them.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
model: The Django model class for which objects are to be created.
|
||||||
|
data_generator: A function that returns a dictionary of attributes for creating a model instance.
|
||||||
|
amount: The number of objects to create.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A list of successfully created model instances.
|
||||||
|
"""
|
||||||
|
created_objects = []
|
||||||
|
for _ in range(amount):
|
||||||
|
try:
|
||||||
|
model_instance = self.create_from_data(data_generator, model)
|
||||||
|
created_objects.append(model_instance)
|
||||||
|
except IntegrityError:
|
||||||
|
self.print_failure_message(model)
|
||||||
|
|
||||||
|
self.print_amount_created(created_objects, model)
|
||||||
|
return created_objects
|
||||||
|
|
||||||
|
def create_users(self, amount: int):
|
||||||
|
def user_data():
|
||||||
|
return {
|
||||||
|
'email': fake.email(),
|
||||||
|
'first_name': fake.first_name(),
|
||||||
|
'last_name': fake.last_name()
|
||||||
|
}
|
||||||
|
created_users = self.create_objects(User, user_data, amount)
|
||||||
|
self.created_users.extend(created_users)
|
||||||
|
|
||||||
|
def create_events(self, amount: int, user=None):
|
||||||
|
def event_data():
|
||||||
|
return {
|
||||||
|
'type': random.randint(1, EXISTING_EVENTS),
|
||||||
|
'message': fake.paragraph(nb_sentences=3),
|
||||||
|
'user': user
|
||||||
|
}
|
||||||
|
self.create_objects(Event, event_data, amount)
|
||||||
|
|
||||||
|
def create_superusers(self, amount=0):
|
||||||
|
"""Superusers can only be created from the specific command"""
|
||||||
|
created_superuser_amount = 0
|
||||||
|
for value in range(0, amount):
|
||||||
|
email = fake.email()
|
||||||
|
try:
|
||||||
|
User.objects.create_superuser(email)
|
||||||
|
created_superuser_amount += 1
|
||||||
|
except IntegrityError:
|
||||||
|
self.stdout.write("Couldn't create superuser " + email)
|
||||||
|
self.stdout.write(f"Created {created_superuser_amount} users")
|
||||||
|
|
||||||
|
def create_services(self, amount: int):
|
||||||
|
def service_data():
|
||||||
|
domain = fake.text(max_nb_chars=200)
|
||||||
|
description = fake.text(max_nb_chars=250)
|
||||||
|
return {
|
||||||
|
'domain': domain,
|
||||||
|
'description': description,
|
||||||
|
}
|
||||||
|
|
||||||
|
created_services = self.create_objects(Service, service_data, amount)
|
||||||
|
self.associate_random_roles(created_services)
|
||||||
|
self.created_services.extend(created_services)
|
||||||
|
|
||||||
|
def create_roles(self, amount: int):
|
||||||
|
def role_data():
|
||||||
|
name = fake.job()
|
||||||
|
description = fake.text(max_nb_chars=250)
|
||||||
|
return {'name': name, 'description': description}
|
||||||
|
|
||||||
|
created_roles = self.create_objects(Rol, role_data, amount)
|
||||||
|
self.created_roles.extend(created_roles)
|
||||||
|
|
||||||
|
def create_user_roles(self, amount: int, user_id=None, service_id=None):
|
||||||
|
def user_role_data():
|
||||||
|
user = self.get_or_create_user(user_id)
|
||||||
|
service = self.get_or_create_service(service_id)
|
||||||
|
return {"user": user, "service": service}
|
||||||
|
|
||||||
|
self.create_objects(UserRol, user_role_data, amount)
|
||||||
|
|
||||||
|
def create_specific_user_role(self, user, service):
|
||||||
|
self.create_user_roles(1, user, service)
|
||||||
|
|
||||||
|
def create_user(self, email: str, password: str):
|
||||||
|
User.objects.create_user(email, password)
|
||||||
|
|
||||||
|
def create_superuser(self, email: str, password: str):
|
||||||
|
User.objects.create_superuser(email, password)
|
||||||
|
|
||||||
|
def print_failure_message(self, model):
|
||||||
|
self.stdout.write(f"Couldn't create {model.__name__} object.")
|
||||||
|
|
||||||
|
def print_amount_created(self, created_objects, model):
|
||||||
|
self.stdout.write(f"Created {len(created_objects)} "
|
||||||
|
f"{model.__name__} objects")
|
||||||
|
|
||||||
|
def create_from_data(self, data_generator, model):
|
||||||
|
model_instance = model.objects.create(**data_generator())
|
||||||
|
return model_instance
|
||||||
|
|
||||||
|
def associate_random_roles(self, created_services):
|
||||||
|
for service in created_services:
|
||||||
|
self.associate_service_to_random_roles(service)
|
||||||
|
|
||||||
|
def associate_service_to_random_roles(self, service):
|
||||||
|
associated_roles = [self.get_or_create_role() for _ in range(
|
||||||
|
random.randint(0, 2))]
|
||||||
|
service.rol.set(associated_roles)
|
||||||
|
|
||||||
|
def get_or_create_user(self, user_id):
|
||||||
|
if user_id is not None:
|
||||||
|
user = User.objects.get(user_id=user_id)
|
||||||
|
elif len(self.created_users) != 0:
|
||||||
|
user = random.choice(self.created_users)
|
||||||
|
else:
|
||||||
|
self.create_users(1)
|
||||||
|
user = self.created_users[0]
|
||||||
|
|
||||||
|
return user
|
||||||
|
|
||||||
|
def get_or_create_service(self, service_id):
|
||||||
|
if service_id is not None:
|
||||||
|
service = Service.objects.get(service_id=service_id)
|
||||||
|
elif len(self.created_services) != 0:
|
||||||
|
service = random.choice(self.created_services)
|
||||||
|
else:
|
||||||
|
self.create_services(1)
|
||||||
|
service = self.created_services[0]
|
||||||
|
|
||||||
|
return service
|
||||||
|
|
||||||
|
def get_or_create_role(self):
|
||||||
|
if len(self.created_roles) != 0:
|
||||||
|
role = random.choice(self.created_roles)
|
||||||
|
else:
|
||||||
|
self.create_roles(1)
|
||||||
|
role = self.created_roles[0]
|
||||||
|
|
||||||
|
return role
|
|
@ -13,3 +13,4 @@ jsonref==1.1.0
|
||||||
pyld==2.0.3
|
pyld==2.0.3
|
||||||
more-itertools==10.1.0
|
more-itertools==10.1.0
|
||||||
dj-database-url==2.1.0
|
dj-database-url==2.1.0
|
||||||
|
faker==21.0.0
|
||||||
|
|
Loading…
Reference in New Issue