I'm currently maintaining a legacy system while a new system is brought up. I noticed recently that I get a timeout when trying to delete certain objects from a specific model. I've tracked this down to being related to the following question which has an accepted answer: Django admin hangs (until timeout error) for a specific model when trying to edit/create
The problem I'm having is that the objects that are related are not directly related from my model in question.
For example I have the following models (generically named to remain vague due to IP at my company):
ModelA which is the model I'm seeing the issue with when deleting from the Django Admin site
ModelB which contains a ForeignKey field to ModelA
ModelC which contains a ForeignKey field to ModelB
ModelD which contains a ForeignKey field to ModelC
ModelE which contains a ForeignKey field to ModelD
Model details:
ModelE can contain tens/hundreds/thousands of entries for any entry of
ModelC. Additionally ModelC can contain tens/hundreds/thousands of entries for any entry of ModelB
Currently when I try to delete ModelA Django attempts to generate all the associated objects all the way down to ModelE which is causing a timeout in certain cases with high number of associated ModelC and ModelE.
Is there a way to avoid this either by overriding a custom template such as delete_confirmation_template or through any other method? Ideally I would like to still show the summary, but I'm not sure that will be possible with the nature of this issue.
A few details for context:
I feel this may be due to a poor overall structure in our DB Schema, but like I mentioned earlier this is a Legacy system.
I do not need an immediate fix for this as I will actually never delete entries for this model except for my current scenario/task of cleaning up duplicated entries(user error not controlled correctly by forms; The forms now check for this) which is being done through a migration script. I simply noticed this when trying to clean up things and leverage this intermediate page as a sanity check when testing said migration script
Timeout screenshot
From discussions in the comments I came to the following conclusion(s):
on_delete being set to CASCADE for models was incorrect and was causing the very high times in retrieving related objects when attempting to delete a model
Since I do not want to allow deleting of these objects when other specific models have associations to them I analyzed and appropriately set on_delete to PROTECT where applicable
Now when I attempt to delete an object it will not allow me to delete the object due to protected related items. It also resolves the timeout issue I was previously observing
This allows me to still delete objects from this model via the Django admin site IF they have no related protected objects, which in the end is the functionality I desire
Related
I have a simple Tag model that many other models have a ManyToMany relationship to. The requirement has come up to be able to query/show the most recently used Tags in the system, across all entities that have Tags.
I can add a used_at attribute to the Tag model, and I could order on that. But obviously, the Tag model doesn't get modified when something else just references it, so an auto_now on that attribute won't help me.
Without the use of a through model (which could have an auto_now_add on it), and without performing any invisible (non-django) magic directly in the DB with triggers, is there a sensible way to update the Tag's timestamp whenever a model is saved that references it?
You could use m2m_changed signal
From docs:
Sent when a ManyToManyField is changed on a model instance. Strictly
speaking, this is not a model signal since it is sent by the
ManyToManyField, but since it complements the pre_save/post_save and
pre_delete/post_delete when it comes to tracking changes to models, it
is included here
I'm not sure if this is a bug, by design or something I've done.
I'm seeing the same issue as this issue when deleting an item with a M2M relationship where the object ID is returned instead of str.
The str is set correctly and displays correctly otherwise, just not when deleting an item. The object ID shows up instead.
I've tested by creating a new Django project and a simple model with 2 classes related by M2M but get the same thing.
The only way I've found, which isn't ideal, is to use the "through" option on the field.
One I have many relationships like this in various models.
Two, returning str on the intermediate model I have to display both fields so that it makes sense when deleting from either of the related models.
Anyone have any thoughts? Is using "through" the only option?
I was just looking the documentation to be able to change the intermediate table but when I implement it, I get into trouble:
https://docs.djangoproject.com/en/2.0/topics/db/models/#extra-fields-on-many-to-many-relationships
The problem as such is that, although I can migrate the database and run the application, when I enter the administrator I do not visualize correctly the relationship of my models through the trough attribute (especially a field of my model called Tested).
Why does this happen and how can it be corrected?
This is by design. Django cannot automatically generate the widget for ManyToMany relations that use a through table because of the extra data needed (tested in your case). From Django docs:
When you specify an intermediary model using the through argument to a ManyToManyField, the admin will not display a widget by default. This is because each instance of that intermediary model requires more information than could be displayed in a single widget, and the layout required for multiple widgets will vary depending on the intermediate model.
However, we still want to be able to edit that information inline. Fortunately, this is easy to do with inline admin models.
Your best bet is to create an inline admin model as explained in the docs.
I'd like to replace an existing ForeignKey pointing at my User model with one pointing at a profile model.
The change in the model is:
created_by=models.ForeignKey(settings.AUTH_USER_MODULE)
To:
created_by=models.ForeignKey(settings.PROFILE_MODEL)
The auto-generated migration looks like (with constants subbed in):
migrations.AlterField(
model_name=MODEL,
name='created_by',
field=models.ForeignKey(to=settings.PROFILE_MODEL),
preserve_default=True,
),
I also have ManyToManyFields to deal with as well. What I have in my head is I'd like a function to run on each MODEL object to resolve the user object to the profile object. How would I go about doing this?
The relationship between user and profile is (and vice versa):
User.profile = Profile
Edit: Forgot to mention, if the auto-generated migration is run you get the following error:
ValueError: Lookup failed for model referenced by field
APP1.MODEL.created_by: APP2.PROFILE_MODEL
As I understand now you want to migrate only our app without expecting anything to be changed to the global auth User model. Then it's easy. Migrations work nice with symbolic settings names.
I tried it with Django 1.7. It is possible to switch between settings.AUTH_USER_MODEL and settings.PROFILE_MODEL back and forth without any problem. A migration can be created and applied after every change. The tested model had also a ManyToManyField and mutual relationships between User and Profile.
I see you have APP1 and APP2. Maybe you make migrations for both and they are circular dependent so that a part of other application migration should be applied before the current one migration can be completely applied and vice versa. It can be simplified by spliting a change to more smaller and making automatic migrations after every change so that they are less dependent. A OneToOneField is better than two mutual foreign keys and its reverse relation is even so useful. A foreign key can be changed temporarily to IntegerField(null=True) in the worst case in order to simplify data migration. It is really viable more or less nice.
The question looked nice initially, but the problem should be better specified to be reproducible.
Edited by removing the original text after reading information in comments:
Instead of deleting records in my Django application, I want to just mark them as "deleted" and have them hidden from my active queries. My main reason to do this is to give the user an undelete option in case they accidentally delete a record (these records may also be needed for certain backend audit tracking.)
There are a lot of foreign key relationships, so when I mark a record as deleted I'd have to "Cascade" this delete flag to those records as well. What tools, existing projects, or methods should I use to do this?
Warning: this is an old answer and it seems that the documentation is recommending not to do that now: https://docs.djangoproject.com/en/dev/topics/db/managers/#don-t-filter-away-any-results-in-this-type-of-manager-subclass
Django offers out of the box the exact mechanism you are looking for.
You can change the manager that is used for access through related objects. If you new custom manager filters the object on a boolean field, the object flagged inactive won't show up in your requests.
See here for more details :
http://docs.djangoproject.com/en/dev/topics/db/managers/#using-managers-for-related-object-access
Nice question, I've been wondering how to efficiently do this myself.
I am not sure if this will do the trick, but django-reversion seems to do what you want, although you probably want to examine to see how it achieves this goal, as there are some inefficient ways to do it.
Another thought would be to have the dreaded boolean flag on your Models and then creating a custom manager that automatically adds the filter in, although this wouldn't work for searches across different Models. Yet another solution suggested here is to have duplicate models of everything, which seems like overkill, but may work for you. The comments there also discuss different options.
I will add that for the most part I don't consider any of these solutions worth the hassle; I usually just suck it up and filter my searches on the boolean flag. It avoids many issues that can come up if you try to get too clever. It is a pain and not very DRY, of course. A reasonable solution would be a mixture of the Custom manager while being aware of its limitations if you try searching a related model through it.
I think using a boolean 'is_active' flag is fine - you don't need to cascade the flag to related entries at the db level, you just need to keep referring to the status of the parent. This is what happens with contrib.auth's User model, remember - marking a user as not is_active doesn't prompt django to go through related models and magically try to deactivate records, rather you just keep checking the is_active attribute of the user corresponding to the related item.
For instance if each user has many bookmarks, and you don't want an inactive user's bookmarks to be visible, just ensure that bookmark.user.is_active is true. There's unlikely to be a need for an is_active flag on the bookmark itself.
Here's a quick blog tutorial from Greg Allard from a couple of years ago, but I implemented it using Django 1.3 and it was great. I added methods to my objects named soft_delete, undelete, and hard_delete, which set self.deleted=True, self.deleted=False, and returned self.delete(), respectively.
A Django Model Manager for Soft Deleting Records and How to Customize the Django Admin
There are several packages which provide this functionality: https://www.djangopackages.com/grids/g/deletion/
I'm developing one https://github.com/meteozond/django-permanent/
It replaces default Manager and QuerySet delete methods to bring in logical deletion.
It completely shadows default Django delete methods with one exception - marks models which are inherited from PermanentModel instead of deletion, even if their deletion caused by relation.