How to access to request.user in def save() in models? - django

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?

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.

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.

Related

Restric a user to get or update a object made by specific user django

I have a task model like
class Tasks(models.Model):
made_by=models.ForeignKey(User , on_delete=models.CASCADE)
title = models.CharField(max_length=100,null=True,blank=True)
I have a view like
def get(self, request, id):
task = Tasks.objects.get(id=id)
if task:
serializer = TasksSerializer(task, many=False)
return Response(success_response(serializer.data, "Contact Information."
status=status.HTTP_200_OK)
And a PUT in same way. I want to check Only User which made_by it can access it . Is there any smart way to do this ? I dont want to query for check again in all views and hereafter.
Since it appears that you are using class-based views I would suggest that you override the dispatch method of your class. This class gets executed every time someone calls the view, no matter the method.
In this dispatch method you could first of all retrieve the task object like you do in the get-function in your current code. After that step you could then perform a check to see whether the request.user equals the object's made_by.
For example:
def dispatch(self, request, id):
self.task = Tasks.objects.get(id=id) # consider using get_object_or_404
# Check if user is owner of task, otherwise throw a 404
if request.user != self.task.made_by:
raise Http404()
# Will continue execution as normal, calling get() if a get-request was made
# the variable self.task will be available in this function, so re-retrieving the
# object is not necessary
return super().dispatch(request, id)
Additionally I would also suggest using the default LoginRequiredMixin (source) to make sure that only logged-in users can access the view. It could eliminate custom written checks in many cases.
The PermissionRequiredMixin (source) is also a great choice when dealing with more general permissions that are not related to specific instances.
For more specific - customized - permissions you could also use the UserPassesTestMixin (source) to write checks in dedicated test funcs to keep your code cleaner.
a couple different ways to go about this but I think the easiest is to test the user's made by status. i.e.
def run_task(self, request, id):
if request.user.made_by == 'Foo Bar'
. . . .
return Response(success_response ...
else
return Response(failure_response ...
another, more complicated way would be to play around with the standard account model perms and set 'task' perms to false. i.e.
def has_task_perms(self):
return False
and then in the process of user creation through whichever user type has the ability to give task perms. The most common way I am aware of to do this is to use an account manager (a whole rabbit whole in and of itself.) i.e.
class AccountManager(BaseUserManager):
def create_user(self, password):
# define regular creation process
. . .
return user
def create_special_user(self, password):
# define special creation process i.e.
has_task_perms == True
return user
I tried so many solution but At the End I made a decorator like this
def task_ownership_check(func):
def wrapper(request,*args, **kwargs):
print(kwargs['id'])
try:
task = Tasks.objects.get(id=kwargs['id'])
except Tasks.DoesNotExist:
return Response(failure_response(data={}, msg='No task matching query found'),
status=status.HTTP_404_NOT_FOUND)
if args[0].user != task.todolist.for_user:
return Response(failure_response(data={'error': 'You are not allowed to access this record'},
msg='You are not allowed to access this record'),
status=status.HTTP_400_BAD_REQUEST)
else:
return func(args[0], kwargs['id'])
return wrapper
So Now I can check easily by including #task_ownership_check on any task

Tastypie obj_create - how to use newly created object?

When a new item is created using Tastypie, I want to be able to add it to a user's attribute which is a many-to-many field.
RIght now my obj_create looks like this:
def obj_create(self, bundle, request=None, **kwargs):
return super(GoalResource, self).obj_create(bundle, request, user=request.user)
I want to create the new object, but when I want to be able to add it to the request.user's attribute goal_list. But, what I have will immediately create the object in the database. How would I create the object and then add it to the user's goal_list attribute?
You didn't show us your resource definition, but assuming you are using tastypie.resources.ModelResource as your base class, this should work:
def obj_create(self, bundle, request=None, **kwargs):
bundle = super(GoalResource, self).obj_create(
bundle, request, user=request.user)
user = request.user
user.goals.add( bundle.obj )
user.save()
return bundle
This is because the obj_create method of ModelResource class returns a bundle which contains the saved object (bundle.obj) and you can manipulate this object in your obj_create method as shown and only then return it.
I have also assumed that request.user contains a valid User object (i.e. authenticated), you need to make sure it does for above to work or you should add some error handling code for the case when it does not.
Hope this helps :)
I don't have enough reputation to comment yet so I figured I would put a second answer. The answer above is correct I just wanted to add that request no longer exists in the obj_create call. You can access the current request via bundle.request:
http://django-tastypie.readthedocs.org/en/latest/resources.html#accessing-the-current-request
Thanks for the answer above, it helped me as well!

Django - Using defaults if query fails

I am trying to create a script which will retrieve a user account and at the same time that particular users account 'theme' in one simple query. Here is the code I am using, please note the get_account method:
class AccountManager(Manager):
def __init__(self, *args, **kwargs):
super(AccountManager, self).__init__(*args, **kwargs)
def get_account(self, slug):
return get_object_or_404(self.select_related('theme'), status__exact=self.model.ACTIVE_STATUS, slug__exact=slug)
It works nicely, but if for whatever reason the 'theme' object returns null (this should never happen but I want to be sure!), it will redirect to a 404 page. What I want it to do is check if the theme object exists and is valid, and if not, default to the standard theme and carry on.
I would still like the query to throw a 404 if the user account is not valid though. Short of using a bucket load of try/exceptions, is there a nice clean way of doing this? I like to keep my Managers nice and tidy :)
Hope someone can help
try:
account = self.select_related('theme').get(
status__exact=self.model.ACTIVE_STATUS, slug__exact=slug
)
except self.model.DoesNotExist:
account = self.select_related('theme').get(slug="default_slug")
return account
In your example, it wouldn't result in a 404 if the related theme was null as it is not the object being retrieved with get_object_or_404(). It will only 404 if it can't find an Account object that is both ACTIVE_STATUS and a matching slug.
My suggestion to ensure you use a default theme with any valid Account object is:
class AccountManager(Manager):
def __init__(self, *args, **kwargs):
super(AccountManager, self).__init__(*args, **kwargs)
def get_account(self, slug):
account = get_object_or_404(self.select_related('theme'), status__exact=self.model.ACTIVE_STATUS, slug__exact=slug)
if not account.theme:
account.theme = default_theme_obj
return account
I'll leave it up to you to determine how you would get default_theme_obj as I don't know your models, caching structure and whatnot.
In the interests of keeping your managers tidy, you don't need the __init__ method either as it doesn't do anything that the default one wouldn't do.

Allow changing of User fields (like email) with django-profiles

Django lets you create a model foreign-keyed to User and define it in settings as the official "profile" model holding additional data for user accounts. django-profiles lets you easily display/create/edit that profile data. But the user's primary email address is part of their main account, not part of their extended profile. Therefore when you put
{{ form }}
in the profile/edit_profile template, the primary email address does not show up. You can retrieve it manually with
{{ user.email }}
but changes to it aren't saved back to the account upon submit of course. I'm assuming a custom ModelForm has been created, such as:
class ProfileForm(ModelForm):
class Meta:
model = Parent
exclude = ('family','user','board_pos','comm_job',)
and that ProfileForm is being passed to django-profiles' view code with a urlconf like:
('^profiles/edit', 'profiles.views.edit_profile', {'form_class': ProfileForm,}),
The same problem would come up if you wanted to let users change their first or last names. What's the best way to let users change their own email addresses or names when using django-profiles?
Here's the solution we ended up using:
# urls.py
# First match /profiles/edit before django-profiles gets it so we can pass in our custom form object.
('^profiles/edit', 'profiles.views.edit_profile', {'form_class': ProfileForm,}),
(r'^profiles/', include('profiles.urls')),
Now we override the save method in the form itself, so that when the form is saved, the email address is pushed into the saving user's User object at the same time. Graceful.
# forms.py , trimmed for brevity
class ProfileForm(ModelForm):
def __init__(self, *args, **kwargs):
super(ProfileForm, self).__init__(*args, **kwargs)
try:
self.fields['email'].initial = self.instance.user.email
except User.DoesNotExist:
pass
email = forms.EmailField(label="Primary email")
class Meta:
model = Parent
def save(self, *args, **kwargs):
"""
Update the primary email address on the related User object as well.
"""
u = self.instance.user
u.email = self.cleaned_data['email']
u.save()
profile = super(ProfileForm, self).save(*args,**kwargs)
return profile
Works perfectly. Thanks mandric.
I think that implementing a Separate page just for change of email is best, since it would need to be verified etc...
If you would like to enable users to modify all their profile info together with their main email address, then you need to create your own Form (ModelForm will not work here). I suggest you start doing this and post a question when you get stuck.
Start by copying all the fields out of django-profile model into your custom form, and add the users primary email field.
You will have to "override" the django-profile edit url and basically copy the html template if there is one.
Another option (bad) would be to hack django-profiles app and change it there. But that will, likely, introduce a lot of bugs, and will render your app unapgradable.
I think the easiest way would definitely be to use a form. Use the form to display their current email address (which they could change), and then use your view to extract the request, retrieve the appropriate profile belonging to that user by matching some other parameter you could pass to the template, and then storing the new data and saving the model.

In a django model custom save() method, how should you identify a new object?

I want to trigger a special action in the save() method of a Django Model object when I'm saving a new record (not updating an existing record.)
Is the check for (self.id != None) necessary and sufficient to guarantee the self record is new and not being updated? Any special cases this might overlook?
Alternative way to checking self.pk we can check self._state of the model
self._state.adding is True creating
self._state.adding is False updating
I got it from this page
Updated: With the clarification that self._state is not a private instance variable, but named that way to avoid conflicts, checking self._state.adding is now the preferable way to check.
self.pk is None:
returns True within a new Model object, unless the object has a UUIDField as its primary_key.
The corner case you might have to worry about is whether there are uniqueness constraints on fields other than the id (e.g., secondary unique indexes on other fields). In that case, you could still have a new record in hand, but be unable to save it.
Checking self.id assumes that id is the primary key for the model. A more generic way would be to use the pk shortcut.
is_new = self.pk is None
The check for self.pk == None is not sufficient to determine if the object is going to be inserted or updated in the database.
The Django O/RM features an especially nasty hack which is basically to check if there is something at the PK position and if so do an UPDATE, otherwise do an INSERT (this gets optimised to an INSERT if the PK is None).
The reason why it has to do this is because you are allowed to set the PK when an object is created. Although not common where you have a sequence column for the primary key, this doesn't hold for other types of primary key field.
If you really want to know you have to do what the O/RM does and look in the database.
Of course you have a specific case in your code and for that it is quite likely that self.pk == None tells you all you need to know, but it is not a general solution.
You could just connect to post_save signal which sends a "created" kwargs, if true, your object has been inserted.
http://docs.djangoproject.com/en/stable/ref/signals/#post-save
Check for self.id and the force_insert flag.
if not self.pk or kwargs.get('force_insert', False):
self.created = True
# call save method.
super(self.__class__, self).save(*args, **kwargs)
#Do all your post save actions in the if block.
if getattr(self, 'created', False):
# So something
# Do something else
This is handy because your newly created object(self) has it pk value
I'm very late to this conversation, but I ran into a problem with the self.pk being populated when it has a default value associated with it.
The way I got around this is adding a date_created field to the model
date_created = models.DateTimeField(auto_now_add=True)
From here you can go
created = self.date_created is None
For a solution that also works even when you have a UUIDField as a primary key (which as others have noted isn't None if you just override save), you can plug into Django's post_save signal. Add this to your models.py:
from django.db.models.signals import post_save
from django.dispatch import receiver
#receiver(post_save, sender=MyModel)
def mymodel_saved(sender, instance, created, **kwargs):
if created:
# do extra work on your instance, e.g.
# instance.generate_avatar()
# instance.send_email_notification()
pass
This callback will block the save method, so you can do things like trigger notifications or update the model further before your response is sent back over the wire, whether you're using forms or the Django REST framework for AJAX calls. Of course, use responsibly and offload heavy tasks to a job queue instead of keeping your users waiting :)
rather use pk instead of id:
if not self.pk:
do_something()
It is the common way to do so.
the id will be given while saved first time to the db
> def save_model(self, request, obj, form, change):
> if form.instance._state.adding:
> form.instance.author = request.user
> super().save_model(request, obj, form, change)
> else:
> obj.updated_by = request.user.username
>
> super().save_model(request, obj, form, change)
Would this work for all the above scenarios?
if self.pk is not None and <ModelName>.objects.filter(pk=self.pk).exists():
...
In python 3 and django 3 this is what's working in my project:
def save_model(self, request, obj, form, change):
if not change:
#put your code here when adding a new object.
To know whether you are updating or inserting the object (data), use self.instance.fieldname in your form. Define a clean function in your form and check whether the current value entry is same as the previous, if not then you are updating it.
self.instance and self.instance.fieldname compare with the new value