Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 10 additions & 49 deletions django_email_learning/admin.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
from django.contrib import admin
from django import forms
from django.http import HttpRequest
from .models import (
Course,
ImapConnection,
Quiz,
Lesson,
Question,
Answer,
CourseContent,
Organization,
OrganizationUser,
BlockedEmail,
Enrollment,
ContentDelivery,
Learner,
DeliverySchedule,
QuizSubmission,
)


Expand Down Expand Up @@ -45,51 +44,13 @@ def get_object(self, *args, **kwargs) -> ImapConnection | None: # type: ignore[
return obj


class QuizAdmin(admin.ModelAdmin):
list_display = ("title", "required_score", "is_published")
search_fields = ("title",)
list_filter = ("is_published",)

def get_fields(
self, request: HttpRequest, obj: Quiz | None = None
) -> tuple[str, ...]:
if obj is None:
return ("title", "required_score")
return ("title", "required_score", "is_published")


class AnswerInline(admin.TabularInline):
model = Answer
extra = 1


class QuestionAdmin(admin.ModelAdmin):
inlines = [AnswerInline]
list_display = ("text", "quiz")
search_fields = ("text",)
list_filter = ("quiz",)


class CourseContentAdmin(admin.ModelAdmin):
list_filter = ("course", "type")
list_display = ("course", "priority", "type", "get_content_title")
ordering = ("course", "priority")

def get_content_title(self, obj: CourseContent) -> str | None:
if obj.type == "lesson" and obj.lesson:
return obj.lesson.title
elif obj.type == "quiz" and obj.quiz:
return obj.quiz.title
return None


admin.site.register(Course, CourseAdmin)
admin.site.register(ImapConnection, ImapConnectionAdmin)
admin.site.register(Lesson)
admin.site.register(Quiz, QuizAdmin)
admin.site.register(CourseContent, CourseContentAdmin)
admin.site.register(Question, QuestionAdmin)
admin.site.register(Answer)
admin.site.register(Organization)
admin.site.register(BlockedEmail)
admin.site.register(OrganizationUser)
admin.site.register(Enrollment)
admin.site.register(ContentDelivery)
admin.site.register(Learner)
admin.site.register(DeliverySchedule)
admin.site.register(QuizSubmission)
232 changes: 128 additions & 104 deletions django_email_learning/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Generated by Django 5.2.8 on 2025-11-29 19:01
# Generated by Django 6.0 on 2026-01-03 12:20

