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!
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.
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...
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.
Related
I am trying to make a template tag that will show a section of code verbatim, and also render it, it is to help me demo some code for my own tooling.
Say I have this in a template:
{% example %}import * as fancy from "{% static 'jslib/fancy.min.js' %}";{% endexample %}
I wish to output it as (I will add some divs and styling, but this is distilling the problem into it's simplest form):
import * as fancy from "{% static 'jslib/fancy.min.js' %}";
import * as fancy from "/static/jslib/fancy.min.js";
I looked at the django {% verbatum %} tag and tried to copy the logic:
# django.template.defaulttags.py
#register.tag
def verbatim(parser, token):
nodelist = parser.parse(('endverbatim',))
parser.delete_first_token()
return VerbatimNode(nodelist.render(Context()))
nodelist.render(Context()) prints out text nodes for the django verbatim tag, but if I copy the code my example tag prints out say StaticNodes and other types of nodes.
The reason why is I think the django parser has some special checks to see if it is a verbatim node and handles if differently, as shown in the code below:
# django.template.base.py -> def create_token
if self.verbatim:
# Then a verbatim block is being processed.
if content != self.verbatim:
return Token(TokenType.TEXT, token_string, position, lineno)
# Otherwise, the current verbatim block is ending.
self.verbatim = False
elif content[:9] in ('verbatim', 'verbatim '):
# Then a verbatim block is starting.
self.verbatim = 'end%s' % content
return Token(TokenType.BLOCK, content, position, lineno)
This means I inspect the nodes of the nodelist to see if they have some sorta of "get original string" method but found nothing.
How would one make a tag to get the rendered and unrendered text within a pair of tags?
Most un-elegant solution, akeen to hammering it. Just load the file, and use a regex to extraxt the portion
import re
from django import template
register = template.Library()
#register.tag
def story(parser, token):
"""Usage: {% story verbatim_1 %} <div>{{ foo }}</div> {% endstory %}
#verbatim_id: str
Attribute value that starts with the text 'verbatim', following it
should be a unique id, e.g. `verbatim_1`, `verbatim_2` etc.
This unique verbatim id is used to open the file and perform a regex
match to find the code and read it verbatim.
"""
bits = token.contents.split()
verbatim_id = next(filter(lambda x: 'verbatim' in x, bits), None)
nodelist = parser.parse(('endstory',))
parser.delete_first_token()
return StoryNode(nodelist,verbatim_id)
class StoryNode(template.Node):
def __init__(self, nodelist, dark, verbatim_id):
self.nodelist = nodelist
self.verbatim_id = verbatim_id
def render(self, context):
example = self.nodelist.render(context)
if self.verbatim_id:
file = self.origin.name
with open(file, 'r') as f:
content = f.read()
pattern = r'{%\s*stZory[^%]*' + self.verbatim_id + r'[^%]*%}(.+?){% endstory %}'
match = re.search(pattern, content, flags=re.M | re.DOTALL)
if not match:
raise ValueError(
f"ERROR: Could not find verbatim_id: '{self.verbatim_id}', line: {self.token.lineno}, file: {self.origin.name}"
)
inner_html = match.group(1).strip()
code = inner_html
else:
code = example
print('The verbatim code is', code)
print('The rendered example is', example)
return mark_safe('')
I would like to add a hyperlink to the related model Training
It would be nice to have declarative solution, since I want to use
this at several places.
The "pencil" icon opens the related model in a popup window. That's not what I want. I want a plain hyperlink to the related model.
BTW, if you use "raw_id_fields", then the result is exactly what I was looking for: There is a hyperlink to the corresponding admin interface of this ForeignKey.
Update Jan 4, 2023
From Django 4.1, this becomes a part of the official build (related PR).
Related widget wrappers now have a link to object’s change form
Result
Previous Answer
The class named RelatedFieldWidgetWrapper is showing the icons on the Django Admin page and thus you need to override the same. So, create a custom class as below,
from django.contrib.admin.widgets import RelatedFieldWidgetWrapper
class CustomRelatedFieldWidgetWrapper(RelatedFieldWidgetWrapper):
template_name = 'admin/widgets/custom_related_widget_wrapper.html'
#classmethod
def create_from_root(cls, root_widget: RelatedFieldWidgetWrapper):
# You don't need this method of you are using the MonkeyPatch method
set_attr_fields = [
"widget", "rel", "admin_site", "can_add_related", "can_change_related",
"can_delete_related", "can_view_related"
]
init_args = {field: getattr(root_widget, field) for field in set_attr_fields}
return CustomRelatedFieldWidgetWrapper(**init_args)
def get_context(self, name, value, attrs):
context = super().get_context(name, value, attrs)
rel_opts = self.rel.model._meta
info = (rel_opts.app_label, rel_opts.model_name)
context['list_related_url'] = self.get_related_url(info, 'changelist')
return context
See, the context variable list_related_url is the relative path that we need here. Now, create an HTML file to render the output,
#File: any_registered_appname/templates/admin/widgets/custom_related_widget_wrapper.html
{% extends "admin/widgets/related_widget_wrapper.html" %}
{% block links %}
{{ block.super }}
- Link To Related Model -
{% endblock %}
How to connect?
Method-1 : Monkey Patch
# admin.py
# other imports
from ..widgets import CustomRelatedFieldWidgetWrapper
from django.contrib.admin import widgets
widgets.RelatedFieldWidgetWrapper = CustomRelatedFieldWidgetWrapper # monket patch
Method-2 : Override ModelAdmin
# admin.py
class AlbumAdmin(admin.ModelAdmin):
hyperlink_fields = ["related_field_1"]
def formfield_for_dbfield(self, db_field, request, **kwargs):
formfield = super().formfield_for_dbfield(db_field, request, **kwargs)
if db_field.name in self.hyperlink_fields:
formfield.widget = CustomRelatedFieldWidgetWrapper.create_from_root(
formfield.widget
)
return formfield
Result
There are several ways to go. Here is one.
Add some javascript that changes the existing link behavior. Add the following script at the end of the overridden admin template admin/widgets/related_widget_wrapper.html. It removes the class which triggers the modal and changes the link to the object.
It will only be triggered for id_company field. Change to your needs.
{% block javascript %}
<script>
'use strict';
{
const $ = django.jQuery;
function changeEditButton() {
const edit_btn = document.getElementById('change_id_company');
const value = edit_btn.previousElementSibling.value;
const split_link_template = edit_btn.getAttribute('data-href-template').split('?');
edit_btn.classList.remove('related-widget-wrapper-link');
edit_btn.setAttribute('href', split_link_template[0].replace('__fk__', value));
};
$(document).ready(function() {
changeEditButton();
$('body').on('change', '#id_company', function(e) {
changeEditButton();
});
});
}
</script>
{% endblock %}
This code can also be modified to be triggered for all edit buttons and not only for the company edit button.
I set up a working custom template tag, it is registered, I can call it, it instantiates a template.Node instance and calls its render() method. The problem is that when I return a simple string like
def render(self, context):
return 'asd'
it works ok, but it fails whenever i try to return something containing html:
def render(self, context):
return mark_safe('<ul class="jqueryFileTree" style="display: none;"><li><ITEM</li></ul>')
it fails silently without rendering a thing. Any help?
EDIT: added mark_safe. Still doesn't work
EDIT: the tag:
import os
import urllib
from django import template
from django.utils.safestring import mark_safe
register = template.Library()
class DirTree(template.Node):
def __init__(self, start_dir):
self.start_dir = start_dir
def render(self, context):
# CODE THAT GENERATES A HTML NESTED LIST
return mark_safe('<ul class="jqueryFileTree"><li><ITEM</li></ul>')
#register.tag
def print_tree(parser, token):
try:
# split_contents() knows not to split quoted strings.
tag_name, start_dir = token.split_contents()
except ValueError:
raise template.TemplateSyntaxError("%r tag requires a single argument" % token.contents.split()[0])
if not (start_dir[0] == start_dir[-1] and start_dir[0] in ('"', "'")):
raise template.TemplateSyntaxError("%r tag's argument should be in quotes" % tag_name)
return DirTree(start_dir[1:-1])
# TEMPLATE.HTML
# HTML 'N STUFF
<div id="file-tree">
{% print_tree "catflow_portal/static/repo/usr/test-user/catalogs/food/" %}
</div>
#END TAGS
I think your problem is you need to use ...|safe in your template to tell django to show this result as html, not force to show it as string with "..."
https://docs.djangoproject.com/en/1.4/ref/templates/builtins/#safe
Update 2021-10-20: Django 3.2
...|safe
Marks a string as not requiring further HTML escaping prior to output. When autoescaping is off, this filter has no effect.
If you are chaining filters, a filter applied after safe can make the contents unsafe again. For example, the following code prints the variable as is, unescaped:
{{ var|safe|escape }}
https://docs.djangoproject.com/en/3.2/ref/templates/builtins/#std:templatefilter-safe
I couldn't find this in the docs, but think it must be possible. I'm talking specifically of the ClearableFileInput widget. From a project in django 1.2.6 i have this form:
# the profile picture upload form
class ProfileImageUploadForm(forms.ModelForm):
"""
simple form for uploading an image. only a filefield is provided
"""
delete = forms.BooleanField(required=False,widget=forms.CheckboxInput())
def save(self):
# some stuff here to check if "delete" is checked
# and then delete the file
# 8 lines
def is_valid(self):
# some more stuff here to make the form valid
# allthough the file input field is empty
# another 8 lines
class Meta:
model = SocialUserProfile
fields = ('image',)
which i then rendered using this template code:
<form action="/profile/edit/" method="post" enctype="multipart/form-data">
Delete your image:
<label> {{ upload_form.delete }} Ok, delete </label>
<button name="delete_image" type="submit" value="Save">Delete Image</button>
Or upload a new image:
{{ upload_form.image }}
<button name="upload_image" type="submit" value="Save">Start Upload</button>
{% csrf_token %}
</form>
As Django 1.3.1 now uses ClearableFileInput as the default widget, i'm pretty sure i can skip the 16 lines of my form.save and just shorten the form code like so:
# the profile picture upload form
class ProfileImageUploadForm(forms.ModelForm):
"""
simple form for uploading an image. only a filefield is provided
"""
class Meta:
model = SocialUserProfile
fields = ('image',)
That would give me the good feeling that i have less customized formcode, and can rely on the Django builtins.
I would, of course, like to keep the html-output the same as before. When just use the existing template code, such things like "Currently: somefilename.png" pop up at places where i do not want them.
Splitting the formfield further, like {{ upload_form.image.file }} does not seem to work. The next thing coming to my mind was to write a custom widget. Which would work exactly against my efforts to remove as many customized code as possible.
Any ideas what would be the most simple thing to do in this scenario?
Firstly, create a widgets.py file in an app. For my example, I'll be making you an AdminImageWidget class that extends AdminFileWidget. Essentially, I want a image upload field that shows the currently uploaded image in an <img src="" /> tag instead of just outputting the file's path.
Put the following class in your widgets.py file:
from django.contrib.admin.widgets import AdminFileWidget
from django.utils.translation import ugettext as _
from django.utils.safestring import mark_safe
import os
import Image
class AdminImageWidget(AdminFileWidget):
def render(self, name, value, attrs=None):
output = []
if value and getattr(value, "url", None):
image_url = value.url
file_name=str(value)
# defining the size
size='100x100'
x, y = [int(x) for x in size.split('x')]
try :
# defining the filename and the miniature filename
filehead, filetail = os.path.split(value.path)
basename, format = os.path.splitext(filetail)
miniature = basename + '_' + size + format
filename = value.path
miniature_filename = os.path.join(filehead, miniature)
filehead, filetail = os.path.split(value.url)
miniature_url = filehead + '/' + miniature
# make sure that the thumbnail is a version of the current original sized image
if os.path.exists(miniature_filename) and os.path.getmtime(filename) > os.path.getmtime(miniature_filename):
os.unlink(miniature_filename)
# if the image wasn't already resized, resize it
if not os.path.exists(miniature_filename):
image = Image.open(filename)
image.thumbnail([x, y], Image.ANTIALIAS)
try:
image.save(miniature_filename, image.format, quality=100, optimize=1)
except:
image.save(miniature_filename, image.format, quality=100)
output.append(u' <div><img src="%s" alt="%s" /></div> %s ' % \
(miniature_url, miniature_url, miniature_filename, _('Change:')))
except:
pass
output.append(super(AdminFileWidget, self).render(name, value, attrs))
return mark_safe(u''.join(output))
Ok, so what's happening here?
I import an existing widget (you may be starting from scratch, but should probably be able to extend ClearableFileInput if that's what you are starting with)
I only want to change the output/presentation of the widget, not the underlying logic. So, I override the widget's render function.
in the render function I build the output I want as an array output = [] you don't have to do this, but it saves some concatenation. 3 key lines:
output.append(u' <div><img src="%s" alt="%s" /></div> %s ' % (miniature_url, miniature_url, miniature_filename, _('Change:'))) Adds an img tag to the output
output.append(super(AdminFileWidget, self).render(name, value, attrs)) adds the parent's output to my widget
return mark_safe(u''.join(output)) joins my output array with empty strings AND exempts it from escaping before display
How do I use this?
class SomeModelForm(forms.ModelForm):
"""Author Form"""
photo = forms.ImageField(
widget = AdminImageWidget()
)
class Meta:
model = SomeModel
OR
class SomeModelForm(forms.ModelForm):
"""Author Form"""
class Meta:
model = SomeModel
widgets = {'photo' : AdminImageWidget(),}
Which gives us:
Is there any clever way to make django forms render field with asterisks after fields that are required? Or to provide some other clever for to mark required fields? I wouldn't like to have to do it again in a template if I already set a field as required in the form.
As of Django 1.2, if your form has an attribute named required_css_class, it will be added to BoundField.css_classes for required fields. You can then use CSS to style the required parts of the form as desired. A typical use case:
# views.py
class MyForm(django.forms.Form):
required_css_class = 'required'
…
…
/* CSS */
th.required { font-weight: bold; }
…
<!-- HTML -->
<tr>
<th class="{{form.name.css_classes}}">{{form.name.label_tag}}</th>
<td>{{form.name.errors}}{{form.name}}</td>
</tr>
If you use Form.as_table(), Form.as_ul, and Form.as_p, they do this automatically, adding the class to <tr>, <li>, and <p>, respectively.
You also can use a field.field.required property:
{% for field in form %}
{{field.label}} {% if field.field.required %} * {% endif %}
{{field}}
{{field.errors}}
{% endfor %}
The best way for such purposes I have found is to render form's output via an html template. How to do this is described here. By luck, the example puts an asterisk after required fields just like you want.
I use a template tag to render form fields with their labels and errors, plus an asterisk and a CSS class for required fields. There are various snippets available to do it on www.djangosnippets.org
Personny I tried something like this, i.e. overriding the default template tag (this adds a red asterisk in all the admin interface for required not-readonly fields. Then i also defined a 2nd tag for my views that mix inline and block labels. See the code below (copy/paste of Django source code with some modifications):
In settings.py :
from django.forms.util import flatatt
from django.utils.html import conditional_escape
from django.utils.safestring import mark_safe
import django.forms.forms as django_forms
def custom_label_tag(self, contents=None, attrs=None):
"""
Wraps the given contents in a <label>, if the field has an ID attribute.
Does not HTML-escape the contents. If contents aren't given, uses the
field's HTML-escaped label.
If attrs are given, they're used as HTML attributes on the <label> tag.
"""
contents = contents or conditional_escape(self.label)
widget = self.field.widget
id_ = widget.attrs.get('id') or self.auto_id
if id_:
attrs = attrs and flatatt(attrs) or ''
if self.field.required:
label = unicode(contents)
label_suffix = ""
if label[-1] == ":":
label = label[:-1]
label_suffix += ":"
contents = u'<label for="%s"%s>%s<span style="color:red;">*</span>%s</label>' % (widget.id_for_label(id_), attrs, label, label_suffix)
else:
contents = u'<label for="%s"%s>%s</label>' % (widget.id_for_label(id_), attrs, unicode(contents))
return mark_safe(contents)
def custom_inline_label_tag(self, contents=None, attrs=None):
if attrs:
if "class" in attrs.keys():
attrs["class"] += " inline"
else:
attrs["class"] = "inline"
else:
attrs = {"class": "inline"}
return self.label_tag(contents, attrs)
django_forms.BoundField.label_tag = custom_label_tag # override django method
django_forms.BoundField.inline_label_tag = custom_inline_label_tag
In my templates
<p>{{ form.fieldname.errors}}{{ form.fieldname.inline_label_tag }}{{form.fieldname}}</p>
OR
<p>{{ form.fieldname.errors}}{{ form.fieldname.label_tag }}{{form.fieldname}}</p>