From 8aa56c463ab8a1f301434440643dd9764b8db44f Mon Sep 17 00:00:00 2001 From: pacnpal <183241239+pacnpal@users.noreply.github.com> Date: Thu, 25 Sep 2025 08:39:09 -0400 Subject: [PATCH] Add initial migration for moderation app and document seed command database migration issue - Created an empty migration file for the moderation app to address missing migrations. - Documented the root cause analysis and solution steps for the seed command failure due to missing moderation tables. - Identified and resolved a VARCHAR(10) constraint violation in the User model during seed command execution. - Updated seed command logic to ensure compliance with field length constraints. --- apps/moderation/migrations/0001_initial.py | 1867 ++++++++++++++++++++ apps/moderation/migrations/__init__.py | 0 memory-bank/seed-command-fix.md | 106 ++ 3 files changed, 1973 insertions(+) create mode 100644 apps/moderation/migrations/0001_initial.py create mode 100644 apps/moderation/migrations/__init__.py create mode 100644 memory-bank/seed-command-fix.md diff --git a/apps/moderation/migrations/0001_initial.py b/apps/moderation/migrations/0001_initial.py new file mode 100644 index 00000000..fd9ef80d --- /dev/null +++ b/apps/moderation/migrations/0001_initial.py @@ -0,0 +1,1867 @@ +# Generated by Django 5.2.6 on 2025-09-24 15:03 + +import apps.core.choices.fields +import django.db.models.deletion +import pgtrigger.compiler +import pgtrigger.migrations +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ("contenttypes", "0002_remove_content_type_name"), + ("django_cloudflareimages_toolkit", "0001_initial"), + ("pghistory", "0007_auto_20250421_0444"), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name="BulkOperation", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "operation_type", + apps.core.choices.fields.RichChoiceField( + allow_deprecated=False, + choice_group="bulk_operation_types", + choices=[ + ("UPDATE_PARKS", "Update Parks"), + ("UPDATE_RIDES", "Update Rides"), + ("IMPORT_DATA", "Import Data"), + ("EXPORT_DATA", "Export Data"), + ("MODERATE_CONTENT", "Moderate Content"), + ("USER_ACTIONS", "User Actions"), + ("CLEANUP", "Cleanup"), + ("OTHER", "Other"), + ], + domain="moderation", + max_length=50, + ), + ), + ( + "status", + apps.core.choices.fields.RichChoiceField( + allow_deprecated=False, + choice_group="bulk_operation_statuses", + choices=[ + ("PENDING", "Pending"), + ("RUNNING", "Running"), + ("COMPLETED", "Completed"), + ("FAILED", "Failed"), + ("CANCELLED", "Cancelled"), + ], + default="PENDING", + domain="moderation", + max_length=20, + ), + ), + ( + "priority", + apps.core.choices.fields.RichChoiceField( + allow_deprecated=False, + choice_group="priority_levels", + choices=[ + ("LOW", "Low"), + ("MEDIUM", "Medium"), + ("HIGH", "High"), + ("URGENT", "Urgent"), + ], + default="MEDIUM", + domain="moderation", + max_length=10, + ), + ), + ( + "description", + models.TextField( + help_text="Description of what this operation does" + ), + ), + ( + "parameters", + models.JSONField( + default=dict, help_text="Parameters for the operation" + ), + ), + ( + "results", + models.JSONField( + blank=True, + default=dict, + help_text="Results and output from the operation", + ), + ), + ( + "total_items", + models.PositiveIntegerField( + default=0, help_text="Total number of items to process" + ), + ), + ( + "processed_items", + models.PositiveIntegerField( + default=0, help_text="Number of items processed" + ), + ), + ( + "failed_items", + models.PositiveIntegerField( + default=0, help_text="Number of items that failed" + ), + ), + ( + "estimated_duration_minutes", + models.PositiveIntegerField( + blank=True, help_text="Estimated duration in minutes", null=True + ), + ), + ( + "schedule_for", + models.DateTimeField( + blank=True, help_text="When to run this operation", null=True + ), + ), + ( + "can_cancel", + models.BooleanField( + default=True, + help_text="Whether this operation can be cancelled", + ), + ), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("started_at", models.DateTimeField(blank=True, null=True)), + ("completed_at", models.DateTimeField(blank=True, null=True)), + ("updated_at", models.DateTimeField(auto_now=True)), + ( + "created_by", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="bulk_operations_created", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "ordering": ["-created_at"], + "abstract": False, + }, + ), + migrations.CreateModel( + name="BulkOperationEvent", + fields=[ + ("pgh_id", models.AutoField(primary_key=True, serialize=False)), + ("pgh_created_at", models.DateTimeField(auto_now_add=True)), + ("pgh_label", models.TextField(help_text="The event label.")), + ("id", models.BigIntegerField()), + ( + "operation_type", + apps.core.choices.fields.RichChoiceField( + allow_deprecated=False, + choice_group="bulk_operation_types", + choices=[ + ("UPDATE_PARKS", "Update Parks"), + ("UPDATE_RIDES", "Update Rides"), + ("IMPORT_DATA", "Import Data"), + ("EXPORT_DATA", "Export Data"), + ("MODERATE_CONTENT", "Moderate Content"), + ("USER_ACTIONS", "User Actions"), + ("CLEANUP", "Cleanup"), + ("OTHER", "Other"), + ], + domain="moderation", + max_length=50, + ), + ), + ( + "status", + apps.core.choices.fields.RichChoiceField( + allow_deprecated=False, + choice_group="bulk_operation_statuses", + choices=[ + ("PENDING", "Pending"), + ("RUNNING", "Running"), + ("COMPLETED", "Completed"), + ("FAILED", "Failed"), + ("CANCELLED", "Cancelled"), + ], + default="PENDING", + domain="moderation", + max_length=20, + ), + ), + ( + "priority", + apps.core.choices.fields.RichChoiceField( + allow_deprecated=False, + choice_group="priority_levels", + choices=[ + ("LOW", "Low"), + ("MEDIUM", "Medium"), + ("HIGH", "High"), + ("URGENT", "Urgent"), + ], + default="MEDIUM", + domain="moderation", + max_length=10, + ), + ), + ( + "description", + models.TextField( + help_text="Description of what this operation does" + ), + ), + ( + "parameters", + models.JSONField( + default=dict, help_text="Parameters for the operation" + ), + ), + ( + "results", + models.JSONField( + blank=True, + default=dict, + help_text="Results and output from the operation", + ), + ), + ( + "total_items", + models.PositiveIntegerField( + default=0, help_text="Total number of items to process" + ), + ), + ( + "processed_items", + models.PositiveIntegerField( + default=0, help_text="Number of items processed" + ), + ), + ( + "failed_items", + models.PositiveIntegerField( + default=0, help_text="Number of items that failed" + ), + ), + ( + "estimated_duration_minutes", + models.PositiveIntegerField( + blank=True, help_text="Estimated duration in minutes", null=True + ), + ), + ( + "schedule_for", + models.DateTimeField( + blank=True, help_text="When to run this operation", null=True + ), + ), + ( + "can_cancel", + models.BooleanField( + default=True, + help_text="Whether this operation can be cancelled", + ), + ), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("started_at", models.DateTimeField(blank=True, null=True)), + ("completed_at", models.DateTimeField(blank=True, null=True)), + ("updated_at", models.DateTimeField(auto_now=True)), + ( + "created_by", + models.ForeignKey( + db_constraint=False, + on_delete=django.db.models.deletion.DO_NOTHING, + related_name="+", + related_query_name="+", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "pgh_context", + models.ForeignKey( + db_constraint=False, + null=True, + on_delete=django.db.models.deletion.DO_NOTHING, + related_name="+", + to="pghistory.context", + ), + ), + ( + "pgh_obj", + models.ForeignKey( + db_constraint=False, + on_delete=django.db.models.deletion.DO_NOTHING, + related_name="events", + to="moderation.bulkoperation", + ), + ), + ], + options={ + "abstract": False, + }, + ), + migrations.CreateModel( + name="EditSubmission", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("updated_at", models.DateTimeField(auto_now=True)), + ("object_id", models.PositiveIntegerField(blank=True, null=True)), + ( + "submission_type", + apps.core.choices.fields.RichChoiceField( + allow_deprecated=False, + choice_group="submission_types", + choices=[("EDIT", "Edit Existing"), ("CREATE", "Create New")], + default="EDIT", + domain="moderation", + max_length=10, + ), + ), + ( + "changes", + models.JSONField( + help_text="JSON representation of the changes or new object data" + ), + ), + ( + "moderator_changes", + models.JSONField( + blank=True, + help_text="Moderator's edited version of the changes before approval", + null=True, + ), + ), + ( + "reason", + models.TextField(help_text="Why this edit/addition is needed"), + ), + ( + "source", + models.TextField( + blank=True, help_text="Source of information (if applicable)" + ), + ), + ( + "status", + apps.core.choices.fields.RichChoiceField( + allow_deprecated=False, + choice_group="edit_submission_statuses", + choices=[ + ("PENDING", "Pending"), + ("APPROVED", "Approved"), + ("REJECTED", "Rejected"), + ("ESCALATED", "Escalated"), + ], + default="PENDING", + domain="moderation", + max_length=20, + ), + ), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("handled_at", models.DateTimeField(blank=True, null=True)), + ( + "notes", + models.TextField( + blank=True, + help_text="Notes from the moderator about this submission", + ), + ), + ( + "content_type", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="contenttypes.contenttype", + ), + ), + ( + "handled_by", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="handled_submissions", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "user", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="edit_submissions", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "ordering": ["-created_at"], + "abstract": False, + }, + ), + migrations.CreateModel( + name="EditSubmissionEvent", + fields=[ + ("pgh_id", models.AutoField(primary_key=True, serialize=False)), + ("pgh_created_at", models.DateTimeField(auto_now_add=True)), + ("pgh_label", models.TextField(help_text="The event label.")), + ("id", models.BigIntegerField()), + ("updated_at", models.DateTimeField(auto_now=True)), + ("object_id", models.PositiveIntegerField(blank=True, null=True)), + ( + "submission_type", + apps.core.choices.fields.RichChoiceField( + allow_deprecated=False, + choice_group="submission_types", + choices=[("EDIT", "Edit Existing"), ("CREATE", "Create New")], + default="EDIT", + domain="moderation", + max_length=10, + ), + ), + ( + "changes", + models.JSONField( + help_text="JSON representation of the changes or new object data" + ), + ), + ( + "moderator_changes", + models.JSONField( + blank=True, + help_text="Moderator's edited version of the changes before approval", + null=True, + ), + ), + ( + "reason", + models.TextField(help_text="Why this edit/addition is needed"), + ), + ( + "source", + models.TextField( + blank=True, help_text="Source of information (if applicable)" + ), + ), + ( + "status", + apps.core.choices.fields.RichChoiceField( + allow_deprecated=False, + choice_group="edit_submission_statuses", + choices=[ + ("PENDING", "Pending"), + ("APPROVED", "Approved"), + ("REJECTED", "Rejected"), + ("ESCALATED", "Escalated"), + ], + default="PENDING", + domain="moderation", + max_length=20, + ), + ), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("handled_at", models.DateTimeField(blank=True, null=True)), + ( + "notes", + models.TextField( + blank=True, + help_text="Notes from the moderator about this submission", + ), + ), + ( + "content_type", + models.ForeignKey( + db_constraint=False, + on_delete=django.db.models.deletion.DO_NOTHING, + related_name="+", + related_query_name="+", + to="contenttypes.contenttype", + ), + ), + ( + "handled_by", + models.ForeignKey( + blank=True, + db_constraint=False, + null=True, + on_delete=django.db.models.deletion.DO_NOTHING, + related_name="+", + related_query_name="+", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "pgh_context", + models.ForeignKey( + db_constraint=False, + null=True, + on_delete=django.db.models.deletion.DO_NOTHING, + related_name="+", + to="pghistory.context", + ), + ), + ( + "pgh_obj", + models.ForeignKey( + db_constraint=False, + on_delete=django.db.models.deletion.DO_NOTHING, + related_name="events", + to="moderation.editsubmission", + ), + ), + ( + "user", + models.ForeignKey( + db_constraint=False, + on_delete=django.db.models.deletion.DO_NOTHING, + related_name="+", + related_query_name="+", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "abstract": False, + }, + ), + migrations.CreateModel( + name="ModerationAction", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "action_type", + apps.core.choices.fields.RichChoiceField( + allow_deprecated=False, + choice_group="moderation_action_types", + choices=[ + ("WARNING", "Warning"), + ("USER_SUSPENSION", "User Suspension"), + ("USER_BAN", "User Ban"), + ("CONTENT_REMOVAL", "Content Removal"), + ("CONTENT_EDIT", "Content Edit"), + ("CONTENT_RESTRICTION", "Content Restriction"), + ("ACCOUNT_RESTRICTION", "Account Restriction"), + ("OTHER", "Other"), + ], + domain="moderation", + max_length=50, + ), + ), + ( + "reason", + models.CharField( + help_text="Brief reason for the action", max_length=200 + ), + ), + ( + "details", + models.TextField(help_text="Detailed explanation of the action"), + ), + ( + "duration_hours", + models.PositiveIntegerField( + blank=True, + help_text="Duration in hours for temporary actions", + null=True, + ), + ), + ( + "expires_at", + models.DateTimeField( + blank=True, help_text="When this action expires", null=True + ), + ), + ( + "is_active", + models.BooleanField( + default=True, + help_text="Whether this action is currently active", + ), + ), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("updated_at", models.DateTimeField(auto_now=True)), + ( + "moderator", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="moderation_actions_taken", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "target_user", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="moderation_actions_received", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "ordering": ["-created_at"], + "abstract": False, + }, + ), + migrations.CreateModel( + name="ModerationQueue", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "item_type", + apps.core.choices.fields.RichChoiceField( + allow_deprecated=False, + choice_group="queue_item_types", + choices=[ + ("CONTENT_REVIEW", "Content Review"), + ("USER_REVIEW", "User Review"), + ("BULK_ACTION", "Bulk Action"), + ("POLICY_VIOLATION", "Policy Violation"), + ("APPEAL", "Appeal"), + ("OTHER", "Other"), + ], + domain="moderation", + max_length=50, + ), + ), + ( + "status", + apps.core.choices.fields.RichChoiceField( + allow_deprecated=False, + choice_group="moderation_queue_statuses", + choices=[ + ("PENDING", "Pending"), + ("IN_PROGRESS", "In Progress"), + ("COMPLETED", "Completed"), + ("CANCELLED", "Cancelled"), + ], + default="PENDING", + domain="moderation", + max_length=20, + ), + ), + ( + "priority", + apps.core.choices.fields.RichChoiceField( + allow_deprecated=False, + choice_group="priority_levels", + choices=[ + ("LOW", "Low"), + ("MEDIUM", "Medium"), + ("HIGH", "High"), + ("URGENT", "Urgent"), + ], + default="MEDIUM", + domain="moderation", + max_length=10, + ), + ), + ( + "title", + models.CharField( + help_text="Brief title for the queue item", max_length=200 + ), + ), + ( + "description", + models.TextField( + help_text="Detailed description of what needs to be done" + ), + ), + ( + "entity_type", + models.CharField( + blank=True, + help_text="Type of entity (park, ride, user, etc.)", + max_length=50, + ), + ), + ( + "entity_id", + models.PositiveIntegerField( + blank=True, help_text="ID of the related entity", null=True + ), + ), + ( + "entity_preview", + models.JSONField( + blank=True, + default=dict, + help_text="Preview data for the entity", + ), + ), + ("assigned_at", models.DateTimeField(blank=True, null=True)), + ( + "estimated_review_time", + models.PositiveIntegerField( + default=30, help_text="Estimated time in minutes" + ), + ), + ( + "tags", + models.JSONField( + blank=True, default=list, help_text="Tags for categorization" + ), + ), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("updated_at", models.DateTimeField(auto_now=True)), + ( + "assigned_to", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="assigned_queue_items", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "content_type", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="contenttypes.contenttype", + ), + ), + ( + "flagged_by", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="flagged_queue_items", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "ordering": ["priority", "created_at"], + "abstract": False, + }, + ), + migrations.CreateModel( + name="ModerationReport", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "report_type", + apps.core.choices.fields.RichChoiceField( + allow_deprecated=False, + choice_group="report_types", + choices=[ + ("SPAM", "Spam"), + ("HARASSMENT", "Harassment"), + ("INAPPROPRIATE_CONTENT", "Inappropriate Content"), + ("MISINFORMATION", "Misinformation"), + ("COPYRIGHT", "Copyright Violation"), + ("PRIVACY", "Privacy Violation"), + ("HATE_SPEECH", "Hate Speech"), + ("VIOLENCE", "Violence or Threats"), + ("OTHER", "Other"), + ], + domain="moderation", + max_length=50, + ), + ), + ( + "status", + apps.core.choices.fields.RichChoiceField( + allow_deprecated=False, + choice_group="moderation_report_statuses", + choices=[ + ("PENDING", "Pending Review"), + ("UNDER_REVIEW", "Under Review"), + ("RESOLVED", "Resolved"), + ("DISMISSED", "Dismissed"), + ], + default="PENDING", + domain="moderation", + max_length=20, + ), + ), + ( + "priority", + apps.core.choices.fields.RichChoiceField( + allow_deprecated=False, + choice_group="priority_levels", + choices=[ + ("LOW", "Low"), + ("MEDIUM", "Medium"), + ("HIGH", "High"), + ("URGENT", "Urgent"), + ], + default="MEDIUM", + domain="moderation", + max_length=10, + ), + ), + ( + "reported_entity_type", + models.CharField( + help_text="Type of entity being reported (park, ride, user, etc.)", + max_length=50, + ), + ), + ( + "reported_entity_id", + models.PositiveIntegerField( + help_text="ID of the entity being reported" + ), + ), + ( + "reason", + models.CharField( + help_text="Brief reason for the report", max_length=200 + ), + ), + ( + "description", + models.TextField(help_text="Detailed description of the issue"), + ), + ( + "evidence_urls", + models.JSONField( + blank=True, + default=list, + help_text="URLs to evidence (screenshots, etc.)", + ), + ), + ( + "resolution_action", + models.CharField( + blank=True, help_text="Action taken to resolve", max_length=100 + ), + ), + ( + "resolution_notes", + models.TextField( + blank=True, help_text="Notes about the resolution" + ), + ), + ("resolved_at", models.DateTimeField(blank=True, null=True)), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("updated_at", models.DateTimeField(auto_now=True)), + ( + "assigned_moderator", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="assigned_moderation_reports", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "content_type", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="contenttypes.contenttype", + ), + ), + ( + "reported_by", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="moderation_reports_made", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "ordering": ["-created_at"], + "abstract": False, + }, + ), + migrations.CreateModel( + name="ModerationQueueEvent", + fields=[ + ("pgh_id", models.AutoField(primary_key=True, serialize=False)), + ("pgh_created_at", models.DateTimeField(auto_now_add=True)), + ("pgh_label", models.TextField(help_text="The event label.")), + ("id", models.BigIntegerField()), + ( + "item_type", + apps.core.choices.fields.RichChoiceField( + allow_deprecated=False, + choice_group="queue_item_types", + choices=[ + ("CONTENT_REVIEW", "Content Review"), + ("USER_REVIEW", "User Review"), + ("BULK_ACTION", "Bulk Action"), + ("POLICY_VIOLATION", "Policy Violation"), + ("APPEAL", "Appeal"), + ("OTHER", "Other"), + ], + domain="moderation", + max_length=50, + ), + ), + ( + "status", + apps.core.choices.fields.RichChoiceField( + allow_deprecated=False, + choice_group="moderation_queue_statuses", + choices=[ + ("PENDING", "Pending"), + ("IN_PROGRESS", "In Progress"), + ("COMPLETED", "Completed"), + ("CANCELLED", "Cancelled"), + ], + default="PENDING", + domain="moderation", + max_length=20, + ), + ), + ( + "priority", + apps.core.choices.fields.RichChoiceField( + allow_deprecated=False, + choice_group="priority_levels", + choices=[ + ("LOW", "Low"), + ("MEDIUM", "Medium"), + ("HIGH", "High"), + ("URGENT", "Urgent"), + ], + default="MEDIUM", + domain="moderation", + max_length=10, + ), + ), + ( + "title", + models.CharField( + help_text="Brief title for the queue item", max_length=200 + ), + ), + ( + "description", + models.TextField( + help_text="Detailed description of what needs to be done" + ), + ), + ( + "entity_type", + models.CharField( + blank=True, + help_text="Type of entity (park, ride, user, etc.)", + max_length=50, + ), + ), + ( + "entity_id", + models.PositiveIntegerField( + blank=True, help_text="ID of the related entity", null=True + ), + ), + ( + "entity_preview", + models.JSONField( + blank=True, + default=dict, + help_text="Preview data for the entity", + ), + ), + ("assigned_at", models.DateTimeField(blank=True, null=True)), + ( + "estimated_review_time", + models.PositiveIntegerField( + default=30, help_text="Estimated time in minutes" + ), + ), + ( + "tags", + models.JSONField( + blank=True, default=list, help_text="Tags for categorization" + ), + ), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("updated_at", models.DateTimeField(auto_now=True)), + ( + "assigned_to", + models.ForeignKey( + blank=True, + db_constraint=False, + null=True, + on_delete=django.db.models.deletion.DO_NOTHING, + related_name="+", + related_query_name="+", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "content_type", + models.ForeignKey( + blank=True, + db_constraint=False, + null=True, + on_delete=django.db.models.deletion.DO_NOTHING, + related_name="+", + related_query_name="+", + to="contenttypes.contenttype", + ), + ), + ( + "flagged_by", + models.ForeignKey( + blank=True, + db_constraint=False, + null=True, + on_delete=django.db.models.deletion.DO_NOTHING, + related_name="+", + related_query_name="+", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "pgh_context", + models.ForeignKey( + db_constraint=False, + null=True, + on_delete=django.db.models.deletion.DO_NOTHING, + related_name="+", + to="pghistory.context", + ), + ), + ( + "pgh_obj", + models.ForeignKey( + db_constraint=False, + on_delete=django.db.models.deletion.DO_NOTHING, + related_name="events", + to="moderation.moderationqueue", + ), + ), + ( + "related_report", + models.ForeignKey( + blank=True, + db_constraint=False, + null=True, + on_delete=django.db.models.deletion.DO_NOTHING, + related_name="+", + related_query_name="+", + to="moderation.moderationreport", + ), + ), + ], + options={ + "abstract": False, + }, + ), + migrations.AddField( + model_name="moderationqueue", + name="related_report", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="queue_items", + to="moderation.moderationreport", + ), + ), + migrations.CreateModel( + name="ModerationActionEvent", + fields=[ + ("pgh_id", models.AutoField(primary_key=True, serialize=False)), + ("pgh_created_at", models.DateTimeField(auto_now_add=True)), + ("pgh_label", models.TextField(help_text="The event label.")), + ("id", models.BigIntegerField()), + ( + "action_type", + apps.core.choices.fields.RichChoiceField( + allow_deprecated=False, + choice_group="moderation_action_types", + choices=[ + ("WARNING", "Warning"), + ("USER_SUSPENSION", "User Suspension"), + ("USER_BAN", "User Ban"), + ("CONTENT_REMOVAL", "Content Removal"), + ("CONTENT_EDIT", "Content Edit"), + ("CONTENT_RESTRICTION", "Content Restriction"), + ("ACCOUNT_RESTRICTION", "Account Restriction"), + ("OTHER", "Other"), + ], + domain="moderation", + max_length=50, + ), + ), + ( + "reason", + models.CharField( + help_text="Brief reason for the action", max_length=200 + ), + ), + ( + "details", + models.TextField(help_text="Detailed explanation of the action"), + ), + ( + "duration_hours", + models.PositiveIntegerField( + blank=True, + help_text="Duration in hours for temporary actions", + null=True, + ), + ), + ( + "expires_at", + models.DateTimeField( + blank=True, help_text="When this action expires", null=True + ), + ), + ( + "is_active", + models.BooleanField( + default=True, + help_text="Whether this action is currently active", + ), + ), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("updated_at", models.DateTimeField(auto_now=True)), + ( + "moderator", + models.ForeignKey( + db_constraint=False, + on_delete=django.db.models.deletion.DO_NOTHING, + related_name="+", + related_query_name="+", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "pgh_context", + models.ForeignKey( + db_constraint=False, + null=True, + on_delete=django.db.models.deletion.DO_NOTHING, + related_name="+", + to="pghistory.context", + ), + ), + ( + "pgh_obj", + models.ForeignKey( + db_constraint=False, + on_delete=django.db.models.deletion.DO_NOTHING, + related_name="events", + to="moderation.moderationaction", + ), + ), + ( + "target_user", + models.ForeignKey( + db_constraint=False, + on_delete=django.db.models.deletion.DO_NOTHING, + related_name="+", + related_query_name="+", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "related_report", + models.ForeignKey( + blank=True, + db_constraint=False, + null=True, + on_delete=django.db.models.deletion.DO_NOTHING, + related_name="+", + related_query_name="+", + to="moderation.moderationreport", + ), + ), + ], + options={ + "abstract": False, + }, + ), + migrations.AddField( + model_name="moderationaction", + name="related_report", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="actions_taken", + to="moderation.moderationreport", + ), + ), + migrations.CreateModel( + name="ModerationReportEvent", + fields=[ + ("pgh_id", models.AutoField(primary_key=True, serialize=False)), + ("pgh_created_at", models.DateTimeField(auto_now_add=True)), + ("pgh_label", models.TextField(help_text="The event label.")), + ("id", models.BigIntegerField()), + ( + "report_type", + apps.core.choices.fields.RichChoiceField( + allow_deprecated=False, + choice_group="report_types", + choices=[ + ("SPAM", "Spam"), + ("HARASSMENT", "Harassment"), + ("INAPPROPRIATE_CONTENT", "Inappropriate Content"), + ("MISINFORMATION", "Misinformation"), + ("COPYRIGHT", "Copyright Violation"), + ("PRIVACY", "Privacy Violation"), + ("HATE_SPEECH", "Hate Speech"), + ("VIOLENCE", "Violence or Threats"), + ("OTHER", "Other"), + ], + domain="moderation", + max_length=50, + ), + ), + ( + "status", + apps.core.choices.fields.RichChoiceField( + allow_deprecated=False, + choice_group="moderation_report_statuses", + choices=[ + ("PENDING", "Pending Review"), + ("UNDER_REVIEW", "Under Review"), + ("RESOLVED", "Resolved"), + ("DISMISSED", "Dismissed"), + ], + default="PENDING", + domain="moderation", + max_length=20, + ), + ), + ( + "priority", + apps.core.choices.fields.RichChoiceField( + allow_deprecated=False, + choice_group="priority_levels", + choices=[ + ("LOW", "Low"), + ("MEDIUM", "Medium"), + ("HIGH", "High"), + ("URGENT", "Urgent"), + ], + default="MEDIUM", + domain="moderation", + max_length=10, + ), + ), + ( + "reported_entity_type", + models.CharField( + help_text="Type of entity being reported (park, ride, user, etc.)", + max_length=50, + ), + ), + ( + "reported_entity_id", + models.PositiveIntegerField( + help_text="ID of the entity being reported" + ), + ), + ( + "reason", + models.CharField( + help_text="Brief reason for the report", max_length=200 + ), + ), + ( + "description", + models.TextField(help_text="Detailed description of the issue"), + ), + ( + "evidence_urls", + models.JSONField( + blank=True, + default=list, + help_text="URLs to evidence (screenshots, etc.)", + ), + ), + ( + "resolution_action", + models.CharField( + blank=True, help_text="Action taken to resolve", max_length=100 + ), + ), + ( + "resolution_notes", + models.TextField( + blank=True, help_text="Notes about the resolution" + ), + ), + ("resolved_at", models.DateTimeField(blank=True, null=True)), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("updated_at", models.DateTimeField(auto_now=True)), + ( + "assigned_moderator", + models.ForeignKey( + blank=True, + db_constraint=False, + null=True, + on_delete=django.db.models.deletion.DO_NOTHING, + related_name="+", + related_query_name="+", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "content_type", + models.ForeignKey( + blank=True, + db_constraint=False, + null=True, + on_delete=django.db.models.deletion.DO_NOTHING, + related_name="+", + related_query_name="+", + to="contenttypes.contenttype", + ), + ), + ( + "pgh_context", + models.ForeignKey( + db_constraint=False, + null=True, + on_delete=django.db.models.deletion.DO_NOTHING, + related_name="+", + to="pghistory.context", + ), + ), + ( + "pgh_obj", + models.ForeignKey( + db_constraint=False, + on_delete=django.db.models.deletion.DO_NOTHING, + related_name="events", + to="moderation.moderationreport", + ), + ), + ( + "reported_by", + models.ForeignKey( + db_constraint=False, + on_delete=django.db.models.deletion.DO_NOTHING, + related_name="+", + related_query_name="+", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "abstract": False, + }, + ), + migrations.CreateModel( + name="PhotoSubmission", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("updated_at", models.DateTimeField(auto_now=True)), + ("object_id", models.PositiveIntegerField()), + ("caption", models.CharField(blank=True, max_length=255)), + ("date_taken", models.DateField(blank=True, null=True)), + ( + "status", + apps.core.choices.fields.RichChoiceField( + allow_deprecated=False, + choice_group="photo_submission_statuses", + choices=[ + ("PENDING", "Pending"), + ("APPROVED", "Approved"), + ("REJECTED", "Rejected"), + ("ESCALATED", "Escalated"), + ], + default="PENDING", + domain="moderation", + max_length=20, + ), + ), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("handled_at", models.DateTimeField(blank=True, null=True)), + ( + "notes", + models.TextField( + blank=True, + help_text="Notes from the moderator about this photo submission", + ), + ), + ( + "content_type", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="contenttypes.contenttype", + ), + ), + ( + "handled_by", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="handled_photos", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "photo", + models.ForeignKey( + help_text="Photo submission stored on Cloudflare Images", + on_delete=django.db.models.deletion.CASCADE, + to="django_cloudflareimages_toolkit.cloudflareimage", + ), + ), + ( + "user", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="photo_submissions", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "ordering": ["-created_at"], + "abstract": False, + }, + ), + migrations.CreateModel( + name="PhotoSubmissionEvent", + fields=[ + ("pgh_id", models.AutoField(primary_key=True, serialize=False)), + ("pgh_created_at", models.DateTimeField(auto_now_add=True)), + ("pgh_label", models.TextField(help_text="The event label.")), + ("id", models.BigIntegerField()), + ("updated_at", models.DateTimeField(auto_now=True)), + ("object_id", models.PositiveIntegerField()), + ("caption", models.CharField(blank=True, max_length=255)), + ("date_taken", models.DateField(blank=True, null=True)), + ( + "status", + apps.core.choices.fields.RichChoiceField( + allow_deprecated=False, + choice_group="photo_submission_statuses", + choices=[ + ("PENDING", "Pending"), + ("APPROVED", "Approved"), + ("REJECTED", "Rejected"), + ("ESCALATED", "Escalated"), + ], + default="PENDING", + domain="moderation", + max_length=20, + ), + ), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("handled_at", models.DateTimeField(blank=True, null=True)), + ( + "notes", + models.TextField( + blank=True, + help_text="Notes from the moderator about this photo submission", + ), + ), + ( + "content_type", + models.ForeignKey( + db_constraint=False, + on_delete=django.db.models.deletion.DO_NOTHING, + related_name="+", + related_query_name="+", + to="contenttypes.contenttype", + ), + ), + ( + "handled_by", + models.ForeignKey( + blank=True, + db_constraint=False, + null=True, + on_delete=django.db.models.deletion.DO_NOTHING, + related_name="+", + related_query_name="+", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "pgh_context", + models.ForeignKey( + db_constraint=False, + null=True, + on_delete=django.db.models.deletion.DO_NOTHING, + related_name="+", + to="pghistory.context", + ), + ), + ( + "pgh_obj", + models.ForeignKey( + db_constraint=False, + on_delete=django.db.models.deletion.DO_NOTHING, + related_name="events", + to="moderation.photosubmission", + ), + ), + ( + "photo", + models.ForeignKey( + db_constraint=False, + help_text="Photo submission stored on Cloudflare Images", + on_delete=django.db.models.deletion.DO_NOTHING, + related_name="+", + related_query_name="+", + to="django_cloudflareimages_toolkit.cloudflareimage", + ), + ), + ( + "user", + models.ForeignKey( + db_constraint=False, + on_delete=django.db.models.deletion.DO_NOTHING, + related_name="+", + related_query_name="+", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "abstract": False, + }, + ), + migrations.AddIndex( + model_name="bulkoperation", + index=models.Index( + fields=["status", "priority"], name="moderation__status_f11ee8_idx" + ), + ), + migrations.AddIndex( + model_name="bulkoperation", + index=models.Index( + fields=["created_by"], name="moderation__created_4fe5d2_idx" + ), + ), + migrations.AddIndex( + model_name="bulkoperation", + index=models.Index( + fields=["schedule_for"], name="moderation__schedul_350704_idx" + ), + ), + migrations.AddIndex( + model_name="bulkoperation", + index=models.Index( + fields=["created_at"], name="moderation__created_b705f4_idx" + ), + ), + pgtrigger.migrations.AddTrigger( + model_name="bulkoperation", + trigger=pgtrigger.compiler.Trigger( + name="insert_insert", + sql=pgtrigger.compiler.UpsertTriggerSql( + func='INSERT INTO "moderation_bulkoperationevent" ("can_cancel", "completed_at", "created_at", "created_by_id", "description", "estimated_duration_minutes", "failed_items", "id", "operation_type", "parameters", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "priority", "processed_items", "results", "schedule_for", "started_at", "status", "total_items", "updated_at") VALUES (NEW."can_cancel", NEW."completed_at", NEW."created_at", NEW."created_by_id", NEW."description", NEW."estimated_duration_minutes", NEW."failed_items", NEW."id", NEW."operation_type", NEW."parameters", _pgh_attach_context(), NOW(), \'insert\', NEW."id", NEW."priority", NEW."processed_items", NEW."results", NEW."schedule_for", NEW."started_at", NEW."status", NEW."total_items", NEW."updated_at"); RETURN NULL;', + hash="6b77f43e19a6d3862cf52ccb8ff5cce33f98c12d", + operation="INSERT", + pgid="pgtrigger_insert_insert_5e87c", + table="moderation_bulkoperation", + when="AFTER", + ), + ), + ), + pgtrigger.migrations.AddTrigger( + model_name="bulkoperation", + trigger=pgtrigger.compiler.Trigger( + name="update_update", + sql=pgtrigger.compiler.UpsertTriggerSql( + condition="WHEN (OLD.* IS DISTINCT FROM NEW.*)", + func='INSERT INTO "moderation_bulkoperationevent" ("can_cancel", "completed_at", "created_at", "created_by_id", "description", "estimated_duration_minutes", "failed_items", "id", "operation_type", "parameters", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "priority", "processed_items", "results", "schedule_for", "started_at", "status", "total_items", "updated_at") VALUES (NEW."can_cancel", NEW."completed_at", NEW."created_at", NEW."created_by_id", NEW."description", NEW."estimated_duration_minutes", NEW."failed_items", NEW."id", NEW."operation_type", NEW."parameters", _pgh_attach_context(), NOW(), \'update\', NEW."id", NEW."priority", NEW."processed_items", NEW."results", NEW."schedule_for", NEW."started_at", NEW."status", NEW."total_items", NEW."updated_at"); RETURN NULL;', + hash="d652ed05c30c256957625d4cec89a30cf30bbcda", + operation="UPDATE", + pgid="pgtrigger_update_update_5b85c", + table="moderation_bulkoperation", + when="AFTER", + ), + ), + ), + migrations.AddIndex( + model_name="editsubmission", + index=models.Index( + fields=["content_type", "object_id"], + name="moderation__content_922d2b_idx", + ), + ), + migrations.AddIndex( + model_name="editsubmission", + index=models.Index(fields=["status"], name="moderation__status_e4eb2b_idx"), + ), + pgtrigger.migrations.AddTrigger( + model_name="editsubmission", + trigger=pgtrigger.compiler.Trigger( + name="insert_insert", + sql=pgtrigger.compiler.UpsertTriggerSql( + func='INSERT INTO "moderation_editsubmissionevent" ("changes", "content_type_id", "created_at", "handled_at", "handled_by_id", "id", "moderator_changes", "notes", "object_id", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "reason", "source", "status", "submission_type", "updated_at", "user_id") VALUES (NEW."changes", NEW."content_type_id", NEW."created_at", NEW."handled_at", NEW."handled_by_id", NEW."id", NEW."moderator_changes", NEW."notes", NEW."object_id", _pgh_attach_context(), NOW(), \'insert\', NEW."id", NEW."reason", NEW."source", NEW."status", NEW."submission_type", NEW."updated_at", NEW."user_id"); RETURN NULL;', + hash="0e394e419ba234dd23cb0f4f6567611ad71f2a38", + operation="INSERT", + pgid="pgtrigger_insert_insert_2c796", + table="moderation_editsubmission", + when="AFTER", + ), + ), + ), + pgtrigger.migrations.AddTrigger( + model_name="editsubmission", + trigger=pgtrigger.compiler.Trigger( + name="update_update", + sql=pgtrigger.compiler.UpsertTriggerSql( + condition="WHEN (OLD.* IS DISTINCT FROM NEW.*)", + func='INSERT INTO "moderation_editsubmissionevent" ("changes", "content_type_id", "created_at", "handled_at", "handled_by_id", "id", "moderator_changes", "notes", "object_id", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "reason", "source", "status", "submission_type", "updated_at", "user_id") VALUES (NEW."changes", NEW."content_type_id", NEW."created_at", NEW."handled_at", NEW."handled_by_id", NEW."id", NEW."moderator_changes", NEW."notes", NEW."object_id", _pgh_attach_context(), NOW(), \'update\', NEW."id", NEW."reason", NEW."source", NEW."status", NEW."submission_type", NEW."updated_at", NEW."user_id"); RETURN NULL;', + hash="315b76df75a52d610d3d0857fd5821101e551410", + operation="UPDATE", + pgid="pgtrigger_update_update_ab38f", + table="moderation_editsubmission", + when="AFTER", + ), + ), + ), + migrations.AddIndex( + model_name="moderationreport", + index=models.Index( + fields=["status", "priority"], name="moderation__status_6aa18c_idx" + ), + ), + migrations.AddIndex( + model_name="moderationreport", + index=models.Index( + fields=["reported_by"], name="moderation__reporte_81af56_idx" + ), + ), + migrations.AddIndex( + model_name="moderationreport", + index=models.Index( + fields=["assigned_moderator"], name="moderation__assigne_c43cdf_idx" + ), + ), + migrations.AddIndex( + model_name="moderationreport", + index=models.Index( + fields=["created_at"], name="moderation__created_ae337c_idx" + ), + ), + pgtrigger.migrations.AddTrigger( + model_name="moderationreport", + trigger=pgtrigger.compiler.Trigger( + name="insert_insert", + sql=pgtrigger.compiler.UpsertTriggerSql( + func='INSERT INTO "moderation_moderationreportevent" ("assigned_moderator_id", "content_type_id", "created_at", "description", "evidence_urls", "id", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "priority", "reason", "report_type", "reported_by_id", "reported_entity_id", "reported_entity_type", "resolution_action", "resolution_notes", "resolved_at", "status", "updated_at") VALUES (NEW."assigned_moderator_id", NEW."content_type_id", NEW."created_at", NEW."description", NEW."evidence_urls", NEW."id", _pgh_attach_context(), NOW(), \'insert\', NEW."id", NEW."priority", NEW."reason", NEW."report_type", NEW."reported_by_id", NEW."reported_entity_id", NEW."reported_entity_type", NEW."resolution_action", NEW."resolution_notes", NEW."resolved_at", NEW."status", NEW."updated_at"); RETURN NULL;', + hash="913644a52cf757b26e9c99eefd68ca6c23777cff", + operation="INSERT", + pgid="pgtrigger_insert_insert_bb855", + table="moderation_moderationreport", + when="AFTER", + ), + ), + ), + pgtrigger.migrations.AddTrigger( + model_name="moderationreport", + trigger=pgtrigger.compiler.Trigger( + name="update_update", + sql=pgtrigger.compiler.UpsertTriggerSql( + condition="WHEN (OLD.* IS DISTINCT FROM NEW.*)", + func='INSERT INTO "moderation_moderationreportevent" ("assigned_moderator_id", "content_type_id", "created_at", "description", "evidence_urls", "id", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "priority", "reason", "report_type", "reported_by_id", "reported_entity_id", "reported_entity_type", "resolution_action", "resolution_notes", "resolved_at", "status", "updated_at") VALUES (NEW."assigned_moderator_id", NEW."content_type_id", NEW."created_at", NEW."description", NEW."evidence_urls", NEW."id", _pgh_attach_context(), NOW(), \'update\', NEW."id", NEW."priority", NEW."reason", NEW."report_type", NEW."reported_by_id", NEW."reported_entity_id", NEW."reported_entity_type", NEW."resolution_action", NEW."resolution_notes", NEW."resolved_at", NEW."status", NEW."updated_at"); RETURN NULL;', + hash="e31bdc9823aa3a8da9f0448949fbc76c46bc7bf3", + operation="UPDATE", + pgid="pgtrigger_update_update_55763", + table="moderation_moderationreport", + when="AFTER", + ), + ), + ), + migrations.AddIndex( + model_name="moderationqueue", + index=models.Index( + fields=["status", "priority"], name="moderation__status_6f2a75_idx" + ), + ), + migrations.AddIndex( + model_name="moderationqueue", + index=models.Index( + fields=["assigned_to"], name="moderation__assigne_2fc958_idx" + ), + ), + migrations.AddIndex( + model_name="moderationqueue", + index=models.Index( + fields=["created_at"], name="moderation__created_fe6dd0_idx" + ), + ), + pgtrigger.migrations.AddTrigger( + model_name="moderationqueue", + trigger=pgtrigger.compiler.Trigger( + name="insert_insert", + sql=pgtrigger.compiler.UpsertTriggerSql( + func='INSERT INTO "moderation_moderationqueueevent" ("assigned_at", "assigned_to_id", "content_type_id", "created_at", "description", "entity_id", "entity_preview", "entity_type", "estimated_review_time", "flagged_by_id", "id", "item_type", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "priority", "related_report_id", "status", "tags", "title", "updated_at") VALUES (NEW."assigned_at", NEW."assigned_to_id", NEW."content_type_id", NEW."created_at", NEW."description", NEW."entity_id", NEW."entity_preview", NEW."entity_type", NEW."estimated_review_time", NEW."flagged_by_id", NEW."id", NEW."item_type", _pgh_attach_context(), NOW(), \'insert\', NEW."id", NEW."priority", NEW."related_report_id", NEW."status", NEW."tags", NEW."title", NEW."updated_at"); RETURN NULL;', + hash="55993d8cb4981feed7b3febde9e87989481a8a34", + operation="INSERT", + pgid="pgtrigger_insert_insert_cf9cb", + table="moderation_moderationqueue", + when="AFTER", + ), + ), + ), + pgtrigger.migrations.AddTrigger( + model_name="moderationqueue", + trigger=pgtrigger.compiler.Trigger( + name="update_update", + sql=pgtrigger.compiler.UpsertTriggerSql( + condition="WHEN (OLD.* IS DISTINCT FROM NEW.*)", + func='INSERT INTO "moderation_moderationqueueevent" ("assigned_at", "assigned_to_id", "content_type_id", "created_at", "description", "entity_id", "entity_preview", "entity_type", "estimated_review_time", "flagged_by_id", "id", "item_type", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "priority", "related_report_id", "status", "tags", "title", "updated_at") VALUES (NEW."assigned_at", NEW."assigned_to_id", NEW."content_type_id", NEW."created_at", NEW."description", NEW."entity_id", NEW."entity_preview", NEW."entity_type", NEW."estimated_review_time", NEW."flagged_by_id", NEW."id", NEW."item_type", _pgh_attach_context(), NOW(), \'update\', NEW."id", NEW."priority", NEW."related_report_id", NEW."status", NEW."tags", NEW."title", NEW."updated_at"); RETURN NULL;', + hash="8da070419fd1efd43bfb272a431392b6244a7739", + operation="UPDATE", + pgid="pgtrigger_update_update_3b3aa", + table="moderation_moderationqueue", + when="AFTER", + ), + ), + ), + migrations.AddIndex( + model_name="moderationaction", + index=models.Index( + fields=["target_user", "is_active"], + name="moderation__target__fc8ec5_idx", + ), + ), + migrations.AddIndex( + model_name="moderationaction", + index=models.Index( + fields=["moderator"], name="moderation__moderat_1c19b0_idx" + ), + ), + migrations.AddIndex( + model_name="moderationaction", + index=models.Index( + fields=["expires_at"], name="moderation__expires_963efb_idx" + ), + ), + migrations.AddIndex( + model_name="moderationaction", + index=models.Index( + fields=["created_at"], name="moderation__created_6378e6_idx" + ), + ), + pgtrigger.migrations.AddTrigger( + model_name="moderationaction", + trigger=pgtrigger.compiler.Trigger( + name="insert_insert", + sql=pgtrigger.compiler.UpsertTriggerSql( + func='INSERT INTO "moderation_moderationactionevent" ("action_type", "created_at", "details", "duration_hours", "expires_at", "id", "is_active", "moderator_id", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "reason", "related_report_id", "target_user_id", "updated_at") VALUES (NEW."action_type", NEW."created_at", NEW."details", NEW."duration_hours", NEW."expires_at", NEW."id", NEW."is_active", NEW."moderator_id", _pgh_attach_context(), NOW(), \'insert\', NEW."id", NEW."reason", NEW."related_report_id", NEW."target_user_id", NEW."updated_at"); RETURN NULL;', + hash="d633c697d9068e2dc4a4e657b9af1c1247ee6e8f", + operation="INSERT", + pgid="pgtrigger_insert_insert_ec7c5", + table="moderation_moderationaction", + when="AFTER", + ), + ), + ), + pgtrigger.migrations.AddTrigger( + model_name="moderationaction", + trigger=pgtrigger.compiler.Trigger( + name="update_update", + sql=pgtrigger.compiler.UpsertTriggerSql( + condition="WHEN (OLD.* IS DISTINCT FROM NEW.*)", + func='INSERT INTO "moderation_moderationactionevent" ("action_type", "created_at", "details", "duration_hours", "expires_at", "id", "is_active", "moderator_id", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "reason", "related_report_id", "target_user_id", "updated_at") VALUES (NEW."action_type", NEW."created_at", NEW."details", NEW."duration_hours", NEW."expires_at", NEW."id", NEW."is_active", NEW."moderator_id", _pgh_attach_context(), NOW(), \'update\', NEW."id", NEW."reason", NEW."related_report_id", NEW."target_user_id", NEW."updated_at"); RETURN NULL;', + hash="58d5350f7ee0230033c491b6cbed1f356459c9da", + operation="UPDATE", + pgid="pgtrigger_update_update_2aec7", + table="moderation_moderationaction", + when="AFTER", + ), + ), + ), + migrations.AddIndex( + model_name="photosubmission", + index=models.Index( + fields=["content_type", "object_id"], + name="moderation__content_7a7bc1_idx", + ), + ), + migrations.AddIndex( + model_name="photosubmission", + index=models.Index(fields=["status"], name="moderation__status_7a1914_idx"), + ), + pgtrigger.migrations.AddTrigger( + model_name="photosubmission", + trigger=pgtrigger.compiler.Trigger( + name="insert_insert", + sql=pgtrigger.compiler.UpsertTriggerSql( + func='INSERT INTO "moderation_photosubmissionevent" ("caption", "content_type_id", "created_at", "date_taken", "handled_at", "handled_by_id", "id", "notes", "object_id", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "photo_id", "status", "updated_at", "user_id") VALUES (NEW."caption", NEW."content_type_id", NEW."created_at", NEW."date_taken", NEW."handled_at", NEW."handled_by_id", NEW."id", NEW."notes", NEW."object_id", _pgh_attach_context(), NOW(), \'insert\', NEW."id", NEW."photo_id", NEW."status", NEW."updated_at", NEW."user_id"); RETURN NULL;', + hash="198c1500ffe6dd50f9fc4bc7bbfbc1c392f1faa6", + operation="INSERT", + pgid="pgtrigger_insert_insert_62865", + table="moderation_photosubmission", + when="AFTER", + ), + ), + ), + pgtrigger.migrations.AddTrigger( + model_name="photosubmission", + trigger=pgtrigger.compiler.Trigger( + name="update_update", + sql=pgtrigger.compiler.UpsertTriggerSql( + condition="WHEN (OLD.* IS DISTINCT FROM NEW.*)", + func='INSERT INTO "moderation_photosubmissionevent" ("caption", "content_type_id", "created_at", "date_taken", "handled_at", "handled_by_id", "id", "notes", "object_id", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "photo_id", "status", "updated_at", "user_id") VALUES (NEW."caption", NEW."content_type_id", NEW."created_at", NEW."date_taken", NEW."handled_at", NEW."handled_by_id", NEW."id", NEW."notes", NEW."object_id", _pgh_attach_context(), NOW(), \'update\', NEW."id", NEW."photo_id", NEW."status", NEW."updated_at", NEW."user_id"); RETURN NULL;', + hash="4757ec44aa21ca0567f894df0c2a5db7d39ec98f", + operation="UPDATE", + pgid="pgtrigger_update_update_9c311", + table="moderation_photosubmission", + when="AFTER", + ), + ), + ), + ] diff --git a/apps/moderation/migrations/__init__.py b/apps/moderation/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/memory-bank/seed-command-fix.md b/memory-bank/seed-command-fix.md new file mode 100644 index 00000000..35971c91 --- /dev/null +++ b/memory-bank/seed-command-fix.md @@ -0,0 +1,106 @@ +# Seed Command Database Migration Issue + +## Problem +The `uv run manage.py seed_comprehensive_data --reset` command failed with: +``` +psycopg2.errors.UndefinedTable: relation "moderation_bulkoperation" does not exist +``` + +## Root Cause Analysis +1. The seed command imports models from `apps.moderation.models` including `BulkOperation` +2. The moderation app exists at `apps/moderation/` +3. However, `apps/moderation/migrations/` directory is empty (no migration files) +4. Django migration status shows no `moderation` app migrations +5. Therefore, the database tables for moderation models don't exist + +## Solution Steps +1. ✅ Identified missing moderation app migrations +2. ✅ Create migrations for moderation app using `makemigrations moderation` +3. ✅ Run migrations to create tables using `migrate` +4. ✅ Retry seed command with `--reset` flag - **ORIGINAL ISSUE RESOLVED** +5. 🔄 Fix new issue: User model field length constraint +6. 🔄 Verify seed command completes successfully + +## Commands to Execute +```bash +# Step 1: Create migrations for moderation app +uv run manage.py makemigrations moderation + +# Step 2: Apply migrations +uv run manage.py migrate + +# Step 3: Retry seed command +uv run manage.py seed_comprehensive_data --reset +``` + +## New Issue Discovered (Phase 3) +After resolving the moderation table issue, the seed command now progresses further but fails in Phase 3 with: +- **Error**: `django.db.utils.DataError: value too long for type character varying(10)` +- **Location**: User model save operation in `create_users()` method around line 880 +- **Additional Error**: `type object 'User' has no attribute 'Roles'` error + +## Root Cause Analysis (New Issue) +1. The seed command creates users successfully until the `user.save()` operation +2. Some field has a database constraint of `varchar(10)` but data being inserted exceeds this length +3. Need to identify which User model field has the 10-character limit +4. Also need to fix the `User.Roles` attribute error that appears before the database error + +## Next Steps for New Issue +1. ✅ COMPLETED - Examine User model definition to identify varchar(10) field +2. ✅ COMPLETED - Check seed data generation to find what value exceeds 10 characters +3. ✅ COMPLETED - Fix the varchar constraint violation (no User.Roles attribute error found) +4. ✅ COMPLETED - Either fix the data or update the model field length constraint +5. 🔄 IN PROGRESS - Re-run seed command to verify fix + +## Issue Analysis Results + +### Issue 1: User.Roles Attribute Error +- **Problem**: Code references `User.Roles` which doesn't exist in the User model +- **Location**: Likely in seed command around user creation area +- **Status**: Need to search and identify exact reference + +### Issue 2: VARCHAR(10) Constraint Violation +- **Problem**: Value `'PROFESSIONAL'` (12 chars) exceeds `role` field limit (10 chars) +- **Location**: `apps/core/management/commands/seed_comprehensive_data.py` line 876 +- **Code**: `user.role = random.choice(['ENTHUSIAST', 'CASUAL', 'PROFESSIONAL'])` +- **Root Cause**: `'PROFESSIONAL'` = 12 characters but User.role has `max_length=10` +- **Solution Options**: + 1. **Fix Data**: Change `'PROFESSIONAL'` to `'PRO'` (3 chars) or `'EXPERT'` (6 chars) + 2. **Expand Field**: Increase User.role max_length from 10 to 15 + +### User Model VARCHAR(10) Fields +1. `user_id` field (max_length=10) - line 38 ✅ OK +2. `role` field (max_length=10) - line 50 ⚠️ CONSTRAINT VIOLATION - **FIXED** +3. `privacy_level` field (max_length=10) - line 72 ✅ OK +4. `activity_visibility` field (max_length=10) - line 89 ✅ OK + +## Solution Applied + +### Fixed VARCHAR Constraint Violation +- **File**: `apps/core/management/commands/seed_comprehensive_data.py` +- **Line**: 876 +- **Change**: Modified role assignment from `['ENTHUSIAST', 'CASUAL', 'PROFESSIONAL']` to `['ENTHUSIAST', 'CASUAL', 'PRO']` +- **Reason**: `'PROFESSIONAL'` (12 characters) exceeded the User.role field's varchar(10) constraint +- **Result**: `'PRO'` (3 characters) fits within the 10-character limit + +### Code Change Details +```python +# BEFORE (caused constraint violation) +user.role = random.choice(['ENTHUSIAST', 'CASUAL', 'PROFESSIONAL']) + +# AFTER (fixed constraint violation) +user.role = random.choice(['ENTHUSIAST', 'CASUAL', 'PRO']) +``` + +**Character Count Analysis**: +- `'ENTHUSIAST'` = 10 chars ✅ (fits exactly) +- `'CASUAL'` = 6 chars ✅ (fits within limit) +- `'PROFESSIONAL'` = 12 chars ❌ (exceeded limit by 2 chars) +- `'PRO'` = 3 chars ✅ (fits within limit) + +## Key Learning +- Always ensure all app migrations are created and applied before running seed commands +- Check `showmigrations` output to verify all apps have proper migration status +- Missing migrations directory indicates app models haven't been migrated yet +- Seed data validation should check field length constraints before database operations +- Attribute errors in seed scripts should be caught early in development \ No newline at end of file