#37. DRF. RESTful, Serializers

API. REST і RESTful. Django REST Framework. Serializers

Що ж таке API?

API (application programming interface, прикладни́й програ́мний інтерфе́йс, інтерфейс програмування застосунків, інтерфейс прикладного програмування,) — набір визначень підпрограм, протоколів взаємодії та засобів для створення програмного забезпечення.
Інтерфейс — це набір чітко визначених методів для взаємодії різних компонентів.

У нашому конкретному випадку під API практично завжди буде матися на увазі REST API, про який ми поговоримо далі.
Зараз для нас - це endpoint (url, на який можна надіслати запит), який виконує будь-які дії або повертає нам інформацію.

Що таке REST?

REST (Representational State Transfer, “передача репрезентативного стану”) - підхід до архітектури мережевих протоколів, які надають доступ до інформаційних ресурсів. Був описаний і популяризований 2000 року Роєм Філдінгом, одним із творців протоколу HTTP. В основі REST закладено принципи функціонування Всесвітньої павутини і, зокрема, можливості HTTP. Філдінг розробив REST паралельно з HTTP 1.1 базуючись на попередньому протоколі HTTP 1.0.

Дані повинні передаватися у вигляді невеликої кількості стандартних форматів (наприклад, HTML, XML, JSON). Будь-який REST протокол (HTTP в тому числі) повинен підтримувати кешування, не повинен залежати від мережевого прошарку, не повинен зберігати інформації про стан між парами «запит-відповідь». Стверджується, що такий підхід забезпечує масштабовність системи і дозволяє їй еволюціонувати з новими вимогами.

Властивості REST архітектури.

Властивості архітектури, які залежать від обмежень, накладених на REST-системи:

  1. Client-Server (Клієнт-сервер). Система має бути розділена на клієнтів і на сервер(и). Поділ інтерфейсів означає, що клієнти не пов’язані зі зберіганням даних, яке залишається всередині кожного сервера, так що мобільність коду клієнта покращується. Сервери не пов’язані з інтерфейсом користувача або станом, тож сервери можуть бути простішими і масштабовані. Сервери і клієнти можуть бути замінні і розроблятися незалежно, поки інтерфейс не змінюється.

  2. Stateless (Відсутність стану). Сервер не повинен зберігати будь-якої інформації про клієнтів. У запиті має зберігатися вся необхідна інформація для обробки запиту і, якщо необхідно, ідентифікації клієнта.

  3. Cacheable (Кешування)․ Кожну відповідь слід позначити, чи є вона кешованою, чи ні, для запобігання повторному
    використання клієнтами застарілих або некоректних даних у відповідь на подальші запити.

  4. Uniform Interface (Однорідний інтерфейс). Єдиний інтерфейс визначає інтерфейс між клієнтами та серверами. Це спрощує та відокремлює
    архітектуру, яка дає змогу кожній частині розвиватися самостійно.

    Чотири принципи однорідного інтерфейсу:

    4.1) Identification of resources (ідентифікація на ресурсах). У REST ресурсом є все те, чому можна дати ім’я.
    Наприклад, користувач, зображення, предмет (дерево, поточна погода) тощо. Кожен ресурс у REST має бути ідентифікований за допомогою стабільного ідентифікатора, який не змінюється при зміні стану ресурсу. Ідентифікатором у REST є URI.

    4.2) Manipulation of resources through representations. (Маніпуляції над ресурсами через представлення).
    Подання в REST використовується для виконання дій над ресурсами. Подання ресурсу являє собою поточний або бажаний стан ресурсу. Наприклад, якщо ресурсом є користувач, то поданням може бути бути XML або HTML опис цього користувача.

    4.3) Self-descriptive messages (самоописові повідомлення). Під само-описністю мається на увазі, що запит і відповідь повинні зберігати в собі всю необхідну інформацію для їх обробки. Не повинні бути додаткові повідомлення або кеші для обробки одного запиту. Іншими словами, відсутність стану, що зберігається між запитами до ресурсів. Це дуже важливо для масштабування системи.

    4.4) HATEOAS (hypermedia as the engine of application state)(гіпермедіа як рушій стану застосунку). Статус ресурсу передається через вміст body,
    параметри рядка запиту, заголовки запитів і запитуваний URI (ім’я ресурсу). Це називається гіпермедіа (або гіперпосилання з гіпертекстом). HATEOAS також означає, що в разі потреби посилання можуть міститися в тілі відповіді (або заголовках) для підтримки URI, вилучення самого об’єкта або запитаних об’єктів.

  5. Layered System (Шари абстракції). У REST допускається розділити систему на ієрархію шарів, але з умовою, що кожен компонент може
    бачити компоненти тільки безпосередньо наступного шару. Наприклад, якщо ви викликаєте службу PayPal, а вона у свою чергу
    черга викликає службу Visa, ви про виклик служби Visa нічого не повинні знати.

  6. Code on Demand (Запитування коду) (опціонально). У REST дозволяється завантаження і виконання коду або програми на стороні клієнта.

Якщо виконані перші 4 пункти і не порушені 5 і 6, такий додаток називається RESTful

Важливо! Сама архітектура REST не прив’язана до конкретних технологій і протоколів, але в реаліях сучасного WEB, побудова RESTful API майже завжди передбачає використання HTTP і будь-яких поширених форматів представлення ресурсів, наприклад, JSON, або менш популярного сьогодні XML.

Ідемпотентність

Ідемпотентність (лат. idem — такий самий, лат. potens — сильний) — властивість унарних та бінарних операцій в алгебрі та логіці. Термін «ідемпотентність» означає властивість, яка проявляється в тому, що повторна її дія над будь-яким об’єктом уже не змінює результату. Тобто повторне виконання операцій з об’єктом не змінює результату, досягнутого при першому виконанні. Термін запропонував американський математик Бенджамін Пірс в статтях 1870-х років.

Якщо ми дотримуємося принципів REST при розробці наших API, ми матимемо автоматично ідемпотентні API REST для методів GET, PUT, DELETE, HEAD, OPTIONS і TRACE. Тільки POST API не будуть ідемпотентними .

  1. POST – Не є ідемпотентним.
  2. GET, PUT, DELETE, HEAD, OPTIONS і TRACE – ідемпотентні.

З погляду RESTful-сервісу операція (або виклик сервісу) ідемпотентна тоді, коли клієнти можуть робити один і той самий той самий виклик неодноразово за одного й того самого результату на сервері. Іншими словами, створення великої кількості ідентичних запитів має такий самий ефект, як і один запит. Зауважте, що в той час, як ідемпотентні операції виробляють один і той самий результат на сервері, відповідь сама по собі може не бути тією самою (наприклад, стан ресурсу може змінитися між запитами).

Методи PUT і DELETE за визначенням ідемпотентні. Проте є один нюанс із методом DELETE. Проблема в тому, що успішний DELETE-запит повертає статус 200 (OK) або 204 (No Content), але для наступних запитів буде весь час повертати 404 (Not Found). Стан на сервері після кожного виклику DELETE той самий, але відповіді різні.

Методи GET, HEAD, OPTIONS і TRACE визначено як безпечні. Це означає, що вони призначені тільки для отримання інформації та не повинні змінювати стан сервера. Вони не повинні мати побічних ефектів, за винятком нешкідливих ефектів таких як: логування, кешування, показ банерної реклами або збільшення веб-лічильника.

За визначенням, безпечні операції ідемпотентні, оскільки вони призводять до одного й того самого результату на сервері.
Безпечні методи реалізовані як операції тільки для читання. Однак безпека не означає, що сервер повинен повертати той самий результат щоразу.

Коди станів HTTP (основні)

Повний список кодів станів тут.

Postman

На практиці зазвичай backend-розробники взагалі не мають стосунку до того, що відбувається на фронті (якщо ти не fullstack :) ). А тільки готують для фронту API для різних дій, найчастіше CRUD.

Для перевірки працездатності API найчастіше використовується postman завантажити можна ТУТ

Це програма, яка дає змогу створювати запити будь-якої складності до сервера. Рекомендую ретельно розібратися, як цим користуватися.

Загальна інформація.

Хоч REST і не є протоколом, але в сучасному вебі це майже завжди HTTP і JSON.

JSON

JSON (JavaScript Object Notation) - текстовий формат обміну даними, легко читається, дуже схожий на словник у Python.

Як це працює на практиці і до чого тут Django?

Для Django існує кілька різних пакетів для застосування REST архітектури, але основним є Django REST Framework дока тут.

Переваги DRF

  • Простота використання: DRF зручний для початківців і пропонує простий спосіб створення API.
  • Усе включено: Він включає такі функції, як серіалізація, автентифікація та класи viewset, які роблять розробку API швидшою та простішою.
  • Налаштовується: Майже кожен компонент DRF можна розширити або перевизначити, що забезпечує велику гнучкість і контроль.
  • Спільнота та документація: DRF має ґрунтовну документацію та велику активну спільноту, що дозволяє легко знайти відповіді на будь-які питання, які у вас можуть виникнути.

Недоліки DRF
Хоча DRF дуже потужний, він може бути не найкращим рішенням для кожного проекту:

  • Занадто складний для невеликих проектів: Якщо ви працюєте над невеликим проектом з парою кінцевих точок, DRF може бути зайвим. Вбудованих у Django представлень може бути достатньо.
  • Крива навчання: Хоча DRF спрощує багато аспектів розробки API, він також вводить нові концепції та класи, які розробникам потрібно вивчити.

Установка

pip install djangorestframework

Не забуваємо додати в INSTALLED_APPS 'rest_framework’


Серіалізація (у програмуванні) — процес перетворення будь-якої структури даних у послідовність бітів. Зворотною до операції серіалізації є операція десеріалізації — відновлення початкового стану структури даних із бітової послідовності.

Serializers

Серіалайзер у DRF - це клас для перетворення даних у потрібний формат (зазвичай JSON).

Припустимо, у нас є така модель:

from django.db import models
from pygments.lexers import get_all_lexers
from pygments.styles import get_all_styles

LEXERS = [item for item in get_all_lexers() if item[1]]
LANGUAGE_CHOICES = sorted([(item[1][0], item[0]) for item in LEXERS])
STYLE_CHOICES = sorted([(item, item) for item in get_all_styles()])


class Snippet(models.Model):
    created = models.DateTimeField(auto_now_add=True)
    title = models.CharField(max_length=100, blank=True, default='')
    code = models.TextField()
    linenos = models.BooleanField(default=False)
    language = models.CharField(choices=LANGUAGE_CHOICES, default='python', max_length=100)
    style = models.CharField(choices=STYLE_CHOICES, default='friendly', max_length=100)

    class Meta:
        ordering = ['created']

Ми можемо описати серіалайзер так:

from rest_framework import serializers
from snippets.models import Snippet, LANGUAGE_CHOICES, STYLE_CHOICES


class SnippetSerializer(serializers.Serializer):
    id = serializers.IntegerField(read_only=True)
    title = serializers.CharField(required=False, allow_blank=True, max_length=100)
    code = serializers.CharField(style={'base_template': 'textarea.html'})
    linenos = serializers.BooleanField(required=False)
    language = serializers.ChoiceField(choices=LANGUAGE_CHOICES, default='python')
    style = serializers.ChoiceField(choices=STYLE_CHOICES, default='friendly')

    def create(self, validated_data):
        """
        Create and return a new `Snippet` instance, given the validated data.
        """
        return Snippet.objects.create(**validated_data)

    def update(self, instance, validated_data):
        """
        Update and return an existing `Snippet` instance, given the validated data.
        """
        instance.title = validated_data.get('title', instance.title)
        instance.code = validated_data.get('code', instance.code)
        instance.linenos = validated_data.get('linenos', instance.linenos)
        instance.language = validated_data.get('language', instance.language)
        instance.style = validated_data.get('style', instance.style)
        instance.save()
        return instance

Як ми можемо цим користуватися? У shell:

from snippets.models import Snippet
from snippets.serializers import SnippetSerializer

snippet = Snippet(code='foo = "bar"\n')
snippet.save()

snippet = Snippet(code='print("hello, world")\n')
snippet.save()

serializer = SnippetSerializer(snippet)
serializer.data
# {'id': 2, 'title': '', 'code': 'print("hello, world")\n', 'linenos': False, 'language': 'python', 'style': 'friendly'}

Перетворення:

import json

string = json.dumps(serializer.data) # Перетворити JSON у рядок
# {'id': 2, 'title': '', 'code': 'print("hello, world")\n', 'linenos': False, 'language': 'python', 'style': 'friendly'}
json.loads(string) # # Перетворити рядок на JSON
# {'id': 2, 'title': '', 'code': 'print("hello, world")\n', 'linenos': False, 'language': 'python', 'style': 'friendly'}

Поля та їхні особливості

Дока тут

Будь-яке з полів може мати такі аргументи:

read_only - поле тільки для читання. Використовується для полів, які не плануються до заповнення (наприклад, час створення коментаря), але плануються до читання (наприклад, відобразити, коли було написано коментар). Такі поля не приймаються при створенні або зміні. За дефолтом False.

write_only - рівно навпаки. Поля, не заплановані для відображення, але необхідні для запису (пароль, номер картки, тощо). За дефолтом False.

required - обов’язковість поля. Поле, яке можна не вказувати під час створення/зміни, але його ж може не бути під час читанні, припустімо, по батькові. За дефолтом True.

default - значення за замовчуванням, якщо не вказано нічого іншого. Не підтримується при частковому оновленні.

allow_null - дозволити значенню поля бути None. За дефолтом False.

source - поле, значення якого необхідно отримати в моделі, припустимо, за допомогою якогось методу (обчислення повної адреси з її частин за допомогою методу моделі та декоратора @property, CharField(source='get_full_address')), або з якогось вкладеного об’єкта (Foreign Key на юзера, але необхідний тільки його імейл, а не цілий об’єкт, EmailField(source='user.email')). Має спец значення: * означає, що джерело даних буде передано пізніше, тоді його потрібно буде вказати в необхідних методах. За дефолтом - це ім’я поля.

validators - список валідаторів, про нього поговоримо нижче.

error_messages - словник із кодом помилок.

Є й інші, але ці найбільш використовувані.

У різних полів можуть бути свої атрибути, такі як максимальна довжина, або кількість знаків після коми.

Види полів за аналогією з моделями і формами можуть бути практично якими завгодно, за деталями в доку.

Специфічні поля

ListField - поле для передачі списку.
ListField(child=<A_FIELD_INSTANCE>, allow_empty=True, min_length=None, max_length=None)

scores = serializers.ListField(
    child=serializers.IntegerField(min_value=0, max_value=100)
)

DictField - поле для передачі словника.
DictField(child=<A_FIELD_INSTANCE>, allow_empty=True)

document = DictField(child=CharField())

HiddenField - приховане поле, може бути потрібне для валідацій.

modified = serializers.HiddenField(default=timezone.now)

SerializerMethodField - поле, засноване на методі.

SerializerMethodField(method_name=None), method_name - назва методу, за дефолтом get_<field_name>

from django.contrib.auth.models import User
from django.utils.timezone import now
from rest_framework import serializers


class UserSerializer(serializers.ModelSerializer):
    days_since_joined = serializers.SerializerMethodField()

    class Meta:
        model = User

    def get_days_since_joined(self, obj):
        return (now() - obj.date_joined).days

Валідація

serializer.is_valid()
serializer.validated_data
# OrderedDict([('title', ''), ('code', 'print("hello, world")\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')])
serializer.save()
# <Snippet: Snippet object>
# True

За аналогією з формами ми можемо додати валідацію кожного окремого поля за допомогою методу validate_<field_name>.

from rest_framework import serializers


class BlogPostSerializer(serializers.Serializer):
    title = serializers.CharField(max_length=100)
    content = serializers.CharField()

    def validate_title(self, value):
        """
        Check that the blog post is about Django.
        """
        if 'django' not in value.lower():
            raise serializers.ValidationError("Blog post is not about Django")
        return value

Повертає значення або порушує помилку валідації.

Також валідація може бути здійснена на рівні об’єкта. Метод validate().

from rest_framework import serializers


class EventSerializer(serializers.Serializer):
    description = serializers.CharField(max_length=100)
    start = serializers.DateTimeField()
    finish = serializers.DateTimeField()

    def validate(self, data):
        """
        Check that start is before finish.
        """
        if data['start'] > data['finish']:
            raise serializers.ValidationError("finish must occur after start")
        return data

Також можна прописати валідатори як окремі функції:

def multiple_of_ten(value):
    if value % 10 != 0:
        raise serializers.ValidationError('Not a multiple of ten')


class GameRecord(serializers.Serializer):
    score = IntegerField(validators=[multiple_of_ten])
    ...

Або вказати в Meta, використовуючи вже наявні валідатори:

class EventSerializer(serializers.Serializer):
    name = serializers.CharField()
    room_number = serializers.IntegerField(choices=[101, 102, 103, 201])
    date = serializers.DateField()

    class Meta:
        # Each room only has one event per day.
        validators = [
            UniqueTogetherValidator(
                queryset=Event.objects.all(),
                fields=['room_number', 'date']
            )
        ]

Також ми можемо передати в серіалайзер список або queryset з об’єктів, вказавши при цьому атрибут many=True

serializer = SnippetSerializer(Snippet.objects.all(), many=True)
serializer.data
# [OrderedDict([('id', 1), ('title', ''), ('code', 'foo = "bar"\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')]), OrderedDict([('id', 2), ('title', ''), ('code', 'print("hello, world")\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')]), OrderedDict([('id', 3), ('title', ''), ('code', 'print('hello, world')'), ('linenos', False), ('language', 'python'), ('style', 'friendly')])])

За аналогією з Формами і ModelForm, у серіалайзерів існують ModelSerializer.

class SnippetSerializer(serializers.ModelSerializer):
    class Meta:
        model = Snippet
        fields = ['id', 'title', 'code', 'linenos', 'language', 'style']

Якщо створити серіалайзер у такому вигляді, то:

from snippets.serializers import SnippetSerializer

serializer = SnippetSerializer()
print(repr(serializer))
# SnippetSerializer():
#    id = IntegerField(label='ID', read_only=True)
#    title = CharField(allow_blank=True, max_length=100, required=False)
#    code = CharField(style={'base_template': 'textarea.html'})
#    linenos = BooleanField(required=False)
#    language = ChoiceField(choices=[('Clipper', 'FoxPro'), ('Cucumber', 'Gherkin'), ('RobotFramework', 'RobotFramework'), ('abap', 'ABAP'), ('ada', 'Ada')...
#    style = ChoiceField(choices=[('autumn', 'autumn'), ('borland', 'borland'), ('bw', 'bw'), ('colorful', 'colorful')...

Найчастіше ви будете користуватися саме ModelSerializer.

Вкладені серіалайзери:

Серіалайзер може бути полем іншого серіалайзера. Такі серіалайзери називаються вкладеними.

class UserSerializer(serializers.Serializer):
    email = serializers.EmailField()
    username = serializers.CharField(max_length=100)


class CommentSerializer(serializers.Serializer):
    user = UserSerializer()
    content = serializers.CharField(max_length=200)
    created = serializers.DateTimeField()

Поєднуємо з попередніми знаннями і отримуємо вкладене поле з атрибутом many=True, а значить воно приймає список або
queryset таких об’єктів:

class CommentSerializer(serializers.Serializer):
    user = UserSerializer(required=False)
    edits = EditItemSerializer(many=True) # Вкладений список елементів 'edit'.
    content = serializers.CharField(max_length=200)
    created = serializers.DateTimeField()

Передавання даних у різні боки:

Зверніть увагу, що коли ми обробляємо дані, отримані від користувача (наприклад запит), то ми передаємо
дані в серіалайзер через атрибут, data= і після цього зобов’язані провалідувати дані, оскільки там можуть бути помилки:

serializer = CommentSerializer(data={'user': {'email': 'foobar', 'username': 'doe'}, 'content': 'baz'})
serializer.is_valid()
# False
serializer.errors
# {'user': {'email': ['Enter a valid e-mail address.']}, 'created': ['This field is required.']}

Якщо помилок немає, то дані перебуватимуть в атрибуті validated_data.

Але якщо ми серіалізуємо дані, які ми взяли з бази, то у нас немає необхідності їх валідувати. Ми передаємо їх без
будь-яких атрибутів, дані перебуватимуть в атрибуті data:

comment = Comment.objects.first()
serializer = CommentSerializer(comment)
serializer.data

Метод save()

У ModelSerializer за аналогією з ModelForm є методsave(), але на відміну від ModelForm додаткові дані
можна передати прямо в атрибути методуsave().

e = EventSerializer(data={'start': "05/05/2021", 'finish': "06/05/2021"})
e.save(description='bla-bla')

Зв’язки в серіалайзерах

Усі ми знаємо, що бувають зв’язки в базі даних. Дані потрібно якимось чином отримувати, але у випадку серіалізації нам
часто немає необхідності отримувати весь об’єкт, а потрібні, припустімо, тільки id або назва. DRF це передбачив.

Припустимо, у нас є ось такі моделі:

class Album(models.Model):
    album_name = models.CharField(max_length=100)
    artist = models.CharField(max_length=100)


class Track(models.Model):
    album = models.ForeignKey(Album, related_name='tracks', on_delete=models.CASCADE)
    order = models.IntegerField()
    title = models.CharField(max_length=100)
    duration = models.IntegerField()

    class Meta:
        unique_together = ['album', 'order']
        ordering = ['order']

    def __str__(self):
        return '%d: %s' % (self.order, self.title)

Щоб отримати в серіалайзері альбому всі його треки, ми можемо зробити, наприклад, так:

class TrackSerializer(serializers.ModelSerializer):
    class Meta:
        model = Track
        fields = ['title', 'duration']


class AlbumSerializer(serializers.ModelSerializer):
    tracks = TrackSerializer(many=True)

    class Meta:
        model = Album
        fields = ['album_name', 'artist', 'tracks']

Але є й інші варіанти отримання даних.

StringRelatedField

class AlbumSerializer(serializers.ModelSerializer):
    tracks = serializers.StringRelatedField(many=True)

    class Meta:
        model = Album
        fields = ['album_name', 'artist', 'tracks']

Поверне значення dunder-методу __str__ для кожного об’єкта:

{
  "album_name": "Things We Lost In The Fire",
  "artist": "Low",
  "tracks": [
    "1: Sunflower",
    "2: Whitetail",
    "3: Dinosaur Act"
  ]
}

PrimaryKeyRelatedField

class AlbumSerializer(serializers.ModelSerializer):
    tracks = serializers.PrimaryKeyRelatedField(many=True, read_only=True)

    class Meta:
        model = Album
        fields = ['album_name', 'artist', 'tracks']

Поверне id:

{
  "album_name": "Undun",
  "artist": "The Roots",
  "tracks": [
    89,
    90,
    91
  ]
}

HyperlinkedRelatedField

class AlbumSerializer(serializers.ModelSerializer):
    tracks = serializers.HyperlinkedRelatedField(
        many=True,
        read_only=True,
        view_name='track-detail'
    )

    class Meta:
        model = Album
        fields = ['album_name', 'artist', 'tracks']

Поверне посилання на обробку об’єкта. Про те, як працює ця магія, поговоримо на наступному занятті.

{
  "album_name": "Graceland",
  "artist": "Paul Simon",
  "tracks": [
    "http://www.example.com/api/tracks/45/",
    "http://www.example.com/api/tracks/46/",
    "http://www.example.com/api/tracks/47/"
  ]
}

SlugRelatedField

class AlbumSerializer(serializers.ModelSerializer):
    tracks = serializers.SlugRelatedField(
        many=True,
        read_only=True,
        slug_field='title'
    )

    class Meta:
        model = Album
        fields = ['album_name', 'artist', 'tracks']

Поверне те, що вказано в атрибутіslug_field.

{
    "album_name": "Dear John",
    "artist": "Loney Dear",
    "tracks": [
        "Airport Surroundings",
        "Everything Turns to You",
        "I Was Only Going Out"
    ]
}

Вкладені серіалізатори, які можна записувати

За замовчуванням вкладені серіалізатори доступні лише для читання. Якщо ви хочете підтримувати операції запису до поля вкладеного серіалізатора, вам потрібно створити методи create() та/або update(), щоб явно вказати спосіб збереження дочірніх зв’язків:

