Dynamic forms same model

In my django application i have a tenant-like setup where each “Project” are strictly separated from each other, meaning that users with access to project A does not necessarily have access to project B.

Each project keeps track of n pupils:

class Pupil(models.Model):
    project = models.ForeignKey(Project, on_delete=models.CASCADE)
    name = models.CharField(max_length=50)

Each project has from 1 to 6 tasks, which all pupils of the specific project must complete.

Each task has an array of fields which needs to be registered for each pupil, such as “Overall status” (select), “Got help” (checkbox), “Date of completion” (date), “Notes” (textarea). But for each task, the labels, order, and input types varies.

To avoid hardcoding a model for each task, ive been trying to figure out how to implement “dynamic” forms, where an admin can setup a task-form in django-admin without having to create a model for each task.

My inexperienced attempt at a solution:

class Task(models.Model):
    project= models.ForeignKey(Project, on_delete=models.CASCADE)
    label = models.CharField(max_length=30)

class TaskField(models.Model):

    class FieldName(models.TextChoices):
        SELECT_1 = "select_1", "Select 1"
        SELECT_N = "select_n", "Select n"
        TEXT_1 = "text_1", "Text 1"
        TEXT_N = "text_n", "Text n"
        TEXTAREA_1 = "textarea_1", "Textarea 1"
        TEXTAREA_N = "textarea_n", "Textarea n"
        CHECKBOX_1 = "checkbox_1", "Checkbox 1"
        CHECKBOX_N = "checkbox_n", "Checkbox n"
        DATE_1 = "date_1", "Date 1"
        DATE_N = "date_n", "Date n"

    task= models.ForeignKey(Task, on_delete=models.CASCADE)
    field_name= models.CharField(max_length=10, choices=FieldName) 
    label = models.CharField(max_length=50)
    rank = models.SmallIntegerField(default=9)
    rows = models.SmallIntegerField(default=2)
    placeholder= models.CharField(max_length=100)
    required= models.BooleanField(default=False)
    select_options = models.TextField(null=True, blank=True) 

    class Meta:
        unique_together = ('task', 'field_name')

These models allows an admin to create a list of TaskFields, which can then be rendered dynamically in the template, as per the TaskField fields.

To keep track of the pupil, i would have:

class TaskValue(models.Model):
    task = models.ForeignKey(Task, on_delete=models.CASCADE) 
    pupil = models.ForeignKey(Pupil, on_delete=models.CASCADE)

    select_n = models.CharField(max_length=255, null=True, blank=True)
    text_n = models.CharField(max_length=255, null=True, blank=True)
    textarea_n = models.TextField(null=True, blank=True)
    checkbox_n = models.BooleanField(null=True, blank=True) 
    date_n  = models.DateField(null=True, blank=True)

*_n means there will be 9 fields of each select/text/checkbox mm, like: select_1, select_2, select_3…

In the business logic when a form is submitted, i would then use the TaskField.task_name to submit to the appropriate TaskValue field.

Does this approach make sense? Would you recommend other solutions?

Thank you