Introduction
I’m developing an article publishing application. Then, in the article detail page(DetailView), I’m implementing display/create/edit/delete functions for comments on the article. I’d like to design edit/delete comments functions are in a modal window.The comment delete function worked correctly, but the comment edit function did not work properly. I have no idea for improvement despite my survey, so please tell me what the problem is or what I should do.
Premise
The functional requirements for the article detail page (article_detail.html) are as follows
・display of article contents
・edit/delete articles (only if the requesting user is the author of the article)
・displaying comments on articles
・creating comments on articles
・edit/delete comments (only if the requesting user is the author of the comment)
Of these functions, editing/deleting an article is handled on a separate page to which the user transitions. On the other hand, the creation of comments and editing/deleting of comments will be handled in the article detail page, and especially editing/deleting of comments will be handled in a modal dialog.
Codes
model.py
The models are CustomUser
for user information, Article
for articles, and Comment
for comments on articles.The CustomUser
is an extension of the AbstractUser
class.In order to use markdown format, the content
of the Article
and the text
of the Comment
use the MDTextField
class.
from mdeditor.fields import MDTextField
class CustomUser(AbstractUser):
username = models.CharField(
_("username"),
max_length=30,
help_text='Required 30 characters or fewer.',
unique=True,
error_messages={
'unique': _("This Username already exists."),
},)
email = models.EmailField(
_('email'),
unique=True,
error_messages={
'unique': _("A user with that email address already exists."),
},)
class Meta:
verbose_name_plural = 'CustomUser'
class Article(models.Model):
post_user = models.ForeignKey(CustomUser, verbose_name='Post User', on_delete=models.CASCADE, related_name='name',)
title = models.CharField(verbose_name='title', max_length=50,)
content = MDTextField()
created_at = models.DateField(verbose_name='created_at', auto_now_add=True,)
class Meta:
verbose_name_plural = 'Article'
def __str__(self):
return self.title
class Comment(models.Model):
writer = models.ForeignKey(CustomUser, on_delete=models.CASCADE,)
text = MDTextField()
target = models.ForeignKey(Article, on_delete=models.CASCADE,)
created_at = models.DateField(verbose_name='created_at', auto_now_add=True,)
updated_at = models.DateField(verbose_name='updated_at', auto_now=True,)
def __str__(self):
return self.text[:20]
forms.py
I use CommentCreateForm
, which extends ModelForm
, to create and edit comments.
class CommentCreateForm(forms.ModelForm):
class Meta:
model = Comment
fields = ("text",)
url.py
Editing comments is done in a modal window in the article detail page, so there is no dedicated page for this purpose.
app_name = 'article'
urlpatterns = [
path('article_detail/<int:pk>', views.ArticleDetailView.as_view(), name='article_detail'),
path('<int:pk>/comment/update', views.CommentUpdateView.as_view(), name='comment_update'),
path('<int:pk>/comment/delete', views.CommentDeleteView.as_view(), name='comment_delete'),
]
views.py
The article detail page is displayed in ArticleDetailView
, and comments associated with the article are returned to the template as context data.Regarding context data, context['form']
for creating comments and context['comment_form']
for editing comments in a modal window
class ArticleDetailView(generic.DetailView):
model = Article
template_name = 'article_detail.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['form'] = CommentCreateForm()
context['comment_form'] = CommentCreateForm()
context['comment_form'] = CommentCreateForm()
context['comment_list'] = Comment.objects.select_related('target').filter(target=self.kwargs['pk'])
return context
class CommentUpdateView(generic.UpdateView, LoginRequiredMixin):
model = Comment
form_class = CommentCreateForm
template_name = 'article_detail.html'
def get_success_url(self):
return reverse_lazy('article:article_detail', kwargs={'pk': self.kwargs['pk']})
def form_valid(self, form):
messages.success(self.request, 'Your comment has been successfully updated.')
return super().form_valid(form)
def form_invalid(self, form):
messages.error(self.request, 'Failed to correct comment.')
return super().form_invalid(form)
class CommentDeleteView(generic.DeleteView, LoginRequiredMixin):
model = Comment
template_name = 'article_detail.html'
def get_success_url(self):
article = Article.objects.get(comment__pk=self.kwargs['pk'])
return reverse_lazy('article:article_detail', kwargs={'pk': article.pk})
def delete(self, request, *args, **kwargs):
messages.success(self.request, "Comment has been Deleted")
return super().delete(request, *args, **kwargs)
templates(article_detail.html)
The modal display trigger for edit/delete comments is set to the pull-down list item associated with each comment.To abbreviate the code, only the comment display area is described.
{% if comment_list %}
<section class="comment-list">
<h2 id="comments" class="comment-title"><i class="fa-regular fa-comments"></i> comment</h2>
{% for comment in comment_list %}
<div class="commets-list">
<li class="comment even thread-even depth-1 parent" id="comment-18730" style="list-style:none">
<div id="div-comment-18730" class="comment-body article">
<div class="comment-author vcard">
<img alt="icon" {% if comment.writer.icon %} src="{{ comment.writer.icon.url }}" {% else %} src="{% static 'img/default_icon.png' %}" {% endif %} class="avatar avatar-55 photo" height="55" width="55">
<a href="{% url 'article:userpage' comment.writer %}">{{ comment.writer }}</a>
</div>
<div class="comment-meta commentmetadata">
<p>{{ comment.created_at|date:"Y-m-j" }}</p>
</div>
{% if user == comment.writer %}
<ul class="navbar-nav">
<li class="nav-item dropdown">
<a class="nav-link" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<img class="rounded-circle u-box-shadow-sm mr-2" width="25" height="25" src="{% static 'img/three_dots_icon.png' %}" alt="Icon">
</a>
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
<a class="dropdown-item" id="showModal" data-toggle="modal" data-target="#commentModal-{{ comment.pk }}">Edit</a>
<a class="dropdown-item" id="showModal2" data-toggle="modal" data-target="#deleteModal-{{ comment.pk }}">Delete</a>
</div>
<!-- Comment Edit Modal Dialog -->
<div class="modal fade bd-example-modal-lg" id="commentModal-{{ comment.pk }}" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Modify Your Comment</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<form class="text-center w-md-75 mx-auto" method="POST" enctype="multipart/form-data" action="{% url 'article:comment_update' comment.pk %}">
{% csrf_token %}
{{ form.non_field_errors }}
{{ form.media }}
{% for field in comment_form %}
<div class="col-xl-12 form-group mb-4">
{{ field|markdown|safe }}
{{ field.errors }}
</div>
{% endfor %}
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-lg btn-primary py-3 px-4" data-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-lg btn-primary py-3 px-4">Save changes</button>
</div>
</div>
</div>
</div>
<!-- Comment Delete Modal Dialog -->
<div class="modal fade bd-example-modal-lg" id="deleteModal-{{ comment.pk }}" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Do you really want to delete your comment?</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<p>{{ comment.text|markdown|safe }}</p>
</div>
<div class="modal-footer">
<form method="POST" action="{% url 'article:comment_delete' comment.pk %}">
{% csrf_token %}
<button type="button" class="btn btn-lg btn-primary py-3 px-4" data-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-lg btn-primary py-3 px-4">Delete</button>
</form>
</div>
</div>
</div>
</div>
</li>
</ul>
{% endif %}
<div class="comment-content">
<p>{{ comment.text|markdown|safe }}</p>
</div>
<div class="reply">
<a class="comment-reply-link" href="#"><span class="fa fa-comment-o"></span> Reply</a>
</div>
</div>
</li>
</div>
{% endfor %}
</section>
{% endif %}
Issues
As shown in the image, the markdown editor is displayed in a modal window, but the data before editing is not displayed and cannot be entered, and pressing the “save changes” button does not take the user to the URL specified in reverse_lazy
.
On the other hand, the ability to delete comments functioned correctly. I checked for typing errors in the modal dialog information (html id and form action specification values) and in the url pattern string, but found no such errors. I think it is probably due to the override method of get_context_data()
in views.py, but I didn’t know how to correct it.