class TrackSerializer(serializers.ModelSerializer):
    class Meta:
        model = Track
        fields = ['order', 'title', 'duration']

class AlbumSerializer(serializers.ModelSerializer):
    tracks = TrackSerializer(many=True)

    class Meta:
        model = Album
        fields = ['album_name', 'artist', 'tracks']

    def create(self, validated_data):
        tracks_data = validated_data.pop('tracks')
        album = Album.objects.create(**validated_data)
        for track_data in tracks_data:
            Track.objects.create(album=album, **track_data)
        return album

>>> data = {
    'album_name': 'The Grey Album',
    'artist': 'Danger Mouse',
    'tracks': [
        {'order': 1, 'title': 'Public Service Announcement', 'duration': 245},
        {'order': 2, 'title': 'What More Can I Say', 'duration': 264},
        {'order': 3, 'title': 'Encore', 'duration': 159},
    ],
}
>>> serializer = AlbumSerializer(data=data)
>>> serializer.is_valid()
True
>>> serializer.errors
{}
>>> serializer.save()
<Album: Album object>
from rest_framework import serializers  
  
from core.models import User  
from notes.models import Notes  
  
  
class UserSerializer(serializers.ModelSerializer):  
    class Meta:  
        model = User  
        fields = [  
            'id',  
            'username',  
            'email',  
            'is_staff',  
        ]  
  
  
class NotesSerializer(serializers.ModelSerializer):  
    user = UserSerializer(many=False)  
  
    class Meta:  
        model = Notes  
        fields = [  
            'id',  
            'name',  
            'user',  
        ]  
  
    def create(self, validated_data):  
        user_data = validated_data.pop('user')  
        user = User.objects.create_user(**user_data)  
        model = self.Meta.model  
        note = model.objects.create(**validated_data, user=user)  
        return note

------

us = UserSerializer(data={  
    'username': 'user_name',  
})  
us.is_valid()  
us.errors  
note = NotesSerializer(data={  
    'name': 'note_1',  
    'user': us.data  
})  
note.is_valid()  
note.errors  
n = note.save()

Трохи забігаючи наперед

Далі ми детально розглядатимемо всі особливості DRF і як перетворити код на API, але наразі найважливішим є для нас є те, що DRF надає для нас повний функціонал роботи з API, найпростіший приклад використання API має такий вигляд:

from django.urls import path, include
from django.contrib.auth import get_user_model
from rest_framework import routers, serializers, viewsets

User = get_user_model()


# Serializers define the API representation.
class UserSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = User
        fields = ['url', 'username', 'email', 'is_staff']


# ViewSets define the view behavior.
class UserViewSet(viewsets.ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer


# Routers provide an easy way of automatically determining the URL conf.
router = routers.DefaultRouter()
router.register(r'users', UserViewSet)

# Wire up our API using automatic URL routing.
# Additionally, we include login URLs for the browsable API.
urlpatterns = [  
    path('', include(router.urls)),  
    path('api-auth/', include('rest_framework.urls', namespace='rest_framework'))  
]

Домашнє завдання:

  1. Створити серіалайзери для User, Order, Product (продовжуємо працювати із проектом по модулю із Django)

    1.1 Створити об’єкти User, Order, Product пов’язаних між собою через серіалізатор OrderSerializer (дані передати через data=).
    Лістинг з shell записати у .py файл.

    1.2 Отримати об’єкти з бази, передати в серіалайзер без data=, подивитися, що у них зберігається в атрибуті .data.

  2. Написати серіалайзер для Order (новий), який зберігатиме вкладений серіалайзер User.

    2.1 Отримати дані будь-якого товару разом із даними про користувача.
    Лістинг з shell записати у .py файл і закомітити…

  3. Написати серіалайзер для User, який буде зберігати всі його Покупки і видавати їх списком зі словників. (many=True)

    3.1 Отримати дані будь-якого юзера.
    Лістинг з shell записати у .py файл.

4*. Дописати серіалайзери з пунктів 2 і 3 так, щоб можна було створювати об’єкти.

Література

  1. Tutorial 1: Serialization