A modern block-based visual editor for Django with inline styles support. No external CSS required - all styles are embedded directly in HTML.
- 6 Block Types: Paragraph, Heading (H1-H6), List (ordered/unordered), Code, Quote, Image
- Contextual Toolbar: Appears when a block is selected, showing relevant formatting options
- Add/Remove Blocks: Easy block management with visual controls
- Block Menu: Quick access to all block types
- Text Alignment: Left, center, right, justify
- Text Size: 5 preset sizes (14px - 24px)
- Text Color: 5 preset colors (gray, blue, green, red, yellow)
- Text Formatting: Bold, italic, underline
- Universal Compatibility: Styles work everywhere without additional CSS files
- Upload: Drag-and-drop or file picker
- Resize: Drag the edge to resize images
- Replace: Double-click image to change it
- Alignment: Left, center, right alignment support
- Metadata Storage: Image URL and width stored in block data
- Content Generation: Create new content from AI prompts
- Content Editing: Improve existing blocks with AI assistance
- Context-Aware: Add blocks to context for better AI understanding
- Multi-Provider Support: Works with OpenAI, Yandex GPT, and any OpenAI-compatible API
- Flexible Configuration: Each model has its own API credentials and settings
- Model Switching: Change between different AI models directly in the UI
- TypeScript: Full TypeScript implementation with type safety
- Block API: Easy to extend with custom block types
- HTML Export: Clean HTML with inline styles
- HTML Import: Parse existing HTML with styles extraction
- Auto Cleanup: Management command to remove unused images
# Install base package
pip install django-visual-editor
# Or install with AI support
pip install django-visual-editor[ai]# Clone repository
git clone https://github.com/hvlads/django-visual-editor.git
cd django-visual-editor
# Install Python dependencies
pip install -e ".[ai]"
# Build frontend
cd frontend
npm install
npm run buildFor development with automatic rebuild:
cd frontend
npm run devAdd to settings.py:
INSTALLED_APPS = [
...
'django_visual_editor',
...
]
# Media files settings
MEDIA_URL = 'media/'
MEDIA_ROOT = BASE_DIR / 'media'Add URL to urls.py:
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
...
path('editor/', include('django_visual_editor.urls')),
...
]
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)python manage.py migrateTo enable AI features, configure in settings.py:
import os
VISUAL_EDITOR_AI_CONFIG = {
'enabled': True,
'default_model': 'yandex-gpt', # ID from models list below
'models': [
{
'id': 'yandex-gpt',
'name': 'YandexGPT',
'provider': 'Yandex',
'model': f"gpt://{os.environ.get('YANDEX_FOLDER_ID')}/yandexgpt/latest",
'api_key': os.environ.get('YANDEX_API_KEY'),
'base_url': 'https://llm.api.cloud.yandex.net/v1',
'project': os.environ.get('YANDEX_FOLDER_ID'),
},
{
'id': 'gpt-4o',
'name': 'GPT-4o',
'provider': 'OpenAI',
'model': 'gpt-4o',
'api_key': os.environ.get('OPENAI_API_KEY'),
'base_url': None, # Uses OpenAI default
'project': None,
},
],
}Set environment variables in .env:
# Yandex Cloud AI
YANDEX_API_KEY=your-api-key
YANDEX_FOLDER_ID=your-folder-id
# OpenAI (optional)
OPENAI_API_KEY=sk-...- Click π€ button on blocks to add them to context
- Open the AI panel on the right side
- Select a model from the dropdown (or use "Default Model")
- Choose mode: Generate (new content) or Edit (improve existing)
- Enter your prompt and optional instructions
- Click Generate/Apply
The simplest way - just use the field in your model:
from django.db import models
from django_visual_editor import VisualEditorField
class BlogPost(models.Model):
title = models.CharField(max_length=200)
content = VisualEditorField(
config={
'min_height': 400,
'max_height': 800,
'placeholder': 'Start typing...',
}
)Then use it in forms and admin - no additional configuration needed:
# forms.py
from django import forms
from .models import BlogPost
class BlogPostForm(forms.ModelForm):
class Meta:
model = BlogPost
fields = ['title', 'content']
# Widget is automatically set from the field!
# admin.py
from django.contrib import admin
from .models import BlogPost
@admin.register(BlogPost)
class BlogPostAdmin(admin.ModelAdmin):
pass # Widget is automatically set from the field!If you prefer to use a regular TextField and configure the widget in forms:
# models.py
from django.db import models
class BlogPost(models.Model):
title = models.CharField(max_length=200)
content = models.TextField() # Regular TextField
# forms.py
from django import forms
from django_visual_editor import VisualEditorWidget
from .models import BlogPost
class BlogPostForm(forms.ModelForm):
class Meta:
model = BlogPost
fields = ['title', 'content']
widgets = {
'content': VisualEditorWidget(
config={
'min_height': 400,
'max_height': 800,
'placeholder': 'Start typing...',
}
),
}
# admin.py
from django.contrib import admin
from django_visual_editor import VisualEditorWidget
from .models import BlogPost
from django import forms
class BlogPostAdminForm(forms.ModelForm):
class Meta:
model = BlogPost
fields = '__all__'
widgets = {
'content': VisualEditorWidget(),
}
@admin.register(BlogPost)
class BlogPostAdmin(admin.ModelAdmin):
form = BlogPostAdminForm<!-- Display content -->
<div class="blog-content">
{{ post.content|safe }}
</div>
<!-- Form -->
<form method="post">
{% csrf_token %}
{{ form.as_p }}
{{ form.media }} <!-- Important! Loads CSS and JS -->
<button type="submit">Save</button>
</form>Available configuration parameters for VisualEditorWidget:
VisualEditorWidget(
config={
'min_height': 300, # Minimum editor height (px)
'max_height': 600, # Maximum editor height (px)
'placeholder': 'Text...', # Placeholder text
}
)Run the management command to remove unused images:
# Show what will be deleted (dry run)
python manage.py cleanup_editor_images --dry-run
# Delete unused images
python manage.py cleanup_editor_imagesIt's recommended to set up this command in cron for periodic cleanup.
Run the example blog:
cd example_project
python manage.py migrate
python manage.py createsuperuser
python manage.py runserverThen open:
- http://localhost:8000/ - Post list
- http://localhost:8000/post/new/ - Create new post
- http://localhost:8000/admin/ - Django Admin
django-visual-editor/
βββ django_visual_editor/ # Django application
β βββ models.py # Model for uploaded images
β βββ widgets.py # Django widget
β βββ fields.py # Custom model field
β βββ views.py # Views for image upload and AI assistant
β βββ ai_service.py # AI service for content generation
β βββ urls.py # URL configuration
β βββ management/ # Management commands
β βββ static/ # Static files (compiled)
β βββ templates/ # Templates
βββ frontend/ # TypeScript sources
β βββ src/
β β βββ blocks/ # Block types (paragraph, heading, list, code, quote, image)
β β βββ editor/ # Block editor, contextual toolbar, block menu, AI panel
β β βββ utils/ # Utils (upload, compression)
β β βββ styles/ # CSS styles (blocks, AI assistant)
β βββ package.json
β βββ tsconfig.json
β βββ webpack.config.js
βββ example_project/ # Usage example
βββ blog/ # Demo blog application
- Backend: Django 4.2+
- Frontend: TypeScript, Webpack
- Architecture: Block-based editor with inline styles
- Styles: No external CSS required (inline styles)
MIT