diff --git a/.gitignore b/.gitignore index e3651d1..7ac2912 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,6 @@ django_mongodb_cli.egg-info/ __pycache__ manage.py mongo_app/ -mongo_migrations/ mongo_project/ node_modules/ server.log diff --git a/django_mongodb_cli/app.py b/django_mongodb_cli/app.py index af47894..b32e64f 100644 --- a/django_mongodb_cli/app.py +++ b/django_mongodb_cli/app.py @@ -1,9 +1,10 @@ import click import os +import shutil import subprocess -from .utils import get_management_command +from .utils import get_management_command, random_app_name class App: @@ -35,17 +36,37 @@ def app(context): @app.command() -@click.argument("app_name", required=False, default="mongo_app") +@click.argument("app_name", required=False) def start(app_name): - """Run startapp command with the template from src/django-mongodb-app.""" + """Run startapp with a custom template and move the app into ./apps/.""" - click.echo("Running startapp.") + if not app_name: + app_name = random_app_name() + + if not app_name.isidentifier(): + raise click.UsageError( + f"App name '{app_name}' is not a valid Python identifier." + ) + + temp_path = app_name # Django will create the app here temporarily + target_path = os.path.join("apps", app_name) + + click.echo(f"Creating app '{app_name}' in ./apps") + + # Make sure ./apps exists + os.makedirs("apps", exist_ok=True) + + # Run the Django startapp command command = get_management_command("startapp") subprocess.run( command + [ - app_name, + temp_path, "--template", - os.path.join("src", "django-project-templates", "app_template"), + os.path.join("templates", "app_template"), ], + check=True, ) + + # Move the generated app into ./apps/ + shutil.move(temp_path, target_path) diff --git a/django_mongodb_cli/proj.py b/django_mongodb_cli/proj.py index 501fbe4..acbf09a 100644 --- a/django_mongodb_cli/proj.py +++ b/django_mongodb_cli/proj.py @@ -4,7 +4,7 @@ import subprocess -from .utils import get_management_command +from .utils import DELETE_DIRS_AND_FILES, get_management_command class Proj: @@ -22,13 +22,27 @@ def __repr__(self): @click.group(invoke_without_command=True) +@click.option("-d", "--delete", is_flag=True, help="Delete existing project files") @click.pass_context -def proj(context): +def proj(context, delete): """ Create Django projects configured to test django-mongodb-backend. """ context.obj = Proj() + if delete: + for item, check_function in DELETE_DIRS_AND_FILES.items(): + if check_function(item): + if os.path.isdir(item): + shutil.rmtree(item) + click.echo(f"Removed directory: {item}") + elif os.path.isfile(item): + os.remove(item) + click.echo(f"Removed file: {item}") + else: + click.echo(f"Skipping: {item} does not exist") + return + # Show help only if no subcommand is invoked if context.invoked_subcommand is None: click.echo(context.get_help()) @@ -69,12 +83,10 @@ def run(): @proj.command() -@click.option("-d", "--delete", is_flag=True, help="Delete existing project files") @click.option("-dj", "--django", is_flag=True, help="Use django mongodb template") @click.option("-w", "--wagtail", is_flag=True, help="Use wagtail mongodb template") @click.argument("project_name", required=False, default="backend") def start( - delete, django, wagtail, project_name, @@ -82,41 +94,6 @@ def start( """Run Django's `startproject` with custom templates.""" if os.path.exists("manage.py"): click.echo("manage.py already exists") - if not delete: - click.echo("Use -d to delete existing project files") - return - if delete: - items = { - ".babelrc": os.path.isfile, - ".dockerignore": os.path.isfile, - ".browserslistrc": os.path.isfile, - ".eslintrc": os.path.isfile, - ".nvmrc": os.path.isfile, - ".stylelintrc.json": os.path.isfile, - "Dockerfile": os.path.isfile, - "apps": os.path.isdir, - "home": os.path.isdir, - "backend": os.path.isdir, - "db.sqlite3": os.path.isfile, - "frontend": os.path.isdir, - "mongo_migrations": os.path.isdir, - "manage.py": os.path.isfile, - "package-lock.json": os.path.isfile, - "package.json": os.path.isfile, - "postcss.config.js": os.path.isfile, - "requirements.txt": os.path.isfile, - "search": os.path.isdir, - } - for item, check_function in items.items(): - if check_function(item): - if os.path.isdir(item): - shutil.rmtree(item) - click.echo(f"Removed directory: {item}") - elif os.path.isfile(item): - os.remove(item) - click.echo(f"Removed file: {item}") - else: - click.echo(f"Skipping: {item} does not exist") return template = None django_admin = "django-admin" @@ -129,9 +106,7 @@ def start( elif django: template = os.path.join(os.path.join("src", "django-mongodb-project")) if not template: - template = os.path.join( - os.path.join("src", "django-mongodb-templates", "project_template") - ) + template = os.path.join(os.path.join("templates", "project_template")) click.echo(f"Using template: {template}") subprocess.run( [ @@ -143,9 +118,7 @@ def start( template, ] ) - frontend_template = os.path.join( - "src", "django-mongodb-templates", "frontend_template" - ) + frontend_template = os.path.join("templates", "frontend_template") click.echo(f"Using template: {frontend_template}") subprocess.run( [ @@ -158,7 +131,7 @@ def start( ] ) if not wagtail: - home_template = os.path.join("src", "django-mongodb-templates", "home_template") + home_template = os.path.join("templates", "home_template") click.echo(f"Using template: {home_template}") subprocess.run( [ diff --git a/django_mongodb_cli/repo.py b/django_mongodb_cli/repo.py index 2a1c2de..838d7a0 100644 --- a/django_mongodb_cli/repo.py +++ b/django_mongodb_cli/repo.py @@ -14,6 +14,7 @@ get_management_command, get_repos, get_status, + get_repo_name_map, install_package, ) @@ -33,15 +34,6 @@ def __repr__(self): pass_repo = click.make_pass_decorator(Repo) -def get_repo_name_map(repos, url_pattern): - """Return a dict mapping repo_name to repo_url from a list of repo URLs.""" - return { - os.path.basename(url_pattern.search(url).group(0)): url - for url in repos - if url_pattern.search(url) - } - - @click.group(invoke_without_command=True) @click.option( "-l", @@ -52,7 +44,7 @@ def get_repo_name_map(repos, url_pattern): @click.pass_context def repo(context, list_repos): """ - Run Django fork and third-party library tests. + Run tests configured to test django-mongodb-backend. """ context.obj = Repo() repos, url_pattern, branch_pattern = get_repos("pyproject.toml") @@ -207,15 +199,75 @@ def makemigrations(context, repo_name, args): click.echo(context.get_help()) +@repo.command() +@click.argument("repo_names", nargs=-1) +@click.option("-a", "--all-repos", is_flag=True, help="Status for all repositories") +@click.option("-r", "--reset", is_flag=True, help="Reset") +@click.option("-d", "--diff", is_flag=True, help="Show diff") +@click.option("-b", "--branch", is_flag=True, help="Show branch") +@click.option("-u", "--update", is_flag=True, help="Update repos") +@click.option("-l", "--log", is_flag=True, help="Show log") +@click.pass_context +@pass_repo +def status(repo, context, repo_names, all_repos, reset, diff, branch, update, log): + """Repository status.""" + repos, url_pattern, _ = get_repos("pyproject.toml") + repo_name_map = get_repo_name_map(repos, url_pattern) + + # Status for specified repo names + if repo_names: + not_found = [] + for repo_name in repo_names: + repo_url = repo_name_map.get(repo_name) + if repo_url: + get_status( + repo_url, + url_pattern, + repo, + reset=reset, + diff=diff, + branch=branch, + update=update, + log=log, + ) + else: + not_found.append(repo_name) + for name in not_found: + click.echo(f"Repository '{name}' not found.") + return + + # Status for all repos + if all_repos: + click.echo(f"Status of {len(repos)} repositories...") + for repo_name, repo_url in repo_name_map.items(): + get_status( + repo_url, + url_pattern, + repo, + reset=reset, + diff=diff, + branch=branch, + update=update, + log=log, + ) + return + + # Show help if nothing selected + click.echo(context.get_help()) + + @repo.command() @click.argument("repo_name", required=False) @click.argument("modules", nargs=-1) @click.option("-k", "--keyword", help="Filter tests by keyword") @click.option("-l", "--list-tests", is_flag=True, help="List tests") -@click.option("-s", "--show", is_flag=True, help="Show settings") +@click.option("-s", "--show-settings", is_flag=True, help="Show settings") +@click.option("-a", "--all-repos", is_flag=True, help="All repos") @click.option("--keepdb", is_flag=True, help="Keep db") @click.pass_context -def test(context, repo_name, modules, keyword, list_tests, show, keepdb): +def test( + context, repo_name, modules, keyword, list_tests, show_settings, keepdb, all_repos +): """Run tests for Django fork and third-party libraries.""" repos, url_pattern, _ = get_repos("pyproject.toml") repo_name_map = get_repo_name_map(repos, url_pattern) @@ -229,7 +281,7 @@ def test(context, repo_name, modules, keyword, list_tests, show, keepdb): ) return - if show: + if show_settings: click.echo(f"⚙️ Test settings for 📦 {repo_name}:") settings_dict = dict(sorted(test_settings_map[repo_name].items())) formatted = format_str(str(settings_dict), mode=Mode()) @@ -327,64 +379,23 @@ def test(context, repo_name, modules, keyword, list_tests, show, keepdb): subprocess.run(command, cwd=settings["test_dir"]) return - # No repo_name, show help - click.echo(context.get_help()) - - -@repo.command() -@click.argument("repo_names", nargs=-1) -@click.option("-a", "--all-repos", is_flag=True, help="Status for all repositories") -@click.option("-r", "--reset", is_flag=True, help="Reset") -@click.option("-d", "--diff", is_flag=True, help="Show diff") -@click.option("-b", "--branch", is_flag=True, help="Show branch") -@click.option("-u", "--update", is_flag=True, help="Update repos") -@click.option("-l", "--log", is_flag=True, help="Show log") -@click.pass_context -@pass_repo -def status(repo, context, repo_names, all_repos, reset, diff, branch, update, log): - """Repository status.""" - repos, url_pattern, _ = get_repos("pyproject.toml") - repo_name_map = get_repo_name_map(repos, url_pattern) - - # Status for specified repo names - if repo_names: - not_found = [] - for repo_name in repo_names: - repo_url = repo_name_map.get(repo_name) - if repo_url: - get_status( - repo_url, - url_pattern, - repo, - reset=reset, - diff=diff, - branch=branch, - update=update, - log=log, - ) + if all_repos and show_settings: + repos, url_pattern, _ = get_repos("pyproject.toml") + repo_name_map = get_repo_name_map(repos, url_pattern) + for repo_name in repo_name_map: + if repo_name in test_settings_map: + click.echo(f"⚙️ Test settings for 📦 {repo_name}:") + settings_dict = dict(sorted(test_settings_map[repo_name].items())) + formatted = format_str(str(settings_dict), mode=Mode()) + rprint(formatted) else: - not_found.append(repo_name) - for name in not_found: - click.echo(f"Repository '{name}' not found.") + click.echo(f"Settings for '{repo_name}' not found.") return - - # Status for all repos - if all_repos: - click.echo(f"Status of {len(repos)} repositories...") - for repo_name, repo_url in repo_name_map.items(): - get_status( - repo_url, - url_pattern, - repo, - reset=reset, - diff=diff, - branch=branch, - update=update, - log=log, - ) + else: + click.echo("Can only use --all-repos with --show-settings") return - # Show help if nothing selected + # No repo_name, show help click.echo(context.get_help()) diff --git a/django_mongodb_cli/settings.py b/django_mongodb_cli/settings.py index 6fd9e2b..24aae0b 100644 --- a/django_mongodb_cli/settings.py +++ b/django_mongodb_cli/settings.py @@ -15,13 +15,8 @@ "test_dir": join("src", "django", "tests"), "clone_dir": join("src", "django"), "migrations_dir": { - "source": join( - "src", - "django-mongodb-templates", - "project_template", - "mongo_migrations", - ), - "target": join("src", "django", "tests"), + "source": "mongo_migrations", + "target": join("src", "django", "tests", "mongo_migrations"), }, "settings": { "test": { @@ -50,12 +45,7 @@ "test_command": "./runtests.py", "test_dir": join("src", "django-filter"), "migrations_dir": { - "source": join( - "src", - "django-mongodb-templates", - "project_template", - "mongo_migrations", - ), + "source": "mongo_migrations", "target": join("src", "django-filter", "tests", "mongo_migrations"), }, "clone_dir": join("src", "django-filter"), @@ -81,7 +71,7 @@ "target": join("src", "django-rest-framework", "tests", "mongo_apps.py"), }, "migrations_dir": { - "source": join("src", "django-mongodb-project", "mongo_migrations"), + "source": "mongo_migrations", "target": join("src", "django-rest-framework", "tests", "mongo_migrations"), }, "test_command": "./runtests.py", @@ -109,12 +99,7 @@ "target": join("src", "wagtail", "wagtail", "test", "mongo_apps.py"), }, "migrations_dir": { - "source": join( - "src", - "django-mongodb-templates", - "project_template", - "mongo_migrations", - ), + "source": "mongo_migrations", "target": join("src", "wagtail", "wagtail", "test", "mongo_migrations"), }, "test_command": "./runtests.py", @@ -241,7 +226,7 @@ }, }, "migrations_dir": { - "source": join("src", "django-mongodb-project", "mongo_migrations"), + "source": "mongo_migrations", "target": join("src", "django-allauth", "allauth", "mongo_migrations"), }, "test_dirs": [ diff --git a/django_mongodb_cli/utils.py b/django_mongodb_cli/utils.py index e203e31..ce915dc 100644 --- a/django_mongodb_cli/utils.py +++ b/django_mongodb_cli/utils.py @@ -2,8 +2,10 @@ import git import os import shutil +import string import sys import toml +import random import re import subprocess @@ -11,6 +13,33 @@ from .settings import test_settings_map +DELETE_DIRS_AND_FILES = { + ".babelrc": os.path.isfile, + ".dockerignore": os.path.isfile, + ".browserslistrc": os.path.isfile, + ".eslintrc": os.path.isfile, + ".nvmrc": os.path.isfile, + ".stylelintrc.json": os.path.isfile, + "Dockerfile": os.path.isfile, + "apps": os.path.isdir, + "home": os.path.isdir, + "backend": os.path.isdir, + "db.sqlite3": os.path.isfile, + "frontend": os.path.isdir, + "manage.py": os.path.isfile, + "package-lock.json": os.path.isfile, + "package.json": os.path.isfile, + "postcss.config.js": os.path.isfile, + "requirements.txt": os.path.isfile, + "search": os.path.isdir, +} + + +def random_app_name(prefix="app_", length=6): + suffix = "".join(random.choices(string.ascii_lowercase + string.digits, k=length)) + return prefix + suffix + + def copy_mongo_apps(repo_name): """ Copy the appropriate mongo_apps file based on the repo name. @@ -267,6 +296,15 @@ def clone_repo(repo_entry, url_pattern, branch_pattern, repo): ) +def get_repo_name_map(repos, url_pattern): + """Return a dict mapping repo_name to repo_url from a list of repo URLs.""" + return { + os.path.basename(url_pattern.search(url).group(0)): url + for url in repos + if url_pattern.search(url) + } + + def install_package(clone_path): """ Install a package from the given clone path. diff --git a/justfile b/justfile index 84810a6..7d98fba 100644 --- a/justfile +++ b/justfile @@ -13,7 +13,6 @@ git-clone: dm repo clone django-mongodb-backend --install dm repo clone django-mongodb-extensions --install dm repo clone django-mongodb-project - dm repo clone django-mongodb-templates dm repo clone mongo-python-driver --install # ---------------------------------------- django ---------------------------------------- diff --git a/mongo_migrations/__init__.py b/mongo_migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/mongo_migrations/admin/0001_initial.py b/mongo_migrations/admin/0001_initial.py new file mode 100644 index 0000000..5c5e8bf --- /dev/null +++ b/mongo_migrations/admin/0001_initial.py @@ -0,0 +1,88 @@ +# Generated by Django 5.0.9 on 2024-10-04 20:15 + +import django.contrib.admin.models +import django.db.models.deletion +import django.utils.timezone +import django_mongodb_backend.fields.auto +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + initial = True + + dependencies = [ + ("contenttypes", "0001_initial"), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name="LogEntry", + fields=[ + ( + "id", + django_mongodb_backend.fields.ObjectIdAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "action_time", + models.DateTimeField( + default=django.utils.timezone.now, + editable=False, + verbose_name="action time", + ), + ), + ( + "object_id", + models.TextField(blank=True, null=True, verbose_name="object id"), + ), + ( + "object_repr", + models.CharField(max_length=200, verbose_name="object repr"), + ), + ( + "action_flag", + models.PositiveSmallIntegerField( + choices=[(1, "Addition"), (2, "Change"), (3, "Deletion")], + verbose_name="action flag", + ), + ), + ( + "change_message", + models.TextField(blank=True, verbose_name="change message"), + ), + ( + "content_type", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="contenttypes.contenttype", + verbose_name="content type", + ), + ), + ( + "user", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + verbose_name="user", + ), + ), + ], + options={ + "verbose_name": "log entry", + "verbose_name_plural": "log entries", + "db_table": "django_admin_log", + "ordering": ["-action_time"], + }, + managers=[ + ("objects", django.contrib.admin.models.LogEntryManager()), + ], + ), + ] diff --git a/mongo_migrations/admin/__init__.py b/mongo_migrations/admin/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/mongo_migrations/auth/0001_initial.py b/mongo_migrations/auth/0001_initial.py new file mode 100644 index 0000000..00f75dd --- /dev/null +++ b/mongo_migrations/auth/0001_initial.py @@ -0,0 +1,202 @@ +# Generated by Django 5.0.9 on 2024-10-04 20:15 + +import django.contrib.auth.models +import django.contrib.auth.validators +import django.db.models.deletion +import django.utils.timezone +import django_mongodb_backend.fields.auto +from django.db import migrations, models + + +class Migration(migrations.Migration): + initial = True + + dependencies = [ + ("contenttypes", "0001_initial"), + ] + + operations = [ + migrations.CreateModel( + name="Permission", + fields=[ + ( + "id", + django_mongodb_backend.fields.ObjectIdAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=255, verbose_name="name")), + ("codename", models.CharField(max_length=100, verbose_name="codename")), + ( + "content_type", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="contenttypes.contenttype", + verbose_name="content type", + ), + ), + ], + options={ + "verbose_name": "permission", + "verbose_name_plural": "permissions", + "ordering": [ + "content_type__app_label", + "content_type__model", + "codename", + ], + "unique_together": {("content_type", "codename")}, + }, + managers=[ + ("objects", django.contrib.auth.models.PermissionManager()), + ], + ), + migrations.CreateModel( + name="Group", + fields=[ + ( + "id", + django_mongodb_backend.fields.ObjectIdAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "name", + models.CharField(max_length=150, unique=True, verbose_name="name"), + ), + ( + "permissions", + models.ManyToManyField( + blank=True, to="auth.permission", verbose_name="permissions" + ), + ), + ], + options={ + "verbose_name": "group", + "verbose_name_plural": "groups", + }, + managers=[ + ("objects", django.contrib.auth.models.GroupManager()), + ], + ), + migrations.CreateModel( + name="User", + fields=[ + ( + "id", + django_mongodb_backend.fields.ObjectIdAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("password", models.CharField(max_length=128, verbose_name="password")), + ( + "last_login", + models.DateTimeField( + blank=True, null=True, verbose_name="last login" + ), + ), + ( + "is_superuser", + models.BooleanField( + default=False, + help_text="Designates that this user has all permissions without explicitly assigning them.", + verbose_name="superuser status", + ), + ), + ( + "username", + models.CharField( + error_messages={ + "unique": "A user with that username already exists." + }, + help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.", + max_length=150, + unique=True, + validators=[ + django.contrib.auth.validators.UnicodeUsernameValidator() + ], + verbose_name="username", + ), + ), + ( + "first_name", + models.CharField( + blank=True, max_length=150, verbose_name="first name" + ), + ), + ( + "last_name", + models.CharField( + blank=True, max_length=150, verbose_name="last name" + ), + ), + ( + "email", + models.EmailField( + blank=True, max_length=254, verbose_name="email address" + ), + ), + ( + "is_staff", + models.BooleanField( + default=False, + help_text="Designates whether the user can log into this admin site.", + verbose_name="staff status", + ), + ), + ( + "is_active", + models.BooleanField( + default=True, + help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.", + verbose_name="active", + ), + ), + ( + "date_joined", + models.DateTimeField( + default=django.utils.timezone.now, verbose_name="date joined" + ), + ), + ( + "groups", + models.ManyToManyField( + blank=True, + help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.", + related_name="user_set", + related_query_name="user", + to="auth.group", + verbose_name="groups", + ), + ), + ( + "user_permissions", + models.ManyToManyField( + blank=True, + help_text="Specific permissions for this user.", + related_name="user_set", + related_query_name="user", + to="auth.permission", + verbose_name="user permissions", + ), + ), + ], + options={ + "verbose_name": "user", + "verbose_name_plural": "users", + "abstract": False, + "swappable": "AUTH_USER_MODEL", + }, + managers=[ + ("objects", django.contrib.auth.models.UserManager()), + ], + ), + ] diff --git a/mongo_migrations/auth/__init__.py b/mongo_migrations/auth/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/mongo_migrations/contenttypes/0001_initial.py b/mongo_migrations/contenttypes/0001_initial.py new file mode 100644 index 0000000..fb7812d --- /dev/null +++ b/mongo_migrations/contenttypes/0001_initial.py @@ -0,0 +1,44 @@ +# Generated by Django 5.0.9 on 2024-10-04 20:15 + +import django.contrib.contenttypes.models +import django_mongodb_backend.fields.auto +from django.db import migrations, models + + +class Migration(migrations.Migration): + initial = True + + dependencies = [] + + operations = [ + migrations.CreateModel( + name="ContentType", + fields=[ + ( + "id", + django_mongodb_backend.fields.ObjectIdAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("app_label", models.CharField(max_length=100)), + ( + "model", + models.CharField( + max_length=100, verbose_name="python model class name" + ), + ), + ], + options={ + "verbose_name": "content type", + "verbose_name_plural": "content types", + "db_table": "django_content_type", + "unique_together": {("app_label", "model")}, + }, + managers=[ + ("objects", django.contrib.contenttypes.models.ContentTypeManager()), + ], + ), + ] diff --git a/mongo_migrations/contenttypes/__init__.py b/mongo_migrations/contenttypes/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/qe.py b/qe.py index 7c50ace..b972224 100644 --- a/qe.py +++ b/qe.py @@ -1,5 +1,3 @@ -import code - from bson.binary import STANDARD from bson.codec_options import CodecOptions from pymongo import MongoClient @@ -25,7 +23,11 @@ client_encryption = ClientEncryption( kms_providers, key_vault_namespace, client, codec_options ) + +client.drop_database("test") + database = client["test"] + encrypted_fields = { "fields": [ { @@ -43,7 +45,21 @@ encrypted_collection = client_encryption.create_encrypted_collection( database, "encrypted_collection", encrypted_fields, "local" ) + patient_document = { + "patientName": "Jon Doe", + "patientId": 12345678, + "patientRecord": { + "ssn": "987-65-4320", + "billing": { + "type": "Visa", + "number": "4111111111111111", + }, + "billAmount": 1500, + }, + } + encrypted_collection = client["test"]["encrypted_collection"] + encrypted_collection.insert_one(patient_document) + print(encrypted_collection.find_one({"patientRecord.ssn": "987-65-4320"})) + except EncryptedCollectionError as e: print(f"Encrypted collection error: {e}") - -code.interact(local=locals()) diff --git a/templates/app_template/.github/workflows/django.yml b/templates/app_template/.github/workflows/django.yml new file mode 100644 index 0000000..9766b45 --- /dev/null +++ b/templates/app_template/.github/workflows/django.yml @@ -0,0 +1,30 @@ +name: Django CI + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + build: + + runs-on: ubuntu-latest + strategy: + max-parallel: 4 + matrix: + python-version: [3.7, 3.8, 3.9] + + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python-version }} + - name: Install Dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + - name: Run Tests + run: | + python manage.py test diff --git a/templates/app_template/__init__.py-tpl b/templates/app_template/__init__.py-tpl new file mode 100644 index 0000000..e69de29 diff --git a/templates/app_template/admin.py-tpl b/templates/app_template/admin.py-tpl new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/templates/app_template/admin.py-tpl @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/templates/app_template/apps.py-tpl b/templates/app_template/apps.py-tpl new file mode 100644 index 0000000..644f491 --- /dev/null +++ b/templates/app_template/apps.py-tpl @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class {{ camel_case_app_name }}Config(AppConfig): + default_auto_field = 'django_mongodb_backend.fields.ObjectIdAutoField' + name = 'apps.{{ app_name }}' diff --git a/templates/app_template/migrations/__init__.py-tpl b/templates/app_template/migrations/__init__.py-tpl new file mode 100644 index 0000000..e69de29 diff --git a/templates/app_template/models.py-tpl b/templates/app_template/models.py-tpl new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/templates/app_template/models.py-tpl @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/templates/app_template/templates/app_name.html b/templates/app_template/templates/app_name.html new file mode 100644 index 0000000..e5c9fbb --- /dev/null +++ b/templates/app_template/templates/app_name.html @@ -0,0 +1,552 @@ +{% load static webpack_loader %} + + + + {% stylesheet_pack 'app' %} + + + + + + + + Dashboard Template · Bootstrap v5.3 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+

