Перейти к содержимому

Сериалайзер что это

  • автор:

Как использовать сериализаторы в веб-платформе Django Python

Сериализация — это процесс преобразования данных в формат, который может быть сохранен или передан, с последующим его восстановлением. Он постоянно используется при разработке приложений или хранении данных в базах данных, в памяти или преобразовании их в файлы.

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

class Order: def __init__(self, product, customer, price, date): self.product = product self.customer = customer self.price = price self.date = date 

Теперь представьте, что вы хотите сохранить и получить данные о заказе из базы данных «ключ-значение». К счастью, его интерфейс принимает и возвращает словари, поэтому вам нужно преобразовать свой объект в словарь:

def serialize_order(order): return

И если вы хотите получить некоторые данные из этой базы данных, вы можете получить данные dict и превратить их в свой объект Order:

def deserialize_order(order_data): return Order( product=order_data['product'], customer=order_data['customer'], price=order_data['price'], date=order_data['date'], ) 

Это довольно просто сделать с простыми данными, но когда вам нужно иметь дело со сложными объектами, состоящими из сложных атрибутов, этот подход плохо масштабируется. Вам также необходимо выполнить проверку различных типов полей, а это придется делать вручную.

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

Django поставляется с модулем сериализации, который позволяет «переводить» модели в другие форматы:

from django.core import serializers serializers.serialize('json', Order.objects.all()) 

Он охватывает наиболее часто используемые случаи для веб-приложений, таких как JSON, YAML и XML. Но вы также можете использовать сторонние сериализаторы или создать свои собственные. Вам просто нужно зарегистрировать его в своем файле settings.py:

settings.py

SERIALIZATION_MODULES =

Чтобы создать свой собственный MyFormatSerializer , вам необходимо реализовать метод .serialize() и принять набор запросов и дополнительные параметры в качестве параметров:

class MyFormatSerializer: def serialize(self, queryset, **options): # serious serialization happening 

Теперь вы можете сериализовать свой запрос в новый формат:

from django.core import serializers serializers.serialize('my_format', Order.objects.all()) 

Вы можете использовать параметры options, чтобы определить поведение сериализатора. Например, если вы хотите определить, что вы собираетесь работать с вложенной сериализацией при работе с ForeignKeys , или вы просто хотите, чтобы эти данные возвращали свои первичные ключи, вы можете передать параметр flat=True в качестве опции и обработать его в своем методе:

class MyFormatSerializer: def serializer(self, queryset, **options): if options.get('flat', False): # don't recursively serialize relationships # recursively serialize relationships 

Один из способов использования сериализации Django — это команды управления loaddata и dumpdata .

Сериализаторы DRF

В сообществе Django фреймворк Django REST (DRF) предлагает самые известные сериализаторы. Хотя вы можете использовать сериализаторы Django для создания JSON, на который вы будете отвечать в своем API, один из фреймворка REST имеет приятные функции, которые помогут вам справиться и легко проверить сложные данные.

В примере заказа вы можете создать сериализатор следующим образом:

from restframework import serializers class OrderSerializer(serializers.Serializer): product = serializers.CharField(max_length=255) customer = serializers.CharField(max_lenght=255) price = serializers.DecimalField(max_digits=5, decimal_places=2) date = serializers.DateField() 

И легко сериализуйте его данные:

order = Order('pen', 'renato', 10.50, date.today()) serializer = OrderSerializer(order) serializer.data #

Чтобы иметь возможность возвращать экземпляр из данных, вам необходимо реализовать два метода — create и update:

from rest_framework import serializers class OrderSerializer(serializers.Serializer): product = serializers.CharField(max_length=255) customer = serializers.CharField(max_length=255) price = serializers.DecimalField(max_digits=5, decimal_places=2) date = serializers.DateField() def create(self, validated_data): # perform order creation return order def update(self, instance, validated_data): # perform instance update return instance

После этого вы можете создавать или обновлять экземпляры, вызывая is_valid() для проверки данных и save() для создания или обновления экземпляра:

serializer = OrderSerializer(**data) ## to validate data, mandatory before calling save serializer.is_valid() serializer.save() 

Сериализаторы моделей

