2011-01-28 38 views
12

Tôi có một thiết lập như thế này (giản cho câu hỏi này):Ngăn chặn xóa trong mô hình Django

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

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

Khi một nhân viên sắp được xóa, tôi muốn kiểm tra hay không, ông được kết nối với bất kỳ dự án . Nếu vậy, việc xóa sẽ không thể thực hiện được.

Tôi biết về các tín hiệu và cách hoạt động. Tôi có thể kết nối với tín hiệu pre_delete và làm cho nó ném một ngoại lệ như ValidationError. Điều này ngăn chặn việc xóa nhưng nó không được xử lý một cách duyên dáng bởi các biểu mẫu và như vậy.

Điều này có vẻ như một tình huống mà người khác sẽ gặp phải. Tôi hy vọng một người nào đó có thể chỉ ra một giải pháp thanh lịch hơn.

+1

Điều này không khả thi chỉ sử dụng mã Python; bản thân cơ sở dữ liệu cũng cần được sửa đổi. –

+0

Cảm ơn nhận xét của bạn. Tôi đang tìm phần Python/Django đầu tiên và xem cách xa mà được tôi trong ứng dụng của tôi. – dyve

Trả lời

2

Tôi có đề xuất nhưng tôi không chắc nó có tốt hơn ý tưởng hiện tại của bạn hay không. Hãy xem câu trả lời here cho một vấn đề ở xa nhưng không liên quan, bạn có thể ghi đè lên quản trị viên django các hành động khác nhau bằng cách xóa chúng và sử dụng riêng của bạn. Vì vậy, ví dụ: nơi họ có:

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 
    # ... 

Nếu bạn không sử dụng quản trị django như bản thân mình, thì chỉ cần tạo kiểm tra đó vào logic UI trước khi cho phép người dùng xóa đối tượng.

+0

Cảm ơn. Tôi không sử dụng quản trị Django cho điều này, mặc dù một giải pháp bao gồm cả quản trị viên Django và mã UI tùy chỉnh sẽ là tuyệt vời. Nếu đó chỉ là quản trị viên Django, giải pháp và tham khảo của bạn sẽ tuyệt vời. 1 cho điều đó. – dyve

5

Nếu bạn biết sẽ không bao giờ có bất kỳ nỗ lực xóa nhân viên đại chúng nào, bạn chỉ có thể ghi đè delete trên mô hình của mình và chỉ gọi super nếu đó là hoạt động pháp lý.

Thật không may, bất cứ điều gì có thể gọi là queryset.delete() sẽ đi thẳng đến SQL: http://docs.djangoproject.com/en/dev/topics/db/queries/#deleting-objects

Nhưng tôi không thấy rằng càng nhiều của một vấn đề bởi vì bạn là người viết mã này và có thể đảm bảo không bao giờ mọi nhân viên queryset.delete(). Gọi delete() theo cách thủ công.

Tôi hy vọng xóa nhân viên tương đối hiếm.

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

Cảm ơn. Tôi biết về điều này, và nó có lẽ sẽ là giải pháp tôi đi cho nếu tín hiệu pre_delete không làm việc ra. 1 để mô tả điều này với ưu và nhược điểm. – dyve

+0

+1 cho những rung cảm tốt. +1 trên nhà! –

+3

Bạn có thể xử lý xóa hàng loạt bằng cách viết 2 lớp: một lớp kế thừa mô hình.Mô nhân và một mô hình kế thừa khác.query.QuerySet Đầu tiên sẽ ghi đè get_query_set, trả về phiên bản của lớp thứ hai. Lớp dẫn xuất QuerySet sẽ ghi đè lên phương thức delete(). Phương thức xóa này sẽ lặp lại trên cá thể lớp và gọi delete() trên mỗi mục. Hy vọng điều này là rõ ràng. –

15

Tôi đang tìm kiếm câu trả lời cho vấn đề này, không thể tìm thấy câu trả lời phù hợp cho cả hai mô hình.Model.delete() và QuerySet.delete(). Tôi đã đi và, sắp xếp, thực hiện giải pháp của Steve K. Tôi đã sử dụng giải pháp này để đảm bảo một đối tượng (Employee trong ví dụ này) không thể bị xóa khỏi cơ sở dữ liệu, theo một trong hai cách, nhưng được đặt thành không hoạt động.

Đó là câu trả lời trễ .. chỉ vì lợi ích của những người khác đang tìm kiếm tôi đang đưa giải pháp của mình vào đây.

Đây là mã:

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() 

Cách sử dụng:

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

hoặc trong admin:

class Employee(admin.ModelAdmin): 

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

Tôi không hiểu cách bạn xóa bất kỳ nhân viên nào vì tôi hiểu bạn đã đặt cờ mà không cần kiểm tra dự án, nhưng câu hỏi muốn xóa (hoặc deactive) một nhân viên nếu nó không liên quan đến bất kỳ dự án nào mà bạn không kiểm tra cái đó. – MohsenTamiz