Dashboard

+
+
+ + +
+ +
+
+ +

Section title

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
#HeaderHeaderHeaderHeader
1,001randomdataplaceholdertext
1,002placeholderirrelevantvisuallayout
1,003datarichdashboardtabular
1,003informationplaceholderillustrativedata
1,004textrandomlayoutdashboard
1,005dashboardirrelevanttextplaceholder
1,006dashboardillustrativerichdata
1,007placeholdertabularinformationirrelevant
1,008randomdataplaceholdertext
1,009placeholderirrelevantvisuallayout
1,010datarichdashboardtabular
1,011informationplaceholderillustrativedata
1,012textplaceholderlayoutdashboard
1,013dashboardirrelevanttextvisual
1,014dashboardillustrativerichdata
1,015randomtabularinformationtext
+
+
+
+
+ + + {% javascript_pack 'app' %} + + diff --git a/templates/app_template/tests.py-tpl b/templates/app_template/tests.py-tpl new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/templates/app_template/tests.py-tpl @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/templates/app_template/urls.py b/templates/app_template/urls.py new file mode 100644 index 0000000..cacc09a --- /dev/null +++ b/templates/app_template/urls.py @@ -0,0 +1,7 @@ +from django.urls import path + +from . import views + +urlpatterns = [ + path("", views.HomeView.as_view(), name="home"), +] diff --git a/templates/app_template/views.py-tpl b/templates/app_template/views.py-tpl new file mode 100644 index 0000000..2d07c3e --- /dev/null +++ b/templates/app_template/views.py-tpl @@ -0,0 +1,7 @@ +from django.views.generic import TemplateView + +# Create your views here. + + +class {{ camel_case_app_name}}View(TemplateView): + template_name = '{{ app_name }}.html' diff --git a/templates/frontend_template/project_name/.gitignore b/templates/frontend_template/project_name/.gitignore new file mode 100644 index 0000000..ddcb880 --- /dev/null +++ b/templates/frontend_template/project_name/.gitignore @@ -0,0 +1,15 @@ +# dependencies +node_modules + +# production +build + +# misc +.DS_Store + +npm-debug.log +yarn-error.log +yarn.lock +.yarnclean +.vscode +.idea diff --git a/templates/frontend_template/project_name/README.md b/templates/frontend_template/project_name/README.md new file mode 100644 index 0000000..0b0d8e0 --- /dev/null +++ b/templates/frontend_template/project_name/README.md @@ -0,0 +1,21 @@ +# README + +This project was created with [python-webpack-boilerplate](https://github.com/AccordBox/python-webpack-boilerplate) + +## Available Scripts + +In the project directory, you can run: + +### `npm run start` + +`npm run start` will launch a server process, which makes `live reloading` possible. + +If you change JS or SCSS files, the web page would auto refresh after the change. Now the server is working on port 9091 by default, but you can change it in `webpack/webpack.config.dev.js` + +### `npm run watch` + +run webpack in `watch` mode. + +### `npm run build` + +[production mode](https://webpack.js.org/guides/production/), Webpack would focus on minified bundles, lighter weight source maps, and optimized assets to improve load time. diff --git a/templates/frontend_template/project_name/vendors/.gitkeep b/templates/frontend_template/project_name/vendors/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/templates/frontend_template/project_name/vendors/images/.gitkeep b/templates/frontend_template/project_name/vendors/images/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/templates/frontend_template/project_name/vendors/images/mongodb.ico b/templates/frontend_template/project_name/vendors/images/mongodb.ico new file mode 100644 index 0000000..237f3bb Binary files /dev/null and b/templates/frontend_template/project_name/vendors/images/mongodb.ico differ diff --git a/templates/frontend_template/project_name/vendors/images/sample.jpg b/templates/frontend_template/project_name/vendors/images/sample.jpg new file mode 100644 index 0000000..257c5dc Binary files /dev/null and b/templates/frontend_template/project_name/vendors/images/sample.jpg differ diff --git a/templates/frontend_template/project_name/vendors/images/webpack.png b/templates/frontend_template/project_name/vendors/images/webpack.png new file mode 100644 index 0000000..6d788d5 Binary files /dev/null and b/templates/frontend_template/project_name/vendors/images/webpack.png differ diff --git a/templates/frontend_template/project_name/webpack/webpack.common.js b/templates/frontend_template/project_name/webpack/webpack.common.js new file mode 100644 index 0000000..9d82a49 --- /dev/null +++ b/templates/frontend_template/project_name/webpack/webpack.common.js @@ -0,0 +1,71 @@ +const glob = require("glob"); +const Path = require("path"); +const { CleanWebpackPlugin } = require("clean-webpack-plugin"); +const CopyWebpackPlugin = require("copy-webpack-plugin"); +const WebpackAssetsManifest = require("webpack-assets-manifest"); + +const getEntryObject = () => { + const entries = {}; + // for javascript/typescript entry file + glob + .sync(Path.join(__dirname, "../src/application/*.{js,ts}")) + .forEach((path) => { + const name = Path.basename(path); + const extension = Path.extname(path); + const entryName = name.replace(extension, ""); + if (entryName in entries) { + throw new Error(`Entry file conflict: ${entryName}`); + } + entries[entryName] = path; + }); + return entries; +}; + +module.exports = { + entry: getEntryObject(), + output: { + path: Path.join(__dirname, "../build"), + filename: "js/[name].js", + publicPath: "/static/", + assetModuleFilename: "[path][name][ext]", + }, + optimization: { + splitChunks: { + chunks: "all", + }, + + runtimeChunk: "single", + }, + plugins: [ + new CleanWebpackPlugin(), + new CopyWebpackPlugin({ + patterns: [ + { from: Path.resolve(__dirname, "../vendors"), to: "vendors" }, + ], + }), + new WebpackAssetsManifest({ + entrypoints: true, + output: "manifest.json", + writeToDisk: true, + publicPath: true, + }), + ], + resolve: { + alias: { + "~": Path.resolve(__dirname, "../src"), + }, + }, + module: { + rules: [ + { + test: /\.mjs$/, + include: /node_modules/, + type: "javascript/auto", + }, + { + test: /\.(ico|jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2)(\?.*)?$/, + type: "asset", + }, + ], + }, +}; diff --git a/templates/frontend_template/project_name/webpack/webpack.config.dev.js b/templates/frontend_template/project_name/webpack/webpack.config.dev.js new file mode 100644 index 0000000..d02292a --- /dev/null +++ b/templates/frontend_template/project_name/webpack/webpack.config.dev.js @@ -0,0 +1,73 @@ +const Path = require("path"); +const Webpack = require("webpack"); +const { merge } = require("webpack-merge"); +const StylelintPlugin = require("stylelint-webpack-plugin"); +const MiniCssExtractPlugin = require("mini-css-extract-plugin"); +const ESLintPlugin = require("eslint-webpack-plugin"); + +const common = require("./webpack.common.js"); + +module.exports = merge(common, { + target: "web", + mode: "development", + devtool: "inline-source-map", + output: { + chunkFilename: "js/[name].chunk.js", + publicPath: "http://localhost:9091/", + }, + devServer: { + hot: true, + host: "0.0.0.0", + port: 9091, + headers: { + "Access-Control-Allow-Origin": "*", + }, + devMiddleware: { + writeToDisk: true, + }, + }, + plugins: [ + new Webpack.DefinePlugin({ + "process.env.NODE_ENV": JSON.stringify("development"), + }), + new StylelintPlugin({ + files: Path.resolve(__dirname, "../src/**/*.s?(a|c)ss"), + }), + new ESLintPlugin({ + extensions: "js", + emitWarning: true, + files: Path.resolve(__dirname, "../src"), + }), + new MiniCssExtractPlugin({ + filename: "css/[name].css", + chunkFilename: "css/[id].css", + }), + ], + module: { + rules: [ + { + test: /\.html$/i, + loader: "html-loader", + }, + { + test: /\.js$/, + include: Path.resolve(__dirname, "../src"), + loader: "babel-loader", + }, + { + test: /\.s?css$/i, + use: [ + MiniCssExtractPlugin.loader, + { + loader: "css-loader", + options: { + sourceMap: true, + }, + }, + "postcss-loader", + "sass-loader", + ], + }, + ], + }, +}); diff --git a/templates/frontend_template/project_name/webpack/webpack.config.prod.js b/templates/frontend_template/project_name/webpack/webpack.config.prod.js new file mode 100644 index 0000000..4a0dd8f --- /dev/null +++ b/templates/frontend_template/project_name/webpack/webpack.config.prod.js @@ -0,0 +1,41 @@ +const Webpack = require("webpack"); +const { merge } = require("webpack-merge"); +const MiniCssExtractPlugin = require("mini-css-extract-plugin"); +const common = require("./webpack.common.js"); + +module.exports = merge(common, { + mode: "production", + devtool: "source-map", + bail: true, + output: { + filename: "js/[name].[chunkhash:8].js", + chunkFilename: "js/[name].[chunkhash:8].chunk.js", + }, + plugins: [ + new Webpack.DefinePlugin({ + "process.env.NODE_ENV": JSON.stringify("production"), + }), + new MiniCssExtractPlugin({ + filename: "css/[name].[contenthash].css", + chunkFilename: "css/[id].[contenthash].css", + }), + ], + module: { + rules: [ + { + test: /\.js$/, + exclude: /node_modules/, + use: "babel-loader", + }, + { + test: /\.s?css/i, + use: [ + MiniCssExtractPlugin.loader, + "css-loader", + "postcss-loader", + "sass-loader", + ], + }, + ], + }, +}); diff --git a/templates/frontend_template/project_name/webpack/webpack.config.watch.js b/templates/frontend_template/project_name/webpack/webpack.config.watch.js new file mode 100644 index 0000000..e8796cb --- /dev/null +++ b/templates/frontend_template/project_name/webpack/webpack.config.watch.js @@ -0,0 +1,61 @@ +const Path = require("path"); +const Webpack = require("webpack"); +const { merge } = require("webpack-merge"); +const StylelintPlugin = require("stylelint-webpack-plugin"); +const MiniCssExtractPlugin = require("mini-css-extract-plugin"); +const ESLintPlugin = require("eslint-webpack-plugin"); + +const common = require("./webpack.common.js"); + +module.exports = merge(common, { + target: "web", + mode: "development", + devtool: "inline-source-map", + output: { + chunkFilename: "js/[name].chunk.js", + }, + plugins: [ + new Webpack.DefinePlugin({ + "process.env.NODE_ENV": JSON.stringify("development"), + }), + new StylelintPlugin({ + files: Path.resolve(__dirname, "../src/**/*.s?(a|c)ss"), + }), + new ESLintPlugin({ + extensions: "js", + emitWarning: true, + files: Path.resolve(__dirname, "../src"), + }), + new MiniCssExtractPlugin({ + filename: "css/[name].css", + chunkFilename: "css/[id].css", + }), + ], + module: { + rules: [ + { + test: /\.html$/i, + loader: "html-loader", + }, + { + test: /\.js$/, + include: Path.resolve(__dirname, "../src"), + loader: "babel-loader", + }, + { + test: /\.s?css$/i, + use: [ + MiniCssExtractPlugin.loader, + { + loader: "css-loader", + options: { + sourceMap: true, + }, + }, + "postcss-loader", + "sass-loader", + ], + }, + ], + }, +}); diff --git a/templates/home_template/__init__.py b/templates/home_template/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/templates/home_template/apps.py b/templates/home_template/apps.py new file mode 100644 index 0000000..5f7be0a --- /dev/null +++ b/templates/home_template/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class HomeAppConfig(AppConfig): + default_auto_field = "django_mongodb_backend.fields.ObjectIdAutoField" + name = "home" diff --git a/templates/home_template/migrations/0001_initial.py b/templates/home_template/migrations/0001_initial.py new file mode 100644 index 0000000..c437c7f --- /dev/null +++ b/templates/home_template/migrations/0001_initial.py @@ -0,0 +1,35 @@ +# Generated by Django 5.0.11.dev20250107212806 on 2025-01-19 02:11 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + initial = True + + dependencies = [ + ("wagtailcore", "0001_initial"), + ] + + operations = [ + migrations.CreateModel( + name="HomePage", + fields=[ + ( + "page_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="wagtailcore.page", + ), + ), + ], + options={ + "abstract": False, + }, + bases=("wagtailcore.page",), + ), + ] diff --git a/templates/home_template/migrations/0002_create_homepage.py b/templates/home_template/migrations/0002_create_homepage.py new file mode 100644 index 0000000..4c02bef --- /dev/null +++ b/templates/home_template/migrations/0002_create_homepage.py @@ -0,0 +1,86 @@ +from bson import ObjectId +from django.db import migrations + +# Do not use settings in migrations under normal circumstances. +from django.conf import settings + + +def create_homepage(apps, schema_editor): + # Get models + ContentType = apps.get_model("contenttypes.ContentType") + Page = apps.get_model("wagtailcore.Page") + Site = apps.get_model("wagtailcore.Site") + HomePage = apps.get_model("home.HomePage") + + # Delete the default homepage + # If migration is run multiple times, it may have already been deleted + Page.objects.filter(id=ObjectId("000000000000000000000001")).delete() + + # Create content type for homepage model + homepage_content_type, __ = ContentType.objects.get_or_create( + model="homepage", app_label="home" + ) + + # Why this is needed in MongoDB and not in PostgreSQL? + locale = None + if settings.DATABASES["default"]["ENGINE"] == "django_mongodb_backend": + Locale = apps.get_model("wagtailcore.Locale") + locale = Locale.objects.get(language_code="en") + + # Create a new homepage + homepage_attrs = { + "title": "Home", + "draft_title": "Home", + "slug": "home", + "content_type": homepage_content_type, + "path": "00010001", + "depth": 1, + "numchild": 0, + "url_path": "/home/", + } + # Add the locale only if it's defined + if locale: + homepage_attrs["locale"] = locale + + homepage = HomePage.objects.create(**homepage_attrs) + + # Create a site with the new homepage set as the root + Site.objects.create(hostname="localhost", root_page=homepage, is_default_site=True) + + +def remove_homepage(apps, schema_editor): + # Get models + ContentType = apps.get_model("contenttypes.ContentType") + HomePage = apps.get_model("home.HomePage") + + # Delete the default homepage + # Page and Site objects CASCADE + HomePage.objects.filter(slug="home", depth=2).delete() + + # Delete content type for homepage model + ContentType.objects.filter(model="homepage", app_label="home").delete() + + +def create_locale(apps, schema_editor): + Locale = apps.get_model("wagtailcore", "Locale") + # Replace 'en' with your desired language code and add other fields as necessary. + Locale.objects.create(language_code="en") + + +def remove_locale(apps, schema_editor): + Locale = apps.get_model("wagtailcore", "Locale") + # Replace 'en' with the language code used in create_locale + Locale.objects.filter(language_code="en").delete() + + +class Migration(migrations.Migration): + dependencies = [ + ("home", "0001_initial"), + ] + + operations = [ + migrations.RunPython(create_homepage, remove_homepage), + ] + + if settings.DATABASES["default"]["ENGINE"] == "django_mongodb_backend": + operations.insert(0, migrations.RunPython(create_locale, remove_locale)) diff --git a/templates/home_template/migrations/__init__.py b/templates/home_template/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/templates/home_template/models.py b/templates/home_template/models.py new file mode 100644 index 0000000..f24c860 --- /dev/null +++ b/templates/home_template/models.py @@ -0,0 +1,5 @@ +from wagtail.models import Page + + +class HomePage(Page): + pass diff --git a/templates/home_template/static/css/welcome_page.css b/templates/home_template/static/css/welcome_page.css new file mode 100644 index 0000000..bad2933 --- /dev/null +++ b/templates/home_template/static/css/welcome_page.css @@ -0,0 +1,184 @@ +html { + box-sizing: border-box; +} + +*, +*:before, +*:after { + box-sizing: inherit; +} + +body { + max-width: 960px; + min-height: 100vh; + margin: 0 auto; + padding: 0 15px; + color: #231f20; + font-family: 'Helvetica Neue', 'Segoe UI', Arial, sans-serif; + line-height: 1.25; +} + +a { + background-color: transparent; + color: #308282; + text-decoration: underline; +} + +a:hover { + color: #ea1b10; +} + +h1, +h2, +h3, +h4, +h5, +p, +ul { + padding: 0; + margin: 0; + font-weight: 400; +} + +svg:not(:root) { + overflow: hidden; +} + +.header { + display: flex; + justify-content: space-between; + align-items: center; + padding-top: 20px; + padding-bottom: 10px; + border-bottom: 1px solid #e6e6e6; +} + +.logo { + width: 150px; + margin-inline-end: 20px; +} + +.logo a { + display: block; +} + +.figure-logo { + max-width: 150px; + max-height: 55.1px; +} + +.release-notes { + font-size: 14px; +} + +.main { + padding: 40px 0; + margin: 0 auto; + text-align: center; +} + +.figure-space { + max-width: 265px; +} + +@keyframes pos { + 0%, 100% { + transform: rotate(-6deg); + } + 50% { + transform: rotate(6deg); + } +} + +.egg { + fill: #43b1b0; + animation: pos 3s ease infinite; + transform: translateY(50px); + transform-origin: 50% 80%; +} + +.main-text { + max-width: 400px; + margin: 5px auto; +} + +.main-text h1 { + font-size: 22px; +} + +.main-text p { + margin: 15px auto 0; +} + +.footer { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + border-top: 1px solid #e6e6e6; + padding: 10px; +} + +.option { + display: block; + padding: 10px 10px 10px 34px; + position: relative; + text-decoration: none; +} + +.option svg { + width: 24px; + height: 24px; + fill: gray; + border: 1px solid #d9d9d9; + padding: 5px; + border-radius: 100%; + top: 10px; + inset-inline-start: 0; + position: absolute; +} + +.option h2 { + font-size: 19px; + text-decoration: underline; +} + +.option p { + padding-top: 3px; + color: #231f20; + font-size: 15px; + font-weight: 300; +} + +@media (max-width: 996px) { + body { + max-width: 780px; + } +} + +@media (max-width: 767px) { + .option { + flex: 0 0 50%; + } +} + +@media (max-width: 599px) { + .main { + padding: 20px 0; + } + + .figure-space { + max-width: 200px; + } + + .footer { + display: block; + width: 300px; + margin: 0 auto; + } +} + +@media (max-width: 360px) { + .header-link { + max-width: 100px; + } +} diff --git a/templates/home_template/static/js/bson_adapter.js b/templates/home_template/static/js/bson_adapter.js new file mode 100644 index 0000000..e845999 --- /dev/null +++ b/templates/home_template/static/js/bson_adapter.js @@ -0,0 +1,17 @@ +(function() { + class ObjectId { + constructor(value) { + this.value = value; + } + + toString() { + return this.value; + } + + static fromArgs(args) { + return new ObjectId(args[0]); + } + } + + window.telepath.register('ObjectId', ObjectId); +})(); diff --git a/templates/home_template/telepath_adapters/__init__.py b/templates/home_template/telepath_adapters/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/templates/home_template/telepath_adapters/bson_adapter.py b/templates/home_template/telepath_adapters/bson_adapter.py new file mode 100644 index 0000000..2d3039d --- /dev/null +++ b/templates/home_template/telepath_adapters/bson_adapter.py @@ -0,0 +1,12 @@ +from bson import ObjectId +from wagtail.telepath import Adapter, register + + +class ObjectIdAdapter(Adapter): + js_constructor = "ObjectId" + + def js_args(self, obj): + return [str(obj)] + + +register(ObjectId, ObjectIdAdapter) diff --git a/templates/home_template/wagtail_hooks.py b/templates/home_template/wagtail_hooks.py new file mode 100644 index 0000000..f83e308 --- /dev/null +++ b/templates/home_template/wagtail_hooks.py @@ -0,0 +1,7 @@ +from wagtail import hooks + + +@hooks.register("insert_editor_js") +def add_objectid_js(): + return """ + """ diff --git a/templates/project_template/project_name/__init__.py b/templates/project_template/project_name/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/templates/project_template/project_name/mongo_apps.py b/templates/project_template/project_name/mongo_apps.py new file mode 100644 index 0000000..0f44846 --- /dev/null +++ b/templates/project_template/project_name/mongo_apps.py @@ -0,0 +1,67 @@ +from django.contrib.admin.apps import AdminConfig +from django.contrib.auth.apps import AuthConfig +from django.contrib.contenttypes.apps import ContentTypesConfig + + +from taggit.apps import TaggitAppConfig +from wagtail.documents.apps import WagtailDocsAppConfig +from wagtail.contrib.redirects.apps import WagtailRedirectsAppConfig +from wagtail.images.apps import WagtailImagesAppConfig +from wagtail.search.apps import WagtailSearchAppConfig +from wagtail.admin.apps import WagtailAdminAppConfig +from wagtail.apps import WagtailAppConfig +from wagtail.contrib.forms.apps import WagtailFormsAppConfig +from wagtail.embeds.apps import WagtailEmbedsAppConfig +from wagtail.users.apps import WagtailUsersAppConfig + + +class MongoTaggitAppConfig(TaggitAppConfig): + default_auto_field = "django_mongodb_backend.fields.ObjectIdAutoField" + + +class MongoWagtailDocsAppConfig(WagtailDocsAppConfig): + default_auto_field = "django_mongodb_backend.fields.ObjectIdAutoField" + + +class MongoAdminConfig(AdminConfig): + default_auto_field = "django_mongodb_backend.fields.ObjectIdAutoField" + + +class MongoAuthConfig(AuthConfig): + default_auto_field = "django_mongodb_backend.fields.ObjectIdAutoField" + + +class MongoContentTypesConfig(ContentTypesConfig): + default_auto_field = "django_mongodb_backend.fields.ObjectIdAutoField" + + +class MongoWagtailRedirectsAppConfig(WagtailRedirectsAppConfig): + default_auto_field = "django_mongodb_backend.fields.ObjectIdAutoField" + + +class MongoWagtailImagesAppConfig(WagtailImagesAppConfig): + default_auto_field = "django_mongodb_backend.fields.ObjectIdAutoField" + + +class MongoWagtailSearchAppConfig(WagtailSearchAppConfig): + default_auto_field = "django_mongodb_backend.fields.ObjectIdAutoField" + + +class MongoWagtailAdminAppConfig(WagtailAdminAppConfig): + default_auto_field = "django_mongodb_backend.fields.ObjectIdAutoField" + + +class MongoWagtailAppConfig(WagtailAppConfig): + default_auto_field = "django_mongodb_backend.fields.ObjectIdAutoField" + + +class MongoWagtailFormsAppConfig(WagtailFormsAppConfig): + default_auto_field = "django_mongodb_backend.fields.ObjectIdAutoField" + + +class MongoWagtailEmbedsAppConfig(WagtailEmbedsAppConfig): + default_auto_field = "django_mongodb_backend.fields.ObjectIdAutoField" + + +class MongoWagtailUsersAppConfig(WagtailUsersAppConfig): + default_auto_field = "django_mongodb_backend.fields.ObjectIdAutoField" diff --git a/templates/project_template/project_name/settings/__init__.py b/templates/project_template/project_name/settings/__init__.py new file mode 100644 index 0000000..dd8469c --- /dev/null +++ b/templates/project_template/project_name/settings/__init__.py @@ -0,0 +1,159 @@ +import os + +from bson import ObjectId +from django_mongodb_backend import parse_uri + + +PROJECT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + +BASE_DIR = os.path.dirname(PROJECT_DIR) + +DEFAULT_AUTO_FIELD = "django_mongodb_backend.fields.ObjectIdAutoField" + +INSTALLED_APPS = [ + "{{ project_name }}.mongo_apps.MongoWagtailFormsAppConfig", + "{{ project_name }}.mongo_apps.MongoWagtailRedirectsAppConfig", + "{{ project_name }}.mongo_apps.MongoWagtailEmbedsAppConfig", + "wagtail.sites", + "{{ project_name }}.mongo_apps.MongoWagtailUsersAppConfig", + "wagtail.snippets", + "{{ project_name }}.mongo_apps.MongoWagtailDocsAppConfig", + "{{ project_name }}.mongo_apps.MongoWagtailImagesAppConfig", + "{{ project_name }}.mongo_apps.MongoWagtailSearchAppConfig", + "{{ project_name }}.mongo_apps.MongoWagtailAdminAppConfig", + "{{ project_name }}.mongo_apps.MongoWagtailAppConfig", + "modelcluster", + "{{ project_name }}.mongo_apps.MongoTaggitAppConfig", + "{{ project_name }}.mongo_apps.MongoAdminConfig", + "{{ project_name }}.mongo_apps.MongoAuthConfig", + "{{ project_name }}.mongo_apps.MongoContentTypesConfig", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "django_extensions", + "webpack_boilerplate", + "debug_toolbar", + "home", + "django_mongodb_extensions", +] + +MIGRATION_MODULES = { + "admin": "mongo_migrations.admin", + "auth": "mongo_migrations.auth", + "contenttypes": "mongo_migrations.contenttypes", + "taggit": "mongo_migrations.taggit", + "wagtaildocs": "mongo_migrations.wagtaildocs", + "wagtailredirects": "mongo_migrations.wagtailredirects", + "wagtailimages": "mongo_migrations.wagtailimages", + "wagtailsearch": "mongo_migrations.wagtailsearch", + "wagtailadmin": "mongo_migrations.wagtailadmin", + "wagtailcore": "mongo_migrations.wagtailcore", + "wagtailforms": "mongo_migrations.wagtailforms", + "wagtailembeds": "mongo_migrations.wagtailembeds", + "wagtailusers": "mongo_migrations.wagtailusers", +} + +DATABASES = { + "default": parse_uri( + os.environ.get("MONGODB_URI", "mongodb://localhost:27017/{{ project_name }}") + ) +} + +DEBUG = True + +ROOT_URLCONF = "{{ project_name }}.urls" + +STATICFILES_DIRS = [os.path.join(BASE_DIR, "frontend", "build")] + +WEBPACK_LOADER = { + "MANIFEST_FILE": os.path.join(BASE_DIR, "frontend", "build", "manifest.json") +} + +SECRET_KEY = "{{ secret_key }}" + +ALLOWED_HOSTS = ["*"] + +STATIC_URL = "static/" + +TEMPLATES = [ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [os.path.join("{{ project_name }}", "templates")], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", + ], + }, + }, +] + +MIDDLEWARE = [ + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", + "django.middleware.security.SecurityMiddleware", + "wagtail.contrib.redirects.middleware.RedirectMiddleware", + "debug_toolbar.middleware.DebugToolbarMiddleware", +] + +WAGTAIL_SITE_NAME = "{{ project_name }}" + +INTERNAL_IPS = [ + "127.0.0.1", +] + +SITE_ID = ObjectId("000000000000000000000001") + + +DEBUG_TOOLBAR_PANELS = [ + "debug_toolbar.panels.history.HistoryPanel", + "debug_toolbar.panels.versions.VersionsPanel", + "debug_toolbar.panels.timer.TimerPanel", + "debug_toolbar.panels.settings.SettingsPanel", + "debug_toolbar.panels.headers.HeadersPanel", + "debug_toolbar.panels.request.RequestPanel", + "django_mongodb_extensions.debug_toolbar.panels.mql.MQLPanel", + "debug_toolbar.panels.staticfiles.StaticFilesPanel", + "debug_toolbar.panels.templates.TemplatesPanel", + "debug_toolbar.panels.alerts.AlertsPanel", + "debug_toolbar.panels.cache.CachePanel", + "debug_toolbar.panels.signals.SignalsPanel", + "debug_toolbar.panels.redirects.RedirectsPanel", + "debug_toolbar.panels.profiling.ProfilingPanel", +] + +LOGGING = { + "version": 1, + "disable_existing_loggers": False, + "formatters": { + "verbose": { + "format": "{levelname} {asctime} {module} {message}", + "style": "{", + }, + "simple": { + "format": "{levelname} {message}", + "style": "{", + }, + }, + "handlers": { + "console": { + "level": "DEBUG", + "class": "logging.StreamHandler", + "formatter": "verbose", + }, + }, + "loggers": { + "django": { + "handlers": ["console"], + "level": "DEBUG", + "propagate": True, + }, + }, +} diff --git a/templates/project_template/project_name/templates/404.html b/templates/project_template/project_name/templates/404.html new file mode 100644 index 0000000..f19ab95 --- /dev/null +++ b/templates/project_template/project_name/templates/404.html @@ -0,0 +1,11 @@ +{% extends "base.html" %} + +{% block title %}Page not found{% endblock %} + +{% block body_class %}template-404{% endblock %} + +{% block content %} +