При сериализации данных вам часто нужно делать это из базы данных, следовательно, из ваших моделей. ModelSerializer, как и ModelForm, предоставляет API для создания сериализаторов из ваших моделей. Предположим, у вас есть модель заказа:

from django.db import models class Order(models.Model): product = models.CharField(max_length=255) customer = models.CharField(max_length=255) price = models.DecimalField(max_digits=5, decimal_places=2) date = models.DateField() 

Вы можете создать для него сериализатор следующим образом:

from rest_framework import serializers class OrderSerializer(serializers.ModelSerializer): class Meta: model = Order fields = '__all__' 

Django автоматически включает в себя все модели поля в сериализатор и создает методы create и update .

Использование сериализаторов в представлениях на основе классов (CBV)

Как и формы с CBV от Django, сериализаторы хорошо интегрируются с DRF. Вы можете установить атрибут serializer_class так, чтобы сериализатор был доступен для представления:

from rest_framework import generics class OrderListCreateAPIView(generics.ListCreateAPIView): queryset = Order.objects.all() serializer_class = OrderSerializer 

Вы также можете определить метод get_serializer_class() :

from rest_framework import generics class OrderListCreateAPIView(generics.ListCreateAPIView): queryset = Order.objects.all() def get_serializer_class(self): if is_free_order(): return FreeOrderSerializer return OrderSerializer 

В CBV есть и другие методы, которые взаимодействуют с сериализаторами. Например, get_serializer() возвращает уже созданный сериализатор, а get_serializer_context() возвращает аргументы, которые вы передадите сериализатору при создании его экземпляра.

Создание вложенных сериализаторов

Мы собираемся создать сериализатор для модели Course. Отредактируйте файл api/serializers.py и добавьте в него следующий код:

from ..models import Course class CourseSerializer(serializers.ModelSerializer): class Meta: model = Course fields = ('id', 'subject', 'title', 'slug', 'overview', 'created', 'owner', 'modules') 

Давайте рассмотрим, как сериализуется объект Course. Откройте терминал, запустите python manage.py shell и выполните следующий код:

from rest_framework.renderers import JSONRenderer from courses.models import Course from courses.api.serializers import CourseSerializer course = Course.objects.latest('id') serializer = CourseSerializer(course) JSONRenderer().render(serializer.data) 

Вы получите объект JSON с полями, которые мы включили в CourseSerializer. Можно увидеть, что связанные объекты диспетчера modules сериализуются как список первичных ключей, например:

"modules": [17, 18, 19, 20, 21, 22] 

Мы хотим включить дополнительные сведения о каждом модуле, поэтому необходимо сериализовать объекты модуля и вложить их. Отредактируйте api/serializers.py так, чтобы выглядеть следующим образом:

from rest_framework import serializers from ..models import Course, Module class ModuleSerializer(serializers.ModelSerializer): class Meta: model = Module fields = ('order', 'title', 'description') class CourseSerializer(serializers.ModelSerializer): modules = ModuleSerializer(many=True, read_only=True) class Meta: model = Course fields = ('id', 'subject', 'title', 'slug', 'overview', 'created', 'owner', 'modules') 

Мы определили ModuleSerializer, чтобы обеспечить сериализацию для модели Module. Затем мы добавляем атрибут modules в класс CourseSerializer, чтобы вложить сериализатор ModuleSerializer. Мы установили many=True, чтобы указать, что мы сериализуемые несколько объектов. Параметр read_only указывает, что это поле предназначено только для чтения и не должно включаться во входные данные для создания или обновления объектов.

Откройте терминал и снова создайте экземпляр CourseSerializer. Он отрисует атрибут data сериализатора с JSONRenderer. На этот раз перечисленные модули сериализуются со вложенным сериализатором ModuleSerializer:

"modules": [ < "order": 0, "title": "Django overview", "description": "A brief overview about the Web Framework." >, < "order": 1, "title": "Installing Django", "description": "How to install Django." >, . ] 

Сериализация простыми словами

Много раз встречал эту «сериализацию» на разных ресурсах, часто связано с JSON. Объясните, пожалуйста, простыми словами, что такое сериализация, где и зачем ее применяют ?

