2011-01-28 5 views
12

У меня есть установки, как это (упрощенный на этот вопрос):Предотвращение удаления в модели 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) 

Когда сотрудник собирается удаляются, я хочу, чтобы проверить, действительно ли он подключен к любым проектам , Если это так, удаление должно быть невозможно.

Я знаю о сигналах и как их обрабатывать. Я могу подключиться к сигналу pre_delete и сделать его отличным от ValidationError. Это предотвращает удаление, но оно не обрабатывается изящно формами и т. Д.

Это похоже на ситуацию, с которой столкнулись другие. Я надеюсь, что кто-то может указать на более элегантное решение.

+1

Это невозможно, только используя код Python; сама база данных также должна быть изменена. –

+0

Спасибо за ваш комментарий. Сначала я ищу часть Python/Django и посмотрю, как далеко это заставляет меня в моем приложении. – dyve

ответ

2

У меня есть предложение, но я не уверен, что оно лучше вашей текущей идеи. Взглянув на ответ here на удаленную, но не несвязанную с этим проблему, вы можете переопределить в django admin различные действия, по существу удалив их и используя свои собственные. Так, например, если у них есть:

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

Если вы не используете Джанго администратора, как и я, то просто построить эту проверку в вашу логику пользовательского интерфейса, прежде чем позволить пользователю удалить объект.

+0

Спасибо. Я не использую администратора Django для этого, хотя решение, включающее как администратор Django, так и пользовательский код пользовательского интерфейса, было бы удивительным. Если бы это был только администратор Django, ваше решение и ссылка были бы превосходными. +1 для этого. – dyve

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

+0

+1 для хорошей вибрации. + 1 на дом! –

+3

Вы можете позаботиться о массовом удалении, написав 2 класса: тот, который наследует models.Manager и другой наследующий models.query.QuerySet Первый будет переопределять get_query_set, возвращая экземпляр второго класса. Производный класс QuerySet переопределит метод delete(). Этот метод удаления будет перебирать экземпляр класса и вызывать delete() для каждого элемента. Надеюсь, это понятно. –

15

Я искал ответ на эту проблему, был не в состоянии найти хороший, который будет работать как для models.Model.delete() и QuerySet.delete(). Я пошел и, вроде, выполнил решение Стива К. Я использовал это решение, чтобы убедиться, что объект (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) 
+0

Я не понимаю, как вы удаляете любого сотрудника, поскольку я понял, что вы устанавливаете флаг без каких-либо проверок проектов, но вопрос хочет удалить (или отключить) сотрудника, если он не связан ни с какими проектами, которые вы не проверяли что. – MohsenTamiz

+1

@MohsenTamiz Это решение касается основного принципа (и элегантного способа) предотвращения удаления в Django. Переопределение метода удаления упрощает использование прецедента. – Elwin

+0

Спасибо за ваш ответ, возможно, это отправная точка, но я сделал это по-другому, и у меня есть некоторые вопросы об этом. Я был бы признателен, если бы вы могли проверить мой вопрос (http://stackoverflow.com/questions/36998620/writing-custom-assignment-operator-for-django-manytomany-field-intermediate-tabl) и получить мне некоторую обратную связь. – MohsenTamiz

3

Это завернуть решение от реализации в моем приложении.Некоторые коды формы LWN's answer.

Есть 4 ситуации, что ваши данные удаляются:

  • SQL запрос
  • Вызывающие delete() на экземпляре модели: project.delete()
  • Вызывающие delete() на QuerySet innstance: Project.objects.all().delete()
  • Удалено иностранным полем на другом языке. Модель

Хотя в первом случае вы ничего не можете сделать, остальные три могут контролироваться мелкозернистым. Советуем, что в большинстве случаев вы никогда не должны удалять сами данные, поскольку эти данные отражают историю и использование нашего приложения. Настройка на active Вместо этого выбрано булевское поле.

Для предотвращения delete() на модели, например, подкласса delete() в вашей модели декларации:

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

Хотя delete() на экземпляре QuerySet нуждается в небольшой настройки с менеджером пользовательских объектов, как в 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 Project(ActiveModel): 
    ... 

Тем не менее наш объект все еще может быть удален, если какой-либо один из его ForeignKey полей удаляются:

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(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 вместо этого поднимет ProtectedError (подкласс IntegrityError). Другая цель заключается в том, что ForeignKey данных следует хранить в качестве ссылки.

+0

Это хорошее резюме, но что происходит, когда эта ошибка возникает? Удалит ли сотрудник? Как разрешить удаление, но допустить, чтобы зависимость зависела и защищала проект – strangetimes

1

Я хотел бы предложить еще один вариант на LWN и anhdat's ответов, в котором мы используем deleted поле вместо в active поле и исключить «удаленные» объекты из QuerySet по умолчанию, так как лечить эти объекты как больше нет если мы специально не включим их.

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, убедитесь, что установить on_delete property на ForeignKeys на вашей модели, чтобы избежать каскадного поведения, например,

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

Примечание:

Подобная функциональность включена в django-model-utils «s SoftDeletableModel, как я только что обнаружил. Стоит проверить. Приходит с некоторыми другими удобными вещами.

0

Для тех, кто ссылается на эти вопросы с той же проблемой с отношением ForeignKey, правильным ответом будет использование поля on_delete=models.PROTECT Djago в отношении ForeignKey. Это предотвратит удаление любого объекта, у которого есть ссылки на внешние ключи. Это НЕ будет работать для отношений ManyToManyField (как обсуждалось в вопросе this), но отлично подойдет для полей ForeignKey.

Так что, если модели были, как это, это будет работать, чтобы предотвратить удаление любого Employee объекта, который имеет один или более Project объект (ы), связанный с ним:

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 ,

Смежные вопросы