Dynamic extra forms on Admin Inlines with initial values

if i have three models:

Class
  name
  date
  
Student
  class # fk to Class

Credit
  student # fk to Student
  class # fk to Class

a Student can be enrolled in a Class

a Credit is associated with a specific class and is assigned to a student.

The ClassAdmin has two inlines:
StudentInline shows who all is enrolled in the class.

CreditInline shows all the credits for the class (basically, who actually showed up)

my issue is this:

the default functionality is to only show Credits in the CreditInline that actually exist.
and you can add “extras” that will let the user add more.

however. i would like to show a row in the CreditInline for each student that is enrolled whether they already have a credit or not.

i would need to dynamically determine the number of extra forms based on the number of credits that already exist. and then pre fill out the initial value for the student field for those extras with whatever students do not already have a credit stored in the db.

and then sort them alphabetically of course… lol.

not sure if it is better to try and do this through the model admin. or if i just need to create a custom template. i’m not immediately seeing a straightforward way to add initial values to the extra forms on an inline.

thanks.

My initial reaction to this is that your models aren’t designed properly.

You’ve created both a Many-To-Many relationship between Class and Student (through Credit), and a One-To-Many relationship between these same two models, which creates a very bad code smell along with the possibility of creating inconsistent relationships.

It seems to me that there should only be the Many-To-Many relationship between Class and Student, with credit being an attribute of the through model joining them.

Mr. Whitesell.

thank you for the quick response.

I’m sorry, I simplified for brevity and I think i over simplified my example.

I can see what you mean, in the simplified example above it would make more sense to just put the credit fields directly on the same model that is relating the student, which would also have the intended side-effect of displaying these fields for everyone enrolled.

with less simplification.
my models are:

Student():
  #student information...
  pass

StudentClass():
  student # fk to Student
  class_instance # fk to Class
  # some metadata...

Class():
  # class information...
  pass

ClassMeeting():
  class_instance # fk to Class
  date 

Credit():
  student_class # fk to StudentClass
  class_meeting # fk to ClassMeeting
  constraints = Unique("student_class", "class_meeting")
  # credit information...

so with this structure, a Student can be enrolled in multiple classes.
a Class can have multiple ClassMeetings , and a Student can receive a credit for each individual ClassMeeting.

So that gives me the following admin structure:


StudentClassInline():
  # shows enrolled students
  pass

ClassMeetingInline():
  # shows class dates
  pass

ClassAdmin():
  # shows info about the class
  inlines = [StudentClassInline, ClassMeetingInline]

# ----

CreditInline():
  # shows credits for students

ClassMeetingAdmin():
  # shows info about this Meeting of the class
  inlines = [CreditInline]

So,

given a ClassMeeting points to a Class which owns the related Students

and the Credit points to the ClassMeeting

the goal is to populate the CreditInline on ClassMeetingAdmin with one entry for each Student that is related by ClassMeeting.class_instance.student_class_set.all()

or something to that effect.

basically trying to show a full table for each meeting instead of just what is already saved in the db.

thanks.

I still don’t think I would do it this way.

From what I can see so far, I would create the Many-to-many between Student and ClassMeeting, where the Credit is an attribute on that join table.

Logically, it is the Student who is scheduled to attend the Meeting.

One approach would be to prepopulate the StudentMeeting relationship. When a student registers for, or is assigned to, a class, I would have that process create the relationships between the student and all instances of the meeting for that class. If a new meeting is added to a class, that process then becomes responsible for adding the relationship to every registered student.

If the join table is prepopulated, then the inline “just works”, since you already have the requisite entries.

If you don’t want to prepopulate those relationships then I think you will need to create both a custom formset and a custom form for that inline. The custom formset is needed to calculate the number of extras to be created and the custom form is needed to populate the student for those forms. (The get_extra method would let you calculate the number of extra forms, but does not provide a hook for initializing those extras.)

But honestly, in this situation, I’d be creating my own view. This fits into one of those cases the Django docs warn you about:

If you need to provide a more process-centric interface that abstracts away the implementation details of database tables and fields, then it’s probably time to write your own views.