I have a simple model:
class InvitationRequest(models.Model):
email = models.EmailField(max_length=255, unique=True)
And a simple model form:
class InvitationRequestForm(forms.ModelForm):
class Meta:
model = InvitationRequest
Now, assuming that I attempt to process it in a standard way:
form = InvitationRequestForm(request.POST)
if form.is_valid():
form.save()
There is a race condition because validation performs a simple SELECT
query to determine whether or not such email is already stored, and if everything is fine then it proceeds to form.save()
line. If there is a concurrent process that does the same at exactly the same moment, then both forms will validate and both processes will call form.save()
thus one will succeed and the other will fail causing an IntegrityError
.
What is the standard way to handle this?
I want to have a standard error in the form object so I can pass it on to the template and notify user about the problem.
I know that:
- I can wrap everything with try/except and add new error to my form manually
- I can wrap everything with
SERIALIZABLE
transaction (in MySQL as it performs next key locking fo every select then) - I can use override
Model._perform_unique_checks
and make it useselect_for_update
(works with MySQL because of next key locking) - I can acquire table-level exclusive lock
None of these solutions is appealing, also I am using PostgreSQL which differs from MySQL in this area.