post save signal only when update called

Viewed 1499

hello i have two models and a post_save signal, but my signal only when object is updated is work and when i create a new object it not work

my code :

class Attendance(models.Model):
    classroom = models.ForeignKey('Class', on_delete=models.CASCADE)
    student = models.ForeignKey(User, on_delete=models.CASCADE)
    is_present = models.BooleanField(default=False)
    present_time = models.DateTimeField(null=True, blank=True)

    def __str__(self):
        return self.student.username


class Class(models.Model):
    course = models.ForeignKey(Course, on_delete=models.CASCADE)
    students = models.ManyToManyField(User)
    quantity = models.PositiveSmallIntegerField()
    start_time = models.DateTimeField()
    end_time = models.DateTimeField()

    class Meta:
        unique_together = ['quantity', 'course']


    def __str__(self):
        return f'{self.quantity} / {self.course}'


@receiver(post_save, sender=Class)
def create_attendance(sender, instance, **kwargs):
    for student in instance.students.all():
        try:
            Attendance.objects.get(classroom=instance, student=student)
        except Attendance.DoesNotExist:
            Attendance.objects.create(classroom=instance, student=student)
2 Answers

At the moment you create the instance, the instance.students is not populated (yet) and thus empty, this means that for student in instance.students.all() will enumerate over an empty collection, and thus not enumerate at all.

This is because it first needs to create a record with a primary key, to then use it for the junction table, which is one of the (many) reasons why using signals is often not a good idea.

It might be better to work with an m2m_changed signal [Django-doc], and thus implement this like:

@receiver(m2m_changed, sender=Class.students.through)
def create_attendance(sender, instance, action, pk_set=None, **kwargs):
    if action == 'post_add':
        for pk in pk_set:
            Attendance.objects.get_or_create(classroom=instance, student_id=pk) 

The modeling however looks a bit odd. Why not make Attendance the through model [Django-doc] of you ManyToManyField? That way Django will create Attendance records, that you then can update accordingly.

There is a created argument in post_save signal which is True when the instance is created and False when the instance is not created(updated).

@receiver(post_save, sender=Class)
def create_attendance(sender, instance, created, **kwargs):
    if not created:  
        for student in instance.students.all():
            try:
                Attendance.objects.get(classroom=instance, student=student)
            except Attendance.DoesNotExist:
                Attendance.objects.create(classroom=instance, student=student)
Related