1

In this question it talks about how to set a customer manager when dealing with related objects however the answers release to Django 1 and 2.

In version 3 of the doco it says the the Base managers aren't used when querying on related models. Using either default_manager_name or base_manager_name has no affect on our related query. Do we have to update all our serializes to include that filter or is there another manager we can use to replicate the feature? Our problem is the same in the documentation where we have a deleted flag.

Rudiger
  • 6,749
  • 13
  • 51
  • 102

1 Answers1

0

My environment is Django3.2.6+sqlite+Python3.8:

# crossover/models.py
from django.db import models


class SoftDeleteQuerySet(models.QuerySet):
    def delete(self):
        return self.update(deleted=True)

    def force_delete(self):
        return super().delete()


class FilteDeletedManager(models.Manager):
    def datas(self):
        return SoftDeleteQuerySet(self.model, self._db)

    def get_queryset(self):
        return SoftDeleteQuerySet(self.model, self._db).filter(deleted=False)


class AbsModel(models.Model):
    objects = FilteDeletedManager()
    deleted = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def delete(self, **kwargs):
        self.deleted = True
        return self.save(**kwargs)

    def force_delete(self, **kwargs):
        return super().delete(**kwargs)

    def to_dict(self):
        return {f.name: getattr(self, f.name) for f in self._meta.fields}

    class Meta:
        abstract = True


class Player(AbsModel):
    name = models.CharField(max_length=20)

    def __str__(self):
        return self.name

Usage::

>>> from crossover.models import *
>>> p1, p2 = Player.objects.create(name='LBJ'), Player.objects.create(name='KD')
>>> Player.objects.all()
<SoftDeleteQuerySet [<Player: LBJ>, <Player: KD>]>
>>> Player.objects.filter(id=1).delete()
1
>>> Player.objects.all()
<SoftDeleteQuerySet [<Player: KD>]>
>>> Player.objects.datas()
<SoftDeleteQuerySet [<Player: LBJ>, <Player: KD>]>
>>> p2.delete()
>>> Player.objects.all()
<SoftDeleteQuerySet []>
>>> Player.objects.datas()
<SoftDeleteQuerySet [<Player: LBJ>, <Player: KD>]>
>>> p1.force_delete()
(1, {'crossover.Player': 1})
>>> Player.objects.datas()
<SoftDeleteQuerySet [<Player: KD>]>
>>> Player.objects.datas().force_delete()
(1, {'crossover.Player': 1})
>>> Player.objects.datas()
<SoftDeleteQuerySet []>

About ForeignKey fields: when mark it as deleted, mark the children of it as deleted too.

from django.db.models.deletion import Collector

class MyCollector(Collector):
    def delete(self):
        ...
        with transaction.atomic(using=self.using, savepoint=False):
            # update deleted=True

class AbsModel(models.Model):
    def delete(self, using=None, keep_parents=False):
        using = using or router.db_for_write(self.__class__, instance=self)
        assert self.pk is not None
        collector = MyCollector(using=using)
        collector.collect([self], keep_parents=keep_parents)
        return collector.delete()
     ...
Waket Zheng
  • 5,065
  • 2
  • 17
  • 30
  • From what I'm reading here, we have the same code and ours works fine. It's when there is a one to many relationship under the (in your case) Player that the objects return everything rather than using the custom Manager. – Rudiger Sep 24 '21 at 04:14
  • I think we can custom `from django.db.models.deletion import Collector` . – Waket Zheng Sep 24 '21 at 04:37