2011-01-28 170 views
12

我有這樣的設置(簡化了這個問題):防止刪除

class Employee(models.Model): 
    name = models.CharField(name, unique=True) 

class Project(models.Model): 
    name = models.CharField(name, unique=True) 
    employees = models.ManyToManyField(Employee) 

當員工即將被刪除,我要檢查他是否被連接到任何項目。如果是這樣,刪除應該是不可能的。

我瞭解信號以及如何工作。我可以連接到pre_delete信號,並使其產生異常,如ValidationError。這可以防止刪除,但不能通過表格等來優雅地處理。

這似乎是其他人會遇到的情況。我希望有人能指出一個更優雅的解決方案。

+1

這僅僅使用Python代碼是不可行的;數據庫本身也需要修改。 – 2011-01-28 07:32:41

+0

感謝您的評論。我首先查找Python/Django部分,然後看看在我的應用程序中有多遠。 – dyve 2011-01-28 07:35:18

回答

2

我有一個建議,但我不知道它是否比你目前的想法更好。看看回答here一個遙遠的問題,但不是無關的問題,你可以重寫在Django管理員的各種行動,基本上刪除它們,並使用自己的。因此,舉例來說,他們有:

def really_delete_selected(self, request, queryset): 
    deleted = 0 
    notdeleted = 0 
    for obj in queryset: 
     if obj.project_set.all().count() > 0: 
      # set status to fail 
      notdeleted = notdeleted + 1 
      pass 
     else: 
      obj.delete() 
      deleted = deleted + 1 
    # ... 

如果你不使用Django管理像我一樣,後來乾脆你允許用戶刪除對象之前構建支票存入你的UI邏輯。

+0

謝謝。我沒有爲此使用Django管理員,儘管包含Django管理員和自定義UI代碼的解決方案將非常棒。如果它只是Django管理員,您的解決方案和參考將非常好。 +1。 – dyve 2011-01-28 09:40:08

5

如果您知道永遠不會有員工大規模刪除企圖,您可以在您的模型上覆蓋delete,只需撥打super即可。

不幸的是,任何可能調用queryset.delete()將直接進入SQL: http://docs.djangoproject.com/en/dev/topics/db/queries/#deleting-objects

但我不認爲這是太大的問題,因爲你是一個編寫這些代碼,可以確保永遠不會有任何員工的queryset.delete()。手動撥打delete()

我希望刪除員工比較少見。

def delete(self, *args, **kwargs): 
    if not self.related_query.all(): 
     super(MyModel, self).delete(*args, **kwargs) 
+0

謝謝。我知道這一點,如果pre_delete信號不能解決,它可能會是我的解決方案。 +1用於利用優點和缺點來描述這一點。 – dyve 2011-01-28 09:38:05

+0

+1,感受良好。房子上的+1! – 2011-01-29 04:04:47

15

我一直在尋找的答案,這個問題,是不是能找到一個好的,這兩個models.Model.delete()和QuerySet.delete()工作。我走了一下,並且實施了Steve K的解決方案。我使用這個解決方案來確保無論如何都不能從數據庫中刪除一個對象(本例中爲Employee),但是它被設置爲非活動狀態。

這是一個遲到的答案..只是爲了其他人看我在這裏把我的解決方案。

下面是代碼:

class CustomQuerySet(QuerySet): 
    def delete(self): 
     self.update(active=False) 


class ActiveManager(models.Manager): 
    def active(self): 
     return self.model.objects.filter(active=True) 

    def get_queryset(self): 
     return CustomQuerySet(self.model, using=self._db) 


class Employee(models.Model): 
    name = models.CharField(name, unique=True) 
    active = models.BooleanField(default=True, editable=False) 

    objects = ActiveManager() 

    def delete(self): 
     self.active = False 
     self.save() 

用法:

Employee.objects.active() # use it just like you would .all() 

或管理員:

class Employee(admin.ModelAdmin): 

    def queryset(self, request): 
     return super(Employee, self).queryset(request).filter(active=True) 
3

這將包裹從我的應用程序的實施方案。一些代碼的形式LWN's answer.

有你的數據被刪除4種情況:

  • SQL查詢
  • 對Model實例調用delete()project.delete()
  • 上查詢集innstance調用delete()Project.objects.all().delete()
  • 外地字段刪除其他型號