Отслеживать
задан 23 дек 2015 в 22:54
researcher researcher
2,465 1 1 золотой знак 17 17 серебряных знаков 34 34 бронзовых знака

5 ответов 5

Сортировка: Сброс на вариант по умолчанию

Сериализация — это преобразование объекта или дерева объектов в какой-либо формат с тем, чтобы потом эти объекты можно было восстановить из этого формата. Используется, например, для сохранения состояния программы (то есть, некоторых её объектов) между запусками. Или для передачи данных между различными экземплярами программы (или различными программами), например, по сети.

Главная идея состоит в том, что сериализованный формат — набор байт или строка, которую можно легко сохранить на диск или передать другому процессу или, например, по сети, в отличие от самого объекта. А значит, задача сохранения объекта/группы объектов при этом сводится к простой задаче сохранения набора байт или строки.

JSON — один из популярных форматов для сериализации, он текстовый, легковесный и легко читается человеком.

Пример: если у вас есть класс

class Test < int length; String name; public Test(int length, String name) < this.length = length; this.name = name; >> 

Объект этого класса в сериализованной форме может иметь вид

Саму сериализацию (и десериализацию) можно производить вручную, или пользоваться соответствующими библиотеками/фреймворками.

Существуют и бинарные форматы сериализации.

Отслеживать
ответ дан 23 дек 2015 в 22:59
206k 28 28 золотых знаков 291 291 серебряный знак 526 526 бронзовых знаков

Если я не ошибаюсь, сериализация это представление сущности в строке. Сохранение это отдельный процесс.

23 дек 2015 в 23:05
@Steve: Не обязательно, вполне возможна и бинарная сериализация.
23 дек 2015 в 23:07
Куда сохраняется сущность при сериализации?
23 дек 2015 в 23:38
@Steve: Куда угодно. В файл, в память, в поток, .
23 дек 2015 в 23:41
Сериализация — преобразование, но не сохранение.
23 дек 2015 в 23:51

Сериализация в общем виде — это процесс сохранения объекта в виде последовательности байт, чтобы в будущем по этой последовательности можно было бы восстановить исходный объект. В частном случае — это может быть сохранение в текстовую строку, хотя это далеко не обязательно.

Если объект сериализуется в текстовую строку, то тут могут быть разные форматы, включая XML и упомянутый в вопросе JSON. Последний для этой цели наиболее популярен в последнее время, т.к. JSON — это и так представление объекта в том формате, как объект создается в JavaScript. Собственно, JSON и переводится как JavaScript Object Notation.

Сериализацию можно использовать для хранения объектов на диске, для передачи его по сети или для передачи объекта другому процессу.

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

Отслеживать
ответ дан 24 дек 2015 в 7:03
1,991 12 12 серебряных знаков 23 23 бронзовых знака
спасибо за пояснение!
24 дек 2015 в 22:05

Если на кошках Вы пишете эмулятор кота — тамагочи. У вас есть объект класс cat

class Cat < private int age; private int weight; // other cat logic >

Вы хотите, чтобы при следующем запуске приложения этот же кот продолжал жить своей жизнью, а не пересоздался заново. Для этого вы реализуете сериализацию\десериализацию кота — то есть созранение\загрузку. Как — так как вам удобно. Можно сделать его Serializable и хранить в бинарном виде, можно сохранять в текстовый файл как JSON (JavaScript Object Notation), можно в базе хранить. Главное — вы сохраняете каким-то образом его состояние (в данном случае — поля), и потом, когда вам это будет нужно, их загружаете. Так же сериализованного кота можно будет например передать по сети на сервер. То есть вы передаете его состояние, а сервер у себя создаст новый объект класса Cat и установит у него это состояние (age и weight).

Отслеживать
ответ дан 24 дек 2015 в 14:57
Ruslan Neruka Ruslan Neruka
226 1 1 серебряный знак 4 4 бронзовых знака
спасибо за простой пример!
24 дек 2015 в 22:06

Сериализация используется для хранения сущностей в виде строки. Например можно взять объект, сериализовать и записать в базу данных.

Json — формат представления данных. Он может представлять объекты или массивы. Эти сущности также можно сериализовать. Если вывести сериализованый объект в Json, то можно получить строку сериализованного объекта.

