Django doesn't work the way you think
When I read the list of goodies that any framework provides me with, I imagine what they mean by them. When I read the documentation for the goodies, I make sure that everything as a whole is really the way I thought. When I write code, I comprehend Tao. Because everything is actually quite wrong.
Many of the mistakes I made were due to the fact that I was sure that this worked the way I think . I believed in this and did not allow the possibility that it could be otherwise. Of course, Captain Evidence will say that you do not need to believe - you need to read the documentation. And we read, read, remember, remember. Is it possible to keep all the little things in memory? And is it right to transfer them to the developer, and not to the framework?
Well, in order not to be unfounded - let's move on to the examples. Waiting for us:
- Unremovable Models We Will Remove
- Validable fields that are not validated
- Two administrators who spoil data
1. Removing objects in the admin panel
Imagine that you have a model for which you want to override the delete method. The simplest example is that you do not want the model c to
name == 'Root'
be removed. The first thing that comes to mind is simply to override the delete () method for the desired model:class SomeModel(models.Model):
name = models.CharField('name', max_length=100)
def delete(self, *args, **kwargs):
if self.name == 'Root':
print('No, man, i am not deletable! Give up!') # Для экземпляра "Root" просто выводим сообщение
else:
super(SomeModel, self).delete(*args, **kwargs) # Для всего остального - вызываем стандартный delete()
Check it out?
Works! And now let's do the same, but from the page of the list of models:
Delete () is not called, the model is deleted.
Is this described in the documentation? Yes.
Why is this done? For efficiency.
Is this efficiency worth the implicit behavior obtained? Unlikely.
What to do? For example, disable mass removal altogether (from the model list page):
class SomeModelAdmin(admin.ModelAdmin):
model = SomeModel
def get_actions(self, request):
actions = super(self.__class__, self).get_actions(request)
if 'delete_selected' in actions:
del actions['delete_selected']
return actions
These were the first django rakes I stepped on. And in every new project I have to keep this behavior in my head and not forget about it.
2. Validators
A validator is a function that takes a value in a throwing ValidationError if the value does not fit by some criterion. Validators can be useful if you need to reuse some kind of validation on different types of fields.
For example, checking a field against a regular expression: RegexValidator .
Let's allow only letters in the field
name
:class SomeModel(models.Model):
name = models.CharField('name', max_length=100, validators=[RegexValidator(regex=r'^[a-zA-Z]+$')])
In the admin panel, validation works. And if so:
# views.py
def main(request):
new_model = SomeModel.objects.create(name='Whatever you want: 1, 2, 3, etc. Either #&*@^%!)(_')
return render(
request,
'app/main.html',
{
'new_model': new_model,
}
)
Using create (), you can specify any name, and validation is not called .
Is this described in the documentation? Yes.
Please note that validators will not be automatically called when the model is saved, but if you use ModelForm, your validators will work for those fields that are included in the form.
Is this obvious? I honestly tried to imagine a situation when you need to validate a field in a form, but not validate in other cases. And I have no ideas. It is more logical to make validation for the field global - that is, if it is specified that only letters, then everything, the enemy will not pass, no pasaran - you can’t get around this in any way. If you want to remove this restriction, redefine model.save () and disable validation if necessary.
What to do? Call validation explicitly before saving:
@receiver(pre_save, sender=SomeModel)
def validate_some_model(instance, **kwargs):
instance.full_clean()
3. Two admins
Misha and Petya administer the site. After the thousandth record, Misha left open the editing form for record # 1001 (“What is the meaning of life”) and went to drink tea. At this time, Petya opened the same record # 1001 and renamed it (“There is no sense”). Misha returned (the “old” record “What is the meaning of life” is still open) and clicked “Save”. Petit’s writings were jammed.
This is called the lack of " Optimistic Locking / Optimistic Concurrency Control ".
If you think that for such a collision you need two simultaneously working admins, and you maintain the site alone and therefore in the house, then I hasten to upset you: it works even if there is only one admin. For example, the admin edits the product, and the user at that moment bought the product, reducing the value in the field
quantity
. Admin fieldquantity
contains the old value, so as soon as it clicks save ... What does django have to do with it? And because django provides an admin panel, but does not provide optimistic locking . And believe me, when you start working with the admin panel, you don’t even think about this problem - exactly until the strange “overwriting” of data and discrepancies in quantities begin. Well, then - a fascinating debag.
Is this described in the documentation? Not.
What to do?
Briefly - for each model we create a field
version
, when saving, check that the version has not changed, and increase its value by 1. If it has changed, throw an exception (it means someone else has already changed the record).Morality
In conclusion, I will repeat what I started with:
Django does not work the way you think. Django works exactly as written in its documentation. No more, no less.
You can not be sure of the existing functionality until you read the entire section of the documentation about it. You can not rely on the availability of functionality if it is not written explicitly in the documentation. These are obvious truths ... exactly until you come across.