Django pre_save signal - would an exception fail the transaction? - python

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!

Related

Automatically filled field in model

I have some model where there are date field and CharField with choices New or Done, and I want to show some message for this model objects in my API views if 2 conditions are met, date is past and status is NEW, but I really don't know how I should resolve this.
I was thinking that maybe there is option to make some field in model that have choices and set suitable choice if conditions are fulfilled but I didn't find any information if something like this is possible so maybe someone have idea how resolve this?
You need override the method save of your model. An overrided method must check the condition and show message
You may set the signal receiver on the post_save signal that does the same like (1).

Django pre-save signal

I have a pre-save signal for one of my models. This pre-save signal does some background API activity to syndicate new and updated objects to service providers and return meaningless data for us to store as references in the places of the original data.
The new and update methods are different in the API.
Ideally, if a user were to perform an update they would be clearing the meaningless data from a field and typing over it. My signal would need to know which fields were updated to send changes for just those fields, as sending all fields in an update would send meaningless references as the raw data in addition to the updates.
The pre-save signal has the argument update_fields. I searched for some details and found that this argument may include all fields when an update is performed.
Regarding update_fields as the docs have little information on this
When creating an object, does anything get passed to update_fields?
When updating an object, do all fields get passed to update_fields, or just the ones that were updated?
Is there some other suggestions on how to tackle this? I know post_save has the created argument, but I'd prefer to operate on the data before it's saved.
When creating an object, does anything get passed to update_fields?
No.
When updating an object, do all fields get passed to update_fields, or just the ones that were updated?
Depends who is calling the save() method. By default, Django doesn't set update_fields. Unless your code calls save() with the update_fields argument set, it will rewrite all the fields in the database and the pre_save signal will see update_fields=None.
My signal would need to know which fields were updated to send changes for just those fields.
Unless you are controlling what calls the save() method on the object, you will not get this information using update_fields. The purpose of that argument is not to let you track which fields have changed - rather it is to facilitate efficient writing of data when you know that only certain columns in the database need to be written.

Can I overwrite User.objects.delete in Django?

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.

How to put form validation in the model using SQLAlchemy?

I see lots of apps using Form objects to validate data and then passing the data to the model, while putting absolutely no validation in the model. I feel it's better to put core validation in the model itself (e.g., no users under the age of 18, ever) to be run regardless of the context. In other words, I don't care how the user is being created (whether through web ui or command line), the core rules should always apply.
I'm using SQLAlchemy (within a Pyramid application), and I would like to define my core validation rules within the model in a way that my forms (WTForms) always respect the core rules defined in the model so that all data is consistent.
Is anybody else already doing this, or something similar?
Something similar to this php solution.
SQLAlchemy allows you to register listeners, which are invoked when certain events occur, for example you can register an event to be triggered when a model's field is modified. From SQLAlchemy documentation:
Listeners have the option to return a possibly modified version of the
value, when the retval=True flag is passed to listen():
def validate_phone(target, value, oldvalue, initiator):
"Strip non-numeric characters from a phone number"
return re.sub(r'(?![0-9])', '', value)
# setup listener on UserContact.phone attribute, instructing
# it to use the return value
listen(UserContact.phone, 'set', validate_phone, retval=True)
A validation function like the above can also raise an exception such
as ValueError to halt the operation.
So, as you see, you can either modify or reject values for certain field at the model level.
I'm not sure if your form library integrates with this feature, but it definitely should not be too difficult to roll your own solution.

Django ModelForm, having a foreign key as a hidden field

I'm basically building a very trivial form. Let's stick to the books/publisher examples given in the django tutorials and build upon that.
I have a user login to the web app, at which point the first thing they can do is click on a publisher. This publisher then gets saved for their session. Upon that I take them to a create book form. In there I embed the the publisher's id from the database into a hidden field.
Upon the user submitting an HTTP POST, I do something like:
mybookform = BookForm(request.POST)
if mybookform.is_valid():
abook = mybookform.save(commit=False)
abook.publisher_id = request.POST['publisher_id']
mybookform.save()
Yes there's a few naive things done here, such as blindly grabbing the publisher_id and verifying if it's indeed a real publisher id, amongst other security issues. Let's just not pay attention to that for the moment.
My question is, is there a better way of handling this? Although hypothetically this example doesn't make logistical sense, in my particular app the example actually makes sense. The problem is I get a ValueError exception saying publisher_id needs to be a Publisher instance.
Now I can easily retrieve a publisher instance with Publisher.objects.filter(id=..) and use that instead. The question is, is it really necessary? Can I avoid the additional query to the database and somehow update this form instance in a more 'elegant' fashion?
Also, is it possible to somehow embed the publisher in a hidden field so that I do not need to do mybookform.save(commit=False) and just do mybookform = BookForm(request.POST) followed by mybookform.save() immediately?
Retrieving the instance of the publisher does protect against client-side changes that might reference a completely invalid publisher.
To your second question, yes you can include that field as a hidden field by overriding the field in the ModelForm with the approriate form field setting the widget to HiddenInput.
There is no better way to do this.
I would use the get_object_or_404 function for this.
And yes, you can prevent this to be modified by the user by setting the model field to editable=False,

Categories

Resources