Отслеживать
ответ дан 23 дек 2015 в 23:00
339 1 1 серебряный знак 13 13 бронзовых знаков

Сериализация — это преобразование данных. Если из более «понятного» в менее — это сериализация. А наоборот — десериализация.

Десериализовывать данные нужно, например, чтобы придать им человеко-читаемы вид, или такой формат, который можно и\или удобно использовать в работе приложение.

Сериализовать данные нужно, например, для хранения или пересылки. Потому что — есть определённый формат хранения\пересылки данных и данные нужно в него преобразовать.

Очень часто речь идёт о преобразовании в JSON — для отправки на сервер — это сериализация. И обратно — для использования в приложении — это десериализация.

Например — в интерфейсе приложения данные должны быть представлены в виде таблицы — и их нужно правильно достать из JSON и напихать в ячейки. Потом эти данные изменит пользователь и их нужно будет преобразовать обратно в дерево JSON, чтобы отправить на сервер.

Вот и всё. А вот это вот «процесс перевода структуры данных в последовательность байтов» — видимо сухая формулировка из старого учебника, которая мало способствует пониманию.

Python-солянка

За последнее время у меня накопилось некоторое количество интересных решений, которые удалось в применить в проектах на Питоне, в том числе использующих Django. Сами по себе они между собой никак не связаны, но вместе образуют достаточно насыщенное фактами повествование, и, я надеюсь, каждый сможет подчерпнуть из изложенного что-то новое.

Если читатель не использует и не интересуется Django, ему все еще может быть интересно начало статьи, где речь пойдет про чистый Python.

Временные зоны

и Про работу с временем и датами в программировании сказано очень и очень много, в том числе применительно к Питону. Однако, несмотря на наличие разнообразных средств, которые призваны облегчить работу с временем, трудности все еще подстерегают нас на каждом шагу.

Основной модуль, которым пользуются в Питоне для работы с датами и временем — это datetime . Он поддерживает два типа объектов: aware и naive (с временной зоной и без нее соответственно). Для многих приложений naive время может быть вполне приемлемым, но если это не ваш случай, то вас подстерегает масса неприятностей.

pytz

Как вам возможно известно, сам модуль datetime предоставляет интерфейс для присваивания своим объектам информации о временных зонах, но не предоставляет конкретной реализации. В качестве конкретной реализации в проектах на Питоне чаще всего используют модуль pytz .

Что вам возможно неизвестно, так это то, что нельзя передавать объекты, предоставляемые pytz везде, где datetime ожидает tzinfo . В документации прямо сказано, что для создания aware объектов существует ровно два способа: localize и astimezone . localize просто приписывает к существующему naive объекту временную зону, а astimezone меняет зону уже существующего aware объекта на новую, не меняя его абсолютного значения.

Пример из документации:

>>> loc_dt = eastern.localize(datetime(2002, 10, 27, 6, 0, 0)) >>> print(loc_dt.strftime(fmt)) 2002-10-27 06:00:00 EST-0500 >>> ams_dt = loc_dt.astimezone(amsterdam) >>> ams_dt.strftime(fmt) '2002-10-27 12:00:00 CET+0100' 

Не было бы смысла об этом рассказывать, если бы очевидный вариант datetime(2002, 10, 27, 12, 0, 0, tzinfo=amsterdam) тоже работал бы. Однако его работоспособность гарантируется только для зон без перехода на летнее время, а значит использовать эту запись вообще бессмысленно (она сломается при первой же правке на зону с летним временем).

Также существует проблема с применением localize к naive времени, которое случалось в истории больше одного раза (такое происходит, когда стрелки часов переводят назад).

Также неожиданностью может быть арифметика дат, которую предоставляет datetime . Возьмем какое-то московское время:

>>> d = pytz.timezone('Europe/Moscow').localize(datetime(2011, 3, 27, 6, 12, 23)) >>> d datetime.datetime(2011, 3, 27, 6, 12, 23, tzinfo=) 

Теперь посмотрим, что было шесть часов назад:

>>> d - timedelta(hours=6) datetime.datetime(2011, 3, 27, 0, 12, 23, tzinfo=) 

