🐍 Django Integration Guide

Django Admin with Kria Lite WYSIWYG Editor

Learn how to integrate Kria Lite WYSIWYG editor into your Django admin and forms. Replace Django's default textarea with a modern, lightweight rich text editor.

⏱️ Time: 10 minutes
📦 Size: ~6KB added
📚 Level: Intermediate

📋 Prerequisites

  • Django 3.2+ (works with Django 4.x and 5.x)
  • Python 3.8+
  • Basic understanding of Django models and admin
1

Installation

First, download Kria Lite and add it to your Django project's static files:

# Create a directory for the editor in your static files mkdir -p static/js/kria-lite mkdir -p static/css/kria-lite # Download from CDN or npm curl -o static/js/kria-lite/kria.editor.min.js \ https://cdn.jsdelivr.net/npm/kria-lite/dist/kria.editor.min.js curl -o static/css/kria-lite/kria.editor.css \ https://cdn.jsdelivr.net/npm/kria-lite/dist/kria.editor.css

Alternatively, you can use the CDN directly in your templates (shown in later steps).

2

Static Files Setup

Ensure your Django settings are configured for static files:

settings.py
# Static files configuration STATIC_URL = '/static/' STATICFILES_DIRS = [ BASE_DIR / 'static', ] STATIC_ROOT = BASE_DIR / 'staticfiles' # Media files for image uploads MEDIA_URL = '/media/' MEDIA_ROOT = BASE_DIR / 'media'
3

Create Custom Widget

Create a custom Django widget that renders Kria Lite:

widgets.py
from django import forms from django.utils.safestring import mark_safe class KriaEditorWidget(forms.Textarea): """ A Django widget that renders Kria Lite WYSIWYG editor. """ def __init__(self, attrs=None, config=None): default_attrs = { 'class': 'kria-editor', 'rows': '10', } if attrs: default_attrs.update(attrs) self.config = config or {} super().__init__(attrs=default_attrs) class Media: css = { 'all': ( 'https://cdn.jsdelivr.net/npm/kria-lite/dist/kria.editor.css', ) } js = ( 'https://cdn.jsdelivr.net/npm/kria-lite/dist/kria.editor.min.js', ) def render(self, name, value, attrs=None, renderer=None): textarea = super().render(name, value, attrs, renderer) # Build config JSON config = { 'height': self.config.get('height', '400px'), 'placeholder': self.config.get('placeholder', 'Start writing...'), } # Add image upload URL if configured if 'imageUploadUrl' in self.config: config['imageUploadUrl'] = self.config['imageUploadUrl'] import json config_json = json.dumps(config) widget_id = attrs.get('id', name) init_script = f''' <script> (function() {{ function initKria() {{ if (typeof WYSIWYG !== 'undefined') {{ WYSIWYG.init('#{widget_id}', {config_json}); }} else {{ setTimeout(initKria, 100); }} }} if (document.readyState === 'loading') {{ document.addEventListener('DOMContentLoaded', initKria); }} else {{ initKria(); }} }})(); </script> ''' return mark_safe(textarea + init_script)
4

Admin Integration

Use the widget in your Django admin:

admin.py
from django.contrib import admin from django import forms from .models import BlogPost from .widgets import KriaEditorWidget class BlogPostAdminForm(forms.ModelForm): class Meta: model = BlogPost fields = '__all__' widgets = { 'content': KriaEditorWidget( config={ 'height': '500px', 'placeholder': 'Write your blog post...', 'imageUploadUrl': '/api/upload-image/', } ), } @admin.register(BlogPost) class BlogPostAdmin(admin.ModelAdmin): form = BlogPostAdminForm list_display = ['title', 'created_at', 'is_published'] search_fields = ['title', 'content']

Or override formfield_overrides for all TextFields:

@admin.register(BlogPost) class BlogPostAdmin(admin.ModelAdmin): formfield_overrides = { models.TextField: {'widget': KriaEditorWidget()}, }
5

Custom Model Field (Optional)

Create a custom model field for automatic widget assignment:

fields.py
from django.db import models from .widgets import KriaEditorWidget class RichTextField(models.TextField): """ A TextField that uses Kria Lite WYSIWYG editor in admin. """ def __init__(self, *args, editor_config=None, **kwargs): self.editor_config = editor_config or {} super().__init__(*args, **kwargs) def formfield(self, **kwargs): kwargs['widget'] = KriaEditorWidget(config=self.editor_config) return super().formfield(**kwargs)

