OneToOneField and Deleting - django

I have the following model:
from django.db import models
from django.contrib.auth.models import User
class Profile(models.Model):
user = models.OneToOneField(User)
# ...
def __unicode__(self):
return u'%s %s' % (self.user.first_name, self.user.last_name)
When using the Django admin to delete the user, the profile gets deleted as well, which is what I want. However, when using the Django admin to delete the profile, the user does not get deleted, which is not what I want. How can I make it so that deleting the profile will also delete the user?

Since Profile links to User, it is the dependent model in the relationship. Therefore when you delete a user, it deletes all dependent models. However when you delete a profile, since User does not depend on profile, it is not removed.
Unfortunately, according to on_delete Django docs, there is no on_delete rule which deletes the parent relations. In order to do that, you can overwrite the Profile's delete method:
class Profile(models.Model):
# ...
def delete(self, *args, **kwargs):
self.user.delete()
return super(self.__class__, self).delete(*args, **kwargs)
Then when doing:
Profile.objects.get(...).delete()
will also delete the profile's user. However the delete method will not be called when deleting profiles using querysets (which is what is called in Django Admin) since then Django uses SQL DELETE to delete objects in bulk:
Profile.objects.filter(...).delete()
In that case, as recommended by Django docs, you will have to use post_delete signal (docs).
from django.dispatch import receiver
from django.db.models.signals import post_delete
#receiver(post_delete, sender=Profile)
def post_delete_user(sender, instance, *args, **kwargs):
if instance.user: # just in case user is not specified
instance.user.delete()

Use a signal on the Profile's delete method to go and delete the related User:
from django.db.models.signals import post_delete
def delete_related_user(sender, **kwargs):
deleted_profile = kwargs['instance']
deleted_profile.user.delete()
post_delete.connect(delete_related_user, sender=Profile)

Related

How to get user in Django signals [duplicate]

