Як створити кілька екземплярів моделі за допомогою Django Rest Framework?


80

Я хотів би зберегти та оновити кілька екземплярів за допомогою Django Rest Framework за допомогою одного виклику API. Наприклад, скажімо, у мене є модель «Класу», в якій може бути кілька «Вчителів». Якби я це зробив, якби я хотів створити декількох вчителів і пізніше оновити всі їхні номери в класах? Чи повинен я робити виклик API для кожного вчителя?

Я знаю, що в даний час ми не можемо зберегти вкладені моделі, але я хотів би знати, чи зможемо ми зберегти їх на рівні вчителя. Дякую!


Ось подібне запитання із рішенням, яке працювало для мене: stackoverflow.com/questions/21439672/…
Marcin Rapacz

Відповіді:


80

Я знаю, що це запитували деякий час тому, але я знайшов це, намагаючись це зрозуміти сам.

Виявляється, якщо ви переходите many=Trueпід час створення екземпляра класу серіалізатора для моделі, він може приймати кілька об'єктів.

Про це йдеться тут у документах django rest framework

Для мого випадку мій погляд виглядав так:

class ThingViewSet(viewsets.ModelViewSet):
    """This view provides list, detail, create, retrieve, update
    and destroy actions for Things."""
    model = Thing
    serializer_class = ThingSerializer

Я насправді не хотів писати навантаження шаблону, щоб просто мати безпосередній контроль над створенням екземпляра серіалізатора та передачі many=True, тому в своєму класі серіалізатора я замінюю __init__замість цього:

class ThingSerializer(serializers.ModelSerializer):
    def __init__(self, *args, **kwargs):
        many = kwargs.pop('many', True)
        super(ThingSerializer, self).__init__(many=many, *args, **kwargs)

    class Meta:
        model = Thing
        fields = ('loads', 'of', 'fields', )

Розміщення даних на URL-адресі списку для цього подання у форматі:

[
    {'loads':'foo','of':'bar','fields':'buzz'},
    {'loads':'fizz','of':'bazz','fields':'errrrm'}
]

Створив два ресурси з цими деталями. Що було приємно.


Ха, це хороший улов. Я оновив свій код, щоб насправді зробити щось із замовчуванням багатьох значень. З мого боку це була помилка друку. Виявляється, просто надсилання даних у показаному форматі робить роботу за допомогою застарілого методу. Попередження, зміни не перевірені.
Том Мантерфілд,

1
Як виглядає request.DATA у цьому випадку? Це не може бути словник - чи вони якось вписують його в дикт?
akaphenom 06.03.14

@akaphenom Я не знаю, чи знайшли ви свою відповідь, але здається, що request.DATA може бути або списком, що містить dict, або dict, що містить список, що містить dict, залежно від того, як ви його серіалізуєте. Принаймні, це був мій досвід.
whoisearth

це добре знати. Я перейшов від роботи з django, тому не зосередився. Але я радий, що ця відповідь трохи повніша.
akaphenom

19
у мене не працює {"non_field_errors": ["Недійсні дані. Очікується словник, але отриманий список." ]}
колекції

58

Я прийшов до подібного висновку, як Даніель Альбаррал, але ось більш коротке рішення:

class CreateListModelMixin(object):

    def get_serializer(self, *args, **kwargs):
        """ if an array is passed, set serializer to many """
        if isinstance(kwargs.get('data', {}), list):
            kwargs['many'] = True
        return super(CreateListModelMixin, self).get_serializer(*args, **kwargs)

3
Це зробило мій день! Я підтверджую, що це працює нормально, і приймаю як списки, так і рішення.
alekwisnia

1
Як це очікується на роботу за запитом. Data - це QueryDict, а не dict або список? Це працює в модульних тестах, але не в реальному виконанні завдяки цьому (принаймні для мене).
alexdlaird

kwargs.get ('data', {}) поверне QueryDict, і, отже, зазнає невдачі, тому для багатьох не буде встановлено значення True.
Роджер Коллінз,

1
@RogerCollins Якщо один із елементів списку викликає помилку перевірки, не вдається виконати весь запит. Чи є спосіб пропустити недійсні елементи та створити інші екземпляри?
pnhegde

@pnhegde Вам потрібно було б включити цю логіку у свій серіалізатор. Ви також мали б багато роботи, переконавшись, що ваш інтерфейс оновив вашу модель з результатами, оскільки відносини не синхронізуються.
Роджер Коллінз

39

Ось ще одне рішення - вам не потрібно замінювати __init__метод серіалізаторів . Просто перевизначте 'create'метод перегляду (ModelViewSet) . Зауважте many=isinstance(request.data,list). Тут, many=Trueколи ви надсилаєте масив об’єктів для створення, і Falseколи ви надсилаєте лише один. Таким чином, ви можете зберегти як елемент, так і список!

from rest_framework import status, viewsets
from rest_framework.response import Response

class ThingViewSet(viewsets.ModelViewSet):

"""This view snippet provides both list and item create functionality."""

    #I took the liberty to change the model to queryset
    queryset = Thing.objects.all()
    serializer_class = ThingSerializer

    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data, many=isinstance(request.data,list))
        serializer.is_valid(raise_exception=True)
        self.perform_create(serializer)
        headers = self.get_success_headers(serializer.data)
        return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

4
Ця відповідь здається більш прямою, і як би я очікував, що ця функціональність буде реалізована.
Пітт,

1
Це працює, і відповідь, що проголосувала з найбільшим рейтингом, не працює для мене.
darcyq

Ви можете додатково додати transaction.atomic()блок, щоб переконатися, що всі елементи додані
Феліпе Буччоні

Це заслуговує на більшу кількість голосів, оскільки це єдине, що у мене вийшло, і це теж досить просто.
Стівен

Я зіткнувся з Expected a dictionary, but got list.помилкою : у прийнятій відповіді, і ця мені це виправила. Дякую.
Lewis Menelaws

13

Я не міг зрозуміти, як отримати запит. DATA для перетворення зі словника в масив - що було обмеженням моєї здатності працювати з рішенням Тома Мантерфілда. Ось моє рішення:

class ThingSerializer(serializers.ModelSerializer):
    def __init__(self, *args, **kwargs):
        many = kwargs.pop('many', True)
        super(ThingSerializer, self).__init__(many=many, *args, **kwargs)

    class Meta:
        model = Thing
        fields = ('loads', 'of', 'fields', )

class ThingViewSet(mixins.CreateModelMixin, viewsets.GenericViewSet ):
    queryset = myModels\
        .Thing\
        .objects\
        .all()
    serializer_class = ThingSerializer

    def create(self, request, *args, **kwargs):
        self.user = request.user
        listOfThings = request.DATA['things']

        serializer = self.get_serializer(data=listOfThings, files=request.FILES, many=True)
        if serializer.is_valid():
            serializer.save()
            headers = self.get_success_headers(serializer.data)
            return Response(serializer.data, status=status.HTTP_201_CREATED,
                            headers=headers)

        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

І тоді я запускаю еквівалент цього на клієнті:

var things = {    
    "things":[
        {'loads':'foo','of':'bar','fields':'buzz'},
        {'loads':'fizz','of':'bazz','fields':'errrrm'}]
}
thingClientResource.post(things)

1
+1 Дякую за приклад. Зауважте, мені не довелося перевизначати init у своєму серіалізаторі, а лише метод create у моєму класі перегляду
Fiver

Я не думав пробувати це без init, я працював над попереднім прикладом. Я неодмінно спробую вашу модифікацію та оновлю свою відповідь до того експерименту. Дякую за "вгору".
akaphenom

3
Думаю, ключовим є включення many=Trueу get_serializerдзвінок
Фівера

Минув більше року, як я написав свою відповідь, і я з силою згадую, що я снідав, тож сприйміть це так, як це варто: я, здається, пам’ятаю, що єдиною причиною, через яку мені довелося переписати свою ініціативу, щоб додати багато прапорців, було те, що я я не хотів безпосередньо створювати екземпляр класу серіалізатора з якихось причин (сподіваюся, він був хорошим, але зараз мені це не вдається). Так що так, проходження багатьох = True тут є ключовим. Перевизначений init можна скинути.
Том Мантерфілд,

Цей приклад єдиний, який працює з django 1.9
Георг Ціммер,

8

Я думаю, що найкращим способом поважати запропоновану архітектуру фреймворку буде створення такого змішування:

class CreateListModelMixin(object):

    def create(self, request, *args, **kwargs):
        """
            Create a list of model instances if a list is provides or a
            single model instance otherwise.
        """
        data = request.data
        if isinstance(data, list):
            serializer = self.get_serializer(data=request.data, many=True)
        else:
            serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        self.perform_create(serializer)
        headers = self.get_success_headers(serializer.data)
        return Response(serializer.data, status=status.HTTP_201_CREATED,
                    headers=headers)

Тоді ви можете замінити CreateModelMixin ModelViewSet таким чином:

class <MyModel>ViewSet(CreateListModelMixin, viewsets.ModelViewSet):
    ...
    ...

Тепер у клієнті ви можете працювати так:

var things = [    
    {'loads':'foo','of':'bar','fields':'buzz'},
    {'loads':'fizz','of':'bazz','fields':'errrrm'}
]
thingClientResource.post(things)

або

var thing = {
    'loads':'foo','of':'bar','fields':'buzz'
}

thingClientResource.post(thing)

РЕДАГУВАТИ:

Як припускає Роджер Коллінз, у своїй відповіді розумніше переписати метод get_serializer, ніж "create".


1
прийшов до подібного висновку, як Даніель Альбаррал, але ось більш коротке рішення: клас CreateListModelMixin (об'єкт): def get_serializer (self, * args, ** kwargs): "" "якщо передано масив, встановіть серіалізатор на багато" " "if isinstance (kwargs.get ('data', {}), list): kwargs ['many'] = True return super (CreateListModelMixin, self) .get_serializer (* args, ** kwargs)
Daniel Albarral

Є відчуття, що Роджер - хлопець
Джош

8

Ви можете просто переписати get_serializerметод у своєму APIView і перейти many=Trueдо get_serializerбазового подання таким чином:

class SomeAPIView(CreateAPIView):
    queryset = SomeModel.objects.all()
    serializer_class = SomeSerializer

    def get_serializer(self, instance=None, data=None, many=False, partial=False):
        return super(SomeAPIView, self).get_serializer(instance=instance, data=data, many=True, partial=partial)

1
При реалізації цього методу ви можете отримати і "AssertionError". Коли серіалізатору передається dataаргумент ключового слова, ви повинні викликати його .is_valid()перед спробою отримати доступ до серіалізованого .dataпредставлення. Вам слід або зателефонувати .is_valid()першим, або .initial_dataзамість цього отримати доступ .
Філіп Мутуа

Спробуйте наступне:from rest_framework.fields import empty def get_serializer(self, instance=None, data=empty, many=False, partial=False): return super(SomeAPIView, self).get_serializer(instance=instance, data=data, many=True, partial=partial)
Гільєрмо Ернандес

3

Generic перегляду сторінки в документації Django REST Рамкової основи держав , що ListCreateAPIView загальний вигляд «використовуваний для кінцевих точок читання-запису для представлення колекції типових екземплярів».

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

Слід також зазначити , що приклади на родовому перегляд сторінка трапляється використання ListCreateAPIView.


Я побачив, що; однак у підручнику не було прикладів, які б показували, як дозволити створення / оновлення декількох елементів. Такі запитання, як я вкладаю ресурси в об'єкт json, якщо він має бути плоским, що трапляється, якщо лише підмножина елементів не перевіряється тощо, не документується. Наразі я зробив дещо неелегантне обхідне рішення, коли перебираю json-об'єкт викладачів і використовую серіалізатор викладачів для перевірки та збереження. Будь ласка, повідомте мене, якщо ви знайдете краще рішення. Дякую
Чаз

Так, схоже, це індивідуальні функції створення та списку. Я не думаю, що там є рішення оновлення / створення декількох записів.
akaphenom 06.03.14

3 з 4 посилань тепер розірвані
e4c5

3

Я придумав простий приклад у post

Serializers.py

from rest_framework import serializers
from movie.models import Movie

class MovieSerializer(serializers.ModelSerializer):

    class Meta:
        model = Movie
        fields = [
            'popularity',
            'director',
            'genre',
            'imdb_score',
            'name',
        ]  

Views.py

from rest_framework.response import Response
from rest_framework import generics
from .serializers import MovieSerializer
from movie.models import Movie
from rest_framework import status
from rest_framework.permissions import IsAuthenticated

class MovieList(generics.ListCreateAPIView):
    queryset = Movie.objects.all().order_by('-id')[:10]
    serializer_class = MovieSerializer
    permission_classes = (IsAuthenticated,)

    def list(self, request):
        queryset = self.get_queryset()
        serializer = MovieSerializer(queryset, many=True)
        return Response(serializer.data)

    def post(self, request, format=None):
        data = request.data
        if isinstance(data, list):  # <- is the main logic
            serializer = self.get_serializer(data=request.data, many=True)
        else:
            serializer = self.get_serializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

Ці рядки є фактичною логікою Multiple Instance -

data = request.data
if isinstance(data, list):  # <- is the main logic
      serializer = self.get_serializer(data=request.data, many=True)
else:
      serializer = self.get_serializer(data=request.data)

Якщо вас плутають з багатьма = True, перегляньте це

Коли ми надсилаємо дані, це буде всередині listприблизно так:

[
    {
        "popularity": 84.0,
        "director": "Stanley Kubrick",
        "genre": [
            1,
            6,
            10
        ],
        "imdb_score": 8.4,
        "name": "2001 : A Space Odyssey"
    },
    {
        "popularity": 84.0,
        "director": "Stanley Kubrick",
        "genre": [
            1,
            6,
            10
        ],
        "imdb_score": 8.4,
        "name": "2001 : A Space Odyssey"
    }
]

1

Найпростіший метод, з яким я стикався:

    def post(self, request, *args, **kwargs):
        serializer = ThatSerializer(data=request.data, many=isinstance(request.data, list))
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        else:
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.