Home > database >  DRF Create: render_to_string() loses it's context variables when the Response is returned
DRF Create: render_to_string() loses it's context variables when the Response is returned

Time:03-13

Background

I want to create and render replies without the need to refresh the page. I know this can be done by submitting the form using AJAX or Fetch(), and use the returned response to append to the bottom of the comment list. I am trying to accomplish this using DRF and AJAX. I opted in to DRF HTMLRenderer Django render_to_string() so I do not have to create a JS template literal to render the template from JSON, and it would also be a lot easier for me to make any template changes if I just let the Django templating system do it for me.

Problem

My response is not populating my template with context variables, despite me passing in the context to the built in render_to_string(). The chances of render_to_string() being at fault is very low, and I am honestly not sure how to resolve this. I also tried switching out the render_to_string() with render() to see if that would somehow fix the problem. However, the problem still remained.

I know this because of the following two attribute errors when viewing the response from the console:

  1. 'str' object has no attribute 'is_authenticated'
  2. Reverse for 'profile_detail' with arguments '('',)' not found. 1 pattern(s) tried: ['account/user/(?P<slug>[^/] )/$']

Both of which would not be happening if the context was being evaluated in my template.

The actual reply itself is created and visible if I manually refresh the page after the AJAX post.

Information

There does not seem to be a problem with the actual context, because the break point shows the expected results (... is me replacing the actual text for brevity):

{'reply': <Reply: Reply object (47527)>, 'post': <Post: ....>, 'recipient': <Profile: ....>}}

Edit:

When I print print(response['render_reply']) the template is populated, however once it returns in return Response(response), the template is no longer populated with the reply object.

Questions

  1. Is my problem happening with the render_to_string(), or is there something else at fault?
  2. Am I on the right track to sending and rendering replies? Is there anything that can be improved? (code review)

So the problem is most likely occurring here, but that would be really odd because what are the chances of a built in function not working?

response['render_reply'] = render_to_string(
   template_name=self.template_name, context=context, request=request
)

Relevant code

Viewset

I created my own action because I wasn't quite sure how to accomplish this with the default create().

class ReplyActionViewSet(viewsets.ModelViewSet):
    queryset = Reply.objects.all()
    serializer_class = ReplySerializer
    permission_classes = [IsAuthenticated]
    renderer_classes = [JSONRenderer, TemplateHTMLRenderer]
    template_name = "post/partials/render-reply.html"

    def get_queryset(self):
        return self.queryset.filter(user=self.request.user)

    @action(detail=True, methods=['post'])
    def create_reply(self, request, pk=None):
        response = {}

        post = get_object_or_404(
            Post.public_or_member_objects.all(), pk=pk
        )
        serializer = ReplySerializer(data=request.POST)

        if serializer.is_valid():
            ...

            reply = Reply.objects.create(**data)
            ...

            context = {'reply': reply, 'post': post, 'data': {"post": post, "recipient": reply.recipient}}

            response['render_reply'] = render_to_string(
                template_name=self.template_name, context=context, request=request
            )
            return Response(response)

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

Snippet of the reply template where one of the issues is happening:

<div >
    <div  style="background-color:{{ post.user.avatar_color }};">
        {{ reply.user.nickname|slice:1 }}
    </div>
    <a href="{% url "profile_detail" reply.user.username %}" >
        <span style="font-weight: 500; color:{{ post.user.avatar_color }};">{{ reply.user.nickname }}</span>
    </a>
</div>

And here is my JS:

'use strict';

$(document).on("submit", ".js-reply-form", function (event) {
  event.preventDefault();

  const form = $(this);
  const url = form.attr('action');

  $.ajax({
    type: "POST",
    url: url,
    dataType: 'html',
    data: form.serialize(),
    success: function (data) {
      form.trigger('reset');
      
    },
    error: function (response) {
      console.log(response);
      console.log("failed!");

    },
  });
});

Included trace:

Environment:


Request Method: POST
Request URL: http://127.0.0.1:8000/api/v1/reply-actions/42874/create_reply/

