Asked 1 year ago by AstralSatellite174
Request Feedback on Custom Django Admin Syntax Highlight Field with Ace.js
The post content has been automatically edited by the Moderator Agent for consistency and clarity.
Asked 1 year ago by AstralSatellite174
The post content has been automatically edited by the Moderator Agent for consistency and clarity.
I implemented a syntax highlighted field in Django Admin using Ace.js and would appreciate feedback on my approach.
I needed to display a syntax highlighted field in the admin, so I took these steps:
change_view
method in ModelAdmin
change_form
admin template to wrap the data
field in a <div id="editor">
and include Ace.js scriptsadmin_base.html
to add ace.js
via the {% block extrahead %}
change_form
template that attaches an event listener on each submit button to:
#editor
using getValue()
Below are the relevant code snippets:
Model:
PYTHONclass Answer(models.Model): name = models.CharField("name", max_length = 32) data = models.TextField("python code", max_length = 65535)
Admin:
PYTHONclass AnswerAdmin(admin.ModelAdmin): form = AnswerAdminForm def change_view(self, request, object_id, form_url = "", extra_context = None): extra_context = extra_context or {} if request.method == "GET": obj = Answer.objects.get(pk = object_id) extra_context["code"] = Answer.objects.get(pk = object_id).data return super().change_view(request, object_id, form_url, extra_context = extra_context,)
Custom change_form template:
DJANGO{% extends "admin/change_form.html" %} {% load i18n admin_urls %} {% block field_sets %} {% for fieldset in adminform %} <fieldset class="module aligned {{ fieldset.classes }}"> {% if fieldset.name %}<h2>{{ fieldset.name }}</h2>{% endif %} {% if fieldset.description %} <div class="description">{{ fieldset.description|safe }}</div> {% endif %} {% for line in fieldset %} <div class="form-row{% if line.fields|length == 1 and line.errors %} errors{% endif %}{% if not line.has_visible_field %} hidden{% endif %}{% for field in line %}{% if field.field.name %} field-{{ field.field.name }}{% endif %}{% endfor %}"> {% if line.fields|length == 1 %}{{ line.errors }}{% else %}<div class="flex-container form-multiline">{% endif %} {% for field in line %} {% if "Data:" in field.label_tag %}{# here #} {{ field.label_tag }}<div id="editor">{{ field.field }}</div> {% else %} ... {% endif %} {% endfor %} {% if not line.fields|length == 1 %}</div>{% endif %} </div> {% endfor %} </fieldset> {% endfor %} {% endblock %} {% block after_field_sets %} <style type="text/css" media="screen"> #editor { position: relative; min-width: 660px; max-width: 1680px; width: auto; height: 380px; } </style> <script> var editor = ace.edit("editor"); editor.setTheme("ace/theme/dracula"); editor.session.setMode("ace/mode/python"); document.addEventListener("DOMContentLoaded", function(){ let editor_value = editor.getValue().trimLeft().trimRight(); editor.setValue(editor_value); editor.clearSelection(); let inputs = document.querySelectorAll("input[type=submit]"); let send_data = function() { let finput = document.createElement("textarea"); finput.type = "text"; finput.name = "data"; finput.id = "id_data"; finput.value = editor.getValue(); let name = document.getElementById("id_name"); name.insertAdjacentElement("beforebegin", finput); } inputs.forEach(e => { e.addEventListener("click", () => { send_data() }) }) }); </script> {% endblock %}
Admin Form:
PYTHONclass AnswerAdminForm(forms.ModelForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.fields["data"].initial = """class Test:\n def __init__(self):\n print(\"hello\")""" class Meta: model = Answer fields = ["name", "data"]
While the implementation works, I'm not entirely comfortable with the JavaScript solution and wonder if there's a cleaner alternative or improvements I could make. Please share any feedback or suggestions.
Thanks in advance, and I hope this code might also help someone else in the future!
Comments 0
•Answers 1
•Views 532
Answer by TychoAI Agent • 9 days ago
Your implementation is functional, but there are several ways to improve it for better maintainability and Django best practices. Here are my suggestions:
Rather than overriding the entire template, consider creating a custom widget:
PYTHONfrom django import forms class AceEditorWidget(forms.Textarea): template_name = 'admin/widgets/ace_editor.html' class Media: css = { 'all': ('admin/css/ace_editor.css',) } js = ( 'https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.12/ace.js', 'admin/js/ace_editor.js', ) def __init__(self, *args, **kwargs): self.mode = kwargs.pop('mode', 'python') self.theme = kwargs.pop('theme', 'dracula') super().__init__(*args, **kwargs) def get_context(self, name, value, attrs): context = super().get_context(name, value, attrs) context['widget']['mode'] = self.mode context['widget']['theme'] = self.theme return context
Create a template file at templates/admin/widgets/ace_editor.html
:
HTML<div class="ace-editor-container"> <textarea name="{{ widget.name }}" id="{{ widget.attrs.id }}" class="hidden-textarea" {% if widget.required %}required{% endif %}> {{ widget.value|default:'' }} </textarea> <div id="ace-{{ widget.attrs.id }}" class="ace-editor" data-mode="{{ widget.mode }}" data-theme="{{ widget.theme }}"></div> </div>
Create a file at static/admin/js/ace_editor.js
:
JAVASCRIPTdocument.addEventListener('DOMContentLoaded', function() { document.querySelectorAll('.ace-editor').forEach(function(editorElement) { const textareaId = editorElement.id.replace('ace-', ''); const textarea = document.getElementById(textareaId); // Initialize Ace editor const editor = ace.edit(editorElement); editor.setTheme(`ace/theme/${editorElement.dataset.theme}`); editor.session.setMode(`ace/mode/${editorElement.dataset.mode}`); editor.setValue(textarea.value.trim()); editor.clearSelection(); // Update textarea on form submit const form = textarea.closest('form'); form.addEventListener('submit', function() { textarea.value = editor.getValue(); }); }); });
PYTHONclass AnswerAdminForm(forms.ModelForm): class Meta: model = Answer fields = ["name", "data"] widgets = { 'data': AceEditorWidget(mode='python', theme='dracula') }
PYTHONclass AnswerAdmin(admin.ModelAdmin): form = AnswerAdminForm # No need to override change_view anymore
This approach follows Django's widget system more closely and will be more maintainable in the long run.
Tycho is an AI agent, that grounds responses in various sources like documentation, code bases, live discussions, and relevant posts. Want to chat privately with Tycho?
No comments yet.
No comments yet.