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:
'str' object has no attribute 'is_authenticated'
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
- Is my problem happening with the render_to_string(), or is there something else at fault?
- 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)