from django import forms 
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Submit, Layout, Row, Column

class RegisterForm(UserCreationForm):

    class Meta:
        model = User 
        fields = ('username', 'email', 'password1', 'password2', 'first_name', 'last_name')
    
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        for field in self.fields:
            self.fields[field].help_text = False 

        self.helper = FormHelper(self)
        self.helper.layout = Layout(
            Row(
                Column('username', css_class='form-group col-md-6 mb-0'),
                Column('email', css_class='form-group col-md-6'),
                css_class='form-row'
            ),
            'first_name',
            'last_name',
            'password1',
            'password2'
        )
        self.helper.help_text_inline = False
        self.helper.error_text_inline = False 
        self.helper.add_input(Submit('submit', 'Register'))
    
    username = forms.CharField(min_length=5, required=True)
    

class LoginForm(forms.Form):
    username = forms.CharField(max_length=50)
    password = forms.CharField(widget=forms.PasswordInput)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.helper = FormHelper(self)
        self.helper.add_input(Submit('login','Login'))

account/forms.py
awesomedev08/Django-Blog
from django.urls import path

from .views import LoginView, RegisterView

app_name = 'account'

urlpatterns = [ 
    path('register', RegisterView.as_view(), name='register_page'),
    path('login',LoginView.as_view(), name='login_page')
]
account/urls.py
awesomedev08/Django-Blog
from django.http import HttpResponse
from django.shortcuts import redirect, render
from django.urls import reverse
from django.views.generic.edit import FormView
from django.contrib.auth import authenticate, login
from django.contrib import messages

from .forms import LoginForm, RegisterForm

class RegisterView(FormView):
    template_name = 'account/register.html'
    form_class = RegisterForm
    

    def dispatch(self, request, *args, **kwargs):
        if request.user.is_authenticated:
            return redirect(reverse('account:login_page'))

        return super(RegisterView, self).dispatch(request, *args, **kwargs)
    
    def get_success_url(self) -> str:
        return reverse('account:login_page')
    
    def form_valid(self, form):
        form.save()
        messages.success(self.request, 'Registered!!!!')
        return redirect(reverse('account:login_page'))
    
    
class LoginView(FormView):
    template_name = 'account/login.html'
    form_class = LoginForm

    def dispatch(self, request, *args, **kwargs):
        if request.user.is_authenticated:
            return redirect(reverse('dashboard:dashboard_index'))

        return super(LoginView, self).dispatch(request, *args, **kwargs)
    
    def get_success_url(self) -> str:
        return reverse('account:login_page')
    
    def post(self, request, *args, **kwargs):
        username = request.POST.get('username')
        password = request.POST.get('password')
        validate = authenticate(username=username, password=password)
        if validate:
            login(request, validate)
            return redirect(reverse('dashboard:dashboard_index'))
            
        messages.warning(request, 'invalid username/password')
        return redirect(reverse('account:login_page'))    


account/views.py
awesomedev08/Django-Blog
from django.apps import AppConfig
from django import template

register = template.Library()

class DashboardConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'dashboard'

dashboard/apps.py
awesomedev08/Django-Blog
from random import choices
from django.forms import ModelForm
from django import forms 
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Submit
from posts.models import Post, Category, Images

from django.contrib.auth.models import User
from django import forms
from django.contrib.auth.forms import PasswordChangeForm

class BlogForm(ModelForm):
    
    def __init__(self, *args, **kwargs):
        super(BlogForm, self).__init__(*args, **kwargs)
        
        self.helper = FormHelper(self)
        self.helper.add_input(Submit('submit','Add Blog '))
    
    class Meta:
        model = Post 
        fields = ('title','edit_permission','categories','body_text', 'img')
    
    edit_permission = forms.ChoiceField(required=False, 
                                        choices=(
                                            (0,'Anyone can edit'),
                                            (1,'Only admin and you can edit')
                                        )
                    )
    
    categories = forms.ModelMultipleChoiceField(required=False, 
                                        queryset=Category.objects.all(),
                                        widget=forms.CheckboxSelectMultiple)
    
    body_text = forms.Textarea(attrs={'id':'editor1'})
    title = forms.CharField(min_length=20, max_length=60)
    img = forms.ImageField(required=False)

