Django reverse (on_delete) protection on a model instance - django

Is is possible to protect a model in a reverse relationship. For instance in the models below:-
class Foo(models.Model):
foo_field1 = models.CharField(max_length=56, unique=True)
class Bar(models.Model):
bar_field1 = models.ForeignKey(Foo, on_delete=models.PROTECT, blank=True)
bar_field2 = models.CharField(max_length=56, unique=True)
If an attempt is made to delete an instance of Foo, it wont be deleted as the on_delete attribute on Bar is set to models.PROTECT. So, is it possible to extend that protection both ways? That is, if an attempt is made to delete an instance of Bar, so can it be protected just like Foo, can someone suggest a solution?.

I don't have a full solution for you, but I would suggest looking into using a Django Signal, specifilly pre-delete. You would in that signal check if bar_field_1 in instance of Bar is null and abort the deletion if it is not null.

Related

How to save Django model instances with circular dependency?

models.py:
class Server(models.Model):
name = models.CharField(max_length=100, unique=True)
last_interaction = models.OneToOneField('Interaction',
on_delete=models.CASCADE,
related_name='server')
class Interaction(models.Model):
client = models.CharField(max_length=100)
time = models.DateTimeField()
server = models.ForeignKey(Server,
on_delete=models.CASCADE,
related_name="interactions")
How do I save instances? (When I want to save one, the other isn't saved yet, so it can't be saved)
Notes:
I been there. The accepted answer doesn't provide a solution to the problem. Indeed at runtime there will be new servers and clients.
I know this schema makes little sense, but I still want to find a way to work with this.
I know the on_delete=cascade in Server is dangerous. I plan to fix it once I solve this problem.
Fundamentally, if you create a circular foreign key relationship, at least one of them must be nullable. For example:
class Interaction(models.Model):
client = models.CharField(max_length=100)
time = models.DateTimeField()
server = models.ForeignKey(Server,
on_delete=models.CASCADE,
related_name="interactions",
null=True
)
You'll be able to create Interaction objects without having the corresponding Server instance ready.

SoftDelete in Django

Problem Statement
I have created a base model:
class CreateUpdateDeleteModel(models.Model):
from django.contrib.auth import get_user_model
from django.utils.text import gettext_lazy as _
from .managers import BakeryManager
from drfaddons.datatypes import UnixTimestampField
create_date = UnixTimestampField(_('Create Date'), auto_now_add=True)
created_by = models.ForeignKey(get_user_model(), on_delete=models.PROTECT,
related_name='%(app_label)s_%(class)s_creator')
delete_date = models.DateTimeField(_('Delete Date'), null=True, blank=True)
deleted_by = models.ForeignKey(get_user_model(), on_delete=models.PROTECT, null=True, blank=True,
related_name='%(app_label)s_%(class)s_destroyer')
update_date = models.DateTimeField(_('Date Modified'), auto_now=True)
updated_by = models.ForeignKey(get_user_model(), on_delete=models.PROTECT, null=True, blank=True,
related_name='%(app_label)s_%(class)s_editor')
objects = BakeryManager()
class Meta:
abstract = True
I want that in my system, all the elements are soft deleted i.e. whenever an object is deleted, it should behave as follow:
Behave like normal delete operation: Raise error if models.PROTECT is set as value for on_delete, set null if it's models.SET_NULL and so on.
Set delete_date
Never show it anywhere (incl. admin) in any query. Even model.objects.all() should not include deleted objects.
How do I do this?
I am thinking to override get_queryset() which may solve problem 3. But what about 1 & 2?
Very strange request.
Point 1. It's not clear. Show an error where?
Point 2. The delete is managed as a status and not a hide of the data. I would suggest you add a status field and manage the delete using a special function that changes the status. You can override the delete, but be aware that the delete in queryset does not call the Model.delete but run directly SQL code.
Saying that it's a bad idea. The delete must be there, but just not used. You can easily remove the delete form the Django admin and a developer has no reason to delete code unless he/she is playing with the Django shell irresponsibly. (DB backup?)
Point 3. If you don't want to show the data in admin, simply override the Admin ModelForm to hide the data when the STATUS of the object is DELETE. It's bad design manipulate the domain to accommodate the presentation layer.

Django constraint between ManyToManyField and ForeignKey

Consider I have following models:
class Cargo(models.Model):
name = models.CharField(default='')
owner = models.ForeignKey(User, on_delete=models.CASCADE)
class Box(models.Model):
name = models.CharField(default='')
owner = models.ForeignKey(User, on_delete=models.CASCADE)
tags = models.ManyToManyField(Cargo, blank=True)
I want to avoid situation when I add some cargo object to box with different owner. For example:
cargo = Cargo(owner=1)
box = Box(owner=2)
box.add(cargo)
How to add such a constraint on a model level?
My initial thought is that a great solution to this problem would be to define a custom RelatedManager that overrides the add() method and validates that the user is the same before you actually do the link. However, after searching through the internet for a while, I was unable to find a way to do a custom RelatedManager on the ManyToManyField (docs on RelatedManager).
As a workaround, I would recommend that you create a method on the Box model called addCargo which you use to add cargo. The method could then enforce the validation of the users before adding the cargo. It could look like:
class Box(models.Model):
name = models.CharField(default='')
owner = models.ForeignKey(User, on_delete=models.CASCADE)
tags = models.ManyToManyField(Cargo, blank=True)
def addCargo(self, cargo):
if self.owner.id != cargo.owner.id:
raise ValueError("cargo and box must have same user")
self.tags.add(cargo)
And your code to add the cargo would look like:
cargo = Cargo(owner=1)
box = Box(owner=2)
box.addCargo(cargo)
Hope this helps!

Creating and saving non-empty Django model instance as part of subclass instance

I am experiencing a very strange behavior inconsistent with the Django documentation while creating and saving (inserting into DB) model instance. I've already run out of ideas for possible reason and will be very grateful for any suggestions why Django fails to save all fields in these cases.
This class I am using:
class Person(models.Model):
user = models.ForeignKey(User)
phone_number = models.CharField(max_length=20, blank=True)
address = models.CharField(max_length=200, blank=True)
And here's code that does't work, few cases:
# First Case
new_person = Person()
new_person.user = request.user
new_person.phone_number = '111111'
new_person.save(force_insert=True)
# Second One
new_person = Person(user=request.user, phone_number='111111')
new_person.save(force_insert=True)
# Third One
new_person = Person.objects.create(user=request.user, phone_number='111111')
Basing on official Django docs in any case django should create an object and insert it into DB.
In fact the object is successfully created (and all relevant fields are set), but row inserted into DB has only id and user_id fields filled correctly while phone_number field that was also set, remains blank.
There is, however, no problem to access and update all fields of existing (saved earlier) objects.
Removing blank=True from Person class declaration (with proper table alteration) does't change anything.
EDIT:
Problem turned out to be more sophisticated. Full description and solution in my own answer beneath
Ok, I found an explanation....
It has something to do with inheritance, namely further in the code I wanted to create instance of Person's subclass. So there was another class:
class Person(models.Model):
user = models.ForeignKey(User)
phone_number = models.CharField(max_length=20, blank=True)
address = models.CharField(max_length=200, blank=True)
class ConnectedPerson(Person):
connection = models.ForeignKey(AnotherClass)
# etc..
And after creating instance of Person, intending to extend it to ConnectedPerson I made such code:
#creating instance of Person:
person = Person(user=request.user, phone_number='111111')
person.save(force_insert=True)
c_person = ConnectedPerson(id=person.id, connection=instance_of_another_c)
and using ConnectedPerson(id=person.id) was in fact killing previously created Person instance by overwritting it in the DB.
So for anyone not too much experienced in managing inheriting instances: if you need to use earlier created super class instance as part of subclass instance do it this way:
#creating person but not saving it
person = Person(user=request.user, phone_number='111111')
######
#later
######
#creating subclass instance and saving
c_person = ConnectedPerson(user=request.user, connection=instance_of_another_c)
c_person.save()
#saving super class instance as part of subclass instance
person.pk = super(ConnectedPerson, c_person).pk
person.save()

Django Relating 2 Models

I'm not too sure how to find this through google and searching hasn't been leading me anywhere.
How do you relate 2 models in such a way that if you delete one entry in the admin panel, the other will automatically be deleted?
Thank You for any help.
EDIT: Updated with example. I want an Event to be able to describe the other competitors, and the Picture with the OneToOne relationship with the Event should be the primary contestant. So once the primary contestant gets deleted, I also want to delete the Event. Unfortunately, I can't just add a ForeignKey relationship in Event or that would cause an error. So, is there someway to do this for a OneToOne relationship?
class Event(models.Model):
competitors = models.ManyToManyField('Picture',null=True,blank=True)
class Picture(models.Model):
competition = models.OneToOneField(Event)
Quoting django docs:
When Django deletes an object, by default it emulates the behavior of
the SQL constraint ON DELETE CASCADE -- in other words, any objects
which had foreign keys pointing at the object to be deleted will be
deleted along with it.
Then, this is the default behavior.
See on_delete options for further details:
user = models.ForeignKey(User, blank=True, null=True, on_delete=models.SET_NULL)
The possible values for on_delete are found in django.db.models:
CASCADE: Cascade deletes; the default.
...
Suppose you have two models:
class ModelA(models.Model):
name = models.CharField(max_length=30)
class ModelB(models.Model):
abc = models.CharField(max_length=30)
model_a = models.ForeignKey(ModelA)
Now lets do some working example:
modelAObj = ModelA.objects.create(name='aamir')
modelBObj = ModelB.objects.create(abc='cde', model_a=modelAObj)
modelAObj.delete() # this will also delete the modelBObj