Automatically add a category to a blog post

Hi there, I’m having some trouble doing something relatively straightforward.

I’m trying to extend a blog project with some additional functionality when creating a post. Essentially, to auto-populate the category without the user having to select it in the form.

In my project, the blog posts are grouped to categories:

models.py

class Category(models.Model):
    title = models.CharField(max_length=255)
    slug = models.SlugField(max_length=255, null=True)

class Post(models.Model):
    title = models.CharField(max_length=255)
    slug = models.SlugField(max_length=255, null=True)
    body = models.TextField()
    category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name='posts')

Because there are potentially lots of categories, I want to avoid the user having to select from a dropdown. Instead this ideally should be populated on form submission.

From reading around and trying to solve the problem, one solution was to use the category slug from the url, and use that in the view to retrieve the topic.

The user clicks “New post” from within each category list view.

urls.py

urlpatterns = [
    path('category/<slug:slug>/', views.category_list, name='category_list'),
    path('category/<slug:slug>/new/', views.ThreadCreateView.as_view(), name='thread_create'),
]

Here is the code for the views. When it comes to trying to retrieve the category using get_slug_field I’m completely fumbling around in the dark. I’m sure there’s a really obvious way to do it that I’m missing!

views.py

def category_list(request, slug):
    object_list = Post.visible.all()
    viewed_category = get_object_or_404(Category, slug=slug)
    object_list = object_list.filter(category__in=[viewed_category])

    paginator = Paginator(object_list, 3)
    page = request.GET.get('page')
    try:
        posts = paginator.page(page)
    except PageNotAnInteger:
        posts = paginator.page(1)
    except EmptyPage:
        posts = paginator.page(paginator.num_pages)
    return render(request, 'blog/category_list.html', {'page': page, 'posts': posts, 'category': viewed_category})


class PostCreateView(CreateView):
    model = Post
    fields = ['title', 'body']
    success_url = reverse_lazy('forum:category_list')
    template_name = 'blog/post_create.html'

    def get_slug_field(self, request, slug):
        category = get_object_or_404(Category, slug=slug)
        return category

    def form_valid(self, form):
        form.instance.category = category
        form.instance.author = self.request.user
        form.instance.slug = slugify(form.instance.title)
        return super().form_valid(form)

This may or may not be production-ready code, but everything here works (adding the user, slugifying the title etc.) APART from the last bit - i.e. adding the category to the form.

Any help or pointers would be greatly appreciated! Many thanks, Tom

I think you could override get_initial on the view to look up the Category instance and populate the category field for your form. https://ccbv.co.uk/projects/Django/3.0/django.views.generic.edit/CreateView/#get_initial

Thanks very much @mblayman! Have managed to get it working now.

1 Like