Making Date-Based Content Reusable
Problem
Content with specific dates can't be reused.
Example: An exam with these dates:
- Grading due: Jan 18, 2024
- Appeal deadline: Jan 20, 2024
- Confirmation due: Jan 22, 2024
Want to reuse this exam in July? You have to manually update every date.
Have 50 exams? Good luck.
Common Approaches (and why they fail)
1. Hard-coded dates
exam.grade_due = datetime(2024, 1, 18)
exam.appeal_deadline = datetime(2024, 1, 20)
- Can't reuse
- Every instance needs manual date updates
2. Copy everything
july_exam = copy(jan_exam)
july_exam.grade_due = datetime(2024, 7, 18)
# Update 10+ date fields...
- Content duplication
- Update original? Copies don't change
- Maintenance nightmare
3. Templates with placeholders
exam.dates = "{{start_date}} + 3 days"
- Complex parsing
- No type safety
- Hard to query
Solution: Store offsets, calculate at runtime
Content layer: Relative dates (logic only)
class Exam(Model):
title = CharField(max_length=255)
description = TextField()
grade_due_days = PositiveSmallIntegerField()
appeal_deadline_days = PositiveSmallIntegerField()
confirm_due_days = PositiveSmallIntegerField()
def get_grading_date(self, access_date: AccessDate):
access_end = access_date["end"]
grade_due = access_end + timedelta(days=self.grade_due_days)
appeal_deadline = grade_due + timedelta(days=self.appeal_deadline_days)
confirm_due = appeal_deadline + timedelta(days=self.confirm_due_days)
return GradingDate(
grade_due=grade_due,
appeal_deadline=appeal_deadline,
confirm_due=confirm_due
)
Context layer: Base dates (when it's actually used)
class Course(Model):
title = CharField(max_length=255)
start = DateTimeField()
end = DateTimeField()
archive = DateTimeField()
class Enrollment(Model):
user = ForeignKey(User, CASCADE)
course = ForeignKey(Course, CASCADE)
active = BooleanField(default=True)
start = DateTimeField() # Can override course dates
end = DateTimeField()
archive = DateTimeField()
In real-world use:
- Course provides base dates for all students
- Enrollment can override for individual students (e.g., extended deadline)
- Either can serve as context for date calculation
Usage: Calculate absolute dates at runtime
async def get_session(cls, *, exam_id: str, learner_id: str, context: str, access_date: AccessDate):
exam = await Exam.objects.select_related("owner", "honor_code", "question_pool").aget(id=exam_id)
session = SessionDict(
access_date=access_date,
grading_date=exam.get_grading_date(access_date), # Calculated here
step=LearningSessionStep.READY,
exam=exam,
)
return session
Why This Works
Single source of truth:
- Exam defines logic: "grading due 3 days after exam ends"
- Course/Enrollment defines dates: "this course runs Jan-Mar"
- Calculation happens when needed
Reusability:
Same exam:
- January course: Grade due Jan 18
- July course: Grade due Jul 18
- September course: Grade due Sep 18
No duplication. No manual updates.
Update logic once:
Change grade_due_days = 3 to grade_due_days = 5
→ Affects all courses using this exam
→ Each with their own dates
Example
Define exam once:
exam = Exam.objects.create(
title="Final Exam",
grade_due_days=3,
appeal_deadline_days=2,
confirm_due_days=1
)
Use in multiple courses:
# January course
jan_course = Course(
title="Spring 2024",
start=datetime(2024, 1, 1),
end=datetime(2024, 1, 15)
)
grading_date = exam.get_grading_date(jan_course)
# grade_due: Jan 18, appeal_deadline: Jan 20, confirm_due: Jan 21
# July course
jul_course = Course(
title="Summer 2024",
start=datetime(2024, 7, 1),
end=datetime(2024, 7, 15)
)
grading_date = exam.get_grading_date(jul_course)
# grade_due: Jul 18, appeal_deadline: Jul 20, confirm_due: Jul 21
Same exam. Different dates. No duplication.
Key Insight
Separate concerns:
- Content: Logic (offsets, rules)
- Context: Base dates (when it happens)
- Runtime: Absolute dates (calculated)
This makes content date-independent and infinitely reusable.
Top comments (0)