"""Initial schema for the community app (hand-written, mirrors models.py).""" import uuid import django.db.models.deletion import django.utils.timezone from django.conf import settings from django.db import migrations, models class Migration(migrations.Migration): initial = True dependencies = [ ("contenttypes", "0002_remove_content_type_name"), ("content", "0001_initial"), migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] operations = [ # ------------------------------------------------------------------ # Review # ------------------------------------------------------------------ migrations.CreateModel( name="Review", fields=[ ("id", models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), ("created_at", models.DateTimeField(auto_now_add=True, db_index=True)), ("updated_at", models.DateTimeField(auto_now=True)), ( "status", models.CharField( choices=[ ("pending", "Pending"), ("in_progress", "In progress"), ("completed", "Completed"), ("withdrawn", "Withdrawn"), ], default="pending", max_length=20, ), ), ( "decision", models.CharField( blank=True, choices=[ ("approve", "Approve"), ("request_changes", "Request changes"), ("reject", "Reject"), ], default="", max_length=20, ), ), ( "summary", models.TextField( blank=True, default="", help_text="Overall review summary shown to the author (Markdown subset).", ), ), ( "rubric", models.JSONField( blank=True, default=dict, help_text=( "Structured rubric scores, e.g. {'correctness': 5, 'clarity': 4, " "'difficulty_calibration': 3, 'accessibility': 4}." ), ), ), ("assigned_at", models.DateTimeField(blank=True, null=True)), ("completed_at", models.DateTimeField(blank=True, null=True)), ( "reviewer", models.ForeignKey( on_delete=django.db.models.deletion.PROTECT, related_name="reviews", to=settings.AUTH_USER_MODEL, ), ), ( "problem_version", models.ForeignKey( blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name="reviews", to="content.problemversion", ), ), ( "course_version", models.ForeignKey( blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name="reviews", to="content.courseversion", ), ), ], options={"ordering": ["-created_at"]}, ), # ------------------------------------------------------------------ # ReviewComment # ------------------------------------------------------------------ migrations.CreateModel( name="ReviewComment", fields=[ ("id", models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), ("created_at", models.DateTimeField(auto_now_add=True, db_index=True)), ("updated_at", models.DateTimeField(auto_now=True)), ("deleted_at", models.DateTimeField(blank=True, null=True)), ( "kind", models.CharField( choices=[ ("comment", "Comment"), ("suggestion", "Suggestion"), ("blocking", "Blocking issue"), ], default="comment", max_length=20, ), ), ("body", models.TextField(help_text="Markdown subset with inline LaTeX.")), ( "content_path", models.CharField( blank=True, default="", help_text="JSON Pointer into the reviewed version's content document.", max_length=255, ), ), ("is_resolved", models.BooleanField(default=False)), ("resolved_at", models.DateTimeField(blank=True, null=True)), ( "review", models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, related_name="comments", to="community.review", ), ), ( "author", models.ForeignKey( on_delete=django.db.models.deletion.PROTECT, related_name="review_comments", to=settings.AUTH_USER_MODEL, ), ), ( "parent", models.ForeignKey( blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name="replies", to="community.reviewcomment", ), ), ( "resolved_by", models.ForeignKey( blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name="+", to=settings.AUTH_USER_MODEL, ), ), ], options={"ordering": ["created_at"]}, ), # ------------------------------------------------------------------ # DiscussionThread # ------------------------------------------------------------------ migrations.CreateModel( name="DiscussionThread", fields=[ ("id", models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), ("created_at", models.DateTimeField(auto_now_add=True, db_index=True)), ("updated_at", models.DateTimeField(auto_now=True)), ("deleted_at", models.DateTimeField(blank=True, null=True)), ("object_id", models.UUIDField()), ("title", models.CharField(max_length=200)), ( "kind", models.CharField( choices=[ ("question", "Question"), ("discussion", "Discussion"), ("clarification", "Clarification request"), ("solution", "Solution discussion"), ("feedback", "Feedback"), ], default="discussion", max_length=20, ), ), ("is_pinned", models.BooleanField(default=False)), ("is_locked", models.BooleanField(default=False)), ( "contains_spoilers", models.BooleanField( default=False, help_text="If true, the thread is hidden until the learner attempts the target.", ), ), ("comment_count", models.PositiveIntegerField(default=0)), ("last_activity_at", models.DateTimeField(default=django.utils.timezone.now)), ( "content_type", models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, to="contenttypes.contenttype", ), ), ( "author", models.ForeignKey( on_delete=django.db.models.deletion.PROTECT, related_name="discussion_threads", to=settings.AUTH_USER_MODEL, ), ), ], options={"ordering": ["-is_pinned", "-last_activity_at"]}, ), # ------------------------------------------------------------------ # DiscussionComment # ------------------------------------------------------------------ migrations.CreateModel( name="DiscussionComment", fields=[ ("id", models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), ("created_at", models.DateTimeField(auto_now_add=True, db_index=True)), ("updated_at", models.DateTimeField(auto_now=True)), ("deleted_at", models.DateTimeField(blank=True, null=True)), ("body", models.TextField(help_text="Markdown subset with inline LaTeX.")), ( "score", models.IntegerField( default=0, help_text="Denormalised vote tally, refreshed by background jobs.", ), ), ( "is_accepted", models.BooleanField( default=False, help_text="Accepted answer for threads of kind 'question'.", ), ), ("edited_at", models.DateTimeField(blank=True, null=True)), ( "thread", models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, related_name="comments", to="community.discussionthread", ), ), ( "author", models.ForeignKey( on_delete=django.db.models.deletion.PROTECT, related_name="discussion_comments", to=settings.AUTH_USER_MODEL, ), ), ( "parent", models.ForeignKey( blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name="replies", to="community.discussioncomment", ), ), ], options={"ordering": ["created_at"]}, ), # ------------------------------------------------------------------ # Vote # ------------------------------------------------------------------ migrations.CreateModel( name="Vote", fields=[ ("id", models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), ("created_at", models.DateTimeField(auto_now_add=True, db_index=True)), ("updated_at", models.DateTimeField(auto_now=True)), ("object_id", models.UUIDField()), ("value", models.SmallIntegerField(choices=[(1, "Up"), (-1, "Down")])), ( "voter", models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, related_name="votes", to=settings.AUTH_USER_MODEL, ), ), ( "content_type", models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, to="contenttypes.contenttype", ), ), ], ), # ------------------------------------------------------------------ # Report # ------------------------------------------------------------------ migrations.CreateModel( name="Report", fields=[ ("id", models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), ("created_at", models.DateTimeField(auto_now_add=True, db_index=True)), ("updated_at", models.DateTimeField(auto_now=True)), ("object_id", models.UUIDField()), ( "reason", models.CharField( choices=[ ("spam", "Spam"), ("plagiarism", "Plagiarism / missing attribution"), ("incorrect_content", "Incorrect content"), ("harassment", "Harassment or abuse"), ("copyright", "Copyright violation"), ("off_topic", "Off topic"), ("security", "Security issue (e.g. malicious widget)"), ("other", "Other"), ], max_length=30, ), ), ("details", models.TextField(blank=True, default="")), ( "status", models.CharField( choices=[ ("open", "Open"), ("triaged", "Triaged"), ("action_taken", "Action taken"), ("dismissed", "Dismissed"), ("duplicate", "Duplicate"), ], default="open", max_length=20, ), ), ("resolution_note", models.TextField(blank=True, default="")), ("resolved_at", models.DateTimeField(blank=True, null=True)), ( "reporter", models.ForeignKey( blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name="reports_filed", to=settings.AUTH_USER_MODEL, ), ), ( "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="reports_handled", to=settings.AUTH_USER_MODEL, ), ), ], options={"ordering": ["-created_at"]}, ), # ------------------------------------------------------------------ # Notification # ------------------------------------------------------------------ migrations.CreateModel( name="Notification", fields=[ ("id", models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), ("created_at", models.DateTimeField(auto_now_add=True, db_index=True)), ("updated_at", models.DateTimeField(auto_now=True)), ( "verb", models.CharField( choices=[ ("review_requested", "Review requested"), ("review_completed", "Review completed"), ("review_comment", "New review comment"), ("content_published", "Content published"), ("content_rejected", "Content rejected"), ("discussion_reply", "New reply in thread"), ("comment_reply", "Reply to your comment"), ("answer_accepted", "Answer accepted"), ("mention", "You were mentioned"), ("vote_milestone", "Vote milestone reached"), ("report_resolved", "Your report was resolved"), ("moderation_action", "Moderation action"), ("system", "System announcement"), ], max_length=40, ), ), ("object_id", models.UUIDField(blank=True, null=True)), ( "payload", models.JSONField( blank=True, default=dict, help_text="Render-ready context: titles, slugs, snippet text, URLs.", ), ), ("read_at", models.DateTimeField(blank=True, null=True)), ("emailed", models.BooleanField(default=False)), ( "recipient", models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, related_name="notifications", to=settings.AUTH_USER_MODEL, ), ), ( "actor", models.ForeignKey( blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name="+", to=settings.AUTH_USER_MODEL, ), ), ( "content_type", models.ForeignKey( blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to="contenttypes.contenttype", ), ), ], options={"ordering": ["-created_at"]}, ), # ------------------------------------------------------------------ # Indexes # ------------------------------------------------------------------ migrations.AddIndex( model_name="review", index=models.Index(fields=["status"], name="comm_review_status_idx"), ), migrations.AddIndex( model_name="review", index=models.Index(fields=["reviewer", "status"], name="comm_review_reviewer_idx"), ), migrations.AddIndex( model_name="reviewcomment", index=models.Index(fields=["review", "created_at"], name="comm_rcomment_review_idx"), ), migrations.AddIndex( model_name="discussionthread", index=models.Index(fields=["content_type", "object_id"], name="comm_thread_target_idx"), ), migrations.AddIndex( model_name="discussionthread", index=models.Index(fields=["-last_activity_at"], name="comm_thread_activity_idx"), ), migrations.AddIndex( model_name="discussioncomment", index=models.Index(fields=["thread", "created_at"], name="comm_dcomment_thread_idx"), ), migrations.AddIndex( model_name="vote", index=models.Index(fields=["content_type", "object_id"], name="comm_vote_target_idx"), ), migrations.AddIndex( model_name="report", index=models.Index(fields=["status", "created_at"], name="comm_report_status_idx"), ), migrations.AddIndex( model_name="report", index=models.Index(fields=["content_type", "object_id"], name="comm_report_target_idx"), ), migrations.AddIndex( model_name="notification", index=models.Index(fields=["recipient", "read_at"], name="comm_notif_recipient_idx"), ), migrations.AddIndex( model_name="notification", index=models.Index(fields=["recipient", "-created_at"], name="comm_notif_recent_idx"), ), # ------------------------------------------------------------------ # Constraints # ------------------------------------------------------------------ migrations.AddConstraint( model_name="review", constraint=models.CheckConstraint( condition=( models.Q(("problem_version__isnull", False), ("course_version__isnull", True)) | models.Q(("problem_version__isnull", True), ("course_version__isnull", False)) ), name="ck_review_one_target", ), ), migrations.AddConstraint( model_name="review", constraint=models.CheckConstraint( condition=( ~models.Q(("status", "completed")) | ~models.Q(("decision", "")) ), name="ck_review_completed_decision", ), ), migrations.AddConstraint( model_name="review", constraint=models.UniqueConstraint( condition=( models.Q(("problem_version__isnull", False)) & ~models.Q(("status", "withdrawn")) ), fields=("reviewer", "problem_version"), name="uq_review_problem_ver_active", ), ), migrations.AddConstraint( model_name="review", constraint=models.UniqueConstraint( condition=( models.Q(("course_version__isnull", False)) & ~models.Q(("status", "withdrawn")) ), fields=("reviewer", "course_version"), name="uq_review_course_ver_active", ), ), migrations.AddConstraint( model_name="reviewcomment", constraint=models.CheckConstraint( condition=~models.Q(("parent", models.F("id"))), name="ck_rcomment_no_self_parent", ), ), migrations.AddConstraint( model_name="discussioncomment", constraint=models.CheckConstraint( condition=~models.Q(("parent", models.F("id"))), name="ck_dcomment_no_self_parent", ), ), migrations.AddConstraint( model_name="vote", constraint=models.UniqueConstraint( fields=("voter", "content_type", "object_id"), name="uq_vote_voter_target", ), ), migrations.AddConstraint( model_name="vote", constraint=models.CheckConstraint( condition=models.Q(("value__in", [-1, 1])), name="ck_vote_value_pm1", ), ), migrations.AddConstraint( model_name="report", constraint=models.UniqueConstraint( condition=models.Q(("status", "open")), fields=("reporter", "content_type", "object_id"), name="uq_report_open_per_target", ), ), ]