LINUX.ORG.RU

Хочется очень странного от Джанги

 , ,


0

2

Шалом, уважаемые.

Начал я как-то углублённо Джангу изучать (о чём сейчас искренне сожалею) и в одном из учебных заданий вылезла бяка: есть следующие модели:

class Pizza(models.Model):
    name = models.CharField(null=False, max_length=200)
    dough = models.ForeignKey(Dough, on_delete=models.CASCADE, default=0)
    topping = models.ForeignKey(Topping, on_delete=models.CASCADE, default=0)
    price = models.DecimalField(max_digits=5, decimal_places=2, default=0)

    def __str__(self):
        return self.name

    def make_order(self, count):
        return InstancePizza.objects.create(name=self.name, price=self.price, pizza_template=self, count=count)


class InstancePizza(models.Model):
    pizza_template = models.ForeignKey(Pizza, related_name='pizza_template', on_delete=models.SET_NULL, null=True, blank=True)
    count = models.PositiveIntegerField(default=1)
    name = models.CharField(null=True, blank=True, max_length=200)
    price = models.DecimalField(max_digits=5, decimal_places=2, default=0, null=True, blank=True)
    
    def __str__(self):
        return 'name: {}, price: {}, full price: {}'.format(self.name, str(self.price), str(self.price * self.count))


class Order(models.Model):
    pizzas = models.ManyToManyField(InstancePizza, related_name='order_template')
    date = models.DateTimeField(auto_now_add=True)
    user = models.ForeignKey(User, null=True, on_delete=models.SET_NULL)
    price = models.DecimalField(default=0, max_digits=7, decimal_places=2, null=True, blank=True)
    
    def __str__(self):
        return 'OrderID: {}, price: {}'.format(str(self.id), str(self.price))

    def get_price(self):
        pizzas = self.pizzas.all()
        price = 0
        for pizza in pizzas:
            price += pizza.price * pizza.count
        return price
    def change_order_price(self):
        user = self.user
        self.refresh_from_db()
        price = 0
        for instance_pizza in InstancePizza.objects.all().filter(order_template__user=user):
            price += instance_pizza.price * instance_pizza.count
            print('Print_____ ', price)
        self.price = price
        self.save()

суть проблемы в следующем: для обновления Order-а я использую вьюшку (на принты и прочий шлак прошу не обращать внимание :)):

class UpdateOrder(UpdateView):
	model = Order
	form_class = UpdateOrderForm
	template_name = 'update_order.html'
	success_url = '/basket/'

	def get_context_data(self, **kwargs):
		context = super().get_context_data(**kwargs)
		curent_order = Order.objects.filter(user=self.request.user)
		user = 0
		context['instances_pizzas'] = InstancePizza.objects.all().filter(order_template__user=self.request.user)
		return context

	def form_valid(self, form):
		curent_order = Order.objects.get(user=self.request.user)
		print('PRICE1', curent_order.price, curent_order.id)
		curent_order.change_order_price()
		Order.objects.filter(user=self.request.user).update(price=Order.objects.get(user=self.request.user).get_price())
		curent_order.change_order_price()
		print('PRICE3', curent_order.price, curent_order.pizzas, curent_order.id)
		order = Order.objects.get(id=1)
		order.price = 200
		order.save()
		print('EXAMPLE: ', order.order_template.all())
		print('PRICE 4', curent_order.price, curent_order.pizzas, curent_order.id)
		return super().form_valid(form)