import django.core.validators
import django.db.models.deletion
Expand Down Expand Up @@ -32,7 +32,30 @@ class Migration(migrations.Migration):
],
),
migrations.CreateModel(
name="EventTimestamp",
name="Course",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("title", models.CharField(max_length=200)),
(
"slug",
models.SlugField(
help_text="A short label for the course, used in URLs or email interactive actions. You can not edit it later."
),
),
("description", models.TextField(blank=True, null=True)),
("enabled", models.BooleanField(default=False)),
],
),
migrations.CreateModel(
name="DeliverySchedule",
fields=[
(
"id",
Expand All @@ -49,6 +72,7 @@ class Migration(migrations.Migration):
db_index=True, default=django.utils.timezone.now
),
),
("is_delivered", models.BooleanField(db_index=True, default=False)),
],
),
migrations.CreateModel(
Expand Down Expand Up @@ -172,7 +196,7 @@ class Migration(migrations.Migration):
},
),
migrations.CreateModel(
name="Course",
name="CourseContent",
fields=[
(
"id",
Expand All @@ -183,29 +207,42 @@ class Migration(migrations.Migration):
verbose_name="ID",
),
),
("title", models.CharField(max_length=200)),
("priority", models.IntegerField()),
(
"slug",
models.SlugField(
help_text="A short label for the course, used in URLs or email interactive actions. You can not edit it later."
"type",
models.CharField(
choices=[("lesson", "Lesson"), ("quiz", "Quiz")], max_length=50
),
),
(
"waiting_period",
models.IntegerField(
help_text="Waiting period in seconds after previous content is sent or submited."
),
),
(
"course",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="django_email_learning.course",
),
),
("description", models.TextField(blank=True, null=True)),
("enabled", models.BooleanField(default=False)),
(
"imap_connection",
"lesson",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="django_email_learning.imapconnection",
on_delete=django.db.models.deletion.CASCADE,
to="django_email_learning.lesson",
),
),
(
"organization",
"quiz",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
to="django_email_learning.organization",
to="django_email_learning.quiz",
),
),
],
Expand Down Expand Up @@ -271,6 +308,52 @@ class Migration(migrations.Migration):
),
],
),
migrations.CreateModel(
name="ContentDelivery",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("hash_value", models.CharField(blank=True, max_length=64, null=True)),
(
"course_content",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="django_email_learning.coursecontent",
),
),
(
"delivery_schedules",
models.ManyToManyField(to="django_email_learning.deliveryschedule"),
),
(
"enrollment",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="django_email_learning.enrollment",
),
),
],
options={
"unique_together": {("enrollment", "course_content")},
},
),
migrations.AddField(
model_name="course",
name="imap_connection",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="django_email_learning.imapconnection",
),
),
migrations.AddField(
model_name="imapconnection",
name="organization",
Expand All @@ -279,6 +362,14 @@ class Migration(migrations.Migration):
to="django_email_learning.organization",
),
),
migrations.AddField(
model_name="course",
name="organization",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="django_email_learning.organization",
),
),
migrations.CreateModel(
name="OrganizationUser",
fields=[
Expand Down Expand Up @@ -353,91 +444,6 @@ class Migration(migrations.Migration):
to="django_email_learning.quiz",
),
),
migrations.CreateModel(
name="CourseContent",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("priority", models.IntegerField()),
(
"type",
models.CharField(
choices=[("lesson", "Lesson"), ("quiz", "Quiz")], max_length=50
),
),
(
"waiting_period",
models.IntegerField(
help_text="Waiting period in seconds after previous content is sent or submited."
),
),
(
"course",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="django_email_learning.course",
),
),
(
"lesson",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
to="django_email_learning.lesson",
),
),
(
"quiz",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
to="django_email_learning.quiz",
),
),
],
),
migrations.CreateModel(
name="SentItem",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("times_sent", models.IntegerField(default=1)),
(
"course_content",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="django_email_learning.coursecontent",
),
),
(
"enrollment",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="django_email_learning.enrollment",
),
),
(
"send_events",
models.ManyToManyField(to="django_email_learning.eventtimestamp"),
),
],
),
migrations.CreateModel(
name="QuizSubmission",
fields=[
Expand All @@ -454,10 +460,10 @@ class Migration(migrations.Migration):
("is_passed", models.BooleanField()),
("submitted_at", models.DateTimeField(auto_now_add=True)),
(
"sent_item",
"delivery",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="django_email_learning.sentitem",
to="django_email_learning.contentdelivery",
),
),
],
Expand All @@ -476,8 +482,26 @@ class Migration(migrations.Migration):
name="course",
unique_together={("slug", "organization"), ("title", "organization")},
),
migrations.AlterUniqueTogether(
name="sentitem",
unique_together={("enrollment", "course_content")},
migrations.AddConstraint(
model_name="coursecontent",
constraint=models.UniqueConstraint(
condition=models.Q(("quiz__isnull", False)),
fields=("course", "quiz"),
name="unique_quiz_per_course",
),
),
migrations.AddConstraint(
model_name="coursecontent",
constraint=models.UniqueConstraint(
condition=models.Q(("lesson__isnull", False)),
fields=("course", "lesson"),
name="unique_lesson_per_course",
),
),
migrations.AddConstraint(
model_name="coursecontent",
constraint=models.UniqueConstraint(
fields=("course", "priority"), name="unique_priority_per_course"
),
),
]
Loading