+1

@MohsenTamiz Giải pháp này là về nguyên tắc cơ bản (và cách thanh lịch) của việc ngăn chặn xóa trong Django. Việc ghi đè phương thức xóa giúp dễ dàng cho phù hợp với trường hợp sử dụng của người hỏi. – Elwin

+0

Cảm ơn phản hồi của bạn có thể là điểm khởi đầu nhưng tôi đã làm điều này theo một cách khác và có một số câu hỏi về điều đó. Tôi sẽ đánh giá cao nếu bạn có thể kiểm tra [câu hỏi] của tôi (http://stackoverflow.com/questions/36998620/writing-custom-assignment-operator-for-django-manytomany-field-intermedi-tabl) và đưa cho tôi một số phản hồi. – MohsenTamiz

3

Điều này sẽ thúc giải pháp từ việc thực hiện trong ứng dụng của tôi.Một số mã là hình thức LWN's answer.

Có 4 tình huống mà dữ liệu sẽ bị xóa:

  • truy vấn SQL
  • Calling delete() trên dụ mẫu: project.delete()
  • Calling delete() trên QuerySet innstance: Project.objects.all().delete()
  • Đã xóa trường ForeignKey trên một Mô hình khác

Trong khi không có gì nhiều bạn có thể làm với trường hợp đầu tiên, ba khác có thể được kiểm soát hạt mịn. Một lời khuyên là, trong hầu hết trường hợp, bạn không bao giờ nên xóa chính dữ liệu, bởi vì những dữ liệu đó phản ánh lịch sử và cách sử dụng ứng dụng của chúng tôi. Thay vào đó, cài đặt trên active Trường Boolean được ưu tiên.

Để ngăn chặn delete() trên dụ Model, lớp con delete() trong việc kê khai mẫu của bạn:

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

Trong khi delete() trên QuerySet dụ cần một chút thiết lập với một người quản lý đối tượng tùy chỉnh như trong LWN's answer.

Gói này lên đến triển khai có thể tái sử dụng:

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() 

Cách sử dụng, chỉ cần su bclass ActiveModel lớp:

class Project(ActiveModel): 
    ... 

Tuy nhiên đối tượng của chúng tôi vẫn có thể bị xóa nếu có một trong các lĩnh vực ForeignKey nó được xóa:

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 

Điều này có thể được ngăn chặn bằng cách thêm on_delete argument của trường mẫu:

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

Mặc định của on_deleteCASCADE sẽ làm cho trường hợp của bạn bị xóa, bằng cách sử dụng PROTECT thay vào đó sẽ tăng ProtectedError (một phân lớp của IntegrityError). Một mục đích khác của điều này là ForeignKey của dữ liệu nên được giữ như một tài liệu tham khảo.

+0

đó là một bản tóm tắt tốt ở đó, nhưng điều gì sẽ xảy ra khi lỗi này được nâng lên? Sẽ xóa một nhân viên thất bại? Làm cách nào để chúng tôi cho phép xóa nhưng vẫn cho phép sự phụ thuộc rơi vào và bảo vệ Dự án – strangetimes

1

Tôi muốn đề nghị thay đổi một thêm về LWNanhdat's câu trả lời trong đó chúng tôi sử dụng một lĩnh vực deleted thay vì một trường active và chúng tôi loại trừ "xóa" các đối tượng từ queryset mặc định, để đối xử với những đối tượng như không còn hiện diện trừ khi chúng tôi đặc biệt bao gồm chúng.

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): 
    ... 

Cách sử dụng:

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 

Như đã nêu trong câu trả lời của anhdat, hãy chắc chắn để thiết lập on_delete property trên ForeignKeys trên mô hình của bạn để tránh hành vi thác, ví dụ

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

Lưu ý:

chức năng tương tự được bao gồm trong django-model-utils 's SoftDeletableModel như tôi chỉ phát hiện. Worth kiểm tra ra. Đi kèm với một số thứ tiện dụng khác.

0

Đối với những người tham khảo câu hỏi này với cùng một vấn đề với mối quan hệ ForeignKey, câu trả lời đúng sẽ là sử dụng trường on_delete=models.PROTECT của Djago trên mối quan hệ ForeignKey. Điều này sẽ ngăn chặn việc xóa bất kỳ đối tượng nào có liên kết khóa ngoài với nó. Điều này sẽ KHÔNG làm việc cho mối quan hệ ManyToManyField (như được thảo luận trong câu hỏi this), nhưng sẽ hoạt động tốt cho các trường ForeignKey.

Vì vậy, nếu các mô hình là như thế này, điều này sẽ làm việc để ngăn chặn việc xóa bất kỳ Employee đối tượng đó có một hoặc nhiều Project đối tượng (s) liên kết với nó:

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) 

Tài liệu có thể được tìm thấy HERE .

Các vấn đề liên quan