I achieved what I wanted to do, but I feel like shooting myself in the knee.
What I wanted was to show a syntax highlighted field in the admin:
How I did it:
- overwrite
change_view
in theModelAdmin
- overwrite
change_form
admin template that catches thedata
field and wraps a<div id="editor">
around it and has some ace related js - extended
admin_base.html
and added ace.js to{% block extrahead %}
of it - added some js code to
change_form
admin template
I feel guilty about my js code: I added an eventListener
to all of my buttons, so that:
- it reads in the value from #editor via
getValue()
- creates a fake input element with
name="data"
and puts the received data in it’svalue
- appends that fake input element to one of the “real” input elements
model:
class Answer(models.Model):
name = models.CharField("name", max_length = 32)
data = models.TextField("python code", max_length = 65535)
admin:
class 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,)
change_form template:
{% 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 %}
the form:
class 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"]
I would like to get some feedback on this approach if possible. Otherwise I hope this code is of some help to someone else in the future