Last active
September 1, 2022 15:25
-
-
Save JackAtOmenApps/4758cdbbb587200feaa97194e9b789e8 to your computer and use it in GitHub Desktop.
This is a super basic example of using django-summernote to create a knowledgebase app in django (Make sure to pip install django-summernote)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{% extends "base.html" %} | |
{% load static %} | |
{% block title %}{% block head_title %}Knowledgebase - Delete {{ article.name }}{% endblock head_title %}{% endblock title %} | |
{% block content %} | |
<div class="container-fluid"> | |
<div class="row ml-1 mr-1"> | |
<div class="col-md-12"> | |
<div class="card card-default mb-5"> | |
<div class="card-body"> | |
{% if article.description %} | |
<header> | |
{{ article.description }} | |
</header> | |
<hr> | |
{% endif %} | |
<section> | |
<h4>Delete this article?</h4> | |
<form method="post">{% csrf_token %} | |
<p>Are you sure you want to delete "{{ object }}"?</p> | |
<input class="btn btn-danger" type="submit" value="Confirm"> | |
</form> | |
</section> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
{% endblock %} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{% extends "base.html" %} | |
{% load static %} | |
{% block title %}{% block head_title %}Knowledgebase - {{ article.name }}{% endblock head_title %}{% endblock title %} | |
{% block css %} | |
<style> | |
/* Makes embedded videos responsive */ | |
iframe { | |
border-top-width: 0px; | |
border-right-width: 0px; | |
border-bottom-width: 0px; | |
border-left-width: 0px; | |
width: 100%; | |
} | |
</style> | |
{% endblock %} | |
{% block content %} | |
<div class="container-fluid"> | |
<div class="row ml-1 mr-1"> | |
<div class="col-md-12"> | |
<article class="mb-4"> | |
{% if article.description %} | |
<header class="mb-4"> | |
{% if article.description %}<p><i>{{ article.description }}</i></p>{% endif %} | |
</header> | |
{% endif %} | |
<div class="card card-default mb-5"> | |
<div class="card-body"> | |
{{ article.content | safe }} | |
</div> | |
<div class="card-footer"> | |
<ul class="list-unstyled mb-0"> | |
<li> | |
<strong>Updated on</strong> <time>{{ article.modified }}</time> | |
</li> | |
<li> | |
<strong>Category:</strong> <a href="{% url 'knowledgebase:category' article.category.slug %}">{{ article.category.name }}</a> | |
</li> | |
</ul> | |
<p></p> | |
{% if 'manager' in header_user_groups or 'administrator' in header_user_groups %} | |
<a href="{% url 'knowledgebase:article_edit' article.slug %}" class="btn btn-success">Edit Article</a> | |
<a href="{% url 'knowledgebase:article_delete' article.slug %}" class="btn btn-danger">Delete Article</a> | |
{% endif %} | |
</div> | |
</div> | |
</article> | |
</div> | |
</div> | |
</div> | |
{% endblock %} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{% extends "base.html" %} | |
{% load crispy_forms_tags %} | |
{% load static %} | |
{% block title %}{% block head_title %}Add / Edit Article{% endblock head_title %}{% endblock title %} | |
{% block css %} | |
<link href="{% static 'plugins/summernote/summernote-bs4.css' %}" rel="stylesheet" /> | |
<link href="{% static 'plugins/summernote/django_summernote.css' %}" rel="stylesheet" /> | |
{% endblock %} | |
{% block content %} | |
<div class="container"> | |
<form class="form-horizontal" method="post"> | |
{% csrf_token %} | |
{% crispy form no_media %} | |
<div class="control-group"> | |
<div class="controls"> | |
<button type="submit" class="btn btn-primary">Update</button> | |
</div> | |
</div> | |
</form> | |
</div> | |
{% endblock %} | |
{% block scripts %} | |
<script src="{% static 'plugins/summernote/jquery.ui.widget.js' %}"></script> | |
<script src="{% static 'plugins/summernote/jquery.iframe-transport.js' %}"></script> | |
<script src="{% static 'plugins/summernote/jquery.fileupload.js' %}"></script> | |
<script src="{% static 'plugins/summernote/summernote-bs4.min.js' %}"></script> | |
<script src="{% static 'plugins/summernote/ResizeSensor.js' %}"></script> | |
{% endblock scripts %} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Just an excerpt of the summernote specific items in my base settings file | |
INSTALLED_APPS = [ | |
... | |
'django_summernote', | |
... | |
] | |
SUMMERNOTE_THEME = 'bs4' | |
SUMMERNOTE_CONFIG = { | |
# Using SummernoteWidget - iframe mode, default | |
'iframe': True, | |
# You can put custom Summernote settings | |
'summernote': { | |
# As an example, using Summernote Air-mode | |
'airMode': False, | |
# Change editor size | |
'width': '100%', | |
'height': '480', | |
# Use proper language setting automatically (default) | |
'lang': None, | |
}, | |
# Need authentication while uploading attachments. | |
'attachment_require_authentication': True, | |
# You can disable attachment feature. | |
'disable_attachment': False, | |
# Lazy initialize | |
# If you want to initialize summernote at the bottom of page, set this as True | |
# and call `initSummernote()` on your page. | |
'lazy': True, | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{% extends "base.html" %} | |
{% load static %} | |
{% block title %}{% block head_title %}Knowledgebase - Delete {{ category.name }}{% endblock head_title %}{% endblock title %} | |
{% block content %} | |
<div class="container-fluid"> | |
<div class="row ml-1 mr-1"> | |
<div class="col-md-12"> | |
<div class="card card-default mb-5"> | |
<div class="card-body"> | |
{% if category.description %} | |
<header> | |
{{ category.description }} | |
</header> | |
<hr> | |
{% endif %} | |
<section> | |
<h4>Delete this category?</h4> | |
<form method="post">{% csrf_token %} | |
<p>Are you sure you want to delete "{{ object }}"?</p> | |
<input class="btn btn-danger" type="submit" value="Confirm"> | |
</form> | |
</section> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
{% endblock %} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{% extends "base.html" %} | |
{% load static %} | |
{% block title %}{% block head_title %}Knowledgebase - {{ category.name }}{% endblock head_title %}{% endblock title %} | |
{% block content %} | |
<div class="container-fluid"> | |
<div class="row ml-1 mr-1"> | |
<div class="col-md-12"> | |
<div class="card card-default mb-5"> | |
<div class="card-body"> | |
{% if category.description %} | |
<header> | |
{{ category.description }} | |
</header> | |
<hr> | |
{% endif %} | |
<section> | |
<h4>Articles in this category:</h4> | |
{% for article in articles_list %} | |
<br> | |
<article class="mb-4"> | |
<a href="{% url 'knowledgebase:article' article.slug %}" class="text-success"> | |
<h4 class="h4">{{ article.name }}</h4> | |
</a> | |
<p> | |
{% if article.description %} | |
<i>{{ article.description | truncatechars:200 }}</i> | |
{% else %} | |
{% endif %} | |
</p> | |
</article> | |
{% if 'manager' in header_user_groups or 'administrator' in header_user_groups %} | |
<a href="{% url 'knowledgebase:category_edit' category.slug %}" class="btn btn-success">Edit Category</a> | |
<a href="{% url 'knowledgebase:category_delete' category.slug %}" class="btn btn-danger">Delete Category</a> | |
{% endif %} | |
{% endfor %} | |
</section> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
{% endblock %} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{% extends "base.html" %} | |
{% load crispy_forms_tags %} | |
{% load static %} | |
{% block title %}{% block head_title %}Add / Edit Category{% endblock head_title %}{% endblock title %} | |
{% block css %} | |
<link href="{% static 'plugins/summernote/summernote-bs4.css' %}" rel="stylesheet" /> | |
<link href="{% static 'plugins/summernote/django_summernote.css' %}" rel="stylesheet" /> | |
{% endblock %} | |
{% block content %} | |
<div class="container"> | |
<form class="form-horizontal" method="post"> | |
{% csrf_token %} | |
{% crispy form no_media %} | |
<div class="control-group"> | |
<div class="controls"> | |
<button type="submit" class="btn btn-primary">Update</button> | |
</div> | |
</div> | |
</form> | |
</div> | |
{% endblock %} | |
{% block scripts %} | |
<script src="{% static 'plugins/summernote/jquery.ui.widget.js' %}"></script> | |
<script src="{% static 'plugins/summernote/jquery.iframe-transport.js' %}"></script> | |
<script src="{% static 'plugins/summernote/jquery.fileupload.js' %}"></script> | |
<script src="{% static 'plugins/summernote/summernote-bs4.min.js' %}"></script> | |
<script src="{% static 'plugins/summernote/ResizeSensor.js' %}"></script> | |
{% endblock scripts %} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from django import forms | |
from django_summernote.widgets import SummernoteWidget | |
from .models import Category, Article | |
class BasicArticleForm(forms.ModelForm): | |
class Meta: | |
model = Article | |
exclude = ('author', 'is_read_only') | |
widgets = { | |
'content': SummernoteWidget(), | |
} | |
class BasicCategoryForm(forms.ModelForm): | |
class Meta: | |
model = Category | |
exclude = ('author', ) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{% extends "base.html" %} | |
{% load static %} | |
{% block title %}{% block head_title %}Knowledgebase - Home{% endblock head_title %}{% endblock title %} | |
{% block dashtitle %}Knowledgebase <small>Home</small>{% endblock dashtitle %} | |
{% block dashright %} | |
<a class="btn btn-success video-btn text-white less-buffer-top" href="{% url "knowledgebase:home" %}">Knowledgebase Home</a> | |
{% endblock dashright %} | |
{% block css %} | |
<link href="{% static 'plugins/summernote/summernote-bs4.css' %}" rel="stylesheet" /> | |
<link href="{% static 'plugins/summernote/django_summernote.css' %}" rel="stylesheet" /> | |
{% endblock %} | |
{% block content %} | |
<div class="container-fluid bg-light pt-4"> | |
<div class="row ml-1 mr-1"> | |
<div class="col-md-12"> | |
<h4>Category List:</h4> | |
<br> | |
<div class="row"> | |
{% for category in categories_list %} | |
<div class="col mb-4"> | |
<a href="{% url 'knowledgebase:category' category.slug %}"> | |
<h4 class="text-success">{{ category.name }} ({{ category.articles.count }})</h4> | |
</a> | |
<div class="description">{{ category.description }}</div> | |
</div> | |
{% if forloop.counter|divisibleby:2 %} | |
<div class="w-100"></div> | |
{% endif %} | |
{% endfor %} | |
</div> | |
</div> | |
</div> | |
</div> | |
{% endblock %} | |
{% block scripts %} | |
<script src="{% static 'plugins/summernote/jquery.ui.widget.js' %}"></script> | |
<script src="{% static 'plugins/summernote/jquery.iframe-transport.js' %}"></script> | |
<script src="{% static 'plugins/summernote/jquery.fileupload.js' %}"></script> | |
<script src="{% static 'plugins/summernote/summernote-bs4.min.js' %}"></script> | |
<script src="{% static 'plugins/summernote/ResizeSensor.js' %}"></script> | |
{% endblock scripts %} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from django.contrib.gis.db import models | |
from django.urls import reverse | |
from django.utils.translation import ugettext_lazy as _ | |
from django_extensions.db.models import TimeStampedModel, AutoSlugField | |
from myproject.users.models import User | |
STATUSES = ( | |
('draft', _('Draft')), | |
('published', _('Published')), | |
('deleted', _('Deleted')), | |
) | |
class CategoryQuerySet(models.QuerySet): | |
def by_author(self, user): | |
return self.filter(author=user) | |
class CategoryManager(models.Manager): | |
def get_queryset(self): | |
return CategoryQuerySet(self.model, using=self._db) | |
class Category(TimeStampedModel): | |
name = models.CharField(_('Category Name'), max_length=255) | |
slug = AutoSlugField(_('Category Slug (a short title used for web addresses)'), unique=True, populate_from='name') | |
category = models.ForeignKey('self', on_delete=models.CASCADE, related_name='categories', blank=True, null=True, | |
verbose_name=_('parent category')) | |
description = models.TextField( | |
_('Description'), | |
max_length=200, | |
blank=True | |
) | |
author = models.ForeignKey( | |
User, | |
on_delete=models.CASCADE) | |
objects = CategoryManager() | |
class Meta: | |
ordering = ['name'] | |
verbose_name = _('Category') | |
verbose_name_plural = _('Categories') | |
def __str__(self): | |
return self.name | |
def get_absolute_url(self): | |
return reverse('knowledgebase:category', args=[str(self.slug)]) | |
class ArticleQuerySet(models.QuerySet): | |
def category(self, category): | |
if isinstance(category, models.Model): | |
qs = self.filter(category=category) | |
elif category: | |
qs = self.filter(category__slug=category) | |
else: | |
qs = self.filter() | |
return qs | |
def by_author(self, user): | |
return self.filter(author=user) | |
def published(self): | |
return self.filter(status='published') | |
def unpublished(self): | |
return self.filter(status='draft') | |
def deleted(self): | |
return self.filter(status='deleted') | |
class ArticleManager(models.Manager): | |
def get_queryset(self): | |
return ArticleQuerySet(self.model, using=self._db) | |
class Article(TimeStampedModel): | |
name = models.CharField(_('Title'), max_length=100) | |
slug = AutoSlugField(_('Article Slug (a short title used for web addresses)'), unique=True, populate_from='name') | |
content = models.CharField(_('Content for this Article'), max_length=5000) | |
description = models.TextField( | |
_('Description'), | |
max_length=200, | |
blank=True | |
) | |
author = models.ForeignKey(User, blank=True, null=True, on_delete=models.SET_NULL, db_index=True) | |
status = models.CharField(_('Current status of the Article'), | |
max_length=32, choices=STATUSES, | |
default='draft') | |
category = models.ForeignKey( | |
Category, | |
related_name='articles', | |
on_delete=models.CASCADE) | |
objects = ArticleManager() | |
class Meta: | |
ordering = ['-modified'] | |
verbose_name = _('Article') | |
verbose_name_plural = _('Articles') | |
def __str__(self): | |
return self.name | |
def get_absolute_url(self): | |
return reverse('knowledgebase:article', args=[str(self.slug)]) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from django.urls import path | |
from . import views | |
app_name = 'knowledgebase' | |
urlpatterns = [ | |
path('', views.HomeView.as_view(), name='home'), | |
path('article/new/', views.ArticleNewView.as_view(), name='new_article'), | |
path('article/<slug:slug>/', views.ArticleDetailView.as_view(), name='article'), | |
path('article/<slug:slug>/edit/', views.ArticleUpdateView.as_view(), name='article_edit'), | |
path('article/<slug:slug>/delete/', views.ArticleDeleteView.as_view(), name='article_delete'), | |
path('category/new/', views.CategoryNewView.as_view(), name='new_category'), | |
path('category/<slug:slug>/', views.CategoryDetailView.as_view(), name='category'), | |
path('category/<slug:slug>/edit/', views.CategoryUpdateView.as_view(), name='category_edit'), | |
path('category/<slug:slug>/delete/', views.CategoryDeleteView.as_view(), name='category_delete'), | |
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# In my project I have limited viewing of articles to the people in the groups selected | |
# via the groups ManyToManyField on the Article model and editing to the people in my | |
# administrator group. I have skipped that here to make things a bit simpler, but you | |
# should absolutely limit access. | |
from django import forms | |
from django.conf import settings | |
from django.contrib.auth.mixins import LoginRequiredMixin | |
from django.core.cache.backends.base import DEFAULT_TIMEOUT | |
from django.views.generic import ( | |
FormView, | |
DetailView, | |
TemplateView, | |
UpdateView, | |
DeleteView | |
) | |
from django.http import HttpResponseRedirect | |
from django_summernote.widgets import SummernoteWidget | |
from .models import Category, Article | |
from .forms import BasicArticleForm, BasicCategoryForm | |
class ArticleNewView(LoginRequiredMixin, FormView): | |
form_class = BasicArticleForm | |
template_name = 'knowledgebase/article_form.html' | |
success_url = '/knowledgebase/' | |
def form_valid(self, form): | |
obj = form.save(commit=False) | |
obj.author = self.request.user | |
obj.save() | |
return HttpResponseRedirect(self.get_success_url()) | |
class ArticleDetailView(LoginRequiredMixin, DetailView): | |
model = Article | |
context_object_name = "article" | |
count_hit = True | |
class ArticleDeleteView(LoginRequiredMixin, DeleteView): | |
model = Article | |
context_object_name = 'article' | |
success_url = '/knowledgebase/' | |
class ArticleUpdateView(LoginRequiredMixin, UpdateView): | |
model = Article | |
form_class = BasicArticleForm | |
template_name = "knowledgebase/article_form.html" | |
class CategoryNewView(LoginRequiredMixin, FormView): | |
form_class = BasicCategoryForm | |
template_name = 'knowledgebase/category_form.html' | |
success_url = '/knowledgebase/' | |
def form_valid(self, form): | |
obj = form.save(commit=False) | |
obj.author = self.request.user | |
obj.save() | |
return HttpResponseRedirect(self.get_success_url()) | |
class HomeView(LoginRequiredMixin, TemplateView): | |
# Home view is the listing of categories to navigate to articles | |
template_name = 'knowledgebase/home.html' | |
def get_context_data(self, **kwargs): | |
context = super(HomeView, self).get_context_data(**kwargs) | |
context['categories_list'] = Category.objects.all() | |
return context | |
class CategoryDetailView(LoginRequiredMixin, DetailView): | |
model = Category | |
context_object_name = 'category' | |
def get_context_data(self, **kwargs): | |
context = super(CategoryDetailView, self).get_context_data(**kwargs) | |
context['articles_list'] = Article.objects.filter(category=self.object).published() | |
return context | |
class CategoryDeleteView(LoginRequiredMixin, DeleteView): | |
model = Category | |
context_object_name = 'category' | |
success_url = '/knowledgebase/' | |
class CategoryUpdateView(UpdateView): | |
model = Category | |
fields = ['name', 'category', 'description'] | |
template_name = "knowledgebase/category_form.html" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment