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!
Related
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.
In a Django Rest Framework app using Django Simple History to track model changes, how would one force a user to pass a Reason for Deletion for all Destroy End Points and then pass that reason to Django Simple History's Change Reason?
Also, for related models with a Delete Cascade, would that reason be passed on to the related deleted entries?
Update:
Tried overriding the destroy method as below following another question here on SO. Issue is, where is the changeReason i.e. history_change_reason located after the delete? I thought it would be a column in the historical table? However, its not there so even if the code below is working, I can't find where the reason has been saved.
class DeleteViewSet(mixins.DestroyModelMixin):
def destroy(self, request, *args, **kwargs):
try:
if 'delete_reason' not in request.data.keys():
return Response(status=status.HTTP_400_BAD_REQUEST,data='{delete_reason: Invalid Delete Reason}')
else:
instance = self.get_object()
instance.changeReason = request.data['delete_reason']
instance.save()
self.perform_destroy(instance)
except Http404:
pass
return Response(status=status.HTTP_204_NO_CONTENT)
Missing History Change Reason Column:
The only history_* I see in all history tables are:
1. history_id
2. history_date
3. history_type
4. history_user_id
I can't find history_change_reason in any of the history tables
Ok. Found the answer to two of my problems.
Missing History Change Reason Column:
This was a version issue. pip3.6 install --upgrade django-simple-history and then migrate solved this.
Deletion Reason as a Mixin:
This works by checking if deletion_reason is provided.
class DeleteViewSet(mixins.DestroyModelMixin):
def destroy(self, request, *args, **kwargs):
try:
if 'delete_reason' not in request.data.keys():
return Response(status=status.HTTP_400_BAD_REQUEST,data='{delete_reason: Invalid Delete Reason}')
else:
instance = self.get_object()
instance.changeReason = request.data['delete_reason']
# instance.save() ==>Don't do this because it will cause a second instance to be saved in the history tables
self.perform_destroy(instance)
except Http404:
pass
return Response(status=status.HTTP_204_NO_CONTENT)
Not solved:
Passing the deletion_reason to all tables that are subsequently deleted when you have a delete_cascade in the relation between models.
I wanted to ask if there is a way to access instance id in ModelForm save method. (Need an object in order to add some extra data).
def save(self, *args, **kwargs):
instance = super(MyForm, self).save(*args, **kwargs)
print instance
return instance
And in all cases I am getting instance before it's saved in database (so it does not have an id and I can't attach objects to it)
It isn't necessary to override the ModelForm save() function. Instead, it's better to call save with commit=False. The Django docs explain this in depth, but here's a quick example:
new_object = form.save(commit=False)
new_object.name = 'Whatever'
new_object.save()
By calling save with commit=False, you get an object back. You can do whatever you want with this object, but make sure to save it once you make your changes!
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.
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