Search code examples
pythondjangodjango-modelsdjango-viewsdjango-generic-views

Django: Change the parent, when the child get deleted(unidirectional OneToOne)


I want to change Photo 'labeled' field False when I delete LabeledPhoto object.

#models.py
class Photo(models.Model):
    image = models.ImageField(upload_to='shoes_data/%Y/%m/%d', name='image')
    created = models.DateTimeField(auto_now_add=True)
    labeled = models.BooleanField(default=False)

class LabeledPhoto(models.Model):
    labeled_image = models.OneToOneField(Photo, on_delete=models.PROTECT, related_name='labeled_image')
    topcategory = models.CharField(max_length=64)
    subcategory = models.CharField(max_length=64)
    labeler = models.CharField(max_length=32)
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)

I tried like this but, It didn't work

# views.py
class LabeledPhotoDelete(DeleteView):
    model = LabeledPhoto
    template_name = 'label/labeled_photo_delete.html'
    success_url = reverse_lazy('photo:labeled_list')

    def delete(self, request, *args, **kwargs):
        self.object = self.get_object()
        success_url = self.get_success_url()
        labeled = LabeledPhoto.objects.get(id=self.object.pk)
        labeled.labeled_image.labeled = False
        labeled.save()
        self.object.delete()
        return reverse(success_url)

Solution

  • You need to .save() the Photo:

    from django.http import HttpResponseRedirect
    
    class LabeledPhotoDelete(DeleteView):
        model = LabeledPhoto
        template_name = 'label/labeled_photo_delete.html'
        success_url = reverse_lazy('photo:labeled_list')
    
        def delete(self, request, *args, **kwargs):
            self.object = self.get_object()
            success_url = self.get_success_url()
            image = self.object.labeled_image
            image.labeled = False
            image.save()
            self.object.delete()
            return HttpResponseRedirect(success_url)

    But there is no reason to store whether the image is labeled in a field: you can simply look that up if you want to know this. Indeed, you can add a property:

    class Photo(models.Model):
        image = models.ImageField(upload_to='shoes_data/%Y/%m/%d', name='image')
        created = models.DateTimeField(auto_now_add=True)
    
        @property
        def labeled(self):
            try:
                return self.labeled_image is not None
            except LabeledPhoto.DoesNotExist:
                return False

    or annotate the queryset with the fact if a LabeledPhoto exists:

    from django.db.models import BooleanField, ExpressionWrapper, Q
    
    Photo.objects.annotate(
        is_labeled=ExpressionWrapper(
            Q(labeled_image__isnull=False),
            output_field=BooleanField()
        )
    )