I have a TextField with text from a txt file in admin. My txt have linebreaks. The problem is when the TextField are in readonly_fields, all linebreaks dissaper and all content is grouped. How to keep the format using this field in readonly_fields mode? The problem does not happen when not in readonly_fields. Thanks!
3 Answers
I'm still using django 1.3 and finally figured out a solution. So in case anyone else is still in this boat:
Override the template fieldset.html (copied from pythondir/djangodir/django/contrib/admin/templates/admin/includes/fieldset.html into djangoprojectdir/templates/admin/includes/fieldset.html)
It contains the lines:
{% if field.is_readonly %}
<p>{{ field.contents }}</p>
Change them to:
{% if field.is_readonly %}
<p>{{ field.contents|linebreaksbr }}</p>
This is after trying Danny's solution end finding that it didn't work because the text returned from the contents function are escaped to replace tags with escape codes ("<" for "<", etc), and then reading this: https://code.djangoproject.com/ticket/19226.

- 341
- 2
- 10
When you view the source of your page, you'll see newlines. That whitespace is shown in the browser like a single space. You would need to convert all newlines (\n
) to HTML linebreaks (<br />
) to make it look the way you want.
Option 1: jQuery to the rescue.
Something like this:
<script type="text/javascript">
(function($) {
$(document).ready(function() {
// Adjustments for read-only fields:
// a) Convert quoted HTML entities back to HTML
$('.readonly').each(function() {
// Ensure there isn't valid html in the field
// The RegEx checks for any valid html opening tag
{% comment %}
TODO: It would be better to check against a special class name
on the widget
{% endcomment %}
if ($(this).html().match(/<(\w+)((?:\s+\w+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/) == null) {
$(this).html($(this).text());
$('ul', this).addClass('with_bullet');
$('li', this).addClass('with_bullet');
}
});
// b) Insert into empty <p>'s (m2m fields) so they don't break layout
// (see comment on text nodes: http://api.jquery.com/empty-selector/)
$('p.readonly:empty').each(function() { $(this).html(' ') })
});
})(django.jQuery);
</script>
(NB: we added "with_bullet" class because we're using grappelli and the ul's and li's get styled without a bullet (list-style-type: none) so this is a way of making them re-appear with our own CSS...) Also note the layout fix at the end, which I think is not needed in later versions of grappelli.
Option 2: monkeypatch django.contrib.admin.helpers.AdminReadonlyField:
from django.contrib.admin import helpers
from django.contrib.admin.util import (lookup_field,
display_for_field, label_for_field, help_text_for_field)
from django.core.exceptions import ObjectDoesNotExist
from django.db.models.fields.related import ManyToManyRel
from django.forms.util import flatatt
from django.template.defaultfilters import capfirst
from django.utils.encoding import force_unicode, smart_unicode
from django.utils.html import escape, conditional_escape
from django.utils.safestring import mark_safe
class BetterAdminReadonlyField(object):
def __init__(self, form, field, is_first, model_admin=None):
label = label_for_field(field, form._meta.model, model_admin)
# Make self.field look a little bit like a field. This means that
# {{ field.name }} must be a useful class name to identify the field.
# For convenience, store other field-related data here too.
if callable(field):
class_name = field.__name__ != '<lambda>' and field.__name__ or ''
else:
class_name = field
self.field = {
'name': class_name,
'label': label,
'field': field,
'help_text': help_text_for_field(class_name, form._meta.model)
}
self.form = form
self.model_admin = model_admin
self.is_first = is_first
self.is_checkbox = False
self.is_readonly = True
def label_tag(self):
attrs = {}
if not self.is_first:
attrs["class"] = "inline"
label = self.field['label']
contents = capfirst(force_unicode(escape(label))) + u":"
return mark_safe('<label%(attrs)s>%(contents)s</label>' % {
"attrs": flatatt(attrs),
"contents": contents,
})
def contents(self):
from django.contrib.admin.templatetags.admin_list import _boolean_icon
from django.contrib.admin.views.main import EMPTY_CHANGELIST_VALUE
field, obj, model_admin = self.field['field'], self.form.instance, self.model_admin
try:
f, attr, value = lookup_field(field, obj, model_admin)
except (AttributeError, ValueError, ObjectDoesNotExist):
result_repr = EMPTY_CHANGELIST_VALUE
else:
if f is None:
boolean = getattr(attr, "boolean", False)
if boolean:
result_repr = _boolean_icon(value)
else:
result_repr = smart_unicode(value)
if getattr(attr, "allow_tags", False):
result_repr = mark_safe(result_repr)
else:
if value is None:
result_repr = EMPTY_CHANGELIST_VALUE
elif isinstance(f.rel, ManyToManyRel):
result_repr = ", ".join(map(unicode, value.all()))
else:
result_repr = display_for_field(value, f)
return conditional_escape(result_repr)
helpers.AdminReadonlyField = BetterAdminReadonlyField
You could put this in a folder "monkeypatches" and call it "admin_readonly_field.py" (don't forget to also add an empty __init__.py
to make that folder a module).
Then in your app's __init__.py
add
from monkeypatches import admin_readonly_field
and you're away.
The above code only contains the relevant imports and code to monkeypatch AdminReadonlyField (copied in this case from Django 1.3). Nothing's actually changed from the original class yet. Change whatever you find most useful in your situation.
In your particular case you could maybe add these two lines to the second last one:
result_repr = display_for_field(value, f)
if isinstance(field, models.TextField):
result_repr = result_repr.replace('\n', '<br />')
(and from django.db import models
at the top)
I'm sorry but the class that ships with Django is so bad, option 2 is my recommended way of doing this. Your TextField is not the only kind of field that looks bad in readonly mode...

- 12,498
- 4
- 43
- 49
-
I like the idea of overriding the admin field to alter the display. +1 from me. – Aidan Ewen Feb 04 '13 at 21:12
-
Your first option with jQuery raises the expectation that a field listed in `ModelAdmin.readonly_fields` automatically gets a `readonly` css class when rendered. This doesn't seem to be the case, at least not in the Django version i'm using (1.4.3). – Dirk Eschler Feb 13 '13 at 15:24
-
Just realized that you are using grappelli. I wish there was an easier way than overriding templates or monkeypatch the entire beast. Basically i just want to add a css class, but that doesn't seem to be possible, `attrs` is hard coded. I'm wondering why Django doesn't use a (stripped down) widget for this. – Dirk Eschler Feb 13 '13 at 15:45
A line break in text is generally represented by the characters \n
or \r
or often \r\n
(check out this article on wikipedia for more info).
The problem you're having is that these characters will be used to display a new line in a text editing field but they don't represent a new line in html (they're ignored).
If you want them to display in a read only field then you could replace them with <br/>
elements.
If you can mark your string as safe (ie if you can safely add html code without the risk of anyone using the field to add malicious code), then you could override the save method on your model to swap out text line breaks for html line breaks -
from django.utils.safestring import mark_safe
def save(self, *args, **kwargs):
self.text_field = mark_safe(self.text_field.replace("\n", "<br/>"))
super(YourModel, self).save(*args, **kwargs)
Another alternative would be to add full text formatting functionality using a plugin like django-tinymce.
My last suggestion would be to hack at it with javascript. Add an admin folder to your templates and then create a base_site.html file which extends the original and adds a simple javascript function (as described here). Something like -
{% extends "admin/base.html" %}
{% block extrahead %}
<script type="text/javascript">
window.onload = function () {
var p_elements = document.getElementById('content-main').getElementsByTagName('p');
var unixNewLine = new RegExp("\n", "g");
for (var i = p_elements.length - 1; i >= 0; i--) {
p_elements[i].innerHTML = p_elements[i].innerHTML.replace(unixNewLine, '<br/>');
}
}
</script>
{% endblock %}
You'll need to add a replace
for every type of new line you have in your text (e.g. \r
, \r\n
). Whilst this may do what you need, it seems like the dirtiest hack of them all.

- 13,049
- 8
- 63
- 88
-
If you save the br tags (which alters the data), the standard textarea won't have line breaks anymore, and an editor like django-tinymce becomes more of a necessity. If you ever need real line breaks, you're facing the same problem in the other direction... – Danny W. Adair Feb 04 '13 at 20:43
-
Yes, that's a good point. I think the solution really depends on what the data is for. – Aidan Ewen Feb 04 '13 at 21:09
-
Thanks for the answers, but the content of txt file is written by a custom command. Using javascript also failed. any other ideas? Thanks! – LinuxMan Feb 07 '13 at 00:00
-
I was suggesting that you alter the text at the point at which it's saved to the database - it doesn't matter how your text file is created, you still save to the database right? I'm afraid the JavaScript solution was untested code, you may have to debug it. How did it fail? Do you get any errors? Have you checked that the script is loaded? Have you stepped through the code to see where it fails? You going to need to use the developer tools for your browser if you want to work with JavaScript. – Aidan Ewen Feb 07 '13 at 07:32
-
OK, I've debugged my code and updated my answer. This code works (just make sure you add all the text replace functions that you need). I would be grateful if you would vote my answer up if you found it helpful (and mark it as the accepted answer if you use it as your solution). – Aidan Ewen Feb 07 '13 at 08:35
-
Thanks Aidan! I tried your code using javascript but only first line is affected. – LinuxMan Feb 09 '13 at 01:49
-
My bad, I only checked it with one new line. Try using regex instead of a string in the replace (I'll updated my answer). It looks like javascript's replace doesn't work the same as other languages. http://stackoverflow.com/questions/2116558/fastest-method-to-replace-all-instances-of-a-character-in-a-string – Aidan Ewen Feb 09 '13 at 08:19
-
Works Aidan! Thank you!! I used the javascript code in change_form.html. =) – LinuxMan Feb 09 '13 at 13:57
-
It is better to use `|linebreaksbr` as suggested by [jenniwren](http://stackoverflow.com/a/21869578/2020214) – shackra Jun 02 '14 at 04:35