class ProfileForm(ModelForm):

    def __init__(self, *args, **kwargs):

        super().__init__(*args, **kwargs)
        self.helper = FormHelper(self)

        for field in self.fields:
            self.fields[field].help_text = False 
        
        self.helper.add_input(Submit('submit', 'Submit'))

    
    class Meta:
        model = User
        fields = ('username', 'email', 'first_name', 'last_name')
    
    first_name = forms.CharField(required=False)
    last_name = forms.CharField(required=False)
    username = forms.CharField(required=False)

class ChangePasswordCustom(PasswordChangeForm):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.helper = FormHelper(self)
        self.helper.add_input(Submit('submit','Change'))
        
        for field in self.fields:
            self.fields[field].help_text = False 

class ImageUploads(ModelForm):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.helper = FormHelper(self)
        self.helper.add_input(Submit('submit','Add Images'))

    class Meta:
        model = Images    
        fields = ('name', 'img')
    


dashboard/forms.py
awesomedev08/Django-Blog
from django import template

register = template.Library()

@register.filter(name='ro')
def ro(value):
    return range(1, value+1)
dashboard/templatetags/my_filter.py
awesomedev08/Django-Blog
from django.urls import path 

from . import views 

app_name = 'dashboard'


urlpatterns = [
    path('', views.index_dashboard, name='dashboard_index'),
    path('add-blog', views.AddBlog.as_view(), name='add_blog'),
    path('logout', views.logout_user, name='logout'),
    path('edit-blog/<int:pk>', views.UpdateBLog.as_view(), name='edit_blog'),
    path('list-blog', views.ListsBlog.as_view(), name='lists_blog'),
    path('delete-blog/<int:pk>', views.DeleteBlog.as_view(), name='delete_blog'),
    path('<int:pk>/update-profile', views.Profile.as_view(), name='profile'),
    path('change-password', views.ChangePassword.as_view(), name='change_password'),
    path('upload-image', views.UploadImage.as_view(), name='upload_image'),
    path('list-images', views.ListImages.as_view(), name='list_images')
]
dashboard/urls.py
awesomedev08/Django-Blog
from django.shortcuts import redirect, render
from django.contrib.auth import logout
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.urls import reverse
from django.utils.decorators import method_decorator

from django.views.generic import (
                                FormView, 
                                UpdateView, 
                                ListView, 
                                DeleteView)
from django.db.models import Q
from django.contrib.auth import update_session_auth_hash
from django.contrib.auth.models import User
from django.contrib.auth.views import PasswordChangeView

from .forms import BlogForm, ProfileForm, ChangePasswordCustom, ImageUploads
from posts.models import Post, Images
import os 

@login_required(login_url='/account/login')
def index_dashboard(request):
    return render(request, template_name='dashboard/index.html')

@login_required(login_url='/account/login')
def logout_user(request):
    logout(request)
    messages.warning(request, 'Logged out')
    return redirect(reverse('account:login_page'))

class AddBlog(FormView):

    template_name = 'dashboard/add-blog.html'
    form_class = BlogForm

    def get_success_url(self, **kwargs) -> str:
        return reverse('dashboard:edit_blog', kwargs={'pk':kwargs.get('pk')})
    
    def form_valid(self, form):
        result = form.save(commit=False)
        result.author = self.request.user 
        result.save()
        
        messages.success(self.request, 'Blog Added')
        return redirect(self.get_success_url(pk=result.pk))
    
    @method_decorator(login_required(login_url='/account/login'))
    def dispatch(self, request, *args, **kwargs):
        return super().dispatch(request, *args, **kwargs)

class UpdateBLog(UpdateView):
    model = Post 
    form_class = BlogForm
    template_name = 'dashboard/add-blog.html'

    def get_success_url(self) -> str:
        return reverse('dashboard:edit_blog', args=(self.kwargs.get('pk'),))
    
    @method_decorator(login_required(login_url='/account/login'))
    def dispatch(self, request, *args, **kwargs):
        try:
            result = self.get_object()
            if result.author != request.user and (not request.user.is_superuser and result.edit_permission != 0):
                return redirect(reverse('dashboard:add_blog'))
        except Exception as e:
            print(e)
            return redirect(reverse('dashboard:add_blog'))
        return super().dispatch(request, *args, **kwargs)

