3

I am trying to create a blog, and want that articles were devided by categories. Version of Django:2.1, Python:3.7 I don't understand how should I change the path in the urlpatterns for displaying the name of category, not <int:pk>. Where can I check examples of the Django code?

I tried to do path('category.title') but it doesn't work.

This is my models.py:

from django.db import models
from django.conf import settings
from django.urls import reverse

class Category(models.Model):
    title = models.CharField(max_length=50)
    image = models.ImageField(upload_to='', blank=True)

    def __str__(self):
        return self.title

class Article(models.Model):
    title = models.CharField(max_length=255)
    body = models.TextField()
    date = models.DateTimeField(auto_now_add=True)
    author = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE
    )
    image = models.ImageField(upload_to='', blank=True)
    cat = models.ForeignKey(
        Category, on_delete=models.CASCADE, null=True,
    )

    def __str__(self):
        return self.title

    def get_absolute_url(self):
        return reverse('article_detail', args=[str(self.id)])

class Comment(models.Model):
    article = models.ForeignKey(Article, on_delete=models.CASCADE, related_name='comments')
    comment = models.CharField(max_length=100)
    author = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
    )

    def __str__(self):
        return self.comment

    def get_absolute_url(self):

     return reverse('article_list')

This is urls.py:

from django.urls import path
from . import views
from .models import Category

urlpatterns = [
    path('', views.ArticleViewList.as_view(), name='article_list'),
    path('<int:pk>/edit/', views.ArticleUpdateView.as_view(), name='article_edit'),
    path('<int:pk>/delete/', views.ArticleDeleteView.as_view(), name='article_delete'),
    path('<int:pk>', views.ArticleDetailView.as_view(), name='article_detail'),
    path('new/', views.ArticleCreateView.as_view(), name='article_new'),
]

This is class-based view for returning the list of articles:

 class ArticleViewList(LoginRequiredMixin, ListView):
    model = models.Article
    template_name = 'article_list.html'
    login_url = 'login'

1)I want that in the adress-bar was displaying the title of the category 2)I want to know is it good decision to use class-based views 3)I would like to know how to do in template loop for displaying articles of the particular category

2 Answers2

2

Expecting you want your urls to looks like:
/ - list of all posts, regardless of categories
/category1 - list of all posts from category 1
/category1/1 - details of the post #1 (optionally, must or must not belong to category1)

You will need to do couple of things:
1) You will need to add SlugField to your category model - that's the actual string that will be in url, since there are some restrictions on what can be in url.

models.py:

class Category(models.Model):
    title = models.CharField(max_length=50)
    slug = models.SlugField(unique=True)
    image = models.ImageField(upload_to='', blank=True)

    def __str__(self):
        return self.title

2) Modify urlpatterns to require slug

urls.py:

urlpatterns = [
    path('', ArticleViewList.as_view(), name='article_list'),
    path('<slug:catslug>', ArticleViewList.as_view(), name='article_list'),
    path('<slug:catslug>/<int:pk>', ArticleDetailView.as_view(), name='article_detail'),
]

3) Add queryset filter for your ArticleViewList so you will only include articles from specific category by their slug field value.

views.py:

from django.shortcuts import get_object_or_404

class ArticleViewList(LoginRequiredMixin, ListView):
    model = Article
    template_name = 'article_list.html'
    login_url = 'login'

    def get_queryset(self):
        queryset = super().get_queryset()
        if 'catslug' in self.kwargs:
            category_slug = self.kwargs['catslug']
            category = get_object_or_404(Category, slug=category_slug)
            queryset = queryset.filter(cat=category)
        return queryset

4) (Optional) Force check on your article details view that it belongs to certain category from url by modifying queryset as well.

views.py:

class ArticleDetailView(LoginRequiredMixin, DetailView):
    model = Article
    template_name = 'article_item.html'
    login_url = 'login'

    def get_queryset(self):
        queryset = super().get_queryset()
        category_slug = self.kwargs['catslug']
        category = get_object_or_404(Category, slug=category_slug)
        queryset = queryset.filter(cat=category)
        return queryset

It is good decision to use CBV - since all your views are simple and exactly what CBV was designed for. It will allow you to have small and readable code.

You can use {% regroup %} to group articles by category:

article_list.html:

{% regroup object_list by cat as categories %}

{% for category in categories %}
    {{ category.grouper }}:</br>
    {% for object in category.list %}
        {{ object }}<br>
    {% endfor %}
{% endfor %}
Gasanov
  • 2,839
  • 1
  • 9
  • 21
  • Спасибо большое за помощь! Единственный вопрос, что такое `self.kwargs` ? – KochetovMXM May 11 '19 at 08:44
  • @KochetovMXM It's name based attributes, in this case used for dynamic filltering. You can read more on [django docs](https://docs.djangoproject.com/en/2.2/topics/class-based-views/generic-display/#dynamic-filtering) – Gasanov May 11 '19 at 09:03
  • Подскажи пожалуйста, как ты учил джанго и сколько времени это заняло? – KochetovMXM May 11 '19 at 09:15
  • Week to get it started and following [official django tutorial](https://docs.djangoproject.com/en/2.2/intro/tutorial01/). I had some background before django. Django has amazing docs, you can google most of the problems and it's easier to learn on practice. – Gasanov May 11 '19 at 09:42
0

Using class based view is fine but also using functional view also works fine in fact am using functional in this case but you can just change some few things and will work for the class base one also. they both work almost the same provided you know what you want. the question about using title in the link instead of primary key is here.

django will escape spaces and special characters in the url by encoding it to safe format but will still understand it. that is why I think this idea is not the best compared to using the unique ID but if you still need it then,

maybe you need something like this in your url

from django.conf.urls import url
url(regex=r'^(?P<category>[\w ]+)/$', view=category_article, name='category_articles'),

and in your view

from.models import Category, Article
def category_article(request, category):
    cat = Category.objects.get(title=category)
    categories = Category.objects.all()
    articles_in_category = Article.objects.filter(cat=cat)

    context = {
   'categories':categories,
   'articles':articles_in_category,
    }
    // render this two now to template
   //I could not coz I dont know path to your template


you see when you want to make a link to this, you use title instead

{% for category in categories %}
<a href="{% url 'category_articles' category.title %}">{{category.title}}</a>
{% endfor %}

or if you wanted to use an article to point to other element element of its category

{% for article in articles %}
<a href="{% url 'category_articles' article.cat.title %}">{{category.title}}</a>
{% endfor %}

Your question was not so clear to me but hope I am answering your quiz,

kim
  • 159
  • 12