/django-comment-system

Django comment system using Tailwindcss.

Primary LanguageCSSMIT LicenseMIT

Django Comment System

PyPI version PyPI - Downloads GitHub

Here is a live demo

Table of Contents

Installation

  1. Install using pip

    python -m pip install django-comment-system

    or Clone the repository then copy comment folder and paste in project folder.

    git clone https://github.com/mahyar-amiri/django-comment-system.git

Configuration

  1. Add comment.apps.CommentConfig to installed_apps in the settings.py file after django.contrib.auth.

    # setting.py
    from django.urls import reverse_lazy
    
    INSTALLED_APPS = [
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
    
        # MY APPS
        'comment.apps.CommentConfig',
    ]
    
    # your account login url
    LOGIN_URL = 'admin:login'  # or reverse_lazy('admin:login')  
    
    MEDIA_URL = '/media/'
    MEDIA_ROOT = BASE_DIR / 'media'
  2. Add path('comment/', include('comment.urls')), to urlpatterns in the urls.py file.

    # urls.py
    
    from django.urls import path, include
    
    urlpatterns = [
         path('comment/', include('comment.urls')),
    ]
    
    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
  3. Connect comments to target model. In models.py add the field comments as a GenericRelation field to the required model.

    NOTE: Please note that the field name must be comments NOT comment.

    # models.py
    
    from django.db import models
    from django.contrib.contenttypes.fields import GenericRelation
    from comment.models import Comment
    
    class Article(models.Model):
        title = models.CharField(max_length=20)
        content = models.TextField()
        # the field name should be comments
        comments = GenericRelation(Comment)
  4. Do migrations

    python manage.py migrate

