Home > Back-end >  How to access to request.user in def save() in models?
How to access to request.user in def save() in models?

Time:11-11

I want to create a user at the creation of an object. This object is linked to the user by a foreign key. I have override the def_save() method to create the user and link it to the object.

Problem: I generate a random password for this user and I would like to send it by e-mail not to the just created user but to the user.

    def save(self, *args, **kwargs):
        if self._state.adding:
            super(Machine, self).save(*args, **kwargs)
            username = f"machine_{slugify(self.site.client.name).lower()}_{self.id}"
            password = User.objects.make_random_password()
            self.user = User.objects.create(
                username=username,
                password=password
            )
            
            self.save(update_fields=['user'])

            send_mail(
                f'Password of {username}',
                f'Password: {password}',
                settings.DEFAULT_FROM_EMAIL,
                [self.request.user.email],
                fail_silently=True,
            )
        else:
            super(Machine, self).save(*args, **kwargs)

The problem is that I don't have access to self.request in this method. How can I access to request in my def save()?

Or how can I get the password value in my view?

CodePudding user response:

I think you should design this differently.

If there is always a view, it suggests that the only legitimate place this object and the related user could be created is inside a particular view. So, use get_or_create and if it was created, then invoke the logic to create and associate the new user and e-mail the password to the current Django user.

You could harden it against object creation outside of an appropriate view by instead using

try:
    existing_instance = MyModel.objects.get( ...)
except MyModel.DoesNotExist
    new = MyModel( ...)
    # create and associate the User object here

    setattr( new, '_foo_bar', 'not_Molly')   # a Mollyguard
    new.save()

and check in MyModel's save method that self._foo_bar is present and correct. Raise a meaningful error if not. This will avoid accidental creation of MyModel instances without an associated User by, say, newly recruited help who don't fully understand the bad implications of doing this.

If you really, really want, you could pass the current request.User as the value of an attribute, and check isinstance( self._foo_bar, User) and then having crashed out if you don't have a valid User, put the logic in the save method. This feels wrong to me.

CodePudding user response:

To answer your question directly (I definitely think you should read the design suggestions here also) but to get the request object throughout the request cycle, one solution is threadlocals. Threadlocals middleware puts the request object on a thread-accessible storage, and then provides a get_current_request handler that you can import anywhere and grab the request off of local storage.

So many caveats here: Django core devs intentionally didn't include this functionality, here is a great discussion of why you shouldn't do this, Python is not 100% thread safe, this may be (and probably is) an anti-pattern, and consider the cases brought up in this thread.

CodePudding user response:

I don't think that save_model method override is the best option. Imagine, for instance, that you want to save the user info or validate the model based on user info and that save() does not come from a view or the adminsite itself.

What people are asking are constructions like those one:

def save(..)
    self.user = current_user()

or

def save(..)
    user = current_user()
    if user.group == 'XPTO':
        error('This user cannot edit this record')

The best approach I found so far is:

here

  • Related