From 9910b386d2d86d18d2e86b64a22d1cb695dd0b1a Mon Sep 17 00:00:00 2001 From: Amirhossein Khalili Date: Fri, 24 Apr 2026 22:18:28 +0330 Subject: [PATCH] chore(django): track initial migrations and test config --- .gitignore | 6 +- apps/clients/migrations/0001_initial.py | 80 +++++ apps/projects/migrations/0001_initial.py | 306 +++++++++++++++++++ apps/tags/migrations/0001_initial.py | 80 +++++ apps/time_entries/migrations/0001_initial.py | 124 ++++++++ apps/users/migrations/0001_initial.py | 213 +++++++++++++ apps/workspaces/migrations/0001_initial.py | 158 ++++++++++ config/settings/test.py | 30 ++ pytest.ini | 3 + 9 files changed, 996 insertions(+), 4 deletions(-) create mode 100644 apps/clients/migrations/0001_initial.py create mode 100644 apps/projects/migrations/0001_initial.py create mode 100644 apps/tags/migrations/0001_initial.py create mode 100644 apps/time_entries/migrations/0001_initial.py create mode 100644 apps/users/migrations/0001_initial.py create mode 100644 apps/workspaces/migrations/0001_initial.py create mode 100644 config/settings/test.py create mode 100644 pytest.ini diff --git a/.gitignore b/.gitignore index 797bfd8..474775d 100644 --- a/.gitignore +++ b/.gitignore @@ -17,10 +17,8 @@ db.sqlite3 media/ staticfiles/ -# Migrations (except __init__.py) -**/migrations/*.py -**/migrations/*.pyc -!**/migrations/__init__.py +# Migrations +**/migrations/*.pyc # Logs *.log diff --git a/apps/clients/migrations/0001_initial.py b/apps/clients/migrations/0001_initial.py new file mode 100644 index 0000000..9001619 --- /dev/null +++ b/apps/clients/migrations/0001_initial.py @@ -0,0 +1,80 @@ +# Generated by Django 5.2.12 on 2026-03-11 10:22 + +import django.db.models.deletion +import uuid +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ("workspaces", "0001_initial"), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name="Client", + fields=[ + ( + "id", + models.UUIDField( + default=uuid.uuid7, primary_key=True, serialize=False + ), + ), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("updated_at", models.DateTimeField(auto_now=True)), + ("deleted_at", models.DateTimeField(blank=True, null=True)), + ("is_deleted", models.BooleanField(default=False)), + ("is_active", models.BooleanField(default=False)), + ("name", models.CharField(max_length=255)), + ("notes", models.TextField(blank=True)), + ( + "created_by", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="created_%(app_label)s_%(class)s_set", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "updated_by", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="updated_%(app_label)s_%(class)s_set", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "workspace", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="clients", + to="workspaces.workspace", + ), + ), + ], + options={ + "db_table": "client", + "ordering": ("-updated_at", "-created_at"), + "indexes": [ + models.Index(fields=["id"], name="client_id_idx"), + models.Index(fields=["workspace"], name="client_workspace_idx"), + ], + "constraints": [ + models.UniqueConstraint( + condition=models.Q(("is_deleted", False)), + fields=("workspace", "name"), + name="unique_client_name_per_workspace", + ) + ], + }, + ), + ] diff --git a/apps/projects/migrations/0001_initial.py b/apps/projects/migrations/0001_initial.py new file mode 100644 index 0000000..e938eec --- /dev/null +++ b/apps/projects/migrations/0001_initial.py @@ -0,0 +1,306 @@ +# Generated by Django 5.2.12 on 2026-03-11 11:01 + +import django.db.models.deletion +import uuid +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ("clients", "0001_initial"), + ("workspaces", "0001_initial"), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name="Project", + fields=[ + ( + "id", + models.UUIDField( + default=uuid.uuid7, primary_key=True, serialize=False + ), + ), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("updated_at", models.DateTimeField(auto_now=True)), + ("deleted_at", models.DateTimeField(blank=True, null=True)), + ("is_deleted", models.BooleanField(default=False)), + ("is_active", models.BooleanField(default=False)), + ("name", models.CharField(max_length=255)), + ("description", models.TextField(blank=True)), + ("is_archived", models.BooleanField(default=False)), + ("color", models.CharField(blank=True, max_length=7)), + ( + "client", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="projects", + to="clients.client", + ), + ), + ( + "created_by", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="created_%(app_label)s_%(class)s_set", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "updated_by", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="updated_%(app_label)s_%(class)s_set", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "workspace", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="projects", + to="workspaces.workspace", + ), + ), + ], + options={ + "db_table": "project", + "ordering": ("-updated_at", "-created_at"), + }, + ), + migrations.CreateModel( + name="ProjectMembership", + fields=[ + ( + "id", + models.UUIDField( + default=uuid.uuid7, primary_key=True, serialize=False + ), + ), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("updated_at", models.DateTimeField(auto_now=True)), + ("deleted_at", models.DateTimeField(blank=True, null=True)), + ("is_deleted", models.BooleanField(default=False)), + ( + "role", + models.CharField( + choices=[("manager", "Manager"), ("member", "Member")], + default="member", + max_length=20, + ), + ), + ("is_active", models.BooleanField(default=True)), + ( + "created_by", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="created_%(app_label)s_%(class)s_set", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "project", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="memberships", + to="projects.project", + ), + ), + ( + "updated_by", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="updated_%(app_label)s_%(class)s_set", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "user", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="project_memberships", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "db_table": "project_membership", + "ordering": ("-created_at",), + }, + ), + migrations.CreateModel( + name="ProjectRate", + fields=[ + ( + "id", + models.UUIDField( + default=uuid.uuid7, primary_key=True, serialize=False + ), + ), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("updated_at", models.DateTimeField(auto_now=True)), + ("deleted_at", models.DateTimeField(blank=True, null=True)), + ("is_deleted", models.BooleanField(default=False)), + ("hourly_rate", models.DecimalField(decimal_places=2, max_digits=10)), + ("currency", models.CharField(default="USD", max_length=3)), + ("effective_from", models.DateTimeField()), + ("is_active", models.BooleanField(default=True)), + ( + "created_by", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="created_%(app_label)s_%(class)s_set", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "project", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="rates", + to="projects.project", + ), + ), + ( + "updated_by", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="updated_%(app_label)s_%(class)s_set", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "db_table": "project_rate", + "ordering": ("-effective_from",), + }, + ), + migrations.CreateModel( + name="ProjectUserRate", + fields=[ + ( + "id", + models.UUIDField( + default=uuid.uuid7, primary_key=True, serialize=False + ), + ), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("updated_at", models.DateTimeField(auto_now=True)), + ("deleted_at", models.DateTimeField(blank=True, null=True)), + ("is_deleted", models.BooleanField(default=False)), + ("hourly_rate", models.DecimalField(decimal_places=2, max_digits=10)), + ("currency", models.CharField(default="USD", max_length=3)), + ("effective_from", models.DateTimeField()), + ("is_active", models.BooleanField(default=True)), + ( + "created_by", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="created_%(app_label)s_%(class)s_set", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "project", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="user_rates", + to="projects.project", + ), + ), + ( + "updated_by", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="updated_%(app_label)s_%(class)s_set", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "user", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="project_rates", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "db_table": "project_user_rate", + "ordering": ("-effective_from",), + }, + ), + migrations.AddIndex( + model_name="project", + index=models.Index(fields=["workspace"], name="project_workspace_idx"), + ), + migrations.AddConstraint( + model_name="project", + constraint=models.UniqueConstraint( + condition=models.Q(("is_deleted", False)), + fields=("workspace", "name"), + name="unique_project_name_per_workspace", + ), + ), + migrations.AddIndex( + model_name="projectmembership", + index=models.Index( + fields=["project"], name="project_membership_project_idx" + ), + ), + migrations.AddIndex( + model_name="projectmembership", + index=models.Index(fields=["user"], name="project_membership_user_idx"), + ), + migrations.AddConstraint( + model_name="projectmembership", + constraint=models.UniqueConstraint( + condition=models.Q(("is_deleted", False)), + fields=("project", "user"), + name="unique_project_membership", + ), + ), + migrations.AddIndex( + model_name="projectrate", + index=models.Index(fields=["project"], name="project_rate_project_idx"), + ), + migrations.AddIndex( + model_name="projectuserrate", + index=models.Index(fields=["project"], name="pur_project_idx"), + ), + migrations.AddIndex( + model_name="projectuserrate", + index=models.Index(fields=["user"], name="pur_user_idx"), + ), + migrations.AddConstraint( + model_name="projectuserrate", + constraint=models.UniqueConstraint( + condition=models.Q(("is_deleted", False)), + fields=("project", "user", "effective_from"), + name="unique_project_user_rate_time", + ), + ), + ] diff --git a/apps/tags/migrations/0001_initial.py b/apps/tags/migrations/0001_initial.py new file mode 100644 index 0000000..1518574 --- /dev/null +++ b/apps/tags/migrations/0001_initial.py @@ -0,0 +1,80 @@ +# Generated by Django 5.2.12 on 2026-03-11 11:16 + +import django.db.models.deletion +import uuid +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ("workspaces", "0001_initial"), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name="Tag", + fields=[ + ( + "id", + models.UUIDField( + default=uuid.uuid7, primary_key=True, serialize=False + ), + ), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("updated_at", models.DateTimeField(auto_now=True)), + ("deleted_at", models.DateTimeField(blank=True, null=True)), + ("is_deleted", models.BooleanField(default=False)), + ("is_active", models.BooleanField(default=False)), + ("name", models.CharField(max_length=100)), + ("color", models.CharField(blank=True, max_length=7)), + ( + "created_by", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="created_%(app_label)s_%(class)s_set", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "updated_by", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="updated_%(app_label)s_%(class)s_set", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "workspace", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="tags", + to="workspaces.workspace", + ), + ), + ], + options={ + "db_table": "tag", + "ordering": ("-updated_at", "-created_at"), + "indexes": [ + models.Index(fields=["id"], name="tag_id_idx"), + models.Index(fields=["workspace"], name="tag_workspace_idx"), + ], + "constraints": [ + models.UniqueConstraint( + condition=models.Q(("is_deleted", False)), + fields=("workspace", "name"), + name="unique_tag_name_per_workspace", + ) + ], + }, + ), + ] diff --git a/apps/time_entries/migrations/0001_initial.py b/apps/time_entries/migrations/0001_initial.py new file mode 100644 index 0000000..0159637 --- /dev/null +++ b/apps/time_entries/migrations/0001_initial.py @@ -0,0 +1,124 @@ +# Generated by Django 5.2.12 on 2026-03-11 11:29 + +import django.db.models.deletion +import uuid +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ("projects", "0001_initial"), + ("tags", "0001_initial"), + ("workspaces", "0001_initial"), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name="TimeEntry", + fields=[ + ( + "id", + models.UUIDField( + default=uuid.uuid7, primary_key=True, serialize=False + ), + ), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("updated_at", models.DateTimeField(auto_now=True)), + ("deleted_at", models.DateTimeField(blank=True, null=True)), + ("is_deleted", models.BooleanField(default=False)), + ("is_active", models.BooleanField(default=False)), + ("description", models.TextField(blank=True)), + ("start_time", models.DateTimeField()), + ("end_time", models.DateTimeField(blank=True, null=True)), + ("duration", models.DurationField(blank=True, null=True)), + ("is_billable", models.BooleanField(default=False)), + ( + "hourly_rate", + models.DecimalField( + blank=True, decimal_places=2, max_digits=10, null=True + ), + ), + ("currency", models.CharField(default="USD", max_length=3)), + ( + "created_by", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="created_%(app_label)s_%(class)s_set", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "project", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="time_entries", + to="projects.project", + ), + ), + ( + "tags", + models.ManyToManyField( + blank=True, related_name="time_entries", to="tags.tag" + ), + ), + ( + "updated_by", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="updated_%(app_label)s_%(class)s_set", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "user", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="time_entries", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "workspace", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="time_entries", + to="workspaces.workspace", + ), + ), + ], + options={ + "db_table": "time_entry", + "ordering": ("-updated_at", "-created_at"), + "indexes": [ + models.Index(fields=["workspace"], name="time_entry_workspace_idx"), + models.Index(fields=["user"], name="time_entry_user_idx"), + models.Index(fields=["project"], name="time_entry_project_idx"), + models.Index(fields=["start_time"], name="time_entry_start_idx"), + models.Index( + fields=["workspace", "start_time"], + name="time_entry_workspace_start_idx", + ), + ], + "constraints": [ + models.UniqueConstraint( + condition=models.Q( + ("end_time__isnull", True), ("is_deleted", False) + ), + fields=("workspace", "user"), + name="unique_running_timer_per_user", + ) + ], + }, + ), + ] diff --git a/apps/users/migrations/0001_initial.py b/apps/users/migrations/0001_initial.py new file mode 100644 index 0000000..9803f2f --- /dev/null +++ b/apps/users/migrations/0001_initial.py @@ -0,0 +1,213 @@ +# Generated by Django 5.2.12 on 2026-03-10 23:27 + +import apps.users.services.managers +import django.db.models.deletion +import django.utils.timezone +import uuid +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ("auth", "0012_alter_user_first_name_max_length"), + ] + + operations = [ + migrations.CreateModel( + name="User", + fields=[ + ("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", + ), + ), + ( + "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" + ), + ), + ( + "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" + ), + ), + ( + "id", + models.UUIDField( + default=uuid.uuid7, primary_key=True, serialize=False + ), + ), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("updated_at", models.DateTimeField(auto_now=True)), + ("deleted_at", models.DateTimeField(blank=True, null=True)), + ("is_deleted", models.BooleanField(default=False)), + ("mobile", models.CharField(max_length=11, unique=True)), + ("email", models.EmailField(blank=True, default="", max_length=254)), + ("description", models.TextField(blank=True, default="")), + ( + "profile_picture", + models.ImageField( + blank=True, null=True, upload_to="profile/users/" + ), + ), + ("birth_date", models.DateField(blank=True, null=True)), + ("password_updated_at", models.DateTimeField(blank=True, null=True)), + ("is_verified", models.BooleanField(default=False)), + ( + "created_by", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="created_%(app_label)s_%(class)s_set", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "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", + ), + ), + ( + "updated_by", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="updated_%(app_label)s_%(class)s_set", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "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", + "db_table": "user", + "ordering": ("-updated_at", "-created_at"), + }, + managers=[ + ("objects", apps.users.services.managers.UserManager(alive_only=True)), + ], + ), + migrations.CreateModel( + name="LoginAttempt", + fields=[ + ( + "id", + models.UUIDField( + default=uuid.uuid7, primary_key=True, serialize=False + ), + ), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("updated_at", models.DateTimeField(auto_now=True)), + ("deleted_at", models.DateTimeField(blank=True, null=True)), + ("is_deleted", models.BooleanField(default=False)), + ("is_active", models.BooleanField(default=False)), + ( + "status", + models.PositiveSmallIntegerField( + choices=[(0, "failed"), (1, "success")], default=0 + ), + ), + ("ip_address", models.GenericIPAddressField(blank=True, null=True)), + ( + "created_by", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="created_%(app_label)s_%(class)s_set", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "updated_by", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="updated_%(app_label)s_%(class)s_set", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "user", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "verbose_name": "login_attempts", + "verbose_name_plural": "login_attempts", + "db_table": "login_attempt", + "ordering": ("-updated_at", "-created_at"), + }, + ), + migrations.AddIndex( + model_name="user", + index=models.Index(fields=["id"], name="user_id_idx"), + ), + migrations.AddIndex( + model_name="user", + index=models.Index(fields=["mobile"], name="user_mobile_idx"), + ), + ] diff --git a/apps/workspaces/migrations/0001_initial.py b/apps/workspaces/migrations/0001_initial.py new file mode 100644 index 0000000..6b581e6 --- /dev/null +++ b/apps/workspaces/migrations/0001_initial.py @@ -0,0 +1,158 @@ +# Generated by Django 5.2.12 on 2026-03-11 09:14 + +import django.db.models.deletion +import uuid +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name="Workspace", + fields=[ + ( + "id", + models.UUIDField( + default=uuid.uuid7, primary_key=True, serialize=False + ), + ), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("updated_at", models.DateTimeField(auto_now=True)), + ("deleted_at", models.DateTimeField(blank=True, null=True)), + ("is_deleted", models.BooleanField(default=False)), + ("is_active", models.BooleanField(default=False)), + ("name", models.CharField(max_length=255)), + ("description", models.TextField(blank=True)), + ( + "created_by", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="created_%(app_label)s_%(class)s_set", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "owner", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name="owned_workspaces", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "updated_by", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="updated_%(app_label)s_%(class)s_set", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "db_table": "workspace", + "ordering": ("-updated_at", "-created_at"), + }, + ), + migrations.CreateModel( + name="WorkspaceMembership", + fields=[ + ( + "id", + models.UUIDField( + default=uuid.uuid7, primary_key=True, serialize=False + ), + ), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("updated_at", models.DateTimeField(auto_now=True)), + ("deleted_at", models.DateTimeField(blank=True, null=True)), + ("is_deleted", models.BooleanField(default=False)), + ( + "role", + models.CharField( + choices=[ + ("owner", "Owner"), + ("admin", "Admin"), + ("member", "Member"), + ("guest", "Guest"), + ], + default="member", + max_length=20, + ), + ), + ("is_active", models.BooleanField(default=True)), + ("joined_at", models.DateTimeField(auto_now_add=True)), + ( + "created_by", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="created_%(app_label)s_%(class)s_set", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "updated_by", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="updated_%(app_label)s_%(class)s_set", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "user", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="workspace_memberships", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "workspace", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="memberships", + to="workspaces.workspace", + ), + ), + ], + options={ + "db_table": "workspace_membership", + "ordering": ("-created_at",), + }, + ), + migrations.AddIndex( + model_name="workspace", + index=models.Index(fields=["owner"], name="workspace_owner_idx"), + ), + migrations.AddIndex( + model_name="workspacemembership", + index=models.Index(fields=["workspace"], name="membership_workspace_idx"), + ), + migrations.AddIndex( + model_name="workspacemembership", + index=models.Index(fields=["user"], name="membership_user_idx"), + ), + migrations.AddConstraint( + model_name="workspacemembership", + constraint=models.UniqueConstraint( + condition=models.Q(("is_deleted", False)), + fields=("workspace", "user"), + name="unique_workspace_membership", + ), + ), + ] diff --git a/config/settings/test.py b/config/settings/test.py new file mode 100644 index 0000000..4503056 --- /dev/null +++ b/config/settings/test.py @@ -0,0 +1,30 @@ +from .base import * + +DEBUG = False +SECRET_KEY = SECRET_KEY or "qlockify-test-secret-key" + +DATABASES = { + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": BASE_DIR / "test.sqlite3", + } +} + +CACHES = { + "default": { + "BACKEND": "django.core.cache.backends.locmem.LocMemCache", + "LOCATION": "qlockify-tests", + } +} + +PASSWORD_HASHERS = [ + "django.contrib.auth.hashers.MD5PasswordHasher", +] + +MEDIA_ROOT = BASE_DIR / "test_media" +STATIC_ROOT = BASE_DIR / "test_static" + +CELERY_TASK_ALWAYS_EAGER = True +CELERY_TASK_EAGER_PROPAGATES = True + +EMAIL_BACKEND = "django.core.mail.backends.locmem.EmailBackend" diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..e203b62 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +DJANGO_SETTINGS_MODULE = config.settings.test +python_files = tests.py test_*.py *_tests.py