📜 ⬆️ ⬇️

Django doesn't work the way you think


When I read the list of buns that a framework provides for me, I imagine what they mean by about. When I read the documentation on the buns - I am convinced that everything in general is really the way I thought. When I write code, I comprehend the dao. Because everything is really not at all like that.

Many of the mistakes that I made were due to the fact that I was sure that it worked the way I thought . I believed in it and did not allow the possibility that it could be otherwise. Of course, Captain Obvious will say that you do not need to believe - you need to read the documentation. And we read, read, memorize, memorize. Is it possible to keep all the little things in memory? And is it right to shift them to the developer, and not to the framework?

Well, in order not to be unfounded - let's move on to examples. Waiting for us:
  1. Undelete models that we remove
  2. Validated fields that are not validated
  3. Two admins that spoil the data


1. Deleting objects in 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 with name == 'Root' be deleted. The first thing that comes to mind is to simply 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?



Works! And now let's do the same, but from the model list page:



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 getting implicit behavior? Hardly.
What to do? For example, generally disable bulk deletion (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 that 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 throws a ValidationError if the value does not match by some criterion. Validators can be useful if you need to reuse some kind of check on different types of fields.

For example, checking a field against a regular expression: RegexValidator .
Let's allow only letters in the name field:
 class SomeModel(models.Model): name = models.CharField('name', max_length=100, validators=[RegexValidator(regex=r'^[a-zA-Z]+$')]) 



In the admin 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, } ) 

Through create () you can specify any name, and validation is not called .

Is this described in the documentation? Yes.
Note that validators will not be automatically called when the model is saved, but if you use ModelForm, your validators will work for the fields that are included in the form.

Is this obvious? I honestly tried to imagine a situation where I need to validate a field in a form, but not validate it in other cases. And I have no ideas. It is more logical to make a validation for the global field - that is, if it is specified that only letters are, then everything, the enemy will not pass, no pasaran - in no way can this be circumvented. If you want to remove such a restriction, override model.save () and disable validation in necessary cases.
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 adminyat site. After the thousandth record, Misha left the form for editing the record # 1001 (“What is the meaning of life”) and left to drink tea. At this time, Peter opened the same record # 1001 and renamed it (“There is no meaning”). Misha returned (the “old” post “What is the meaning of life” is still open) and clicked “Save”. Petya's works are stuck.

This is called the absence of " Optimistic Locking / Optimistic Concurrency Control ".

If you think that for such a collision it is necessary to have two administrators working simultaneously, and you support the site alone and therefore in the house, then I hurry to upset you: it works, even if there is one admin. For example, the admin edits the product, and the user bought the product at that moment, reducing the value in the quantity field. The admin field quantity contains the old value, so that as soon as he clicks to save ...

What does django have to do with it? And because django provides 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 “mashing” of data and inconsistencies in quantities begin. Well, then - an exciting debag.

Is this described in the documentation? Not.
What to do?

In short, we create a version field for each model, while saving, check that the version has not changed, and increase its value by 1. If it has changed, we throw an exception (it means that someone else has already changed the record).

Morality


In conclusion, I will repeat what I started with:
Django doesn't 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 presence of functionality, if it is not written explicitly in the documentation. These are the obvious truths ... exactly until you get caught.

Source: https://habr.com/ru/post/194974/


All Articles