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.
Related
I am using standard models.Model in Django. I changed my primary key to _id (mongo). After that, actions in Django Admin don't work anymore. For example, when I use the default Django action – Delete selected, no object is found through the query. However, no problem occurs when I edit or delete objects manually. Is there any way to fix it?
Are you sure you want to delete the selected companies? -> Summary, objects: None
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)
I want to do some custom actions before a user is created. and I thought of using the pre_save signal for that. And in case one of those action would raise an exception stop the transaction, abort creating the user etc.
Is this the way to go? would it abort the save if something fails in this step (this is the required behavior) I suspect so but couldn't find docs about it.
What is the best practice for getting the future user.id. from my understanding it doesn't exists yet in pre-save but I need it as input for some of the extra custom actions.
Thanks
From the docs:
There's no way to tell what the value of an ID will be before you call
save(), because the value is determined by your database, not by
Django.
So if your pre-save processing requires the user.id, I am afraid this is not possible.
Here is the two part answer:
Yes, raising an exception in the pre_save() signal will abort the call to the save() method. For example, I did this in my pre_save():
if SOME_TEST:
raise exceptions.ParseError(detail="I reject your data. Here is why.")
Note: This was using the DRF exceptions, but I will assume you know how to raise whatever exception you prefer.
You could use the post_save() signal to get the id AFTER the save() method. But if that will not work for you, then below is my original explanation of some tactics to do what you want in the pre_save():
In the pre_save you can access User.objects.all() (if you import the User model, of course). If you are using the standard User model then the User.id value is AUTO INCREMENT. So if you NEED to know what the value WILL be for the new user, just get the highest User.id currently in the database (ie: the id of the last user created). Here is a simple query to do this:
last_user = User.objects.latest('id')
Then of course, last_user.id will give you the value of the id for the last user created and if you add one to that you will have the next user id. Of course, if you have a busy site you may have some problems with simultaneous user creation. The risk is that this value is received by two (or more) User creation attempts and one (or more) of them ends up wrong.
Of course, you can always set another field to be primary_key=True and this will replace the id field on the model. Then you can devise any sort of indexing mechanism that you can dream up! You could use a hash value of a unique characteristic. For example: The User.username has a Unique constraint, you could hash or hex encode that as a numeric ID to pre-determine the User.id. Or you could leave the id field in place and set it manually in the pre_save by assigning a value to obj.id. It could be anything you want. Even a hash value!
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)
With Django 1.5 and the introduction of custom user models the AUTH_PROFILE_MODULE became deprecated. In my existing Django application I use the User model and I also have a Profile model with a foreign key to the User and store other stuff about the user in the profile. Currently using AUTH_PROFILE_MODULE and this is set to 'app.profile'.
So obviously, my code tends to do lots of user.get_profile() and this now needs to go away.
Now, I could create a new custom user model (by just having my profile model extend User) but then in all other places where I currently have a foreign key to a user will need to be changed also... so this would be a large migration in my live service.
Is there any way - and with no model migration - and only by creating/overriding the get_profile() function with something like my_user.userprofile_set.all()[0]) somewhere?
Anyone out there that has gone down this path and can share ideas or experiences?
If I where to do this service again now - would obviously not go this way but with a semi-large live production system I am open for short-cuts :-)
Using a profile model with a relation to the built-in User is still a totally legitimate construct for storing additional user information (and recommended in many cases). The AUTH_PROFILE_MODULE and get_profile() stuff that is now deprecated just ended up being unnecessary, given that built-in Django 1-to-1 syntax works cleanly and elegantly here.
The transition from the old usage is actually easy if you're already using a OneToOneField to User on your profile model, which is how the profile module was recommended to be set up before get_profile was deprecated.
class UserProfile(models.Model):
user = OneToOneField(User, related_name="profile")
# add profile fields here, e.g.,
nickname = CharField(...)
# usage: no get_profile() needed. Just standard 1-to-1 reverse syntax!
nickname = request.user.profile.nickname
See here if you're not familiar with the syntactic magic for OneToOneField's that makes this possible. It ends up being a simple search and replace of get_profile() for profile or whatever your related_name is (auto related name in the above case would be user_profile). Standard django reverse 1-1 syntax is actually nicer than get_profile()!
Change a ForeignKey to a OneToOneField
However, I realize this doesn't answer your question entirely. You indicate that you used a ForeignKey to User in your profile module rather than a OneToOne, which is fine, but the syntax isn't as simple if you leave it as a ForeignKey, as you note in your follow up comment.
Assuming you were using your ForeignKey in practice as an unique foreign key (essentially a 1-to-1), given that in the DB a OneToOneField is just a ForeignKey field with a unique=True constraint, you should be able to change the ForeignKey field to a OneToOneField in your code without actually having to make a significant database migration or incurring any data loss.
Dealing with South migration
If you're using South for migrations, the code change from the previous section may confuse South into deleting the old field and creating a new one if you do a schemamigration --auto, so you may need to manually edit the migration to do things right. One approach would be to create the schemamigration and then blank out the forwards and backwards methods so it doesn't actually try to do anything, but so it still freezes the model properly as a OneToOneField going forward. Then, if you want to do things perfectly, you should add the unique constraint to the corresponding database foreign key column as well. You can either do this manually with SQL, or via South (by either editing the migration methods manually, or by setting unique=True on the ForeignKey and creating a first South migration before you switch it to a OneToOneField and do a second migration and blank out the forwards/backwards methods).