1

I'm creating an app for an educational school. The school has a RegularSchoolClass class (inherits from a base SchoolClass class) and also has a LessonSchedule class that tracks the schedule of a class.

In particular, the SchoolClass (base class) has a many-to-many relationship with LessonSchedule. In particular, a SchoolClass object should have 48 lesson schedules (e.g. week 0, week 1, week 2; 48 lessons in a year). At the same time, a particular lesson schedule could be shared by multiple SchoolClass objects, hence I chose a many-to-many relationship.

I wrote some logic that whenever a user changes the day of a class in the admin console, it should automatically change all the LessonScheules linked to the class. For instance, if a class was originally on a tuesday, and it switched to a wednesday, then all the lesson schedules should be switched accordingly.

What's weird is that this doesn't show up in the admin console - it defaults to 0 lesson schedule, even after I save. But in the console, self.lesson_schedule.all().count() shows 48. Wondering if someone could guide me to the right answer?

I checked (Saving Many To Many data via a modelform in Django) and my case seems to be different since I have already saved the parent object (I'm using post-save)

models.py (for school class)

class SchoolClass(TimeStampedModel):
    lesson_schedule = models.ManyToManyField('LessonSchedule', null=True, blank=True)
    day = models.IntegerField(choices=DAYS, null=True)
    start_date = models.DateField(null=True)

    def __str__(self):
        return '{}'.format(self.lesson_schedule.all().count())

class RegularSchoolClass(DirtyFieldsMixin, SchoolClass):    
    def save(self, *args, **kwargs):
        self.clean()
        super().save(*args, **kwargs)    

    def save_without_signals(self):
        setattr(self, '_disable_signals', True)
        self.save()
        #this correctly prints 48 -> so lesson_schedule should be linked correctly
        print(self.lesson_schedule.all().count())
        setattr(self, '_disable_signals', False)

@receiver(post_save, sender=RegularSchoolClass)
def post_save_regular_school_class(sender, instance, created, **kwargs):
    #https://stackoverflow.com/questions/10840030/django-post-save-preventing-recursion-without-overriding-model-save
    #prevents infinite saving
    if getattr(instance, '_disable_signals', False):
        return

    #for new instances or when we have changed the day, we want to update start_date
    #and assign lesson schedules
    if created or (instance.is_dirty() and 'day' in instance.get_dirty_fields()):

        # existing lesson schedule needs to be unlinked
        for l in instance.lesson_schedule.all():
            instance.lesson_schedule.remove(l)

        #assigning new lesson schedule
        for l in LessonSchedule.objects.filter(start_date=instance.start_date):
            instance.lesson_schedule.add(l)

        instance.save_without_signals()

models.py (for lessonschedule)

#used to track the day for a lesson
#for regular classes, start_date should be the date of the first lesson in the year 
class LessonSchedule(models.Model):
    start_date = models.DateField()
    lesson_index = models.IntegerField()
    lesson_date = models.DateField()

    class Meta:
        unique_together = 'start_date', 'lesson_index')
Dan Tang
  • 1,273
  • 2
  • 20
  • 35

2 Answers2

1

If you using ModelForm you can try this way

class GrupoIncidenciaForm(forms.ModelForm):
id = forms.CharField(required=False, widget=forms.HiddenInput())

codigo = forms.CharField(required=True,
                         label='Código',
                         widget=forms.TextInput(attrs={'maxlength': '5', 'class': 'text-uppercase'}))

titulo = forms.CharField(required=True, label='Título', widget=forms.TextInput(attrs={'maxlength': '255'}))

class Meta:
    model = GrupoIncidencia
    fields = '__all__'
    labels = {
        'tipo': 'Tipo'
    }

def clean_codigo(self):
    return self.cleaned_data['codigo'].upper().zfill(5)

def __init__(self, *args, **kwargs):
    if kwargs.get('instance'):
        initial = kwargs.setdefault('initial', {})
        initial['incidencias'] = [t.pk for t in kwargs['instance'].incidencias.all()]
    forms.ModelForm.__init__(self, *args, **kwargs)

def save(self, commit=True):
    instance = forms.ModelForm.save(self, False)
    old_save_m2m = self.save_m2m

    def save_m2m():
        old_save_m2m()
        instance.incidencias.clear()
        for incidencia in self.cleaned_data['incidencias']:
            instance.incidencias.add(incidencia)

    self.save_m2m = save_m2m
    if commit:
        instance.save()
        self.save_m2m()

    return instance
0

example

See image attachment. Just using admin from django.contrib import admin .

Truong Cong Hau
  • 151
  • 1
  • 6