I have a forum app, its structure is “forum → subforum → topic → comment”, and now I’m trying to implement the inner (not via admin page) addition of topics and comments. As I also have an authorization system (by embedded User class), after successful authorization the username must be autosubstituted in case of creating a new topic/comment.
Well, if I was able to google down to solution for “user” field (form.instance.author = self.request.user
), then in case of comment addition this scheme doesn’t work, because it demands a “topic” Foreign Key, which is, somehow, not so easy to obtain. With my desperate googling (e.g., here: Automatically Foreign Key in Form. How?? - #3 by nimdagh) I’ve tried something like that:
AddComment view from views.py:
class AddComment(LoginRequiredMixin, DataMixin, CreateView):
model = Comment
form_class = AddCommentForm
template_name = 'forum/addcomment.html'
page_title = 'Leave a comment'
success_url = reverse_lazy('topic')
def form_valid(self, form):
topic = self.model.objects.get(topic=self.kwargs['topic'])
form.instance.author = self.request.user
form.instance.topic = topic
return super(AddComment, self).form_valid(form)
But it returned me a KeyError:
KeyError at /forum/production/topics/topic-scenario-timing-plan/add_comment/
'topic_id'
Request Method: POST
Request URL: http://127.0.0.1:8000/forum/production/topics/topic-scenario-timing-plan/add_comment/
Django Version: 5.1.1
Exception Type: KeyError
Exception Value:
'topic_id'
Exception Location: <path>\django_project\forum\views.py, line 71, in form_valid
Raised during: forum.views.AddComment
Python Executable: D:\PyCharm Community Edition 2024.1.3\PycharmProjects\django_project\.venv\Scripts\python.exe
Python Version: 3.12.3
I’ve tried to use lookups (topic = self.model.objects.get(topic__id=self.kwargs['topic_id'])
), but they weren’t useful as well. What’s more interesting, when I used a field that wasn’t included (like, topic = self.model.objects.get(topic_slug=self.kwargs['topic_slug'])
), the program returned an error, suggesting me a list of fields, among which were both “topic”, and “topic_id”. I’d really like to see what the hell is going on inside those “self.kwargs”, but cannot achieve them.
My question(s) is/are:
- What is the optimal way to implement this automatical setting of Foreign keys inside Django forms (only authorised users may create a topic and leave comments, so for comments addition there must be a User and Topic foreign keys)?
- (Optional) How do those “get_form_kwargs”, “get_initial”, “form_valid” and alike work with forms, what is better to use basing on the situation? I’ve searched through a number of sources, Django docs as well, and noone of them approaches this problem scrupulous enough.
forms.py:
from django import forms
from django.core.exceptions import ValidationError
from forum import models
class AddTopicForm(forms.ModelForm):
subject = forms.CharField(label="Заголовок", max_length=100, min_length=7)
first_comment = forms.CharField(label="Сообщение", widget=forms.Textarea())
class Meta:
model = models.Topic
fields = ['subject', 'first_comment']
def clean_subject(self):
subject = self.cleaned_data['subject']
if len(subject) > 100:
raise ValidationError("Длина превышает 100 символов")
if len(subject) < 7:
raise ValidationError("Слишком короткое заглавие, требуется не менее 7 символов")
return subject
class AddCommentForm(forms.ModelForm):
content = forms.CharField(label="Text", max_length=2000, min_length=1, widget=forms.Textarea())
class Meta:
model = models.Comment
fields = ['content']
Topic and Comment models from models.py:
class Topic(models.Model):
subject = models.CharField(verbose_name='Заголовок', max_length=255, unique=True)
first_comment = models.TextField(verbose_name='Сообщение', max_length=2000, default='')
slug = models.SlugField(default='', unique=True, max_length=25, editable=False)
subforum = models.ForeignKey('Subforum',
verbose_name='Раздел',
on_delete=models.CASCADE,
related_name='subforum')
creator = models.ForeignKey(User,
verbose_name='Создатель темы',
on_delete=models.SET('deleted'),
related_name='creator')
created = models.DateTimeField(auto_now_add=True)
closed = models.BooleanField(default=False)
objects = models.Manager()
class Meta:
ordering = ['id']
verbose_name = 'Обсуждения'
verbose_name_plural = 'Обсуждения'
def __str__(self):
return self.subject
def save(self, *args, **kwargs):
if not self.id:
self.slug = f'topic-{slugify(self.subject)}'
return super(Topic, self).save(*args, **kwargs)
def get_absolute_url(self):
return reverse('forum:topic', kwargs={'topic_slug': self.slug, 'subforum_slug': self.subforum.slug})
class Comment(models.Model):
topic = models.ForeignKey('Topic',
verbose_name='Тема',
on_delete=models.CASCADE,
related_name='comments')
author = models.ForeignKey(User,
verbose_name='Комментатор',
on_delete=models.SET('deleted'),
related_name='author')
content = models.TextField(verbose_name='Текст', max_length=2000)
created = models.DateTimeField(verbose_name='Дата публикации', auto_now_add=True)
updated = models.DateTimeField(verbose_name='Дата изменения', auto_now=True)
objects = models.Manager()
class Meta:
ordering = ['created']
verbose_name = 'Комментарии'
verbose_name_plural = 'Комментарии'
def __str__(self):
return f'Post of {self.topic.subject} is posted by {self.author.username}.'
urls.py:
from django.urls import path
from forum.views import *
app_name = 'forum'
urlpatterns = [
path('', SubForumListView.as_view(), name='forum'),
path('<slug:subforum_slug>/', TopicListView.as_view(), name='subforum'),
path('<slug:subforum_slug>/add_topic/', AddTopic.as_view(), name="add_topic"),
path('<slug:subforum_slug>/topics/<slug:topic_slug>/', ShowTopic.as_view(), name='topic'),
path('<slug:subforum_slug>/topics/<slug:topic_slug>/add_comment/', AddComment.as_view(), name="add_comment"),
path('<slug:subforum_slug>/topics/<slug:topic_slug>/edit/<int:id>/', UpdateComment.as_view(), name="edit_comment"),
If any additional info is necessary, I’m ready to provide it.