Да уж, не поспоришь, в зоне +4 — 6–6=0 . Проблема в том, что люди никогда не жили по этому времени, т. к. за шесть часовв Москве была зона +3 . Мало того, что datetime возвращает нам время, которое хоть и формально верно, но не использовалось человечеством, в tzinfo еще издевательски присутсвует запись Europe/Moscow , хотя это уже и не он. Чтобы гарантировано получить нормальное время, нужно после арифметических операций снова призывать pytz :

>>> (d - timedelta(hours=6)).astimezone(pytz.timezone('Europe/Moscow')) datetime.datetime(2011, 3, 26, 23, 12, 23, tzinfo=) 

Однако по этому поводу уже сломано немало копий, стоит перейти к еще более неожиданным вещам.

strftime

Объекты datetime поддерживают метод strftime — метод, форматирующий дату в соответсвии с шаблоном. Наиболее популярным ее использованием, возможно, является получение количества секунд с начала эпохи. Не исключено, что и у вас в проекте есть что-то подобное:

def datetime_to_epoch(datetime_object): return int(datetime_object.strftime("%s"))

(Если вы пользуетесь третьей версией Питона, вам повезло, вам доступен банальный datetime_object.timestamp() .)

Однако у strftime есть для вас сюрприз:

>>> naive = datetime(2016, 1, 1, 12, 12, 12) >>> moscow = pytz.timezone('Europe/Moscow').localize(naive) >>> london = pytz.timezone('Europe/London').localize(naive) 

Мы получили два объекта. Одни указывает на время, когда в Москве было «2016-01-01 12:12:12», другой – когда то же время было в Лондоне. Ясно, что в Лондоне это время наступило позже (если быть точным, на три часа):

>>> london > moscow True >>> london - moscow datetime.timedelta(0, 10800) 

Пока все в порядке. А теперь посмотрим разницу между их количеством секунд с начала эпохи:

>>> int(london.strftime('%s')) - int(moscow.strftime('%s')) 0 

Ой. Не может быть, попробуем еще раз:

>>> int(london.strftime('%s')) - int(moscow.strftime('%s')) 0 

Хмм. Может, strftime не получает данные о зонах? Проверим:

>>> london.strftime('%z') '+0000' >>> moscow.strftime('%z') '+0300' 

Нет, получает. В чем же может быть дело? Не буду вас утомлять серией догадок, которые пришлось отбросить, правда такова: strftime для получения количества секунд с начала эпохи использует текущую локальную временную зону Питона, а не ту, что содержится в объекте. Какой в этом смысл? Никакого, это, очевидно, баг.

Как решать это проблему? Наименее уродливое решение, к которому мы пришли – приводить объект к локальной зоне перед тем, как вызывать strftime :

def datetime_to_epoch(datetime_object): return int(datetime_object.astimezone(default_timezone).strftime('%s'))

Выяснить default_timezone тоже может быть непросто, то в Django это просто django.utils.timezone.get_default_timezone() .

Полночь

Если вы много работали с временем и датами, вы могли неоднократно встречаться с советом приводить все даты к UTC, работать в нем, а зону присваивать непосредственно перед показом данных пользователю. Даже в документации к pytz сказано:

The preferred way of dealing with times is to always work in UTC, converting to localtime only when generating output to be read by humans.

Это неплохой совет, аналогичным образом часто советуют поступать с кодировками (использовать внутри приложения только utf-8, например), но в действительности не все действия над временем возможны в отрыве от реальной пользовательской зоны.

Рассмотри простую задачу: по заданному времени вернуть ближайшую предшествующую полночь. Само понятия полночи в отрыве от зоны никакого смысла не имеет, поэтому фокус с приведением всех дат в UTC здесь не работает. Простым решением, кажется, могла бы быть просая замена времени на 00:00:00 , но это не сработает, если этой ночью переводили часы. Мое решение, учитывающее эту проблему:

def midnight(cls, datetime_object, tz): date_in_given_timezone = datetime_object.astimezone(tz).date() # Берем дату с учетом зоны midnight = datetime.datetime.combine(date_in_given_timezone, datetime.time()) # Приписываем нули return tz.localize(midnight) # Возвращаем зону

Всегда будьте начеку, когда работаете с временем, даже адекватные современные средства могут вас подвести (например, datetime не поддерживает високосную секунду).

