Combining DetailView and CreateView in Django 3.2/4.0, as explained in the docs
I'm building a generic blog and trying to enable users to make comments directly on the article page. I am trying to implement this by combining DetailView
with CreateView
.
The docs present 3 different solutions to this issue:
FormMixin
+DetailView
: this is the answer that Django docs advise against, but that is advised by most answers on SO that I could findDetailView
only + write thepost
method: "a better solution" according to Django docsDetailView
+FormView
: "an alternative better solution", and the one I'm trying to implement.
The "alternative better" solution consists in making a DetailView
for articles and a FormView
for comments, but the docs state that "This approach can also be used with any other generic class-based views", which means that DetailView + CreateView should be possible.
- This SO question suggests mixing DetailView and CreateView. However, the explanation in that answer is incomplete.
- Another SO question, among advice to use
FormMixin
s, has this answer that is close, but different. - Other questions (1, 2, etc.) only address the
FormMixin
andDetailView
+post
methods.
Here's my implementation for now:
models.py:
class Article(models.Model):
slug = models.SlugField()
# title, body, author
def get_absolute_url(self):
return reverse("article_detail", kwargs={"slug": self.slug})
class Comment(models.Model):
article = models.ForeignKey(Article, on_delete=models.CASCADE, related_name="comments", to_field="slug")
body = models.TextField()
# ...
def get_absolute_url(self):
return reverse("article_detail", kwargs={"slug": self.article.slug})
views.py:
class ArticleDetailView(DetailView):
model = Article
template_name = "article_detail.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["form"] = CommentCreateView()
return context
class CommentCreateView(CreateView):
"""create comment"""
model = Comment
fields = ["body"]
template_name = "comment_create.html"
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.article = Article.objects.filter(
slug=self.kwargs.get("slug")
).first()
self.object.author = self.request.user
self.object.save()
return super().form_valid(form)
def post(self, request, *args, **kwargs):
self.object = self.get_object()
return super().post(request, *args, **kwargs)
def get_success_url(self):
return reverse("article_detail", kwargs={"slug": self.object.article.slug})
class ArticleCommentView(View):
def get(self, request, *args, **kwargs):
view = ArticleDetailView.as_view()
return view(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
view = CommentCreateView.as_view()
return view(request, *args, **kwargs)
urls.py:
urlpatterns = [
# ...
path("article/<slug:slug>", ArticleDetailView.as_view(), name="article_detail"),
path("comment/new", CommentCreateView.as_view(), name="comment_create"),
]
article_detail.html:
{% extends 'base.html' %}
{% block content %}
{{ article.title }}
{{ article.author }}
{{ article.body }}
<form method="post" action="{% url 'comment_create'%}">
{% csrf_token %}
<textarea name="{{ form.body.name }}">{{ form.body.value|default_if_none:'' }}</textarea>
</div>
<button type="submit">
Post Comment
</button>
</div>
</form>
<!-- {% for comment in article.comments.all %} ...-->
{% endblock %}