dlteel
dlteel

Reputation: 55

Populate Django ManyToManyField Options Based on Other Field in Admin

I am having problems filtering options for a ManyToManyField on the Django Admin Add screen based on input to another field on the same form. I am new to Django and have been unable to use any of the generic fixes described elsewhere because they are all slightly different than my situation. Here is my situation:

I have three models in my project: Class, Student, and AttendanceRecord. In the Django Admin, when adding an attendance record, I would like to change the options for the field Absent_Students based on the selection made for the field Associated_Class. So, for example, if Associated_Class "CS 450" is selected, the options for Absent_Students should change to only students whose class_list includes CS 450.

Here are my models:

from __future__ import unicode_literals

from django.db import models
from django.contrib.auth.models import User
from django.utils.encoding import python_2_unicode_compatible
import random, string

# Create your models here.

#This is the model for a student
@python_2_unicode_compatible
class Student(models.Model):
    pass
    Student_First_Name = models.CharField(max_length=200)
    Student_Last_Name = models.CharField(max_length=200)
    Student_ID_Number = models.CharField(max_length=200)
    Student_Class = models.ForeignKey('Class', null=True)
    def __str__(self):
        return self.Student_Last_Name + ',' + self.Student_First_Name

# This is the model for a class
@python_2_unicode_compatible
class Class(models.Model):  

    class Meta:
        verbose_name_plural = "Classes"
    Class_Name = models.CharField(max_length=200)
    Student_List = models.ManyToManyField('Student', related_name='class_list')
    Professor = models.ForeignKey(User,null=True)
    AddCode = models.IntegerField
    pass
    def __str__(self):
        return self.Class_Name
    def getName(self):
        return self.Class_Name
    def getProfessor(self):
        return self.Professor.id
    def getProf(self):
        return self.Professor
    def getStudents(self):
        return self.Student_List

#This is the model for attendance records
class AttendanceRecord(models.Model):
    class Meta:
        verbose_name = "Attendance Record"
    Associated_Class = models.ForeignKey(Class, on_delete=models.CASCADE, related_name='Attendance_Records')
    Date = models.DateField()
    Absent_Students = models.ManyToManyField('Student', blank=True)
    Present_Students = models.ManyToManyField('Student', related_name='a')
    def get_associated_class_id(self):
        return self.Associated_Class
    def __str__(self):
        return self.Associated_Class.__str__() + ' on date ' + self.Date.__str__(self)

I have tried doing this by editing the AttendanceRecordAdminForm class and AttendanceRecordAdmin class. My problem is that when setting the self.fields['Absent_Students].queryset I do not know how to access the currently selected Associated_Class on the form. I keep getting an error that "AttendanceRecord has no Associated_Class". Here are those classes just discussed in their entirety:

class AttendanceRecordAdminForm(forms.ModelForm):

  class Meta:
    model = AttendanceRecord
    fields = '__all__'

  def __init__(self, *args, **kwargs):
    super(AttendanceRecordAdminForm, self).__init__(*args, **kwargs)
    instance = kwargs.get('instance', None)
    self.fields['Absent_Students'].queryset = Student.objects.filter(class_list__id=self.instance.get_associated_class_id())
    self.fields['Present_Students'].queryset = Student.objects.filter(class_list__id=1)


class AttendanceRecordAdmin(admin.ModelAdmin):
  form = AttendanceRecordAdminForm
  filter_horizontal = ('Absent_Students', 'Present_Students',)

Basically, I am looking for a way to access the currently entered Associated_Class on the admin form so I can properly filter the queryset.

Upvotes: 2

Views: 1565

Answers (1)

dlteel
dlteel

Reputation: 55

After hours more of online searching I finally found what I needed. A chained ManyToMany from the smart_select app makes this very easy. This link: How to use django-smart-select describes the install process and also links to the documentation for using it once it is installed. Hopefully this helps some others as well.

Upvotes: 1

Related Questions