Watched all the previous related topics devoted to this problem, but haven't found a proper solution, so decided to create my own question.
I'm creating a forum project (as a part of the site project). Views are made via class-based views: SubForumListView leads to a main forum page, where its main sections ("subforums") are listed. TopicListView, in its turn, leads to the pages of certain subforums with the list of active topics created within the subforum. ShowTopic view leads to a certain topic page with a list of comments.
The problem manifests itself because of:
get_absolute_url
in the model Subforum
, which in its return section's reverse
function takes a view as the 1st argument; I've tried to avoid a direct import of the view, but the program doesn't accept other variants;model = Subforum
), or in methods using querysets (like in get_context_data
: topics = Topic.objects.all()
); I can't surely say whether the change of instance argument model = Subforum
to model = 'Subforum'
really helps, as it's impossible to do with queryset methods and thus can't be proved;forms.ModelForm
and include class Meta
, where the model
instance argument is provided the same way as in 2): model = Topic
. For now I've commented them (again, without being sure whether it was helpful or not), as well as the import of models, but when they were active, there was a triple circular import "models-views-forms" (funny enough).I see this problem, I know what and where provokes it, but I don't know how to solve it, that is: I don't know how to better define views and forms (or, maybe, models with their "get_absolute_url" methods) to avoid CI and how to better organize the connection between different parts of the program.
Corresponding files:
models.py:
from django.db import models
from django.contrib.auth.models import User
from django.urls import reverse
from django.utils.text import slugify
from .consts import *
from .views import TopicListView, ShowTopic
'''
class User(AbstractUser):
class Meta:
app_label = 'forum'
'''
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
surname = models.CharField(max_length=32, default='')
name = models.CharField(max_length=32, default='')
email = models.EmailField(max_length=254, blank=True, unique=True)
bio = models.TextField(max_length=500, default="Write a couple of words about yourself")
avatar = models.ImageField(default=None, blank=True, max_length=255)
status = models.CharField(max_length=25, blank=True, default='')
slug = models.SlugField()
age = models.IntegerField(verbose_name='Возраст', null=True, blank=True)
gender = models.CharField(verbose_name='Пол', max_length=32, choices=Genders.GENDER_CHOICES, default="H", blank=True)
reputation = models.IntegerField(verbose_name='Репутация', default=0)
def __str__(self):
return f'{self.user} profile'
def get_absolute_url(self):
return reverse('user_profile', kwargs={'profile_slug': self.slug})
def save(self, *args, **kwargs):
if not self.id:
self.slug = slugify(self.user.username)
return super(Profile, self).save(*args, **kwargs)
class Subforum(models.Model):
title = models.CharField(verbose_name='Название', max_length=32, choices=Theme.THEME_CHOICES, default=1)
slug = models.SlugField(default='News')
objects = models.Manager()
class Meta:
ordering = ['title']
verbose_name = 'Разделы форума'
verbose_name_plural = 'Разделы форума'
def __str__(self):
return self.title
def save(self, *args, **kwargs):
if not self.id:
self.slug = slugify(self.title)
return super(Subforum, self).save(*args, **kwargs)
def get_absolute_url(self):
return reverse(TopicListView, kwargs={'name': self.title, 'subforum_slug': self.slug})
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)}'[0:25]
return super(Topic, self).save(*args, **kwargs)
def get_absolute_url(self):
return reverse(ShowTopic, kwargs={'topic_slug': self.slug})
class Comment(models.Model):
topic = models.ForeignKey('Topic',
verbose_name='Тема',
on_delete=models.CASCADE,
related_name='topic')
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}.'
views.py:
from django.contrib.auth.mixins import LoginRequiredMixin
from django.shortcuts import get_object_or_404
from django.urls import reverse_lazy
from django.views.generic import ListView, DetailView, CreateView, UpdateView, DeleteView
from core.views import menu
from .forms import AddTopicForm, AddCommentForm
from .models import Subforum, Topic, Comment, Profile
from .utils import DataMixin
class SubForumListView(ListView):
model = Subforum
context_object_name = 'subforum_list'
template_name = "forum/forum.html"
def get_context_data(self, **kwargs):
subforums = Subforum.objects.all()
context = {'subforums': subforums}
return context
class TopicListView(ListView):
model = Topic
template_name = "forum/subforum.html"
slug_url_kwarg = 'subforum_slug'
context_object_name = 'subforum'
def get_context_data(self, **kwargs):
topics = Topic.objects.all()
context = {'topics': topics}
return context
class ShowTopic(DetailView):
model = Topic
template_name = "forum/topic.html"
slug_url_kwarg = 'topic_slug'
context_object_name = 'topic'
def get_context_data(self, topic_slug, **kwargs):
topic = get_object_or_404(Topic, slug=topic_slug)
comments = Comment.objects.filter(topic=topic)
comments_number = len(Comment.objects.filter(topic=topic))
context = {'menu': menu,
'topic': topic,
'comments': comments,
'comm_num': comments_number}
return context
class AddTopic(LoginRequiredMixin, DataMixin, CreateView):
form_class = AddTopicForm
template_name = 'forum/addtopic.html'
page_title = 'Создание новой темы'
class AddComment(LoginRequiredMixin, DataMixin, CreateView):
form_class = AddCommentForm
template_name = 'forum/addcomment.html'
page_title = 'Оставить комментарий'
success_url = reverse_lazy('topic')
class UpdateComment(LoginRequiredMixin, DataMixin, UpdateView):
form_class = AddCommentForm
template_name = 'forum/addcomment.html'
page_title = 'Редактировать комментарий'
success_url = reverse_lazy('topic')
class UserProfile(DetailView):
model = Profile
template_name = "profile.html"
forms.py:
from django import forms
from django.core.exceptions import ValidationError
#from forum.models import Topic, Comment
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 = 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="Текст комментария", max_length=2000, min_length=1, widget=forms.Textarea())
class Meta:
#model = Comment
fields = ['content']
I am not sure whether it's necessary or not, but also urls.py for you:
from django.urls import path
from forum.views import *
urlpatterns = [
#path('<slug:profile_slug>/', user_profile, name='user_profile'),
path('', SubForumListView.as_view(), name='forum'),
path('<slug:subforum_slug>/', TopicListView.as_view(), name='subforum'),
path('subforum/<slug:topic_slug>/', ShowTopic.as_view(), name='topic'),
path('subforum/add-topic/', AddTopic.as_view(), name="add_topic"),
path('subforum/<slug:topic_slug>/add-comment/', AddComment.as_view(), name="add_comment"),
path('subforum/<slug:topic_slug>/edit/<int:id>/', UpdateComment.as_view(), name="edit_comment"),
If some additional files/information are necessary, I'm ready to provide them. For now, I can't continue implementation of the project, as CI doesn't allow me to test the forum page. So I have to solve this problem before any further actions.
Don't import views in the models. Views should depend on models, never in the opposite way.
You can use the name of the path, so:
class Subforum(models.Model):
# …
def get_absolute_url(self):
return reverse(
'subforum',
kwargs={'name': self.title, 'subforum_slug': self.slug},
)
The same for ShowTopic
, and thus remove the imports.
Note: You can make use of
django-autoslug
[GitHub] to automatically create a slug based on other field(s).