poetry run python -c — запуск Django в контесте менеджера проекта poetry
from django.conf import settings;print(settings.CACHES["default"]["LOCATION"]) — запуск python команды импортирующей настройки и отображающий необходимый параметр
У Django есть возможность расширить функционал списка объектов дополнительными функциюми через управление списком функций actions
Например вот так выглядит расширение для чистки кеша
def update_cdn_cache(modeladmin, request, queryset):
for obj in queryset:
obj.file.clean_cache()
А затем эта функция добавляется в класс админки
class ImageAdmin(admin.ModelAdmin):
actions = (update_cdn_cache, show_cdn_url)
По умолчанию эти функции отображаются над списком объектов в выпадающем списке и имена формируются из названий
Видно, что выглядит для наглядности необходимо добавить описание и делается это путём добавления свойства short_description для каждой функции actions, но выглядит этот вариант не красиво, особенно когда расширений админки много
def update_cdn_cache(modeladmin, request, queryset):
for obj in queryset:
obj.file.clean_cache()
update_cdn_cache.short_description = _("Remove CDN file")
Чтобы избежать такого способа можно применить параметризованный декоратор
def add_short_description(short_description: str):
def decorator(admin_action):
def wrapper(*args, **kwargs):
return admin_action(*args, **kwargs)
wrapper.__name__ = admin_action.__name__ # принудительная смена названия функции
wrapper.short_description = _(short_description) # перевод описания
return wrapper
return decorator
с таким декоратором код выглядит более читаемым
@add_short_description("Remove CDN file")
def update_cdn_cache(modeladmin, request, queryset):
for obj in queryset:
obj.file.clean_cache()
@add_short_description("Show CDN file")
def show_cdn_url(modeladmin, request, queryset):
for obj in queryset:
print(obj.file.image())
Модель UploadFile содержит данные о загруженных файла FileField, md5hash
class UploadFile(models.Model):
file = models.FileField(max_length=255)
md5hash = models.CharField(max_length=32, editable=False, unique=True)
Модель Image содержит ссылку на UploadFile, а так же дополнительные поля Alt, Name и Tags
class Image(TranslatableModel):
translations = TranslatedFields(
alt=models.CharField(max_length=50)
)
name = models.CharField(_("Name"), max_length=50)
file = models.ForeignKey(
UploadFile,
related_name="images",
on_delete=models.DO_NOTHING,
)
То-есть, объект Image может быть связан только с одним UploadFile, а объект UploadFile может быть связан с несколькими Image, а так же File, это необходимо для того чтобы получить централизованое хранилище всех загружаемых файлов, исключить дублирование файлов, а так же позвонить создавать наборы различных описаний файлов.
Необходимо в админ панели объекта Image встроить форму добавления объекта UploadFile, стандартными методами можно получит переопределив классы admin.ModelAdmin, но проще воспользовать готовыми переопределениями в пакете django-reverse-admin
Для создания такой вывернутой наизнанку админки необходимо определить такой класс
В инфраструктуре Flask есть фреймворк flask_restful для реализации Rest API и последние несколько лет там начались проблемы с развитием, в результате чего появилось несколько форков. В частности я решил попробовать построить один проект на flask_restx.
В этой заметке я буду описывать, что из этого вышло и насколько это решение хорошо для использования
С помощью RESTX я реализовал следующие API
авторизация на основе JWT токен
обновление JWT токена с помощью Refresh токента
получение и редактирование профиля пользователя
создание точки загрузки файла
загрука файла частями (chunks)
просмотр списка загруженных файлов
просмотр свойств файла по ID
Выглядит хорошо и лаконично, вот например API получения списка публикаций пользователя
Но если смотреть в целом, то приходится для каждой модели базы писать модель сериализации
А ещё больше напрягло то, что фреймворк просто не предлагает ни какого способа пагинации. То-есть, API легко отдаёт всю выборку, а для того чтобы отдать сред выборки в стиле Django, необходимо руками высчитать total и сломать весь красивый механизм marshal_with
Так же, нет интеграции со sqlalchemy, конечно можно найти способ
но как по мне конструкция ModelX.query.filter(**kwargs).all() выглядит так себе
Задача реализовать прозрачное связывание "союзами" пользователей сайта. Такое необходимо когда пользователь А запросил союз у пользователя Б и тот одобрил этот союз, тогда у обоих пользователей должен обновиться список союзников и табличная связь будет выглядеть следующим образом
таблица пользователей
таблице связей
1
осёл
2
козёл
3
мишка
4
пятачёк
5
винипух
1
2
1
3
4
5
Связав таблицы где 1 колонка это номер строки хозяин, а вторая колонка это номер строки союзника получется, что у осла в союзниках козёл и мишка, а у пятачка союзник только винипух винипух
С точки зрения базы данных это связь многие ко многим и на Sqlalchemy решается через создание ассоциативной таблиц, а учитывая что помимо союзной связи необходимо сохранить состояние связи и даты создания и обновления, то ассоциативная таблица превращается в полноценную модель
class Union(db.Model):
__tablename__ = "user_unions"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
user1_id = db.Column(db.Integer, db.ForeignKey('users.id'), primary_key=True)
user2_id = db.Column(db.Integer, db.ForeignKey('users.id'), primary_key=True)
created = db.Column(db.DateTime(timezone=True), server_default=func.now())
updated = db.Column(db.DateTime(timezone=True), onupdate=func.now())
enable = db.Column(db.Boolean, default=False, nullable=False)
def __repr__(self):
if self.enable:
return "союз с {} заключён".format(self.user2)
return "союз с {} не подписан".format(self.user2)
class User(db.Model):
__tablename__ = "users"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(40), unique=False, nullable=False)
email = db.Column(EmailType, unique=False)
def add_user(self, user):
""" добавление союза с user"""
union = Union(user1=self, user2=user)
db.session.add(union)
db.session.flush()
Всё будет прекрасно до тех пор пока не потребуется у объектов User обратиться к списку союзных пользователей. Дело в том, что метол обращения к списку союзников выглядит по разному для разныз пользователей. Если для того чтобы узнать список союзников осла неоходимо выбрать все вторые колонки в строка в которых в первой колонке номер строки осла, а чтобы выбрать союзников козла, необходимо выбрать все первые колонки в строках где вторая колонка равна номеру строки козла. Таки образом получается метод выбора союзников осла и козла будет разный, а это не красиво.
Но всё таки можно реализовать универсальный метод поиск союзников для каждого пользователя и в SQL виде он будет выглядеть вот так
select *
from users
where id in (
select user2_id id
from user_unions
where user1_id = 1
union
select user1_id id
from user_unions
where user2_id = 1
)
тут производится выборка id союзников в ассоциативной таблице для обоих типов запросов, затем результаты объединяются и используются во вложенном запросе при обращении к таблице пользователей.
Sqlalchemy позволяет реализовать и такой достаточно сложный запрос с помощью объектного ORM и тогда метод выбора союзников пользователя будет выглядет следующим образом
После миграции на более новую версию Django при изменении любой модели может вылезать вот такая ошибка
django.db.utils.IntegrityError: null value in column "name" violates not-null constraint
DETAIL: Failing row contains (101, null, history, historymodel).
Это связано с тем что в старой версии для хранения имени модуля использовалось поле name а в новой app_label, при этом поле name является обязательным к заполнению и не учавствует в работе миграции, отсюда и происходит ошибка
Для решения проблемы достаточно удалить поле name из таблицы django_content_type
Чтобы сайт работал быстро, необходимо чтобы быстро загружалась статика сайта, для этого используется сжатие ccs и js файлов (Настройка расширения gulp-clean-css для сжати css), а так же кеширование на уровне веб-сервера nginx c помощью опции expires иgzip, например вот так:
тут задаёт 30 дневный период хранения кеша статики и включается поточное сжатие статики в архив. Таким образом на продакшене образуется ситуация когда настроенный сайт очень быстро получает статику и при повторных запросах использует кеш на уровне веб-серера и веб-браузера. В случае когда присходит редкое обновление фронта требуется чтобы клиенты обновили кеши со статикой сайта. Принудить клиентов обновить кеш можно добавив номер коммита в урлы к статике. Я делаю это так: в моё конфиге я объявил переменную VERSION и инициализирую её при запуске Flask приложения следующим образом:
import os
import subprocess
import datetime
basedir = os.path.abspath(os.path.dirname(__file__))
class Config(object):
PROJECT_DIR = basedir
DEBUG = False
TESTING = False
CSRF_ENABLED = not True
ENABLED_PRODUCTMARKUP = True
VERSION = '{}.{}'.format(
str(subprocess.check_output(['git', 'describe', '--tags']),'utf-8').strip(),
int( subprocess.check_output(['git', 'rev-list', '--all', '--count']) )
)
То-есть, после каждого обновления продакшена у клиентов будет обновляться статика, а значит и фронт
Но бывает необходимо чтобы статика закачивалась при каждом новом запросе, это необходимо бывает для верстальщикам. Для решения этой задачи можно подменить значение переменной VERSION текущим значением времени. Я делаю переопределив в DevelopmentConfig аттрибут VERSION следующим образом:
class classproperty(property):
def __get__(self, cls, owner):
return classmethod(self.fget).__get__(None, owner)()
class DevelopmentConfig(Config):
DEVELOPMENT = True
DEBUG = True
#~ SQLALCHEMY_ECHO = True
UPLOAD_FOLDER = 'media/'
@classproperty
def VERSION(self):
return str(datetime.datetime.now())
тут я создаю специальный класс, который декорирует функцию VERSION, которая возращает каждый раз новую дату и время и представляет эту функцию в виде статического аттрибута класса, таким образом в ссылках на статику при каждой генерации страницы будет новый, уникальный хешь и статика будет всегда самой свежей
Использование даты в этом формате необходимо для обработки HTTP заголовка Last-Modified. Это когда сервер отдаёт дату изменения страницы в заголовке Last-Modified а затем клиет отправляе HEAD запрос с этим заголовком и датой и если страница не менялась, то ваш сервер отвечает 200 и отправляет пустое тело страницы. Это экономит и трафик и ресурсы процессора, а так же ускоряет повторную индексацию сайта и просто более технологично чем просто отдават страницы на каждый запрос.
Итак настроить дату обновления страницы в таком формате очень легко и делается это вот так:
И всё будет прекрасно работать до тех пор пока вам не потребуется локализовать даты (это когда месяцы на русском). Для этого вы сделаете вот так где в __init__.py
Возможно, раз вы используете babel, то у вас будет сменная локаль, это всё круто. Но этот системный вызов изменит поведение вашей get_last_modified и все ваши усилия по обработке Last-Modified приведут к тому, что появится вот такая ошибка
TypeError: http header must be encodable in latin1
По-этому, формировать дату в RFC формате формате необходимо правильным образом, а именно вот так
from time import mktime
from wsgiref.handlers import format_date_time
class BaseClass(SQLAModel):
__abstract__ = True
....
updated_on = Column(DateTime, default=func.now(), onupdate=func.now(), doc=_('дата редактирования'))
def get_last_modified(self):
stamp = mktime(self.updated_on.timetuple())
return format_date_time(stamp)
08 января 2018 29 января 2018 Python
| решать тесты
Что такое декоратор?
Можно ли использовать декоратор для?
Во время развития функционала любого приложения появляется необходимость дополнить или изменить поведение ранее созданных функций. Например, вы разработали вычисление некоторых функций для себя, но c изменением круга пользователей программе уже не достаточно просто вычислять значения, появляется необходимость вести учёт правильным или неправильным вычислениям или проводить эти вычисления с учётом авторизации пользователя в системе и самым простым решением будет дополнить код функции дополнительным кодом. Но в результате вы получите пухлый, рыхлый и плохо управляемый программный код.
# начальная версия кода
def вычисление_значения(a, b):
return a+b
# код после добавления новых требований
def вычисление_значения(a, b):
if проверка_пользователя:
учёт_результатов
результат = a+b
учёт_результатов(результат)
печать_результатов(результат)
Более эфектным и эффективным будет поместить созданный ранее код внутрь другого кода, который будет предварять или завершать ваш код по новым задачам. И сделать это можно с помощью декораторов.
В программировании декоратор это обёртка функционального кода применяемая относительно другого функционального кода
Можно декорировать любой функциональный код
Можно использовать цепочки декораторов, подобно конфетам которые завёрнуты в фантики, упакованны в коробке и лежат в сумке
Декораторам можно передавать параметры для использования текущего контекста
Использование декораторов позволяет создавать более абстрактный и менее зависящий от контекста код.
Используя декораторы вы можете разделять ваш основной абстрактный код от кода контекста в разных файлах и даже модулях.
Вот так будет выглядеть необходимые нам декораторы
Функционально декораторы это простой функциональный код который запускается на этапе интерпретации и в качестве входных аргументов получив имя декорируемой функции (в даннам случае указатель на функцию) манипулирует аргументами и результатами декорируемой функции
Переменная в программе это объект с именем в оперативной памяти компьютера.
Оперативная память компьютера это множество пронумерованных ячеек в которых хранится информация в виде 0 и 1.
Объект в оперативной памяти это участок памяти который интерпретируется процессором в зависимости от типа объекта.
Самый простой тип объекта это бит, бит может принимать только два значений 0(ноль) или 1(единица).
Бит может использоваться в качестве индикаторов состояния включено или выключено.
Если объединить несколько бит то можно интерпретировать состояние бит в виде числа, так например в с помощью 8 бит можно преставить последовательность и 256 цифр (2 значения в 8 степени).
С объединением нескольких значений одного или нескольких типов объектов создаёт составной тип данных, самый простой составной тип данных это байт.
С помощью 1 байта из 8 бит можно представить последовательность из цифр 256 либо последовательность из 256 символов алфавита и технических символов.
Последовательность символов составляет ещё один тип данных — строка символов. Строки могут объединяться в массивы строк.
С помощью последовательности бит можно сформировать активный код выполняеымй процессором, это называет код функции. Код функций это последовательность бит, который выполняется процессором в командном режиме, что приводит к изменению состояния процессора, а следовательно и к изменению состояния объектов в оперативной памяти.
Возможно создание комбинированных типов данных, включающих в себя цифры, строки, массивы цифр и строк, исполняемый код функций. Этот объект уже является на объектом с собственной функциональностью. В высокоуровневых языка программирования создают объекты классов, которые помимо данных имеют исполняемый код изменяющий состояние объекта.
Для обращения к объекту необходимо знать адерес объекта в оперативной компьютера, так как работать с адресам для человека не удобно, существую таблицы имён созданых человеком и связанных с именами адресов объектов в оперативной памяти. При этом адрес объекта в оперативной памяти может постоянно меняться но связь с именем всегда позволяет обратиться к объекту.
Так как объекты бывают составными, то существуют иерархически составные названия объектов по аналогии с реальным миром, например: дом, улица, квартира, стул номер 3, ножка стула номер 2, длинна ножки стула.
Таким образом:
Переменная это объект созданный в оперативной памяти компьютера, имещий имя которое используется для обращения к объекту, а так же информацию о типе объекта, а от типа объекта зависит интерпретация процессором оперативной памяти занимаемой объектом.
Типы объектов крайне важны в программировании. Так например, можно изготовить из пластилина молоток на 100% похожий на настоящий молоток, только забивать гвозди им не получится.
Первое с чем сталкивает человек изучающий программирование это концепция переменных(Что такое переменные). Это сложная тема абстрактных понятий очень часто является барьером в изучении компьютерных наук. У новичков сразу возникают вопросы что такое перменные, какие типы переменных существуют, как правильно и не правильно использовать переменные, почему всё устроенно именно так. Если не разобраться с этим понятием то невозможно продолжить изучение программирования, а если углубиться в изучение концепции переменнных, то очень многие абстракции в программировании станут понятны сами по себе, по крайней мере интуиция будет подсказывать почему всё устроено именно так, а не иначе.
Понимание концепции переменных является ключевым пунктом к изучению программирования. Компьютерные переменные это абстракция которую придумали инженеры и окружили её аурой загадачного жаргона. Но если немного отвлечься от программирования и поглядеть на нашу жизнь со стороны то можно увидеть следующее: когда человек рождается то его память пуста, он не знает ни одного слова и даже не знает своего имени. Но постепенно общаясь с родителями, с другими людьми он учится связывать объекты реального мира со звуками которые произносят люди. Так он узнат что женщина которая его кормит и ухаживает за ним это мама, что когда мама произносит некоторое имя, например Вова, то это значит, что она обращается к нему, он начинает различать интонации в произношении различных слов учится работать с контекстом. Этот процесс можно назвать загрузкой сознания человека. В сознании ребёнка образы различных объектов начинают связываются с их названиями. Когда он впервые слышит слово "ты" то для него это слово ничего не означает, но в результате многократного повторения слова и связанного действия в памяти ребёнка закрепляется связь слова "ты" с набором объектов и ощущений. Все прекрасно помнят те моменты когда ребёнок начинает спрашивать у родителей "что это такое?" показывая на различные предметы и родители, отвечая на это вопросы, загружают в сознание ребёнка названия объектов и тем самым формируют его сознание и направление развития. Получается, что в этой ситуции ребёнок выступает в роли своеобразного компьтера, а родители в роли программистов которые составляют программу с помощью которой этот компьютер будет развиваться и взаимодействовать с другими живыми системами. А в компьютерном понимании это выглядит следующим образом: существует реальность в которой существуют различные объекты и явления с которыми необходимо воздействовать и название этих объектов и явлений необходимы для того чтобы к ним можно было обращаться с помощью речевых команд. Так например простой вопрос "где бобо" подразумевает, что ребёнок должен понять что от него хотят чтобы он указал на то место которое у него болит, то-есть происходит обращение к двум алгоритмам:
определение места где болит
указать на него каким либо способом
Если родители уже успели заложить в него связи между этими словами и алгоритмами то ребёнок сумеет выполнить команду. Этот пример наглядно показывает, что названия крайне необходимы для выполнения любых разумных и осмысленных действий. Если эти связи не установлены и для ребёнка слова родителей ничего не значат, то не возможно и управлять действиями ребёнка. Тут я ввожу понятие: алгоритм это набор действий приводящий к определённом результату, очевидно что алгоритмы так же должны иметь название и по сути так же являются объектами к которым можно обратиться через название.
Получается, что процесс воспитания ребёнка является программированием сознания ребёнка с помощью выстраивания связей между объектами и явлениями реального мира с названиями объектов и явлений в нашей речи, на нашем языке. В компьютерной среде происходит тоже самое программист работает в среде оперативной памяти компьютера, а в этой памяти располагаться объекты которые создаёт сам программист или которые там были созданы другими программистами. Например, программист создаёт внутри оперативной памяти список учеников и даёт ему некоторое название (можно сказать даёт ему кличку) которое существует в контексте этой программы и для того чтобы повторно обратиться к этому списку программа использует название которое программист присвоил ему при создании. По сути, вся работа программиста заключается в том, что он создаёт объекты в оперативной памяти компьютера, даёт названия объектам и создаёт алгоритмы взаимодействия объектов с помощью названий. От того насколько эффективно он создаёт эти объект и насколько то что он создаёт понятно другим людям зависит профессионализм программиста. Для сравнения можно взять Льва Толстого и его роман "Война и Мир". В этой книге, писатель с помощью правильных связей с названий и удачных конструкций определяющих конекст создал программу которая вызывает практически во всех людях одинаковые эмоции -- именно по этому этот писатель считается Великим, такое могут далеко не все, чаще всего люди собственной речью не могут выразить то что хотят сказать и тем более у них не получается вызывать те же образы что и самого автора.
Я использовал аналогию с обучением ребёнка для того, чтобы стало очевидно то, что для нашей разумной деятельность крайне важно то что мы знаем названия того с чем мы взаимодействуем, без этого невозможно выполнение разумной и упорядоченной деятельности и точно так же в программирование без знания названий сущностей внутри оперативной памяти невозможно делать что либо разумное. Таким образом получается, что на самом деле самос компьютерные "переменные" это всего лишь объекты и названия объектов внутри оперативной памяти. Но очевидно что использование слова "переменные" более удобно и наглядно чем выражение "объекты и названия объектов" для тех понимает о чём речь.
Поняв вышеизложенное становится очевидно, что когда программист говорит, что он создал переменную то он всего лишь создал объект в оперативной памяти компьютера и дал название этому объекту, когда программист говорит что он обратился к переменной то это означает что он заставил программу обратиться к объекту по его имени. В этом смысле программисты подобны творцам, которые создают объекты, дают им названия, учат эти объекты взаимодействовать друг с другом, создают целые миры из объектов подчняющихся действию алгоримов взаимодействия и даже общению с объектами из миров созданных другими творцами. Так что на самом деле программирование это очень простое и увлекательное творчество, посоревноваться с которым может лишь творчество которое вызывает у людей сильные эмоции, это я сейчас про кино, музыку и всякую прочую живопись, только в программировании гораздо меньше возможностей для субъективизма и больше логики.
... статья в разработке, продолжение следует
Так же с помощью этого примера можно понять что названия не являются постоянными(константыми), например, объект реального мира "яблоко" может иметь название "яблоко" или "apple" так как это зависит от того в какой семье воспитывается ребёнок, это явление назвается контекст, то-есть одно и тоже явление может называться по разному в зависимости от контекста.
И так в нашем собственном сознании присутвтвуют переменные. Среди них можно выделить переменные долгосрочные и короткосрочные. Например, слово мама почти у каждого человека связыватся с одним единственным человеком на свете, то-есть в сознании этого человека есть конкретная связь между этим названием и конкретным объектом реального мира. Связь с составными названиеми "моя машина", "твоя машина" и реальным объектом реального мира показывает, что переменные бывают не постоянными, а с деньгами понимание недолговременности существования переменных становится ещё очевиднее.