которая НЕ ОБНОВЛЯЕТ ордер((

Из найдёного в гугле есть подозрение на m2m, который работает несколько странно в джанге. Но как это разрулить - хз ((

У кого нибудь есть предположения? Буду рад любым, т.к. сам я иссяк ((

У кого нибудь есть предположения?

Где результаты выполнения кода? И что ты вообще хочешь сделать?

gruy ★★★★★ ()

order.save()

Ну наверное тут не срабатывает же, валидация может не проходит, может в БД есть констрейнты?

Alve ★★★★★ ()
Ответ на: комментарий от gruy

хочу сделать пересчёт price в Order при изменении InstancePizza. А не получается. Выхлоп тут не привёл, т.к. он сильно загаженый принтами )) Но суть ясна: в объекте Order-а связанные InstancePizza отображаются, а вот итоговый прайс сохранить к оному - нет.

zad1ra ()
curent_order.change_order_price()

Неясно, что тут происходит

Order.objects.filter(user=self.request.user).update(price=Order.objects.get(user=self.request.user).get_price())

Эта строчка не имеет никакого эффекта. Ты записываешь в поле старое значение. И это упадет с MultipleObjectsReturned, когда у текущего пользователя окажется больше одного заказа.

order.price = 200

Мне кажется, что сюда надо передавать Decimal. Хотя не уверен.

HerrWeigel ★★★★ ()
Последнее исправление: HerrWeigel (всего исправлений: 1)
Ответ на: комментарий от cnsr

тут немного не то. Как раз связанные через м2м модели подтягиваются. проблемы начинаются, когда сейвится пересчётанный из них итоговый прайс Order.price.

zad1ra ()
Ответ на: комментарий от HerrWeigel

curent_order.change_order_price()

тут как раз и считается потребный итоговый прайс. Но почему-то не сохраняется ((

Эта строчка не имеет никакого эффекта. Ты записываешь в поле старое значение. И это упадет с MultipleObjectsReturned, когда у текущего пользователя окажется больше одного заказа.

Не, тут я записываю в order.price пересчитанное в get_price() новое значение прайса.

Мне кажется, что сюда надо передавать Decimal. Хотя не уверен

всё верно, это я для теста воткнул. На самом деле его тут быть не должно ))

zad1ra ()
Ответ на: комментарий от zad1ra

вероятно так и есть. Но я не вдупляю ПОЧЕМУ??? Агрррррррррр…

посмотреть, какой он SQL генерит?

возвращает ли .save() что-то? статус? ошибки?

в Rails например на объекте есть массив errors куда после сохранения складываются все ошибки валидаций, в джанге по-любому что-то подобное должно быть

Alve ★★★★★ ()
Ответ на: комментарий от zad1ra

тут как раз и считается потребный итоговый прайс. Но почему-то не сохраняется ((

Код-то у этого метода какой?

Не, тут я записываю в order.price пересчитанное в get_price() новое значение прайса.

Судя по тому коду, который ты предоставил, get_price() возвращает 0. Потому что никаких пицц ты к заказу не привязал.

Тебя не смущает, что после получения формы данные из неё не используются?

HerrWeigel ★★★★ ()
Ответ на: комментарий от HerrWeigel

метод пропустил((. Добавил в модель Order.

.Судя по тому коду, который ты предоставил, get_price() возвращает 0. Потому что никаких пицц ты к заказу не привязал.

так ведь в UpdateOrder определяется апдейт заказа. И 0 этот метод не возвращает. В принтах можно увидеть, что цена таки считается. Проблема даже не в этом (если бы хоть 0 сохранился - уже гуд был бы), а в том, что не смотря на апдейты список связанных через м2м InstancePizza меняется, а order.price - нет ((

zad1ra ()
Ответ на: комментарий от zad1ra

уже ответил на свой вопрос )) Можно.

Там странное:

[DEBUG:django.db.backends:110] (0.000) SELECT "dj_pizzas_order"."id", "dj_pizzas_order"."date", "dj_pizzas_order"."user_id", "dj_pizzas_order"."price" FROM "dj_pizzas_order" WHERE "dj_pizzas_order"."id" = 1; args=(1,)
[DEBUG:django.db.backends:110] (0.010) UPDATE "dj_pizzas_order" SET "date" = '2019-11-26 07:01:07.405046', "user_id" = 1, "price" = '40.00' WHERE "dj_pizzas_order"."id" = 1; args=('2019-11-26 07:01:07.405046', 1, '40.00', 1)
PRICE 4 40.00 dj_pizzas.InstancePizza.None 1
[DEBUG:django.db.backends:110] (0.008) UPDATE "dj_pizzas_order" SET "date" = '2019-11-26 07:01:07.405046', "user_id" = 1, "price" = '30.00' WHERE "dj_pizzas_order"."id" = 1; args=('2019-11-26 07:01:07.405046', 1, '30.00', 1)

по расположению принтов видно, что второй UPDATE с какого-то перепугу переписывает price. Хотя я его вообще не вызывал!!!! Млять, как я ненавижу джангу ((((((

zad1ra ()

из парсинга sql-запросов выяснилось, что старое значение price устанавливается супером return super().form_valid(form) Но как?????

zad1ra ()
Ответ на: комментарий от zad1ra

Млять, как я ненавижу джангу ((((((

Ха-ха. Плохому танцору тоже ноги мешают. Инструмент обвинить легко. Найти ошибку у себя в коде сложнее, но тоже можно.

HerrWeigel ★★★★ ()
  1. Если ты хочешь посмотреть, какие запросы делает Django к базе, то поставь и настрой django-debug-toolbar.

  2. Order.objects.get(user=self.request.user). Насчет этого уже писали тебе выше. В таком случае, если вернется из базы несколько записей, то получишь исключение. Надо делать filter и first()

  3. Принтами не дебажат (в php через echo - тоже). В Pycharm легко настраивается дебаггер, который очень помогает.

  4. «как-то углублённо Джангу изучать» - на мой взгляд, если Джангу и изучать, то не формошлепство, а django rest framework. Сейчас современные тенденции таковы, что бэкенд представляет собой просто API, а все остальное пилится на JS (привет, React/Angular/Vue)

Думаю, тебе надо примерно так:

class UpdateOrder(UpdateView):
    def form_valid(self, form):
        instance = form.save(commit=False)
        instance.price = 200
        super().save(form)
dimuska139 ★★ ()
Ответ на: комментарий от zad1ra

Млять, как я ненавижу джангу

Можешь попробовать что-то другое. Но плюс джанго в том, что она дает готовый каркас для приложения. Так что новичку писать код на джанго проще, чем на каком-нибудь Flask, особенно если ты не силен в шаблонах проектирования и не знаешь, как, скажем, написать код так, чтобы его удобно было тестами покрывать, мокать и т.п.

dimuska139 ★★ ()
Ответ на: комментарий от dimuska139

Ещё есть django-silk. А вообще есть интерактивный режим. ./manage.py shell_plus –print-sql будет печатать sql запросов, которые ты выбираешь.

pawnhearts ★★★★★ ()
Ответ на: комментарий от dimuska139

django норм для прототипа ну и всяких интернет-магазинов и прочих визиток. Дальше конечно начинаются пляски, хотя, они везде начинаются))

pawnhearts ★★★★★ ()
Ответ на: комментарий от pawnhearts

django норм для прототипа ну и всяких интернет-магазинов и прочих визиток. Дальше конечно начинаются пляски, хотя, они везде начинаются

Знаешь, я когда смотрю на кишки диджанги - прям кровавые слеза наворачиваются. Это как спагетти код, только классовое спагетти. Каюсь, в 23 года сам этой заразой переболел. Но организм справился, и теперь у меня хороший иммунитет.
О чем я пишу? Давайте возьмем упомянутый UpdateView:

UpdateView
+-SingleObjectTemplateResponseMixin
|  +-TemplateResponseMixin
+-BaseUpdateView
   +-ModelFormMixin
   |  +-FormMixin
   |  |  +-ContextMixin
   |  +-SingleObjectMixin
   |  |  +-ContextMixin
   |  +ProcessFormView
   +-View
Здесь классы переплетены намертво, их работу может гарантировать только тот, кто досконально знает работу всех связанных классов. При этом, задача написания функционала, значительно отличающегося от стандартного, фактически сводится к переписыванию кода заново и/или копипасте. Радует лишь то, что в этих классах кода почти нету - всего пару тысяч строк на все отображения. Это к слову о талантливых людях, которые десять строчек превращают в десять классов.

byko3y ★★★ ()
Ответ на: комментарий от byko3y

Есть целый сайт, чтобы там разобраться лол https://ccbv.co.uk/

Но вообще все эти формы это легаси. И class-based view позволяет намного больше переиспользовать код, чем function-based views до этого. Сейчас джанга актуальна в контексте drf, а там всё очень неплохо. И очень круто кастомизируется, если ориентироваться. Я даже видел как люди копируют структуру оттуда - https://github.com/Skorpyon/aiorestframework (да я и сам таким страдаю иногда https://bitbucket.org/robotnaoborot/aiowebapi/src/master/ https://github.com/pawnhearts/aiorf/tree/master/aiorf )

Но вообще беда джанги в синхронности и производительности. Я тут сделал прототип на drf и уже столкнулся с проблемой производительности. Там всё оказалось чуть сложнее и вот несколько вложенных сериализаторов и все эти prefetch_related уже не спасают. Пришлось выхлоп особо тяжелых сериализаторов кешировать в jsonfield, чтобы это быстро шевелилось. Что-то денормализовать. Кое где всю вьюшку целиком кешировать. И это прототип с одним пользователем. Ради смеха я сделал management command который складывает выхлоп сериализатора особо тяжелого listview в коллекцию монго. И набросал на aiohttp/aiomongo все фильтры которые мне нужны. И это просто летает.

pawnhearts ★★★★★ ()
Последнее исправление: pawnhearts (всего исправлений: 1)
Ответ на: комментарий от dimuska139

Я тоже пробовал. И что-то не сильно в восторге, хотя норм. И react мне не зашел, vue как-то больше по душе.

Да чего я только не пробовал. Когда-то мы вообще выкинули бэкенд и написали модуль для nginx который отдавал данные из базы.

pawnhearts ★★★★★ ()
Ответ на: комментарий от pawnhearts

Есть целый сайт, чтобы там разобраться лол https://ccbv.co.uk/

Я удивлен, но они говорят моими словами... или я их словами... Но я о них узнал только что - разве что они читают ЛОР и прямо сейчас мои слова добавили на сайт:

For example, trying to work out exactly which method you need to customise, and what its keyword arguments are, on your UpdateView can feel a little like wading through spaghetti — it has 10 separate ancestors (plus object), spread across 3 different python files. This site shows you exactly what you need to know.

Это к слову о том, что, внезапно, этим модулями понадобились инструменты для того, чтобы прочитать их функции. Многие люди называют это «инструменты помогают мне еще лучше писать и разбираться в коде». Увы, причинно-следственная связь обстоит наоборот: ущербность организации классов вынуждает прибегать к инструментам для того, чтобы не сломать в этих классах ногу.

И class-based view позволяет намного больше переиспользовать код, чем function-based views до этого.

Да, я уже написал: такая организация позволяет сделать удобной копипасту функций этих классов в свои классы.

Я даже видел как люди копируют структуру оттуда - https://github.com/Skorpyon/aiorestframework (да я и сам таким страдаю иногда https://bitbucket.org/robotnaoborot/aiowebapi/src/master/ https://github.com/pawnhearts/aiorf/tree/master/aiorf )

Я не совсем вкуриваю, в каком месте там копирование структуры.

Но вообще беда джанги в синхронности и производительности. Я тут сделал прототип на drf и уже столкнулся с проблемой производительности. Там всё оказалось чуть сложнее и вот несколько вложенных сериализаторов и все эти prefetch_related уже не спасают.

Это ощнь странно. Неужели там сериализация (в JSON?) не функциями на Си, а голым питоновым кодом идет? Оно же по идее должно со скоростью около машинный кодов выполняться, не? Я просто с DRF очень мало знаком.

byko3y ★★★ ()
Ответ на: комментарий от byko3y

Эта либа сама по себе очень медленная. Хз почему, но сериализаторы данных там реально медленные

dimuska139 ★★ ()
Ответ на: комментарий от byko3y

Дело не в самой генерации json из питоновского словаря. Ты получаешь объект из бд, который уже жирный объект orm, заворачиваешь поля в fields и так для каждого объекта и для связанных объектов, а на практике там как правило много всяких SerializerMethodField. И для связанных данных будут отдельных запросы, их количество можно сократить если юзать prefetch, но не всегда.

На практике в нагруженных проектах надо очень тщательно прописывать все эти prefetch и оставлять только нужные поля через only(если забудешь указать нужное поле-привет по запросу на запись ещё) как в самом qs так и prefetch objects. Правда в drf можно автоматически это делать если не накрутить много кастомного.

pawnhearts ★★★★★ ()
Ответ на: комментарий от dimuska139

«И что-то не сильно в восторге, хотя норм» - а чего не норм? Сама гошка как язык?

Я могу назвать с моей точки зрения основные проблемы Go. Это язык для высокопроизводительных веб-сервисов, потому много низкоуровневости и мало инструментов для создания DSL, к которому неизбежно приходит любой высокоуровневый фреймворк. Это как бы к вопросу о том, почему все не пишут веб на жаве, и почему так много людей пишут сервисы на тормознутом питоне, который позволяет в широких пределах издеваться над языком. Какой процент пишет высокопроизводительный веб, и какой процент пишет что попало? Причем, «что попало» - это у нас еще и часть каких-то крупных сервисов, вроде youtube или dropbox, готовые жертвовать частью производительности серверов ради простоты и гибкости системы.

Вот на чем точно я бы не стал делать сервисы - так это на C/C++, вот это реально неэффективный инструмент. Здесь я не имею в виду «использовать бибилиотеки на C/C++», а именно писать код под клиента на этих языках.

byko3y ★★★ ()
Ответ на: комментарий от byko3y

Ну если рассматривать сайты «по стандарту», где бэк рендерит шаблоны, а фронт - это несколько js-файлов, которые к html, полученному с серверу, добавляют динамику, то да, никакого толку от go нет, потому что не особо это удобно (нет фреймворков с кучей батареек, таких, как Django и Laravel).

Но если рассматривать сайты как фронт, сделанный на React (например) и бэк в виде api, то получается, что джанга (ларавель и прочие) для создания апи просто избыточна. Можно взять какой-нибудь fastapi вместо джанги, например, для создания апи, но в таком случае вообще без разницы (в плане трудозатрат), пилить API на Python или Go, потому что количество работы будет примерно таким же, т.к. fastapi - это микрофреймворк. Если трудозатраты получаются одинаковыми, то в го имеется преимущество в виде бинаря, производительности, меньшего количества отъедаемой памяти и нормальных потоков.

dimuska139 ★★ ()
Последнее исправление: dimuska139 (всего исправлений: 1)
Ответ на: комментарий от dimuska139

Можно взять какой-нибудь fastapi вместо джанги, например, для создания апи, но в таком случае вообще без разницы (в плане трудозатрат), пилить API на Python или Go, потому что количество работы будет примерно таким же, т.к. fastapi - это микрофреймворк. Если трудозатраты получаются одинаковыми, то в го имеется преимущество в виде бинаря, производительности и нормальных потоков.

Микросервис вообще фиолево на чем писать. Потоки не нужны, потому что не хайлоад - основная нагрузка лежит на БД. Ну и да, сейчас пошла мода на SPA, потому логика с бэка уходит.

byko3y ★★★ ()
Ответ на: комментарий от byko3y

Отмечу, кстати, бэк в виде апи - это не обязательно прямо микросервис. Апи-монолиты тоже бывают)

Потоки не нужны, потому что не хайлоад - основная нагрузка лежит на БД

Ну вот надо тебе запускать отправку письма юзеру после регистрации, например. В случае с Питоном тебе нужно делать очередь, а в случае с многопоточными языками запросто можно обойтись отправкой в отдельном потоке - и юзер не будет ждать, пока письмо отправляется. Конечно, если речь о каких-то массовых рассылках, то очередь в любом случае потребуется (например, чтобы при рестарте сервиса в результате деплоя не херились не отправленные письма), но в интернет-магазах каких-нибудь вполне можно обойтись и потоком.

dimuska139 ★★ ()
Последнее исправление: dimuska139 (всего исправлений: 1)
Ответ на: комментарий от dimuska139

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

Это уже не монолит, потому что кусок логики у тебя уплыл на фронт.

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

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

byko3y ★★★ ()
Ответ на: комментарий от byko3y

Это уже не монолит, потому что кусок логики у тебя уплыл на фронт.

Кусок логики и в базе есть частенько. Но микросервис - это то, что можно разрабатывать одной небольшой командой (до 8 человек). API-монолиты бывают гораздо более крупными.

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

Ясно. А как это реализовать в Django (например)?

dimuska139 ★★ ()
Ответ на: комментарий от dimuska139

Но микросервис - это то, что можно разрабатывать одной небольшой командой (до 8 человек)

Чиво? Команда из 8 человек за полгода пишет аналог SQLite с нуля (без тщательной отладки и оптимизации). Но мы все-таки говорили про простенькие прокладки, которые можно довольно быстро писать на Go.

Ясно. А как это реализовать в Django (например)?

Никак, Джанга не для этого. Для этого starlette, для этого uvicorn.

byko3y ★★★ ()

Спасибо всем, кто пытался помочь. Всё оказалось до смешного просто: super().form_valid(form) тупо переопределял весь мой код в содержимое по умолчанию (( Потому и не получалось посчитать правильно прайс. Всего лишь поменял функцию местами и всё заработало.

	def form_valid(self, form):
		Order.objects.filter(user=self.request.user).update(price=Order.objects.get(user=self.request.user).get_price())
		return super().form_valid(form)
zad1ra ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.