Форум программистов, компьютерный форум, киберфорум
Наши страницы
netBool
Войти
Регистрация
Восстановить пароль
Рейтинг: 4.00. Голосов: 1.

Что особенного в переопределении конструктора модели Django?

Запись от netBool размещена 14.02.2018 в 13:31

Доброго дня и настроения всем любителям Джанго!

Эта статья больше рассчитана на аудиторию начинающих джангистов, но предполагается, что читатель владеет основами языка. Все программеры, более менее освоившие python, знают что такое наследование, конструкторы и переопределение и наверняка пользовались этим не раз. Наверняка вам приходилось переопределять конструктор родителя, как то:
Python
1
2
3
4
5
6
7
8
9
class Car:      
      def __init__(w_a):
              weight_auto = w_a 
              koleco = 4
 
class Kran(Car):
      def __init__(w_a, weight_all):
              super(Volc, self).__init__()
              weight_allpwed= weight_all
В этом примере мы создали класс Car, в конструкторе которого определили два поля. В наследуемом классе Kran мы переопределили конструктор, вызвав в нем родительский конструктор и добавив поле weight_allowed. Это простой пример

В django мы имеем дело с моделями, которые по сути представляют из себя такие же классы, которые можно наследовать и в которых можно переопределять методы. И казалось бы, что может быть проще, но...

Есть один нюанс. Если вы попробуете провернуть трюк с переопределением __init__, как в примере выше, вы можете натолкнуться на айсберг.

Нет, не то, чтобы разработчики джанго запретили переопределять конструктор, но официально это не советуют:
Цитата:
You may be tempted to customize the model by overriding the __init__ method. If you do so, however, take care not to change the calling signature as any change may prevent the model instance from being saved. Rather than overriding __init__, try using one of these approaches:
Если по русски, то
Цитата:
Возможно вам захочется переопределить метод __init__. В таком случае не переопределяйте сигнатуру вызова этого метода, иначе объект модели может не сохраняться. Вместо переопределения __init__ лучше используйте один из следующих подходов:
И предлагают два варианта:
1. Добавить метод класса в модель:
Python
1
2
3
4
5
6
7
8
9
10
class Book(models.Model):
    title = models.CharField(max_length=100)
 
    @classmethod
    def create(cls, title):
        book = cls(title=title)
        # do something with the book
        return book
 
book = Book.create("Pride and Prejudice")
2. Добавить метод в менеджер модели(лучший вариант):
Python
1
2
3
4
5
6
7
8
9
10
11
12
class BookManager(models.Manager):
    def create_book(self, title):
        book = self.create(title=title)
        # do something with the book
        return book
 
class Book(models.Model):
    title = models.CharField(max_length=100)
 
    objects = BookManager()
 
book = Book.objects.create_book("Pride and Prejudice")
И нет проблем, если вам всего лишь нужно переопределить создание модели... Тогда на этих примерах и ссылкой на оф.док можно и закончить. Но вдруг вам надо все-таки переопределить именно конструктор... Для чего?

Например, у менеджера моделей есть такой удобный метод, как create. Для не особо искушенных я отмечу, что он не только создает модель, но так же и делает ее save(). В общем-то все и началось с того, что я решил использовать именно ее в своих вью. Конечно, можно ее использовать просто передавая именнованные параметры в аргументы без всякого переопределения. Зачем извращаться?

Но что делать, если вы хотите передать в качестве параметра какой-то флаг или значение, которое не является полем модели и класса вообще, но на основе этого значения выполнялась бы какая-то логика, в результате выполнения которой полю модели должно быть присвоено значение. Скажете вы - ну и нафиг, всю эту логику можно прописать во view. Хорошо. Но что делать, если с этой моделью нуно очень часто create()? Прописывать ее каждый раз? Ну тоже вариант. Хорошо. Но мы не ищем легких путей