Use in your models:

models.py
from django.db import models from .fields import RichTextField class BlogPost(models.Model): title = models.CharField(max_length=200) slug = models.SlugField(unique=True) # Uses Kria Lite automatically in admin content = RichTextField( editor_config={ 'height': '500px', 'imageUploadUrl': '/api/upload-image/', } ) excerpt = RichTextField( blank=True, editor_config={ 'height': '200px', 'placeholder': 'Short description...', } ) created_at = models.DateTimeField(auto_now_add=True) is_published = models.BooleanField(default=False) def __str__(self): return self.title
6

Image Upload View

Create a view to handle image uploads from the editor:

views.py
import os import uuid from django.http import JsonResponse from django.views.decorators.csrf import csrf_exempt from django.views.decorators.http import require_POST from django.contrib.admin.views.decorators import staff_member_required from django.core.files.storage import default_storage from django.conf import settings @staff_member_required @require_POST def upload_image(request): """ Handle image uploads from Kria Lite editor. Only accessible by staff members. """ if 'file' not in request.FILES: return JsonResponse({ 'error': 'No file provided' }, status=400) uploaded_file = request.FILES['file'] # Validate file type allowed_types = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'] if uploaded_file.content_type not in allowed_types: return JsonResponse({ 'error': 'Invalid file type. Allowed: JPEG, PNG, GIF, WebP' }, status=400) # Validate file size (max 5MB) max_size = 5 * 1024 * 1024 if uploaded_file.size > max_size: return JsonResponse({ 'error': 'File too large. Maximum size: 5MB' }, status=400) # Generate unique filename ext = os.path.splitext(uploaded_file.name)[1].lower() filename = f'editor/{uuid.uuid4().hex}{ext}' # Save file saved_path = default_storage.save(filename, uploaded_file) file_url = default_storage.url(saved_path) return JsonResponse({ 'url': file_url, 'filename': os.path.basename(saved_path) })

Add the URL pattern:

urls.py
from django.urls import path from . import views urlpatterns = [ # ... other URLs path('api/upload-image/', views.upload_image, name='upload_image'), ]
7

Frontend Display

Display the rich text content safely in your templates:

blog_detail.html
<!-- blog_detail.html --> {% extends "base.html" %} {% block content %} <article class="blog-post"> <h1>{{ post.title }}</h1> <div class="post-meta"> <time datetime="{{ post.created_at|date:'c' }}"> {{ post.created_at|date:"F j, Y" }} </time> </div> <!-- Render HTML content safely --> <div class="post-content prose lg:prose-xl"> {{ post.content|safe }} </div> </article> {% endblock %}

⚠️ Note: Using |safe on user-generated content requires proper sanitization. See the Security section below.

8

Security Best Practices

While Kria Lite has built-in XSS protection, always sanitize HTML on the server side:

# Install bleach for HTML sanitization pip install bleach
models.py
import bleach from django.db import models class BlogPost(models.Model): title = models.CharField(max_length=200) content = models.TextField() # Allowed HTML tags for rich text ALLOWED_TAGS = [ 'p', 'br', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'strong', 'em', 'u', 's', 'mark', 'ul', 'ol', 'li', 'a', 'img', 'table', 'thead', 'tbody', 'tr', 'th', 'td', 'blockquote', 'pre', 'code', ] ALLOWED_ATTRS = { 'a': ['href', 'title', 'target', 'rel'], 'img': ['src', 'alt', 'width', 'height'], '*': ['class'], } def save(self, *args, **kwargs): # Sanitize HTML before saving self.content = bleach.clean( self.content, tags=self.ALLOWED_TAGS, attributes=self.ALLOWED_ATTRS, strip=True ) super().save(*args, **kwargs) def get_safe_content(self): """Get sanitized content for display.""" return bleach.clean( self.content, tags=self.ALLOWED_TAGS, attributes=self.ALLOWED_ATTRS )

✅ Security Checklist

  • • Sanitize HTML on save (server-side)
  • • Restrict image uploads to authenticated staff
  • • Validate file types and sizes
  • • Use CSRF protection on upload endpoints
  • • Serve uploaded files from a separate domain if possible

🎉 Congratulations!

You now have Kria Lite WYSIWYG editor integrated into your Django admin! Your editors can now create rich content with images, tables, and formatting.