I have done the below post_save signal in my project.
from django.db.models.signals import post_save
from django.contrib.auth.models import User
# CORE - SIGNALS
# Core Signals will operate based on post
def after_save_handler_attr_audit_obj(sender, **kwargs):
print User.get_profile()
if hasattr(kwargs['instance'], 'audit_obj'):
if kwargs['created']:
kwargs['instance'].audit_obj.create(operation="INSERT", operation_by=**USER.ID**).save()
else:
kwargs['instance'].audit_obj.create(operation="UPDATE").save()
# Connect the handler with the post save signal - Django 1.2
post_save.connect(after_save_handler_attr_audit_obj, dispatch_uid="core.models.audit.new")
The operation_by column, I want to get the user_id and store it. Any idea how can do that?
Can't be done. The current user is only available via the request, which is not available when using purely model functionality. Access the user in the view somehow.
I was able to do it by inspecting the stack and looking for the view then looking at the local variables for the view to get the request. It feels like a bit of a hack, but it worked.
import inspect, os
#receiver(post_save, sender=MyModel)
def get_user_in_signal(sender, **kwargs):
for entry in reversed(inspect.stack()):
if os.path.dirname(__file__) + '/views.py' == entry[1]:
try:
user = entry[0].f_locals['request'].user
except:
user = None
break
if user:
# do stuff with the user variable
Ignacio is right. Django's model signals are intended to notify other system components about events associated with instances and their respected data, so I guess it's valid that you cannot, say, access request data from a model post_save signal, unless that request data was stored on or associated with the instance.
I guess there are lots of ways to handle it, ranging from worse to better, but I'd say this is a prime example for creating class-based/function-based generic views that will automatically handle this for you.
Have your views that inherit from CreateView, UpdateView or DeleteView additionally inherit from your AuditMixin class if they handle verbs that operate on models that need to be audited. The AuditMixin can then hook into the views that successfully create\update\delete objects and create an entry in the database.
Makes perfect sense, very clean, easily pluggable and gives birth to happy ponies. Flipside? You'll either have to be on the soon-to-be-released Django 1.3 release or you'll have to spend some time fiddlebending the function-based generic views and providing new ones for each auditing operation.
You can do that with the help of middleware. Create get_request.py in your app. Then
from threading import current_thread
from django.utils.deprecation import MiddlewareMixin
_requests = {}
def current_request():
return _requests.get(current_thread().ident, None)
class RequestMiddleware(MiddlewareMixin):
def process_request(self, request):
_requests[current_thread().ident] = request
def process_response(self, request, response):
# when response is ready, request should be flushed
_requests.pop(current_thread().ident, None)
return response
def process_exception(self, request, exception):
# if an exception has happened, request should be flushed too
_requests.pop(current_thread().ident, None)
Then add this middleware to your settings:
MIDDLEWARE = [
....
'<your_app>.get_request.RequestMiddleware',
]
Then add import to your signals:
from django.db.models.signals import post_save
from django.contrib.auth.models import User
from <your_app>.get_request import current_request
# CORE - SIGNALS
# Core Signals will operate based on post
def after_save_handler_attr_audit_obj(sender, **kwargs):
print(Current User, current_request().user)
print User.get_profile()
if hasattr(kwargs['instance'], 'audit_obj'):
if kwargs['created']:
kwargs['instance'].audit_obj.create(operation="INSERT", operation_by=**USER.ID**).save()
else:
kwargs['instance'].audit_obj.create(operation="UPDATE").save()
# Connect the handler with the post save signal - Django 1.2
post_save.connect(after_save_handler_attr_audit_obj, dispatch_uid="core.models.audit.new")
Why not adding a middleware with something like this :
class RequestMiddleware(object):
thread_local = threading.local()
def process_request(self, request):
RequestMiddleware.thread_local.current_user = request.user
and later in your code (specially in a signal in that topic) :
thread_local = RequestMiddleware.thread_local
if hasattr(thread_local, 'current_user'):
user = thread_local.current_user
else:
user = None
For traceability add two attributes to your Model(created_by and updated_by), in "updated_by" save the last user who modified the record. Then in your signal you have the user:
models.py:
class Question(models.Model):
question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published')
created_by = models. (max_length=100)
updated_by = models. (max_length=100)
views.py
p = Question.objects.get(pk=1)
p.question_text = 'some new text'
p.updated_by = request.user
p.save()
signals.py
#receiver(pre_save, sender=Question)
def do_something(sender, instance, **kwargs):
try:
obj = Question.objects.get(pk=instance.pk)
except sender.DoesNotExist:
pass
else:
if not obj.user == instance.user: # Field has changed
# do something
print('change: user, old=%s new=%s' % (obj.user, instance.user))
You could also use django-reversion for this purpose, e.g.
from reversion.signals import post_revision_commit
import reversion
#receiver(post_save)
def post_revision_commit(sender, **kwargs):
if reversion.is_active():
print(reversion.get_user())
Read more on their API https://django-reversion.readthedocs.io/en/stable/api.html#revision-api
You can do a small hack by overriding you model save() method and setting the user on the saved instance as additional parameter. To get the user I used get_current_authenticated_user() from django_currentuser.middleware.ThreadLocalUserMiddleware (see https://pypi.org/project/django-currentuser/).
In your models.py:
from django_currentuser.middleware import get_current_authenticated_user
class YourModel(models.Model):
...
...
def save(self, *args, **kwargs):
# Hack to pass the user to post save signal.
self.current_authenticated_user = get_current_authenticated_user()
super(YourModel, self).save(*args, **kwargs)
In your signals.py:
#receiver(post_save, sender=YourModel)
def your_model_saved(sender, instance, **kwargs):
user = getattr(instance, 'current_authenticated_user', None)
PS: Don't forget to add 'django_currentuser.middleware.ThreadLocalUserMiddleware' to your MIDDLEWARE_CLASSES.
I imagine you would have figured this out, but I had the same problem and I realised that all the instances I create had a reference to the user that creates them (which is what you are looking for)
it's possible i guess.
in models.py
class _M(models.Model):
user = models.ForeignKey(...)
in views.py
def _f(request):
_M.objects.create(user=request.user)
in signals.py
#receiver(post_save, sender=_M)
def _p(sender, instance, created, **kwargs):
user = instance.user
No ?
Request object can be obtained from frame record by inspecting.
import inspect
request = [
frame_record[0].f_locals["request"]
for frame_record in inspect.stack()
if frame_record[3] == "get_response"
][0]
def get_requested_user():
import inspect
for frame_record in inspect.stack():
if frame_record[3] == 'get_response':
request = frame_record[0].f_locals['request']
return request.user
else:
return None
context_processors.py
from django.core.cache import cache
def global_variables(request):
cache.set('user', request.user)
----------------------------------
in you model
from django.db.models.signals import pre_delete
from django.dispatch import receiver
from django.core.cache import cache
from news.models import News
#receiver(pre_delete, sender=News)
def news_delete(sender, instance, **kwargs):
user = cache.get('user')
in settings.py
TEMPLATE_CONTEXT_PROCESSORS = (
'web.context_processors.global_variables',
)

pre_delete signal error

I want to delete all Generic Foreign Key relationships belonging to a Contact when said contact is deleted.
This is what I have tried so far:
#receiver(pre_delete, sender=Contact):
def contact_delete(sender, instance, **kwargs):
from unsubscribe.models import Unsubscribe
unsubscribe_list = Unsubscribe.objects.filter(object_id=instance)
for item in unsubscribe_list:
item.delete()
My issues are, how do I get the object_id of the instance. I only want to delete the related items of the object I'm deleting?
instance is the Contact object here. So, instance.id would give you the id of the contact object
from django.db.models.signals import pre_delete
from django.dispatch import receiver
#receiver(pre_delete, sender=Contact, dispatch_uid='<whatever>')
def contact_delete(sender, instance, using, **kwargs):
from unsubscribe.models import Unsubscribe
unsubscribe_list = Unsubscribe.objects.filter(object_id=instance.id, content_type__model='contact')
for item in unsubscribe_list: #This should be a single element in the queryset.
item.delete()

Execute code on model creation in Django

I want to execute some code in a Django model when it is first created. After that whenever it is saved I want to execute some other code. The second task can be easily done by overriding the save() method.
How can I do the first task?
Extending sdolan's answer by using receiver decorator:
from django.db import models
from django.dispatch import receiver
class MyModel(models.Model):
pass
#receiver(models.signals.post_save, sender=MyModel)
def execute_after_save(sender, instance, created, *args, **kwargs):
if created:
# code
You can use django signals' post_save:
# models.py
from django.db.models import signals
class MyModel(models.Model):
pass
def my_model_post_save(sender, instance, created, *args, **kwargs):
"""Argument explanation:
sender - The model class. (MyModel)
instance - The actual instance being saved.
created - Boolean; True if a new record was created.
*args, **kwargs - Capture the unneeded `raw` and `using`(1.3) arguments.
"""
if created:
# your code goes here
# django 1.3+
from django.dispatch import dispatcher
dispatcher.connect(my_model_post_save, signal=signals.post_save, sender=MyModel)
# django <1.3
from django.db.models.signals import post_save
post_save.connect(my_model_post_save, sender=MyModel)
field.default
The default value for the field. This can be a value or a callable object. If callable it will be called every time a new object is created.
So, we can use any field callable to do what we want on creation, huh? ;-)

How would i use signals to create a new "UserProfile" object when a "User" object is created in django?

Hay, I'm using django's contrib.auth system which obviously allows me to create User objects, I'm also using the profile module. This is loaded through AUTH_PROFILE_MODULE.
Using signals how would i got about create a new UserProfile object when a User is created?
Thanks
I'm creating a new entry in Account which acts as my UserProfile:
from django.db.models.signals import post_save
from django.contrib.auth.models import User
from wizard.models import Account
def make_account(sender, **kwargs):
if 'created' not in kwargs or not kwargs['created']:
return
user = kwargs["instance"]
account = Account(user=user, name="Account for %s" % user.username)
account.save()
post_save.connect(make_account, sender=User, weak=False)

django override User model

I'm trying to override the default User model in Django to add some logic into the save() method. I'm having a hard time trying to figure out out to go about this.
I'm using Django 1.1 if that helps.
I used post_save since i need to add the user into ldap.. I just added this into a models.py
from django.db import models
from django.contrib.auth.models import User
from django.db.models import signals
from django.dispatch import dispatcher
def user_post_save(sender, instance, **kwargs):
print "got here"
models.signals.post_save.connect(user_post_save, sender=User)
Don't. Instead catch the pre_save signal.
You'd better use a Proxy model, so to use the same table but overriding behavior.
This is the standard way to extend Django's own models, because they cannot be made abstract.
So declare your model as:
from django.contrib.auth.models import User
class CustomUser(User):
class Meta:
proxy = True
def save(self, *args, **kwargs):
# do anything you need before saving
super(CustomUser, self).save(*args, **kwargs)
# do anything you need after saving
and you are done.