Django Version: 3.2.7
Python Version: 3.10.2
Installed Applications:

Template error:
In template C:\Users\...\Documents\Repo\drf_project\journal\templates\journal\partials\reply-render-reply.html, error at line 36
   'str' object has no attribute 'is_authenticated'
   26 :         </div>
   27 : 
   28 :         <div>
   29 :             {% if reply.user.is_premium %}
   30 :                 <span title="{% trans "drf_project Premium" %}" >
   31 :                     <i ></i>
   32 :                 </span>
   33 :             {% endif %}
   34 : 
   35 :             <span title="{% trans "X ratio" %}" >
   36 :                 <i ></i>  {{ reply.user|get_X_ratio }} 
   37 :             </span>
   38 :         </div>
   39 :     </div>
   40 : 
   41 :     <!-- Reply body -->
   42 :     <div>
   43 :         <div >{{ reply.date }}</div>
   44 : 
   45 :         <!-- Quoted content -->
   46 :         {% if reply.y_row_id %}


Traceback (most recent call last):
  File "C:\Users\...\Documents\Repo\drf_project\venv\lib\site-packages\django\core\handlers\exception.py", line 47, in inner
    response = get_response(request)
  File "C:\Users\...\Documents\Repo\drf_project\venv\lib\site-packages\django\core\handlers\base.py", line 204, in _get_response
    response = response.render()
  File "C:\Users\...\Documents\Repo\drf_project\venv\lib\site-packages\django\template\response.py", line 105, in render
    self.content = self.rendered_content
  File "C:\Users\...\Documents\Repo\drf_project\venv\lib\site-packages\rest_framework\response.py", line 70, in rendered_content
    ret = renderer.render(self.data, accepted_media_type, context)
  File "C:\Users\...\Documents\Repo\drf_project\venv\lib\site-packages\rest_framework\renderers.py", line 167, in render
    return template.render(context, request=request)
  File "C:\Users\...\Documents\Repo\drf_project\venv\lib\site-packages\django\template\backends\django.py", line 61, in render
    return self.template.render(context)
  File "C:\Users\...\Documents\Repo\drf_project\venv\lib\site-packages\django\template\base.py", line 170, in render
    return self._render(context)
  File "C:\Users\...\Documents\Repo\drf_project\venv\lib\site-packages\django\test\utils.py", line 100, in instrumented_test_render
    return self.nodelist.render(context)
  File "C:\Users\...\Documents\Repo\drf_project\venv\lib\site-packages\django\template\base.py", line 938, in render
    bit = node.render_annotated(context)
  File "C:\Users\...\Documents\Repo\drf_project\venv\lib\site-packages\django\template\base.py", line 905, in render_annotated
    return self.render(context)
  File "C:\Users\...\Documents\Repo\drf_project\venv\lib\site-packages\django\template\base.py", line 988, in render
    output = self.filter_expression.resolve(context)
  File "C:\Users\...\Documents\Repo\drf_project\venv\lib\site-packages\django\template\base.py", line 698, in resolve
    new_obj = func(obj, *arg_vals)
  File "C:\Users\...\Documents\Repo\drf_project\journal\templatetags\navbar_tags.py", line 15, in get_X_ratio
    if user.is_authenticated:

Exception Type: AttributeError at /api/v1/reply-actions/42874/create_reply/
Exception Value: 'str' object has no attribute 'is_authenticated'

CodePudding user response:

Please post the whole trace for the 1st error

<str> object has no ... 

Your 2nd error means that in

<a href="{% url "profile_detail" reply.user.username %}"

the username is empty.

CodePudding user response:

The solution was honestly quite straightforward. response['render_reply'] evaluated to {'render_reply':'long string'} which is JSON.

So I needed to change the dataType in my AJAX post to use json instead of html.

I also just passed in the rendered template so I did not have to call data["render_reply"].

        context = {
            'reply': reply,
            'post': post,
            'recipient': reply.recipient
        }

        rendered_template = render_to_string(
            template_name='journal/partials/reply-render-reply.html',
            context=context,
            request=request
        )

        return Response(rendered_template)
  • Related