Usage

  1. In the template (e.g. post_detail.html) add the following template tags where obj is the instance of post model.

    {% load comment_tags %}
  2. Add the following template tags where you want to render comments.

    {% render_comments request obj settings_slug='default-config' %}  {# Render all the comments belong to the passed object "obj" #}

    if your context_object_name is not obj (e.g. article) replace obj with context_object_name.

    {% render_comments request obj=article settings_slug='default-config' %}

Settings

Global Settings

You can customize global settings by adding keywords to COMMENT_SETTINGS dictionary in project settings.py.

# setting.py
COMMENT_SETTINGS = {
    # generated urlhash length
    'URLHASH_LENGTH': 8,

    # if True, tailwindcss and jquery package will be loaded from static files.
    'OFFLINE_IMPORTS': True,

    # if None, comments will be shown without profile image
    # you should set this value as profile image field name
    # for example our abstract user profile picture field is profile_image
    # <img src="{{ user.profile_image.url }}" /> so we set PROFILE_IMAGE_FIELD = 'profile.image'
    # see link blew to create abstract user model
    # https://docs.djangoproject.com/en/4.1/topics/auth/customizing/#substituting-a-custom-user-model
    'PROFILE_IMAGE_FIELD': None,
}

Config Settings

This settings can be configured in admin panel. Set your config in CommentSettings model.

# image file for default profile image, it null the image will not be shown
DEFAULT_PROFILE_IMAGE

# the comments need to be set as a(Accepted) to be shown in the comments list.
# if True, comment status will be set as d(Delivered) otherwise it will be set as a(Accepted).
STATUS_CHECK = False
# the comments need to be set as a(Accepted) to be edited.
# if True, comment status will be set as d(Delivered) otherwise it will be set as a(Accepted).
STATUS_EDITED_CHECK = False

# activate spoiler comment mode
ALLOW_SPOILER = True
# let users reply to a comment
ALLOW_REPLY = True
# let users edit their comment
ALLOW_EDIT = True
# let users delete their comment
ALLOW_DELETE = True

# more than this value will have Read More button in comment content
CONTENT_WORDS_COUNT = 40

# let users react to a comment
ALLOW_REACTION = True
# get emoji or from file source
REACTION_TYPE = 'emoji'  # emoji / source

# number of comments per page
# set 0 if you don't want pagination
PER_PAGE = 10

TIME_TYPE = 1  # 1.both 2.from_now 3.date_time
TIME_DAYS = 3  # less will use type 2 , more will use type 3

# set direction of comment section
THEME_DIRECTION = 'ltr'  # ltr / rtl
# set True for dark mode
THEME_DARK_MODE = False

Reactions

  1. Use admin panel to add react emoji. you will need an emoji and an emoji name as slug. You can use image or gif instead of emoji character: In your admin panel, add image or gif file in React object.

  2. Add MEDIA_URL and MEDIA_ROOT in settings.py.

    # settings.py
    
    MEDIA_URL = '/media/'
    MEDIA_ROOT = BASE_DIR / 'media'
  3. Add root to urlpatterns in project urls.py.

    # urls.py
    
    from django.conf import settings
    from django.conf.urls.static import static
    
    urlpatterns = [...]
    
    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
  4. Rerun project and setup Reaction type to source in admin panel Comment Settings.

Translation

  1. Add locale folder to your app folder.

  2. Run command below to create django.po file for your language.

    Find your language code here.

    python manage.py makemessages -l MY_LANGUAGE_CODE
    # for generating translations corresponding to javascript code
    python manage.py makemessages -l MY_LANGUAGE_CODE -d djangojs

    e.g. The persian language code is fa.

    python manage.py makemessages -l fa
    python manage.py makemessages -l fa -d djangojs

    This will create two .po files inside the locale/{MY_LANGUAGE_CODE}/LC_MESSAGES/ directory.

  3. After adding translation to both files, run the following command to verify everything is working.

    python manage.py compilemessages -l MY_LANGUAGE_CODE
    # e.g. for persian translation use fa instead of MY_LANGUAGE_CODE

    If you don't see an error in the last command, your translations have been added in the correct format.

  4. In settings.py to enable internationalization in your django applications.

    # settings.py
    
    USE_I18N = True
    USE_L18N = True
    LANGUAGE_CODE = '{MY_LANGUAGE_CODE}'  # 'en-us' for english , 'fa-ir' for persian , ...

Front-End

Tailwind Colors Customization

colors: {
   // LIGHT
   'text-light': '#ffffff',
   'background-light': '#f8fafc',
   // TEXTAREA
   'textarea-bg-light': '#e5e7eb',
   'textarea-scroll-light': '#9ca3af',
   'textarea-text-light': '#000000',
   'textarea-text-selection-light': '#c7d2fe',
   'textarea-text-placeholder-light': '#6b7280',
   'textarea-border-empty-light': '#f87171',
   // ICON
   'icon-spoiler-light': '#6b7280',
   'icon-spoiler-option-light': '#111827',
   'icon-dots-light': '#6b7280',
   'icon-pin-light': '#6b7280',
   'icon-edit-light': '#16a34a',
   'icon-delete-light': '#ef4444',
   'icon-pagination-light': '#9ca3af',
   'icon-pagination-hover-light': '#374151',
   // BUTTON
   'btn-send-bg-light': '#000000',
   'btn-send-text-light': '#ffffff',
   'btn-edit-bg-light': '#16a34a',
   'btn-edit-text-light': '#ffffff',
   'btn-reply-bg-light': '#2563eb',
   'btn-reply-text-light': '#ffffff',
   'btn-delete-bg-light': '#dc2626',
   'btn-delete-text-light': '#ffffff',
   'btn-cancel-bg-light': '#6b7280',
   'btn-cancel-text-light': '#ffffff',
   'btn-login-text-light': '#1d4ed8',
   // DELETE FORM
   'delete-from-bg-light': '#ffffff',
   'delete-from-text-light': '#111827',
   'delete-from-subtext-light': '#6b7280',
   // COUNTER
   'section-primary-light': '#e5e7eb',
   'section-secondary-light': '#000000',
   'section-text-light': '#000000',
   'section-number-bg-light': '#e5e7eb',
   'section-number-text-light': '#000000',
   // PAGINATION
   'page-current-bg-light': '#000000',
   'page-current-text-light': '#ffffff',
   'page-bg-light': 'transparent',
   'page-bg-hover-light': '#9ca3af',
   'page-text-light': '#9ca3af',
   'page-text-hover-light': '#ffffff',
   // COMMENT
   'comment-parent-bg-light': '#f8fafc',
   'comment-parent-border-light': '#e5e7eb',
   'comment-child-bg-light': '#f8fafc',
   'comment-child-border-light': '#a5b4fc',
   // REPLY
   'reply-text-light': '#1d4ed8',
   'reply-border-light': '#4b5563',
   // REACTION
   'react-default-bg-light': '#f3f4f6',
   'react-default-border-light': '#e5e7eb',
   'react-selected-bg-light': '#dbeafe',
   'react-selected-border-light': '#bfdbfe',
   'react-count-text-light': '#000000',
   // COMMENT BODY
   'comment-name-text-light': '#000000',
   'comment-time-text-light': '#6b7280',
   'comment-option-bg-light': '#f3f4f6',
   'comment-option-borer-light': '#6b7280',
   'comment-read-more-light': '#1d4ed8',
   // DARK
   'text-dark': '#000000',
   'background-dark': '#1e293b',
   // TEXTAREA
   'textarea-bg-dark': '#475569',
   'textarea-scroll-dark': '#9ca3af',
   'textarea-text-dark': '#f3f4f6',
   'textarea-text-selection-dark': '#4338ca',
   'textarea-text-placeholder-dark': '#94a3b8',
   'textarea-border-empty-dark': '#f87171',
   // ICON
   'icon-spoiler-dark': '#e5e7eb',
   'icon-spoiler-option-dark': '#e5e7eb',
   'icon-dots-dark': '#e5e7eb',
   'icon-pin-dark': '#d1d5db',
   'icon-edit-dark': '#4ade80',
   'icon-delete-dark': '#f87171',
   'icon-pagination-dark': '#9ca3af',
   'icon-pagination-hover-dark': '#6b7280',
   // BUTTON
   'btn-send-bg-dark': '#e2e8f0',
   'btn-send-text-dark': '#000000',
   'btn-edit-bg-dark': '#16a34a',
   'btn-edit-text-dark': '#ffffff',
   'btn-reply-bg-dark': '#2563eb',
   'btn-reply-text-dark': '#ffffff',
   'btn-delete-bg-dark': '#ef4444',
   'btn-delete-text-dark': '#ffffff',
   'btn-cancel-bg-dark': '#e2e8f0',
   'btn-cancel-text-dark': '#000000',
   'btn-login-text-dark': '#60a5fa',
   // COUNTER
   'section-primary-dark': '#374151',
   'section-secondary-dark': '#e5e7eb',
   'section-text-dark': '#ffffff',
   'section-number-bg-dark': '#4b5563',
   'section-number-text-dark': '#000000',
   // DELETE FORM
   'delete-from-bg-dark': '#475569',
   'delete-from-text-dark': '#f3f4f6',
   'delete-from-subtext-dark': '#d1d5db',
   // PAGINATION
   'page-current-bg-dark': '#475569',
   'page-current-text-dark': '#ffffff',
   'page-bg-dark': 'transparent',
   'page-bg-hover-dark': '#334155',
   'page-text-dark': '#9ca3af',
   'page-text-hover-dark': '#ffffff',
   // COMMENT
   'comment-parent-bg-dark': '#1e293b',
   'comment-parent-border-dark': '#4b5563',
   'comment-child-bg-dark': '#1e293b',
   'comment-child-border-dark': '#a5b4fc',
   // REPLY
   'reply-text-dark': '#93c5fd',
   'reply-border-dark': '#4b5563',
   // REACTION
   'react-default-bg-dark': '#334155',
   'react-default-border-dark': '#6b7280',
   'react-selected-bg-dark': '#64748b',
   'react-selected-border-dark': '#1e293b',
   'react-count-text-dark': '#f3f4f6',
   // COMMENT BODY
   'comment-name-text-dark': '#f3f4f6',
   'comment-time-text-dark': '#d1d5db',
   'comment-option-bg-dark': '#475569',
   'comment-option-borer-dark': '#6b7280',
   'comment-read-more-dark': '#93c5fd',
}

Templates Folder Tree

templates/comment
   ├── comment
   │    ├── comments.html
   │    ├── comment_list.html
   │    ├── comment_counter.html
   │    ├── comment_body.html
   │    ├── comment_reactions.html
   │    └── object_info.html
   │
   ├── forms
   │    ├── comment_form_create.html
   │    ├── comment_form_reply.html
   │    ├── comment_form_edit.html
   │    └── comment_form_delete.html
   │
   ├── icons
   │    ├── icon_arrow_backward.html
   │    ├── icon_arrow_forward.html
   │    ├── icon_delete.html
   │    ├── icon_dots.html
   │    ├── icon_down.html
   │    ├── icon_edit.html
   │    ├── icon_eye.html
   │    ├── icon_eye_off.html
   │    └── icon_up.html
   │
   └── utils
        ├── comment_list_pagination.html
        ├── comment_list_loader.html
        ├── comment_list_empty.html
        ├── IMPORTS.html
        └── SCRIPTS.html

Static Folder Tree

static
   ├── css
   │    ├── comment.css
   │    └── comment.min.css
   ├── font
   │    ├── Vazir
   │    └── Vazir-FD
   ├── img
   │    └── profile.png
   ├── js
   │    ├── comment.js
   │    ├── comment.min.js
   │    └── jquery.min.js
   └── tailwindcss
        ├── style.css
        └── tailwind.config.js

IDs

#comments
   ├── #comment-modal
   ├── #comment-list
   ├── #comment-react-list
   │
   ├── #comment-{urlhash}
   │
   ├── forms
   │    ├── #form-comment-create
   │    ├── #form-comment-edit-{urlhash}
   │    ├── #form-comment-delete-{urlhash}
   │    ├── #form-comment-reply-{urlhash}
   │    └── #form-comment-react-{urlhash}
   │
   └── toggles
        ├── #toggle-spoiler-{urlhash}
        ├── #toggle-edit-{urlhash}
        ├── #toggle-reply-{urlhash}
        └── #toggle-more-{urlhash}

Handle 403 ERROR Template Page

  1. Create 403.html in your template path.
  2. Add custom view in views.py.
    # views.py
    from django.shortcuts import render
    def custom_error_403(request, exception):
        return render(request, '403.html', {'exception': exception})
  3. Add handler403 in your project urls.py
    # urls.py
    handler403 = 'my_project.views.custom_error_403'

Minify Static Files

  1. Installation
    npm i minify -g
  2. Usage
    npm static/css/comment.css > static/css/comment.min.css
    npm static/js/comment.js > static/js/comment.min.js