How to Cleanup Django Orphan Foreign Key Values? - python

I'm creating a model that refers to a model within a 3rd party package -- Celery (CrontabSchedule and PeriodicTask). My model (let's called it ScheduledRun) will contain a foreign key to a PeriodicTask.
I know that a cascade delete will happen if I delete a foreign key itself, and that the parents referring to that foreign key will also get deleted. (Unless overridden by on_delete...)
But due to my situation of pointing ScheduledRun at a FK of a PeriodicTask, PeriodicTask won't be automatically deleted when I delete a ScheduledRun. (Nor should it as there might be other models pointing to that foreign key!)
So how could I cleanup PeriodicTasks that are orphans -- i.e., when no model instances point to it anymore.
I could add a post_delete signal and check it this way (this example is deleting extraneous CrontabSchedules not associated with a periodic task anymore:
# periodictask below is actually a related periodictask_set,
# but in Django you refer to the empty set as 'periodictask=None'
CrontabSchedule.objects.filter(id=instance.crontab.id,
periodictask=None).delete()
But I'm not guaranteed there aren't other related relations that could cause a cascade drop.
I could subclass the table PeriodicTask as ScheduledRun .... but would rather not integrate that tightly with the 3rd party model.
It's almost as if I want a .delete(do_not_cascade=True) and if it fails due to constraints, just ignore the failure. If it succeeded, then it was an orphan. on_delete=DO_NOTHING is similar to this, but I only want it on only temporarily for the scope of a single delete, and I don't want to modify the third party package.
Are there other/better ways for dealing with this?

Here's my solution ... seems like it might be robust enough. My goal is to only delete the foreign key value if no other model instances still refer to it.
So, what I will try is a delete based on each of the related keys value being None:
# This is a class method to my ScheduledRun class that has
# a foreign key to a PeriodicTask. PeriodicTask has a
# FK to a CrontabSchedule, which I'd like to "trim"
# if nothing points to that FK anymore.
#classmethod
def _post_delete(cls, instance, **kwargs):
instance.periodic_task.delete()
# Delete the crontab if no other tasks point to it.
# This could cause a cascade if we don't check all related...
filter = dict(id=instance.crontab.id)
for related in instance.crontab._meta.get_all_related_objects():
filter[related.var_name] = None
assert('id' in filter)
assert('schedule' in filter)
assert('periodictask' in filter)
CrontabSchedule.objects.filter(**filter).delete()
It would be easier if I could say:
instance.crontab.delete(NO_CASCADE=True)

Related

Possible source of cascade deletion on Django 1.9 project?

I'm working on a Django 1.9 project with a huge source and lots of is inherited from previous developers.
When I delete a User object from the shell there are many other related objects that get deleted as well. I get a print output with a Tuple containing:
(# of deleted objects, {u'CLASS_NAME': #number of deleted objects...})
I've searched all the code throughly and there are no Signals attached to User pre_ post_ or on_ delete, neither I can find where this print output is constructed... so, I have no idea where this is coming from.
Any ideas for other possible ways this can be happening?
A ForeignKey [Django-doc] has an on_delete=… parameter. Prior to django-2.0, you did not have to state this explicitly, in that case it used uses CASCADE, as is specified in the documentation:
on_delete will become a required argument in Django 2.0. In older versions it defaults to CASCADE.
This thus means that if you did not specify an on_delete=… parameter, or you specified this as CASCADE, then if the target record is removed, that model record is removed as well.
You can specify another handler for this. In fact you can even write your own handler, but unless you have to do something very sophisticated, the builtin handlers will likely be sufficient, you can choose between:
CASCADE
PROTECT
SET_NULL
SET_DEFAULT
SET(…)
DO_NOTHING
There are basically four categories: a cascade (CASCADE), prevent the removal (PROTECT), set it to another value (SET_NULL, SET_DEFAULT, SET(…), and DO_NOTHING.
DO_NOTHING is often not a good idea, since most databases enforce referential integrity and will thus refuse to update/delete, since then the foreign key column no longer points to a valid record. By SET_NULL, the field needs to be NULLable (so null=True), you can also set t to a different view.
You should thus pick the strategy that you think is the best, and then the ForeignKey looks like:
class MyModel(models.Model):
my_field = models.ForeignKey(OtherModel, null=True, on_delete=models.SET_NULL)

Proper way of choosing the right corresponding class in python

I'm using Django and have a few models. They correlate to each other without any foreign keys, but I want to be able to select them in a centralized place, here are the models (without the inheritance and fields so tests are easy):
class ItemTypeOne:
pass
class ItemOneExtra:
pass
# -----------------------------
class ItemTypeTwo:
pass
class ItemTwoExtra:
pass
# ... and so on
What I thought of using so far its a dict to map them, like so:
correlated_extra_model = {ItemTypeOne: ItemOneExtra, ItemTypeTwo: ItemTwoExtra}[ItemTypeOne]
This works, but I'm not sure if it's acceptable
Firstly, in a relational setting, your tables are related only if you have sort of foreign key or one-to-one field pointing to another table. Without it, it does not any relation at db level. You may be enforcing this behaviour through your logic.
Secondly, if you have to create an auxiliary models for each of your base model, I think it's an indicator that the current design is flawed and you need to rethink your models. Of course, this is based on my assumptions and they might not be very much applicable to your use case. So, if you can, please share some more details about the problem statement.

Can I overwrite User.objects.delete in Django?

I used to delete a user when the user is left, but a lot of models relates User which I need to set related foreign key to empty or delete them since then.
But some models would be pointless since the related User is deleted, such as Order. Thus I need to set User.is_active or something similar to invalid instead of delete the data.
I think it would be best If I can override User.objects.delete, so I don't need to modify a lot of business functions relates to it.
The django.contrib.auth.User already has an is_active parameter, so you can just set that.
In fact, from the docs linked above:
We recommend that you set this flag to False instead of deleting accounts; that way, if your applications have any foreign keys to users, the foreign keys won’t break.
Yes, technically you can override delete by setting a new Manager, but its the wrong approach.

Django - any way to fetch foreign key objects instantly?

I've got a question about foreign key behaviour in Django.
I've defined a tree hierarchy in my models, where a parent-son relation is represented as a foreign key in the son model. Now, starting at the leaf level, I'd like to retrieve the parent, the parent's parent etc. as the objects I've defined.
This is possible by simply calling Leaf.objects.all() and accessing the objects normally from Python code.
But here come the troubles. For each such call, Django makes a SELECT query for the appropriate foreign ID. This is obviously terribly slow and inefficient. I'd like to tell Django something like "hey, just fetch me all the data including the foreign keys at once, just do the joins and all the stuff at the database side". Is that somehow posible?
Just use select_related():
Leaf.objects.select_related().all()

What is the best way to override the delete function in django python

I have the Model where i have relations with 3 diff models.
Now i know that if i use
object.delete() , then child objects will also gets deleted.
Now the problem is that in my whole models classes i have the database column called DELETED which i want to set to 1 whenever someone deletes some object.
I can override the deleted function in class called BaseModel and and override the custom delete method of updating field to 1. But the problem is
If i do that way then i have to manually go through all the cascading relationships and manually call the delete ob every object.
Is there any way that by just calling object.delete(). It automatically traverses through child objects as well
Please look at Django: How can I find which of my models refer to a model.
You can use a Collector to get all references to all the necessary items using collect(). This is the code Django is using to simulate the CASCADE behavior. Once you have collected all the references, for each of those items you can update the DELETED column.
More info in the code.
Good luck.

Categories

Resources