The ESP Website uses a flexible registration system that tracks how students relate to classes through different registration types. This allows for various registration workflows including:
A RegistrationType defines a way a student can be associated with a class. It’s like a label that describes the relationship.From esp/esp/program/models/__init__.py:
class RegistrationType(models.Model): name = models.CharField(max_length=32) # e.g., "Enrolled" displayName = models.CharField(max_length=32) # e.g., "Enrolled in class" description = models.TextField() # Detailed explanation category = models.CharField(max_length=32) # "student" or other grouping
# Get map of all registration typesreg_map = RegistrationType.get_map()enrolled_type = reg_map['Enrolled']applied_type = reg_map['Applied']# Ensure specific types existreg_map = RegistrationType.get_map( include=['Enrolled', 'Waitlisted'], category='student')# Get a specific type (cached)enrolled = RegistrationType.get_cached('Enrolled', 'student')
Registration types are created automatically as needed. The first “Enrolled” type is created during system installation.
# Get registration typeenrolled = RegistrationType.get_map()['Enrolled']# Create registrationreg = StudentRegistration.objects.create( user=student, section=section, relationship=enrolled)# The student is now enrolled!
# Get all valid (non-expired) registrations for a sectionregistrations = StudentRegistration.valid_objects().filter( section=section, relationship__name='Enrolled')# Get students from registrationsstudents = [reg.user for reg in registrations]# Or use the section's helper methodenrolled_students = section.students() # Default: Enrolled
For lottery systems, students can mark interest in entire classes (not specific sections):
class StudentSubjectInterest(ExpirableModel): subject = models.ForeignKey(ClassSubject) # The class, not section user = models.ForeignKey(ESPUser) # start_date and end_date from ExpirableModel
This is typically represented by “stars” in a two-phase lottery interface.
# Student stars a class in lotteryinterest = StudentSubjectInterest.objects.create( user=student, subject=class_subject)# Get all classes a student is interested ininterests = StudentSubjectInterest.valid_objects().filter(user=student)interested_classes = [i.subject for i in interests]# Get all students interested in a classclass_interests = StudentSubjectInterest.valid_objects().filter( subject=class_subject)interested_students = [i.user for i in class_interests]
Some programs limit how many “priority” registrations students can make:
# Program configurationscrmi = program.studentclassregmoduleinfoscrmi.use_priority = Truescrmi.priority_limit = 10 # Max 10 priority classes# Check student's priority level for a timeslotpriority = student.getRegistrationPriority(program, [timeslot])# Returns:# - 0 if already enrolled in that timeslot# - N (1, 2, 3...) for the next available priority levelif priority > scrmi.priority_limit: # Student has used all priority slots return "Cannot add more priority classes"
# Get all sections student is enrolled insections = student.getEnrolledSections(program)# Get with specific registration typessections = student.getSections( program, verbs=['Enrolled', 'Waitlisted'], valid_only=True)# Get just the classes (not sections)classes = student.getEnrolledClasses(program)
Student enters contact info, emergency contacts, grade, etc.
3
Profile Saved
RegistrationProfile created for this program
4
Access Class Registration
Student can now browse and register for classes
# Get or create profile for a programprofile = RegistrationProfile.getLastForProgram(student, program)if profile.id is None: # New profile - student needs to fill it out redirect_to_profile_form()else: # Profile exists - allow class registration show_catalog()
Programs can define constraints that limit what students can register for:
# Example: Can't take Advanced Physics without Basic Physicsconstraint = ScheduleConstraint.objects.create( program=program, requirement=Requirement(label="take Basic Physics first"))# When student tries to add Advanced Physics:sm = ScheduleMap(student, program)sm.add_section(advanced_physics_section)if not constraint.evaluate(sm): return "You must take Basic Physics first"
def complete_registration(student, section): """Complete registration process with all checks.""" # 1. Check if student can join program program = section.parent_class.parent_program if not program.user_can_join(student): return False, "Program is at capacity" # 2. Ensure student has a profile profile = RegistrationProfile.getLastForProgram(student, program) if not profile.student_info: return False, "Please complete your profile first" # 3. Check if student can add this section error = section.cannotAdd(student, checkFull=True) if error: return False, error # 4. Create registration enrolled = RegistrationType.get_map()['Enrolled'] StudentRegistration.objects.create( user=student, section=section, relationship=enrolled ) return True, "Successfully registered!"
def switch_sections(student, old_section, new_section): """Move student from one section to another.""" # Must be sections of the same class if old_section.parent_class != new_section.parent_class: return False, "Sections must be from the same class" # Check if new section is available error = new_section.cannotAdd(student, checkFull=True) if error: return False, error # Remove from old section old_regs = StudentRegistration.valid_objects().filter( user=student, section=old_section, relationship__name='Enrolled' ) old_regs.update(end_date=datetime.now()) # Add to new section enrolled = RegistrationType.get_map()['Enrolled'] StudentRegistration.objects.create( user=student, section=new_section, relationship=enrolled ) return True, "Successfully switched sections"