雖然第一種情況沒有什麼可以做,但其他三種可以進行細粒度控制。 一個建議是,在大多數情況下,您絕對不應該刪除數據本身,因爲這些數據反映了我們應用程序的歷史和使用情況。設置爲active而不是布爾字段。

爲了防止Model實例,子類delete()delete()在模型聲明:

def delete(self): 
     self.active = False 
     self.save(update_fields=('active',)) 

雖然delete()上查詢集實例需要用自定義對象管理一個小設置爲LWN's answer.

總結這部長達一個可重用的實現:

class ActiveQuerySet(models.QuerySet): 
    def delete(self): 
     self.save(update_fields=('active',)) 


class ActiveManager(models.Manager): 
    def active(self): 
     return self.model.objects.filter(active=True) 

    def get_queryset(self): 
     return ActiveQuerySet(self.model, using=self._db) 


class ActiveModel(models.Model): 
    """ Use `active` state of model instead of delete it 
    """ 
    active = models.BooleanField(default=True, editable=False) 
    class Meta: 
     abstract = True 

    def delete(self): 
     self.active = False 
     self.save() 

    objects = ActiveManager() 

用法,只是su bclass ActiveModel類:

class Employee(models.Model): 
    name = models.CharField(name, unique=True) 

class Project(models.Model): 
    name = models.CharField(name, unique=True) 
    manager = purchaser = models.ForeignKey(
     Employee, related_name='project_as_manager') 

>>> manager.delete() # this would cause `project` deleted as well 

這是可以預防通過增加模型字段on_delete argument

class Project(ActiveModel): 
    ... 

仍然是我們的目標仍然可以如果ForeignKey的領域中的任何一個被刪除刪除

class Project(models.Model): 
    name = models.CharField(name, unique=True) 
    manager = purchaser = models.ForeignKey(
     Employee, related_name='project_as_manager', 
     on_delete=models.PROTECT) 

on_delete的缺省值是CASCADE這將導致您的實例被刪除,通過使用PROTECT取而代之的將會產生ProtectedErrorIntegrityError的子類)。這樣做的另一個目的是數據的ForeignKey應該保留作爲參考。

1

我想提出一個更多的變化上LWNanhdat's答案,其中我們使用deleted場,而不是一個active領域,我們排除從默認查詢集「已刪除」的對象,這樣才能把這些對象爲不再存在除非我們特別包含它們。

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


class SoftDeleteManager(models.Manager): 
    use_for_related_fields = True 

    def with_deleted(self): 
     return SoftDeleteQuerySet(self.model, using=self._db) 

    def deleted(self): 
     return self.with_deleted().filter(deleted=True) 

    def get_queryset(self): 
     return self.with_deleted().exclude(deleted=True) 


class SoftDeleteModel(models.Model): 
    """ 
    Sets `deleted` state of model instead of deleting it 
    """ 
    deleted = models.NullBooleanField(editable=False) # NullBooleanField for faster migrations with Postgres if changing existing models 
    class Meta: 
     abstract = True 

    def delete(self): 
     self.deleted = True 
     self.save() 

    objects = SoftDeleteManager() 


class Employee(SoftDeleteModel): 
    ... 

用法:

Employee.objects.all()   # will only return objects that haven't been 'deleted' 
Employee.objects.with_deleted() # gives you all, including deleted 
Employee.objects.deleted()  # gives you only deleted objects 

正如anhdat的回答說,一定要設置ForeignKeys的on_delete property你的模型,以避免級聯行爲,例如

class Employee(SoftDeleteModel): 
    latest_project = models.ForeignKey(Project, on_delete=models.PROTECT) 

注:

類似的功能包含在django-model-utilsSoftDeletableModel,因爲我剛剛發現。值得檢查。附帶一些其他便利的東西。

0

對於那些提及ForeignKey關係時出現同樣問題的問題,正確的答案是在ForeignKey關係中使用Djago的on_delete=models.PROTECT字段。這將防止刪除任何具有外鍵鏈接的對象。這不適用於ManyToManyField關係(如this問題中所述),但對於ForeignKey字段非常有用。

因此,如果模型是這樣的,這樣的工作,以防止具有與其關聯的一個或多個Project對象(S) 任何Employee對象的刪除:

class Employee(models.Model): 
    name = models.CharField(name, unique=True) 

class Project(models.Model): 
    name = models.CharField(name, unique=True) 
    employees = models.ForeignKey(Employee, on_delete=models.PROTECT) 

文檔可以發現HERE