DEV Community

Johnston Kweku Abubakar
Johnston Kweku Abubakar

Posted on

I built the same database pattern a third time today — then caught an AI tool trying to quietly break it

 I'm building EduTrack, a Django-based school management system for Ghanaian primary and JHS schools. Yesterday I wrote about independently recognizing a structural pattern across three different features in the same codebase: a parent record describing an event, and child records describing individual outcomes of that event, each linked back via a foreign key.
I'd used it for Fee/FeePayment and Attendance/AttendanceRecord. Yesterday I recognized I needed it a third time, for tracking student assessment scores.
Today's build: Assessment / AssessmentRecord
``
python
class Assessment(models.Model):
class AssessmentType(models.TextChoices):
QUIZ = 'QUIZ', 'Quiz'
CLASS_TEST = 'CLASS TEST', 'Class Test'
EXAM = 'EXAM', 'Exam'
EXERCISE = 'EXERCISE', 'Exercise'

date = models.DateTimeField(default=timezone.now)
assessment_type = models.CharField(max_length=30, choices=AssessmentType.choices)
recorded_by = models.ForeignKey(User, on_delete=models.PROTECT)
subject = models.ForeignKey(Subject, on_delete=models.PROTECT)
term = models.ForeignKey(Term, on_delete=models.PROTECT)
academic_year = models.ForeignKey(AcademicYear, on_delete=models.PROTECT)
student_class = models.ForeignKey(Class, on_delete=models.PROTECT)
max_score = models.DecimalField(max_digits=5, decimal_places=2)
Enter fullscreen mode Exit fullscreen mode

class AssessmentRecord(models.Model):
assessment = models.ForeignKey(Assessment, on_delete=models.PROTECT)
student = models.ForeignKey(Student, on_delete=models.PROTECT)
score = models.DecimalField(max_digits=5, decimal_places=2)

class Meta:
    unique_together = ['student', 'assessment']

def save(self, *args, **kwargs):
    if self.score < 0 or self.score > self.assessment.max_score:
        raise ValidationError(
            f'Score must be between 0 and {self.assessment.max_score}'
        )
    super().save(*args, **kwargs)`
Enter fullscreen mode Exit fullscreen mode

`The validation reads self.assessment.max_score directly rather than hardcoding assumed maximums per test type — because the actual max score is whatever the teacher set when creating that specific assessment instance, not a fixed global constant.
Then I asked an AI tool to scaffold the admin interface
I wanted to manage assessments and scores through Django's built-in admin panel rather than building a custom view immediately. I asked an AI tool to generate the admin configuration, including an inline formset that would filter the student dropdown to only show students relevant to the assessment being edited.
The generated code was structurally impressive — it even handled an edge case I hadn't explicitly asked for, showing an empty student queryset for brand-new, unsaved assessments instead of loading every student in the database.
It also had three real bugs:
Bug 1 — wrong model reference. The inline class was configured with model = Assessment instead of model = AssessmentRecord. This would nest the parent model inside its own admin page rather than managing the actual child records.
Bug 2 — filtering through the wrong relationship. The code filtered students by joining through Subject and a ClassSubject mapping table, rather than using the student_class field that already exists directly on Assessment. Since a subject like Mathematics can be taught across multiple classes, this filter would have surfaced students from classes that had nothing to do with the specific assessment being edited — a subtle, hard-to-notice bug that wouldn't throw an error, just quietly show the wrong group of people.
Bug 3 — mismatched condition. The guard clause checked if obj and obj.subject: while the actual filter used obj.student_class. Two different fields, same conditional gate.
None of these would crash. All three would ship silently, pass a casual test, and only reveal themselves as a confused teacher staring at the wrong student list weeks or months later.
What let me catch them
Not a vague sense that "something feels off" — a specific question applied at each line: what is this code actually trying to achieve, and does this exact line achieve that, or something merely adjacent to it?
The pattern recognition from building the same shape correctly twice before made this fast. I knew precisely what the correct version should look like, which made the drift in the generated version obvious rather than something I had to puzzle out from scratch.
The actual takeaway
AI-generated code — admin scaffolding, view logic, anything — is a draft. It can be well-structured, thoughtful, even anticipate edge cases, and still be wrong in ways that don't announce themselves. The understanding of why the code should look a certain way has to be yours, built through repetition and real bugs, not borrowed from whatever generated the suggestion.
Still building, still verifying every line against intent rather than vibes.

Top comments (0)