Для начала я бы хотел рассмотреть несколько примеров, как и почему не работает переопределение сигнатуры конструкторов моделей. Например:
Python
1
2
3
4
5
class Dialogue(models.Model):
    Partakers = ArrayField(models.BigIntegerField(), null=False, unique=True)
    def __init__(self, partrs, *args, **kwargs):
        super(Dialogue, self).__init__(*args, **kwargs)
        self.Partakers = partrs
Это класс диалог, Partakers - это участники диалога. Далее я создаю его так:
Python
1
Dialog = Dialogue.objects.create(partrs=[int(self.request.POST['userid'])])
И создав, я обнаруживаю, что он работает вопреки всем заявлениям разработчиков: модель создалась, корректно прописалась в БД. В общем, решив, что они там чего-то перепутали, я продолжил работу над другими моделями

Но какого же было мое удивление, когда я попытался выполнить следующее:
Python
1
id = Dialogue.objects.get(id=1).id
В переменную id вместо индекса я получил массив ArrayField!!!

Впрочем, если рассказывать по порядку, то обнаружил это не сразу

Сперва я создал модель сообщения:
Python
1
2
3
4
5
6
class Message(models.Model):
 
    Dialog = models.ForeignKey(Dialogue)
    Sender = models.ForeignKey(Profile)
    Content = models.TextField()
    DateTime = models.DateTimeField()
И переопределил ее конструктор:
Python
1
2
def __init__(self, receiver, sender, content,*args,**kwargs):
          super(Message, self).__init__(*args,**kwargs)
(ну а почему бы нет?)
Но на строке
Python
1
msg = Message.objects.create(sender=request.user, content=request.POST.get('value', ''),receiver=receivers)
Интерпретатор мне постоянно стал выбрасывать ошибку:
Цитата:
TypeError: __init__() takes exactly 2 arguments (3 given)
Которая поставила меня в полный тупик.
Попробовал через конструктор Message(...). То же самое. Тогда я пошел спрашивать у гугла, и тут вспомнил о советах разработчиков так не делать: не переопределять конструктор, а если переопределять, то не менять сигнатуру.
Странно, подумал я, там они писали, что модель может всего лишь не сохраниться, а тут...
Я убрал все лишнее, оставил только **kwargs и туда помимо всех полей передал переменную для выполнения логики. Замечу, что делал я уже все не через create менеджера, а через обычный конструктор Message(...). И о чудо, модель была создана и не выдала ошибку... Тогда я попробовал все это сделать через create. Но ошибка вернулась
Собственно, оказывается для create даже в kwargs не должно приходить ничего лишнего

В чем же проблема?

Мы знаем, что python ооп-ный язык и поддерживает не только наследование, но и полиморфизм
Метод create работает непосредственно с классом Model и его конструктором, с сигнатурой конструктора Model и ни с какой другой. В этом методе идет перечисление полей args и kwargs, в которых предполагаются поля нашей пользовательской модели и ничего лишнего.

Вопрос, что же делать, если все-таки есть желание работать через create?

Зная принцип работы create мы можем его использовать как хотим при одном маленьком условии: при вызове конструктора Model надо привести его сигнатуру в соответствии с ожидаемой. Например, так:
Python
1
2
3
4
5
6
7
8
    def __init__(self, **kwargs): #
 
        if kwargs.has_key('receiver'): receiver = kwargs['receiver']; kwargs.pop('receiver')
 
        super(Message, self).__init__(**kwargs) #receiver, sender, content,
 
        if dir().__contains__('receiver'):
                  #далее ваш код
В вышеприведенном коде в kwargs пришло одно неожидаемое значение: 'receiver'. Сохраняю его в отдельную переменную, удаляю из kwargs и спокойно вызываю конструктор родителя.

Надеюсь, статья была полезной. Хоть кому-нибудь Писал на скорую руку. Может быть, куча ошибок. Но общий принцип и идею, думаю, передал
Размещено в Django
Просмотров 392 Комментарии 0
Всего комментариев 0
Комментарии
 
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2018, vBulletin Solutions, Inc.
Рейтинг@Mail.ru