Visitor

Перейдем к совершенно другой истории. В веб-приложении перед нами стояла совершенно стандартная проблема: вьюшки должны уметь отдавать данные по моделям пользователю. Т. е. необходимо софрмировать некоторое «представление» по модели. Где именно разместить логику формирования этого представления?

Так себе решение №1

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

Так себе решение №2

Заведем в каждой модели метод dump : пусть модель сама знает, как ей отдаваться пользователю (из всех связанных моделей при этом можно снова звать dump ). Это, пожалуй, еще хуже: модели вообще не стоит знать о том, что существует ее представление для какого-то «пользователя». Плюс, неясно, что надо делать, если представлений несколько, заводить во всех моделях метод dump2 ? Можно смело отбросить эту идею.

Приличное решение

Давайте у нас будет еще один слой абстракции: представления. Они знают, как именно забирать данные из модели, а вьюшки знают, какое представление позвать. В случае разнообразных представлений, их легко наследовать друг от друга или включать одно в другое. Надо сказать, это вовсе не изобретение, идея давно известна, подобные классы предлагаются модулем Django REST framework под именем сериалайзер (ими мы и воспользовались).

Вытекающая проблема

Перейдем сходу к примеру.

Есть сериалайзер, который сериализует пользователей. Но понятие пользователя — абстрактное, у него есть несколько типов пользователей — клиенты и персонал. Вьюшка понятия не имеет, с каким типом пользователя имеет дело (разумеется), поэтому передает его в общий для всех пользователь сериалайзер. Сериалайзер же должен как-то определить способ сериализации на основе переданных данных.

Так себе решение №1

Сериалайзер просто посмотрит на класс переданного ей объекта и на его основе выберет тип сериализации. Я сказал «так себе»? Было бы верней назвать это решение «чудовищным». Вся архитектура с базовым и дочерними классами была призвана избавить нас от проверки типа раз и навсегда, и решать все проблемы переопределением методов.

Так себе решение №2

Пусть классы конкретных пользователей просто переопределят метод «сериализуйся», а сериализатор просто вызывает этот метод. Решение, очевидно, не работает, т. к. мы вернулись к изначально проблеме с методом dump : в данной схеме модель снова знает слишком много, а сериалайзер не делает вообще ничего.

Паттерн Visitor спешит на помощь

Решение, которое нас удовлетворило, таково: пусть каждый сериалайзер умеет сериализовать пользователей с типа staff и client, но решение о том, как именно надо сериализовать текущего пользователя данным сериалайзером, принимает сам объект пользователя (но без лишних деталей). Взглянем на пример:

class UserSerializer(serializers.Serializer): def to_representation(self, user): """ Этот метод изначально вызывает из сериалайзера """ return user.serialize(self) def client_representation(self, measure): return < # способ сериализации клиентов > def staff_representation(self, user): return < # способ сериализации персонала > class User(object): pass class ClientUser(User): def serialize(self, serializer): return serializer.client_representation(self) class StaffMeasure(Measure): def serialize(self, serializer): return serializer.staff_representation(self)

И если вдруг надо больше одного сериалайзера, то прекрасно срабатывает классическая двойная диспетчеризация:

class UserMinimalisticSerializer(serializers.Serializer): def to_representation(self, user): return user.serialize(self) def client_representation(self, measure): return < # минималистичный способ сериализации клиентов > def staff_representation(self, user): return < # минималистичный способ сериализации персонала > class UserFullSerializer(serializers.Serializer): def to_representation(self, user): return user.serialize(self) def client_representation(self, measure): return < # полный способ сериализации клиентов > def staff_representation(self, user): return < # полный способ сериализации персонала > Стоит заметить, что подобное решение, вообще говоря, не несете в себе ничего нового, но является неожиданным (по крайней мере для нас) приложением всем известного метода ко всем известной задаче, при том, что этот метод для этой задачи обычно не применяется. --- * Manager/QS * urls.py * Expression * Таймзоны в конфиге джанги * visitor * Наследование моделей, cast_to_subclass, register_subclass * Тест на сигнал * Factory boy * nginx: runserver VS uswgi (В. Рудных

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *