Фреймворк Django REST, согласование содержимого

#django #django-rest-framework

#django #django-rest-framework

Вопрос:

Я пытаюсь заставить свою конечную точку возвращать uri-list при запросе на это и строку json по умолчанию. Я тестирую это в модульном тестировании, которое выглядит немного как:

 [...]
headers = {'Accept': 'text/uri-list'}
response = self.client.get('/api/v1/licenses/', headers=headers)
[...]
  

Я написал URIListRenderer вот так:

 from rest_framework import renderers


class URIListRenderer(renderers.BaseRenderer):
media_type = 'text/uri-list'

def render(self, data, media_type='None', renderer_context=None):
    return "n".join(data).encode()
  

Далее я пытаюсь получить свой ответ, на мой взгляд, для отображения с помощью моего средства визуализации:

 class RestLicenses(APIView):    
    """
    List all licenses, or create a new license
    """
    permission_classes = (IsAuthenticated,)
    parser_classes = (MultiPartParser,)
    renderer_classes = (JSONRenderer, URIListRenderer,)

    def get(self, request, format=None,):
        models = LicenseModel.objects.all()
        if len(models) == 0 :
            return Response('[]',status=204)
        if request.META.get('headers') is not None :
            if request.META.get('headers').get('Accept') == 'text/uri-list' :
                result = [];
                for m in models :
                    result.append(reverse('downloadLicense', args=[m.pk], request=request))
                return Response(result, status=200)

        serializer = LicenseJSONSerializer(request, models, many=True)
        serializer.is_valid()
        return HttpResponse(JSONRenderer().render(serializer.data), content_type='application/json', status=200)
  

Но кажется невозможным заставить его выбрать любой другой рендерер, кроме первого в списке. Как мне заставить его выбрать мой URIListRenderer , а не json?

Комментарии:

1. Вы отладили то, что request.META.get('headers').get('Accept') на самом деле возвращает? == это довольно строгий тип сравнения по ходу строк.

2. Это возвращает true , но кажется, что фактическое согласование содержимого происходит в каком-то совершенно другом месте, и это всегда заканчивается тем, что выбирается первый класс рендеринга.

3. Есть ли в запросе ?format=json случайно параметр запроса?

Ответ №1:

Ваш модульный тест неправильно устанавливает заголовки. Как описано здесь, при использовании тестового клиента Django следует использовать заголовки в стиле CGI:

 response = self.client.get('/api/v1/licenses/', HTTP_ACCEPT='text/uri-list')
  

Для согласования содержимого используется реальный заголовок HTTP Accept. В вашем коде вы проверяете, что установлен параметр «headers», но это не настоящий заголовок HTTP Accept. Это должно быть:

 if request.META.get('HTTP_ACCEPT') == "text/uri-list":
    ... 
  

Комментарии:

1. Точно! после проверки соответствия средств визуализации я также пришел к выводу, что заголовок вводится неправильно, но я полностью пропустил ту часть, где он неправильно настраивает его в тесте

Ответ №2:

Это блок кода из метода finalize_response :

 if isinstance(response, Response):
    if not getattr(request, 'accepted_renderer', None):
        neg = self.perform_content_negotiation(request, force=True)
        request.accepted_renderer, request.accepted_media_type = neg

    response.accepted_renderer = request.accepted_renderer
    response.accepted_media_type = request.accepted_media_type
    response.renderer_context = self.get_renderer_context()
  

Как вы можете видеть, прежде чем выполнять согласование содержимого, он проверяет, установил ли представление необходимый рендерер, и, если нет, пытается выполнить согласование самостоятельно.
Итак, вы можете сделать это в своем методе get:

 if request.META.get('headers') is not None :
        if request.META.get('headers').get('Accept') == 'text/uri-list' :
            request.accepted_renderer = URIListRenderer
            result = [];
            for m in models :
                result.append(reverse('downloadLicense', args=[m.pk], request=request))
            return Response(result, status=200)
  

Еще одна вещь, которую вам, вероятно, следует сделать, это поместить ваш пользовательский класс средства визуализации перед JSONRenderer в списке render_classes. Таким образом, сначала будет проверен специальный случай перед более общим случаем во время согласования содержимого. Есть подозрение, что формат запроса также соответствует JSOnRender и он затмевает пользовательский рендерер. Надеюсь, это поможет

Комментарии:

1. Это не должно быть необходимо. Настройка accepted_renderer будет работать, но странно, почему она не сопоставляется с использованием согласования по умолчанию. JSONRenderer Имеет только «application / json» в качестве типа носителя, поэтому он не должен его перехватывать. Также вы должны установить accepted_renderer после второго if условия.

2. @dirkgroten да, я это исправил.