Django doesn't work the way you think


When I read the list of buns that any framework provides me with, I imagine what about them mean. When I read the documentation for the goodies, I am convinced that everything in general is really the way I thought. When I write code, I comprehend Tao. Because everything is actually quite wrong.

Many of the mistakes that 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:
  1. Unremovable Models We Will Remove
  2. Validable fields that are not validated
  3. 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? Hardly.
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


Валидатор — функция, которая принимает значение в кидает ValidationError, если значение не подходит по какому-то критерию. Валидаторы могут быть полезны, если нужно многократно использовать какую-то проверку на разных типах полей.

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.
Обратите внимание, что валидаторы не будут автоматически вызываться при сохранении модели, но если вы используете ModelForm, ваши валидаторы сработают для тех полей, что включены в форму.

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 conflict 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 . The admin field quantity has the old value, so as soon as he 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.