class ListsBlog(ListView):
    model = Post 
    template_name = 'dashboard/lists-blog.html'
    context_object_name = 'blog_lists'
    paginate_by = 7

    @method_decorator(login_required(login_url='/account/login'))
    def dispatch(self, request, *args, **kwargs):
        return super().dispatch(request, *args, **kwargs)
    
    def get_queryset(self):
        if self.request.user.is_superuser:
            result = self.model.objects.all()
        else:
            result = self.model.objects.filter(
                    Q(author=self.request.user) | Q(edit_permission=0)
            )
        return result

class DeleteBlog(DeleteView):
    model = Post 
    template_name = 'dashboard/delete-blog.html'

    def get_success_url(self) -> str:
        return reverse('dashboard:lists_blog')
    
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['title'] = self.model.objects.get(pk=self.kwargs.get('pk')).title
        return context
    
    def form_valid(self, form):
        self.object = self.get_object()
        if os.path.exists(self.object.img.path):
            os.remove(self.object.img.path)
        self.object.delete()
        return redirect(self.get_success_url())

class Profile(UpdateView):
    template_name = 'dashboard/profile.html'
    model = User 
    form_class = ProfileForm

    def get_success_url(self) -> str:
        return reverse('dashboard:profile', kwargs={'pk':self.request.user.pk})
    
    def dispatch(self, request, *args, **kwargs):
        """
        validating, if user visit another edit profile page user, 
        it will be redirecting to user edit profile page 
        """
        if request.user.pk != self.kwargs.get('pk') and not request.user.is_superuser:
            return redirect(reverse('dashboard:profile', kwargs={'pk':request.user.pk}))
        return super().dispatch(request, *args, **kwargs)
    
    def form_valid(self, form):
        messages.success(self.request, 'Profile Edited!')
        return super().form_valid(form)

class ChangePassword(PasswordChangeView):
    form_class = ChangePasswordCustom
    template_name = 'dashboard/change-password.html'

    def get_success_url(self) -> str:
        return reverse('dashboard:change_password')
    
    def post(self, request, *args, **kwargs):
        form = self.get_form()
        if form.is_valid():
            update_session_auth_hash(request, request.user)
        return super().post(request, *args, **kwargs)

class UploadImage(FormView):
    model = Images
    form_class = ImageUploads
    template_name = 'dashboard/upload-images.html'
    
    def get_success_url(self) -> str:
        return reverse('dashboard:list_images')
    
    def form_valid(self, form):
        ins = form.save(commit=False)
        ins.author = self.request.user 
        ins.save()
        
        return super().form_valid(form)

class ListImages(ListView):
    model = Images
    context_object_name = 'images'
    
    template_name = 'dashboard/list-images.html'

    def get_queryset(self):
        return self.model.objects.filter(author=self.request.user)
    

dashboard/views.py
awesomedev08/Django-Blog
"""
Django settings for django_blog project.

Generated by 'django-admin startproject' using Django 4.0.1.

For more information on this file, see
https://docs.djangoproject.com/en/4.0/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/4.0/ref/settings/
"""

from pathlib import Path
import os 
import dj_database_url

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-8%(9o6ed=abewj#ex=+ov9afyn_6s9)wy8+_9vi1k&!97_%ny1'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = os.environ.get('DEBUG', default=False)

ALLOWED_HOSTS = ['0.0.0.0','127.0.0.1','.herokuapp.com','gibranabdillah.my.id']


# Application definition
CRISPY_TEMPLATE_PACK = 'bootstrap4'

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'account',
    'posts',
    'dashboard',
    'django_blog',
    'crispy_forms'
]

MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'whitenoise.middleware.WhiteNoiseMiddleware'
]

ROOT_URLCONF = 'django_blog.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

WSGI_APPLICATION = 'django_blog.wsgi.application'
LOGIN_REDIRECT_URL = '/account'

# Database
# https://docs.djangoproject.com/en/4.0/ref/settings/#databases

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }
}

if not DEBUG:
    db_from_env = dj_database_url.config(conn_max_age=500)
    DATABASES['default'].update(db_from_env)


# Password validation
# https://docs.djangoproject.com/en/4.0/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]



LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_TZ = True


STATIC_URL = '/static/'

STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static'),]
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'

DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

django_blog/settings.py
awesomedev08/Django-Blog
from django.contrib import admin
from django.urls import path, include, re_path
from django.conf.urls.static import static
from django.views.static import serve
from django.conf import settings
from django.shortcuts import render

def index_page(request):
    return render(request, 'main/index.html')

app_name = 'main'


urlpatterns = [
    path('', index_page, name='index_page'),
    path('admin/', admin.site.urls),
    path('account/', include('account.urls'), name='account'),
    path('blog/', include('posts.urls')),
    path('dashboard/', include('dashboard.urls'), name='dashboard'),
    re_path(r'^media/(?P<path>.*)$', serve,{'document_root': settings.MEDIA_ROOT}), 
    re_path(r'^static/(?P<path>.*)$', serve,{'document_root': settings.STATIC_ROOT}), 
]
    
if settings.DEBUG:
    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
django_blog/urls.py
awesomedev08/Django-Blog
# Generated by Django 4.0.1 on 2022-01-31 01:35

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

    initial = True

    dependencies = [
        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
    ]

    operations = [
        migrations.CreateModel(
            name='Post',
            fields=[
                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('created_at', models.DateField(auto_now_add=True)),
                ('body_text', models.TextField()),
                ('title', models.CharField(max_length=50)),
                ('slug', models.SlugField(unique=True)),
                ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
            ],
        ),
    ]

posts/migrations/0001_initial.py
awesomedev08/Django-Blog
# Generated by Django 4.0.1 on 2022-01-31 01:39

from django.db import migrations, models


class Migration(migrations.Migration):

    dependencies = [
        ('posts', '0001_initial'),
    ]

    operations = [
        migrations.AlterField(
            model_name='post',
            name='slug',
            field=models.SlugField(default='', editable=False, unique=True),
        ),
    ]

posts/migrations/0002_alter_post_slug.py
awesomedev08/Django-Blog
# Generated by Django 4.0.1 on 2022-02-09 01:58

from django.db import migrations, models


class Migration(migrations.Migration):

    dependencies = [
        ('posts', '0002_alter_post_slug'),
    ]

    operations = [
        migrations.CreateModel(
            name='Category',
            fields=[
                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('name', models.CharField(max_length=20)),
            ],
        ),
        migrations.AlterModelOptions(
            name='post',
            options={'ordering': ('created_at',)},
        ),
        migrations.AddField(
            model_name='post',
            name='edit_permission',
            field=models.IntegerField(choices=[(0, 'Anyone can edit '), (1, 'Only you and admin can edit')], default=1),
        ),
        migrations.AddField(
            model_name='post',
            name='categories',
            field=models.ManyToManyField(default='uncategorized', to='posts.Category'),
        ),
    ]

posts/migrations/0003_category_alter_post_options_post_edit_permission_and_more.py
awesomedev08/Django-Blog
# Generated by Django 4.0.1 on 2022-02-12 14:51

from django.db import migrations, models
import django.utils.timezone


class Migration(migrations.Migration):

    dependencies = [
        ('posts', '0003_category_alter_post_options_post_edit_permission_and_more'),
    ]

    operations = [
        migrations.AddField(
            model_name='post',
            name='img',
            field=models.ImageField(default=django.utils.timezone.now, upload_to='blog'),
            preserve_default=False,
        ),
    ]

posts/migrations/0004_post_img.py
awesomedev08/Django-Blog
# Generated by Django 4.0.1 on 2022-02-12 15:01

from django.db import migrations, models


class Migration(migrations.Migration):

    dependencies = [
        ('posts', '0004_post_img'),
    ]

    operations = [
        migrations.AlterField(
            model_name='post',
            name='img',
            field=models.ImageField(upload_to='images'),
        ),
    ]

posts/migrations/0005_alter_post_img.py
awesomedev08/Django-Blog
# Generated by Django 4.0.1 on 2022-02-15 10:05

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

    dependencies = [
        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
        ('posts', '0005_alter_post_img'),
    ]

    operations = [
        migrations.CreateModel(
            name='Images',
            fields=[
                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('img', models.ImageField(upload_to='images')),
                ('name', models.CharField(default='Undefined', max_length=40, null=True)),
                ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
            ],
        ),
    ]