Page not found

+ +

Sorry, this page could not be found.

+{% endblock %} diff --git a/templates/project_template/project_name/templates/500.html b/templates/project_template/project_name/templates/500.html new file mode 100644 index 0000000..77379e5 --- /dev/null +++ b/templates/project_template/project_name/templates/500.html @@ -0,0 +1,13 @@ + + + + + Internal server error + + + +

Internal server error

+ +

Sorry, there seems to be an error. Please try again soon.

+ + diff --git a/templates/project_template/project_name/templates/base.html b/templates/project_template/project_name/templates/base.html new file mode 100644 index 0000000..5d8b2bb --- /dev/null +++ b/templates/project_template/project_name/templates/base.html @@ -0,0 +1,70 @@ +{% load static webpack_loader %} + + + + + + {% block title %}{% endblock %} + {% block title_suffix %}{% endblock %} + + + {% stylesheet_pack 'app' %} + {% block extra_css %}{# Override this in templates to add extra stylesheets #}{% endblock %} + + {% include 'favicon.html' %} + {% csrf_token %} + + +
+
+ {% include 'header.html' %} + {% if messages %} +
+ {% for message in messages %} + + {% endfor %} +
+ {% endif %} +
+ {% block content %}{% endblock %} +
+
+ {% include 'footer.html' %} + {% include 'offcanvas.html' %} + {% javascript_pack 'app' %} + {% block extra_js %}{# Override this in templates to add extra javascript #}{% endblock %} + + diff --git a/templates/project_template/project_name/templates/favicon.html b/templates/project_template/project_name/templates/favicon.html new file mode 100644 index 0000000..a7f0c0f --- /dev/null +++ b/templates/project_template/project_name/templates/favicon.html @@ -0,0 +1,2 @@ +{% load static %} + diff --git a/templates/project_template/project_name/templates/footer.html b/templates/project_template/project_name/templates/footer.html new file mode 100644 index 0000000..6a6862d --- /dev/null +++ b/templates/project_template/project_name/templates/footer.html @@ -0,0 +1,15 @@ + diff --git a/templates/project_template/project_name/templates/header.html b/templates/project_template/project_name/templates/header.html new file mode 100644 index 0000000..5456cde --- /dev/null +++ b/templates/project_template/project_name/templates/header.html @@ -0,0 +1,65 @@ +
+
+ +
+
diff --git a/templates/project_template/project_name/templates/offcanvas.html b/templates/project_template/project_name/templates/offcanvas.html new file mode 100644 index 0000000..29c1171 --- /dev/null +++ b/templates/project_template/project_name/templates/offcanvas.html @@ -0,0 +1,38 @@ +
+
+ {{ current_site.site_name|default:"Django MongoDB" }} + +
+
+ +
+
diff --git a/templates/project_template/project_name/urls.py b/templates/project_template/project_name/urls.py new file mode 100644 index 0000000..7706e1c --- /dev/null +++ b/templates/project_template/project_name/urls.py @@ -0,0 +1,11 @@ +from debug_toolbar.toolbar import debug_toolbar_urls +from django.urls import path, include +from django.contrib import admin +from .views import BaseView + + +urlpatterns = [ + path("django/", admin.site.urls), + path("wagtail/", include("wagtail.admin.urls")), + path("", BaseView.as_view(), name="base"), +] + debug_toolbar_urls() diff --git a/templates/project_template/project_name/utils.py b/templates/project_template/project_name/utils.py new file mode 100644 index 0000000..c4fd679 --- /dev/null +++ b/templates/project_template/project_name/utils.py @@ -0,0 +1,37 @@ +from django.urls import URLResolver +import requests + + +def get_ec2_metadata(): + try: + # Step 1: Get the token + token_url = "http://169.254.169.254/latest/api/token" + headers = {"X-aws-ec2-metadata-token-ttl-seconds": "21600"} + response = requests.put(token_url, headers=headers) + response.raise_for_status() # Raise an error for bad responses + + token = response.text + + # Step 2: Use the token to get the instance metadata + metadata_url = "http://169.254.169.254/latest/meta-data/local-ipv4" + headers = {"X-aws-ec2-metadata-token": token} + response = requests.get(metadata_url, headers=headers) + response.raise_for_status() # Raise an error for bad responses + + metadata = response.text + return metadata + except requests.RequestException as e: + print(f"Error retrieving EC2 metadata: {e}") + return None + + +# Function to remove a specific URL pattern based on its route (including catch-all) +def remove_urlpattern(urlpatterns, route_to_remove): + urlpatterns[:] = [ + urlpattern + for urlpattern in urlpatterns + if not ( + isinstance(urlpattern, URLResolver) + and urlpattern.pattern._route == route_to_remove + ) + ] diff --git a/templates/project_template/project_name/views.py-tpl b/templates/project_template/project_name/views.py-tpl new file mode 100644 index 0000000..188d033 --- /dev/null +++ b/templates/project_template/project_name/views.py-tpl @@ -0,0 +1,7 @@ +from django.views.generic import TemplateView + +# Create your views here. + + +class BaseView(TemplateView): + template_name = 'base.html' diff --git a/test/settings/django.py b/test/settings/django.py index e1b3baa..edfe5d6 100644 --- a/test/settings/django.py +++ b/test/settings/django.py @@ -11,16 +11,19 @@ kms_providers=KMS_PROVIDERS, ) +ENCRYPTED_DB_ALIAS = encryption.ENCRYPTED_DB_ALIAS +ENCRYPTED_APPS = encryption.ENCRYPTED_APPS + DATABASE_URL = os.environ.get("MONGODB_URI", "mongodb://localhost:27017") DATABASES = { "default": parse_uri( DATABASE_URL, db_name="test", ), - "encrypted": parse_uri( + ENCRYPTED_DB_ALIAS: parse_uri( DATABASE_URL, options={"auto_encryption_opts": AUTO_ENCRYPTION_OPTS}, - db_name="encrypted", + db_name=ENCRYPTED_DB_ALIAS, ), } @@ -29,13 +32,4 @@ SECRET_KEY = "django_tests_secret_key" USE_TZ = False - -class TestRouter: - def allow_migrate(self, db, app_label, model_name=None, **hints): - if db == "encrypted": - if app_label != "encryption_": - return False - return None - - -DATABASE_ROUTERS = [TestRouter()] +DATABASE_ROUTERS = [encryption.EncryptedRouter()] diff --git a/test/settings/wagtail.py b/test/settings/wagtail.py index 3a64633..722ecfa 100644 --- a/test/settings/wagtail.py +++ b/test/settings/wagtail.py @@ -49,10 +49,10 @@ }, } -if os.environ.get("STATICFILES_STORAGE", "") == "manifest": - STORAGES["staticfiles"]["BACKEND"] = ( - "django.contrib.staticfiles.storage.ManifestStaticFilesStorage" - ) +if os.environ.get("STATICFILES_STORAGE", "") == "manifest": # noqa + STORAGES["staticfiles"][ # noqa + "BACKEND" # noqa + ] = "django.contrib.staticfiles.storage.ManifestStaticFilesStorage" # noqa USE_TZ = not os.environ.get("DISABLE_TIMEZONE")