Jakie są najlepsze praktyki w budowaniu API za pomocą Django-Rest-Framework?

0

Aplikacja DRF ma spełniać rolę backendu natomiast frontend będzie napisany w Angular lub innym frameworku.
Wszysktie widoki muszą być dostępne za pomocą żądań http i reagować na te żądania.
Docelowo nie potrzebuję tworzyć żadnego frontendu w Django.

  1. Na jakim typie widoku powinienem się skupić?
    Obecnie zaczynam pisać widoki używając "viewsets.ModelViewSet", podoba mi się w nim, iż generuje automatycznie frontend w którym mogę testować to co napisałem. Zastanawiam się tylko, czy może powinienem używać innego typu np. "APIView" bo na przykład te są z jakichś powodów bardziej odpowiednie.

  2. Jakie metody powinienem definiować w Modelu, jakie w **Serializerze ** a jakie w Widoku?

  3. Co jeszcze jest ważne? Na co warto zwrócić uwagę?

4
  1. ModelViewSet jest zazwyczaj spoko w większości przypadków.
    To generowanie automatyczne frontendu, to nie kwestia użycia modelviewsetu a drfa, on sobie ogólnie generuje taki froncik pomocny. To raz.
    Testowanie jedynie przez przeklikiwanie jest złe. Pisz testy i koniec.
    Tam gdzie potrzebujesz sztampowego ModelViewSeta, używaj go. Tam, gdzie jakieś mocno Customowe rzeczy, możesz ApiView. Tam gdzie tylko np. niektóre metody to po prostu rzeczy typu generics.ListCreateAPIView. Patrz czego ci potrzeba.
  2. W serializerze raczej te funkcje, które dotyczą walidacji/serializacji danych. W widoku te, odpowiedzialne za ich prezentacje/responsa. W modelu jakies constarainty na fieldach itd + rzeczy które dotyczą samego modelu.

Pisz dokumentacje: czy to autogenerowaną np. drf swagger generatorem jakimś, czy też ręcznie swaggera.
Trzymaj się jakiś konkrentych guidelinów - jednolicie pisz to api. Jakiś spec typu https://jsonapi.org. Do teog warto poczytać chociaż by zalando api guidelines.
Używaj raczej Routera z drfa.

To takie pierwsze rzeczy, które przychodzą mi do głowy.

4
  1. W sumie to co napisał @grski wyzej, przy czym dodałbym zebyś skupił się do dokumentacji DRF, sam DRF jest dość rozbudowany i znając dobrze dokumentację bedziesz w stanie idealnie dobrać element do potrzeb. Więc w sumie to będzie najlepsza odpowiedź na pytane nr1.

  2. Programista pisze testy, testowanie REST API jest dość przyjemne. Wiadomo warto sobie przeklikać co tam leci na response i najlepiej uzywaj do tego Postmana lub Inosimnii. Zapewne dojdzie Ci z czasem przekazywanie roznych parametrów w headerach i innych miejscach i klikanie tego przez przeglądarke robi sie bezsensowne.

  3. Skup sie dobrze na autoryzacji i autentykacji. Dobierz rozwiazania z głową, jesli bedziesz potrzebował integrować logowanie za pomocą OAuth ogarnij temat już na początku.

  4. Co do tego co gdzie i jak wrzucać to w sumie też grski dobrze napisał, jesli bedziesz musiał wrzucać wieksze elementy logiki biznesowej nie rób tego ani w seriallizerze ani w wiodku. Ja staram się customową logikę którą nie spotyka w takich miejscach wrzucać do services.py czy utils.py i stamtąd importuje jej elementy gdzie przeba. Niepotrzebnie przerośnięty serializer czy widok to zło.

  5. Testy jeszcze raz, pisz testy.

  6. Jeśli korzystasz z djangowego modelu usera ogarnij jak poprawnie nim operować, co to CustomUser i get_user_model() jeśli jeszcze tego nie wiesz.

  7. Jeśli przewidujesz translacje stringów to myśl o tym już od początku.

  8. Walidacja i obsluga wyjątków to twoi najlepsi przyjaciele.

0
grski napisał(a):
  1. ModelViewSet jest zazwyczaj spoko w większości przypadków.
    To generowanie automatyczne frontendu, to nie kwestia użycia modelviewsetu a drfa, on sobie ogólnie generuje taki froncik pomocny. To raz.
    Testowanie jedynie przez przeklikiwanie jest złe. Pisz testy i koniec.
    Tam gdzie potrzebujesz sztampowego ModelViewSeta, używaj go. Tam, gdzie jakieś mocno Customowe rzeczy, możesz ApiView. Tam gdzie tylko np. niektóre metody to po prostu rzeczy typu generics.ListCreateAPIView. Patrz czego ci potrzeba.
  2. W serializerze raczej te funkcje, które dotyczą walidacji/serializacji danych. W widoku te, odpowiedzialne za ich prezentacje/responsa. W modelu jakies constarainty na fieldach itd + rzeczy które dotyczą samego modelu.

Pisz dokumentacje: czy to autogenerowaną np. drf swagger generatorem jakimś, czy też ręcznie swaggera.
Trzymaj się jakiś konkrentych guidelinów - jednolicie pisz to api. Jakiś spec typu https://jsonapi.org. Do teog warto poczytać chociaż by zalando api guidelines.
Używaj raczej Routera z drfa.

To takie pierwsze rzeczy, które przychodzą mi do głowy.

Bardzo fajnie to napisałeś.
Od siebie chciałem zapytać o podejście do pewnego typu problemu.
Otóż, zdarzyło mi się stworzyć korpo aplikację typu: Cheklista,
trochę taki excel z pytaniami i odpowiedziami w stylu True/False, historią zmian i tworzeniem szablonów).

Właśnie kiedy w osobnej JS-owej aplikacji na froncie tworzę taki szablon nowej Checklisty, to wysyłam JSON-a do API w Django Rest Frameworku.

Mam dwie koncepcje.

podejście 1.
W np. ModelViewSet jechać pętlami po kolejnych zagnieżdżeniach tego JSON-a i odpalać kolejne serializery i zapisywać do bazy.
W tym podejściu mam (wydaje się) pełną kontrolę nad tym jak serializuję takiego JSON-a. Czy tworzę nowe obiekty w bazie, czy np. dodaje tylko nowe powiązania (np. jedno pytanie może należeć do wielu Checklist, itp.)

plusy:

  • łatwy dostęp do danych z sesji
    minusy:
  • dużo kodu w ModelViewSet

podejście 2.
JSON powinien być tak przygotowany (w pełni serializowalny), że wrzucam go Od razu w pierwszy serializer od Checklisty i dalej "automat" Django Rest Frameworku.

Tutaj wydaje mi się, że trochę tracę kontrolę nad tym jak serializowane są kolejne stopnie zagnieżdżenia takiego JSON-a, choć... można to obejść poprzez grzebanie w odpowiednich serializerach.

plusy:

  • ładny i czytelny kod warstwy prezentacji
    minusy:
  • czasami bałagan z serializerami (przynajmniej ja czasami potrzebowałem tworzyć po 2-3 serializery dla jednego modelu, w zależności czy chciałem go odczytać czy zapisać)
  • trudny dostęp do danych sesji, parametrów zapytania w request, itp.

Może jest jakaś trzecia droga? Poradnik/Tutorial jak zapisywać rozbudowane relacje przez DRF? Ja znajdywałem zwykle tutoriale pokazujące bardzo proste przypadki.

PS. Aplikacja powstała, działa, podoba się "użyszkodnikom", tylko jak ja patrzę w kod, to widzę że można było lepiej i stąd to raczej luźne pytanie. Jeśli potrzeba to dostarczę jakiś pseudokod :)

EDIT: Doklejam kodzik:

from django.db import models


class Template(models.Model):
    """
    Used for preparing new Checklist.
    Allows to fully edit content and order of question rows and columns.
    Answers for questions can be set as "default answer".
    """
    name = models.CharField(max_length=100)


class FeatureChecklist(models.Model):
    """
    Used for storing answers for question provided by users.
    Only answers are fully editable, order of questions and columns is fixed.
    """
    name = models.CharField(max_length=100)


class Checklist(models.Model):
    """
    Used by Template as well as FeatureChecklist as "storage"
    for question rows and columns.
    """
    # Relation to Template model
    template = models.OneToOneField('Template',
                                    on_delete=models.CASCADE,
                                    related_name='checklist',
                                    default=None,
                                    blank=True,
                                    null=True)
    feature_checklist = models.OneToOneField('FeatureChecklist',
                                             on_delete=models.CASCADE,
                                             related_name='checklist',
                                             default=None,
                                             blank=True,
                                             null=True)

    def save(self, *args, **kwargs):
        """
        Prevent Checklist against having two owners from Template or FeatureChecklist.
        """
        if self.template and self.feature_checklist:
            raise ValueError("Checklist can have only one owner: "
                             "One of fields 'template' or 'feature_checklist' "
                             "must be None.")
        else:
            return super().save(*args, **kwargs)


class QuestionColumn(models.Model):
    """
    Holds column names for related Checklist object.
    """
    name = models.CharField(max_length=100)
    column_order = models.IntegerField()
    # Relation to Checklist model
    checklist = models.ForeignKey('Checklist', null=True, blank=True, related_name='question_columns')


class QuestionRow(models.Model):
    """
    Holds question names for related Checklist object.
    """
    name = models.TextField()  # Question text should be stored
    row_order = models.IntegerField()
    # Relation to Checklist model
    checklist = models.ForeignKey('Checklist', null=True, blank=True, related_name="question_rows")


class AnswerCell(models.Model):
    """
    Like a cell in Excel. It has relation QuestionColumn and QuestionRow
    """
    value = models.TextField()
    question_column = models.ForeignKey('QuestionColumn', on_delete=models.CASCADE, related_name="answer_cells")
    question_row = models.ForeignKey('QuestionRow', on_delete=models.CASCADE, related_name="answer_cells")


example_Template_JSON_from_FrontEnd = {
    "name": "Test Template",
    "checklist": {
        "question_column": [
            {
                "name": "QuestionColumn1",
                "column_order": 1
            },
            {
                "name": "QuestionColumn2",
                "column_order": 2
            },
            {
                "name": "QuestionColumn3",
                "column_order": 3
            },
        ],
        "question_row": [
            {
                "name": "QuestionRow1",
                "row_order": 1
            },
            {
                "name": "QuestionRow2",
                "row_order": 2
            },
            {
                "name": "QuestionRow3",
                "row_order": 3
            },
        ],
        "answer_cells": [
            {
                "value": "AnswerCell11",
                "column_order": 1,
                "row_order": 1,
            },
{
                "value": "AnswerCell21",
                "column_order": 2,
                "row_order": 1,
            },
{
                "value": "AnswerCell31",
                "column_order": 3,
                "row_order": 1,
            },
            # :
            # :
            # etc.
        ]

    }
}

Rezultat to trochę bardziej interaktywne to:
Checklista

Chodzi o to, że właściwie wszystkie obiekty są dopiero tworzone z tego JSON-a na końcu kodu (brak PK)
I np. aby zapisać Checklistę muszę najpierw zapisać Template i pozykać Template PK. Zatem po kolei jadę pętlą FOR i wypakowuję kolejne...
Czy to złe? :)

1

To podejście numer 1+2 w zasadzie :D
Wydaje mi się, że DRF obsługuje coś takiego jak many=True w arugmentach serializera typu ForeignKey, gdzie tam pętla stanie się trochę zbędna, ewentualnie możesz zamiast ForeignKeya
Utworzyć serializer dla QuestionRow i QuestionColumn
A potem w serializerze templatki:

question_row = QuestionRow(many=True)
question_Column = QuestionColumn(many=True

coś takiego.

Różnica między FK a QuestionRow będzie taka, że frontowi będziesz zwracał albo tylko FK albo cały serializer z danymi, także chyba lepsza jest opcja z całym serializerem.

Do tego obczaj: https://www.django-rest-framework.org/api-guide/serializers/#listserializer customizing multiple create

Wtedy masz pełną kontrolę nad tym jak ci serializuje + masz ładny, koszerny serializer.

powinno pomóc

1 użytkowników online, w tym zalogowanych: 0, gości: 1