posts/migrations/0006_images.py
awesomedev08/Django-Blog
# Generated by Django 4.0.1 on 2022-02-15 10:21

from django.db import migrations, models


class Migration(migrations.Migration):

    dependencies = [
        ('posts', '0006_images'),
    ]

    operations = [
        migrations.AlterField(
            model_name='images',
            name='name',
            field=models.CharField(default='Undefined', max_length=40),
        ),
    ]

posts/migrations/0007_alter_images_name.py
awesomedev08/Django-Blog
from random import choice
from django.db import models
from django.contrib.auth.models import User
from django.utils.text import slugify

import os


CHARS = 'abcdefghijklmnopqrstuvwxyz0123456789'

PERMISSION_CHOICES = (
    (0, 'Anyone can edit '),
    (1, 'Only you and admin can edit')
)


class Category(models.Model):
    name = models.CharField(max_length=20)

    def __str__(self):
        return self.name

class Post(models.Model):


    created_at = models.DateField(auto_now_add=True)
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    body_text = models.TextField()
    title = models.CharField(max_length=50)
    slug = models.SlugField(unique=True, default='', editable=False)
    img = models.ImageField(upload_to='images')
    
    edit_permission = models.IntegerField(choices=PERMISSION_CHOICES, default=1)

    categories = models.ManyToManyField(Category, default='uncategorized')

    def __str__(self):
        return self.title
    
    def save(self, *args, **kwargs):
        
        title = self.title + '-' + ''.join(choice(CHARS) for _ in range(5))
        self.slug = slugify(title, allow_unicode=False)
        super().save(*args, **kwargs)
    
    class Meta:
        ordering = ('created_at',)
    
    

class Images(models.Model):
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    img = models.ImageField(upload_to='images')
    name = models.CharField(default='Undefined', max_length=40)

    def __str__(self):
        return self.name
posts/models.py
awesomedev08/Django-Blog
from django import template
from django.template.loader import get_template
from posts.models import Post, Category
import re , os 
register = template.Library()

@register.simple_tag(takes_context=True)
def recent_posts(context):
    return Post.objects.all().order_by('-created_at')[0:5]

@register.simple_tag(takes_context=True)
def get_categories(context):
    # only show for 6 categories 
    return Category.objects.all()[0:6]
    
@register.simple_tag(takes_context=True)
def all_categories(contest):
    return Category.objects.all()

@register.filter(name='short_description')
def short_description(value, l=200):
    return ' '.join(re.findall('<p>([\w\d\s\,\.\-]+)</p>', value))[0:int(l)]

@register.filter(name='is_exists')
def is_exists(value):
    if value and os.path.exists(value.path):
        return True 
posts/templatetags/post_tags.py
awesomedev08/Django-Blog
from django.urls import re_path, path

from . import views

app_name = 'blog'

urlpatterns = [
    re_path('detail/(?P<slug>[\w\d-]+)$', views.BlogDetail.as_view(), name='detail_blog'),
    re_path('category/(?P<slug>[\w\d-]+)$', views.PostCategories.as_view(), name='category'),
    path('list-categories', views.ListCategories.as_view(), name='list_categories'),
    path('', views.BlogList.as_view(), name='bloglist')
]

posts/urls.py
awesomedev08/Django-Blog
from re import template
from django.views.generic import DetailView, ListView
from .models import Post, Category

class BlogDetail(DetailView):
    model = Post 
    context_object_name = 'post'
    template_name = 'posts/show-blog.html'

class BlogList(ListView):
    template_name = 'posts/index.html'
    context_object_name = 'posts'
    model = Post 

class PostCategories(ListView):
    """
    Show Posts by Category
    """
    template_name = 'posts/index.html'
    context_object_name = 'posts'
    model = Post 
    
    def get_queryset(self):
        return self.model.objects.filter(categories__name=self.kwargs.get('slug').title()).all()

class ListCategories(ListView):
    template_name = 'posts/list-categories.html'
    model = Category
    context_object_name = 'categories'

    def get_queryset(self):
        return self.model.objects.all().order_by('-name').reverse()

posts/views.py
awesomedev08/Django-Blog