2

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 find
  • DetailView only + write the post method: "a better solution" according to Django docs
  • DetailView + 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.

I've gone through a number of SO items that reference this solution, but I am unable to implement any of them.
  • This SO question suggests mixing DetailView and CreateView. However, the explanation in that answer is incomplete.
  • Another SO question, among advice to use FormMixins, has this answer that is close, but different.
  • Other questions (1, 2, etc.) only address the FormMixin and DetailView + 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 = "article_detail.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>", ArticleCommentView.as_view(), name="article_detail"),
]

article_detail.html:

{% extends 'base.html' %}
{% block content %}
    {{ article.title }}
    {{ article.author }}
    {{ article.body }}
    {% include "comment_create.html" %}
    <!-- list existing comments with {#% for comment in article.comments.all %#}, etc.-->
{% endblock %}

comment_create.html:

<form method="post" action="{% url 'article_detail' slug=article.slug %}">
    {% csrf_token %}
    <textarea name="{{ form.body.name }}">{{ form.body.value|default_if_none:'' }}</textarea>
    </div>
        <button type="submit">
            Post Comment
        </button>
    </div>
</form>

I'm currently getting a

NoReverseMatch at /article/createview-cd7a7040-c4ca-4289-8f53-6676f27c3aa9
Reverse for 'editor_update' with keyword arguments '{'slug': ''}' not found. 1 pattern(s) tried: ['editor/(?P<slug>[-a-zA-Z0-9_]+)$']`

where editor_update is a path at editor/<slug> to an UpdateView. Can't understand how that is related to anything.

The article.slug in comment_create.html returns the correct slug, but form.body.name returns an empty string, if that helps.

Edited based on Abdul Aziz Barkat's comment below.

harabat
  • 127
  • 2
  • 5
  • 1
    Hint: The purpose was to use _one_ url for a view that will 1) show the article 2) Create comments on that article. i.e. what you currently do incorrectly is you create another url for the createview and you also create another template for it. Also `` HTML comments don't stop Django from executing that (Use Django comments `{# some code here #}` or multiline: `{% comment %}some code here{% endcomment %}`) – Abdul Aziz Barkat Dec 14 '21 at 17:05
  • Hey, thanks for the tip, I'm looking into it right now. The is for illustration purposes, not the actual code, but I'll make it clearer. – harabat Dec 14 '21 at 17:09
  • @AbdulAzizBarkat, I've edited my code, but no joy. – harabat Dec 14 '21 at 17:45
  • 1
    Is this a typo in `ArticleDetailView.get_context_data`: `context["form"] = CommentCreateView()`? If not, you seem to be passing a view instance instead of a form instance to the template. – CoffeeBasedLifeform Dec 14 '21 at 18:28
  • @CoffeeBasedLifeform, I think you might have solved this! I replaced the line you mentioned with `context['form'] = CommentCreateView().get_form_class()`, and it seems to be working now. I'm going to check everything thoroughly, but this might have been one of the missing pieces. – harabat Dec 14 '21 at 19:33
  • 1
    Try `get_form()` instead, because I'm pretty sure that a form *instance* is needed - not a class. Also, as a sidenote: this kind of issue is a good example why tests are useful. Even a super simple test, that checks that a form instance is present in `get_context_data` return value, would have saved you the trouble. – CoffeeBasedLifeform Dec 14 '21 at 20:03

0 Answers0