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:
Related
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'm using Wagtail 2.0 with a custom Block that has the following code:
class LinkButtonBlock(blocks.StructBlock):
label = blocks.CharBlock()
URL = blocks.CharBlock()
styling = blocks.ChoiceBlock(
choices=[
('btn-primary', 'Primary button'),
('btn-secondary', 'Secondary button'),
('btn-success', 'Success button'),
('btn-info', 'Info button'),
('btn-warning', 'Warning button'),
('btn-error', 'Error button'),
],
default='btn-info',
)
outline = blocks.BooleanBlock(
default=False
)
#property
def css(self):
btn_class = self.styling
if self.outline is True:
btn_class = btn_class.replace('btn-', 'btn-outline-')
return btn_class
class Meta:
icon = 'link'
template = 'testapp/blocks/link_button_block.html'
If I then try to access this css "property" in my template, nothing seems to happen. Putting a print(self) as first line inside the css def also shows nothing on the console suggesting the function never even gets called.
Using the following template:
{% load wagtailcore_tags %}
<a class="btn {{ block.value.css }}" href="{{ block.value.URL }}">{{ block.value.label }}</a>
Simply yields:
<a class="btn " href="actual.url.from.instance">actual.label.from.instance</a>
Also, block.value.styling and block.value.outline on their own work just fine, so... what am I doing wrong here?
The thing that's tripping you up is that the value objects you get when iterating over a StreamField are not instances of StructBlock. Block objects such as StructBlock and CharBlock act as converters between different data representations; they don't hold on to the data themselves. In this respect, they work a lot like Django's form field objects; for example, Django's forms.CharField and Wagtail's CharBlock both define how to render a string as a form field, and how to retrieve a string from a form submission.
Note that CharBlock works with string objects - not instances of CharBlock. Likewise, the values returned from StructBlock are not instances of StructBlock - they are a dict-like object of type StructValue, and this is what you need to subclass to implement your css property. There's an example of doing this in the docs: http://docs.wagtail.io/en/v2.0/topics/streamfield.html#custom-value-class-for-structblock. Applied to your code, this would become:
class LinkButtonValue(blocks.StructValue):
#property
def css(self):
# Note that StructValue is a dict-like object, so `styling` and `outline`
# need to be accessed as dictionary keys
btn_class = self['styling']
if self['outline'] is True:
btn_class = btn_class.replace('btn-', 'btn-outline-')
return btn_class
class LinkButtonBlock(blocks.StructBlock):
label = blocks.CharBlock()
URL = blocks.CharBlock()
styling = blocks.ChoiceBlock(choices=[...])
outline = blocks.BooleanBlock(default=False)
class Meta:
icon = 'link'
template = 'testapp/blocks/link_button_block.html'
value_class = LinkButtonValue
(Hopefully) not a duplicate:
I know this might seem to be quite similar to Django admin overriding - adding a print button
But the answer there is to use django-object-actions, which I already tried but it looks a bit too overloaded for such an simple task. Furthermore the buttons there are not placed behind the row.
My question:
I would like to create a printable view of some fields of a Django models instance.
Let's say I want to print an users
Name
Last Name
Number
What I image is something like this:
Clicking on a print button, shown at the list view:
An preformatted and easy to print website opens which contains the data:
What I have so far
I added the button by using the following code:
class MyModelAdmin(admin.ModelAdmin):
list_display = ('number', 'name', 'last_name', ..., 'account_actions')
...
def account_actions(self, obj):
return format_html(
'<form method="post" action="/print_view.htm"> \
<input type="hidden" name="name" value="{}"> \
<button type="submit" name="action">Print</button> \
</form>',
obj.name
)
account_actions.short_description = 'Actions'
account_actions.allow_tags = True
So my idea is to send the data which I want to get printed to another Website (via POST, on the same server). I would extract the data from the request then and create a printable view.
My question is:
Is it possible to do the same within Django (without leaving DjangoAdmin for the printable view)?
The current approach doesn't feel right too me, I bet there is a way to do that using just Django - a way which I don't know of since I am quite a beginner here.
I found a great module out there that is called django-admin-object-actions, it can be found here: https://github.com/ninemoreminutes/django-admin-object-actions
The maintainer/owner #cchurch helped me out with the following answer:
You can specify a custom view method that can render any template or
return any content you'd like. Here's the simplest example I can come
up with to do that:
class TestModelAdmin(ModelAdminObjectActionsMixin, admin.ModelAdmin):
# all of the normal model admin code here
object_actions = [
{
'slug': 'print',
'verbose_name': _('Print'),
'form_method': 'GET',
'view': 'print_view',
},
]
def print_view(self, request, object_id, form_url='', extra_context=None, action=None):
from django.template.response import TemplateResponse
obj = self.get_object(request, object_id)
return TemplateResponse(request, 'print.html', {'obj': obj})
Using the following template (print.html):
<p>Name: {{ obj.name }}</p>
<p>Enabled: {{ obj.enabled }}</p>
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.
Is there a simple way to make Django render {{myform.name}} as
John Smith
instead of
<input id="id_name" name="name" value="John Smith" />
inside <form> tags? Or am I going about this the wrong way?
<form>
{% for field in form %}
{{ field.label }}: {{ field.value }}
{% endfor %}
</form>
Take a look here Form fields and Working with forms
Old topic, but I think some people still comes here.
You can do something like this too:
from django.utils.safestring import mark_safe
class PlainTextWidget(forms.Widget):
def render(self, _name, value, _attrs):
return mark_safe(value) if value is not None else '-'
And in your form
class SomeForm(Form):
somename = forms.CharField(widget=PlainTextWidget)
Under Django 2.1+ you'll need the following:
from django.utils.safestring import mark_safe
class PlainTextWidget(forms.Widget):
def render(self, name, value, attrs=None, renderer=None):
return mark_safe(value) if value is not None else '-'
Also, don't forget you can also do {{myform.instance.name}}
You can also use a new widget: I did this so that I could have a widget that created a text display of a date, and a hidden form with the same date in it, so it could be visible to the user, but they cannot change it.
Here is an initial (still testing/to be cleaned up) version:
class DayLabelWidget(forms.Widget):
def render(self, name, value, attrs):
final_attrs = self.build_attrs(attrs, name=name)
if hasattr(self, 'initial'):
value = self.initial
if type(value) == type(u''):
value = datetime.date(*map(int, value.split('-')))
return mark_safe(
"%s" % value.strftime("%A (%d %b %Y)")
) + mark_safe(
"<input type='hidden' name='%s' value='%s' />" % (
name, value
)
)
def _has_changed(self, initial, data):
return False
You then use this in the field as (widget=DayLabelWidget,).
I think this is what you want:
http://code.djangoproject.com/ticket/10427
I patched my django and voila...
Why not use {{ field.data }} ?
Here is a take on the approach suggested by #Matthew_Schinkel above, using Django 2.2 and python 3.
from django.utils.safestring import mark_safe
class PlainTextWidgetWithHiddenCopy(forms.Widget):
def render(self, name, value, attrs, renderer=None):
if hasattr(self, 'initial'):
value = self.initial
return mark_safe(
(str(value) if value is not None else '-') +
f"<input type='hidden' name='{name}' value='{value}'>"
)
This is a custom widget that renders the contents of a field as plaintext followed by an html tag that creates a hidden field with the same name and value as the original field.
This means that as well as the value being displayed to the user, it is held by the browser and sent onwards to the view that processes the submitted form.
This is particularly handy if the form POSTs to itself, as the plaintext data would otherwise disappear because the field's initial value hadn’t been set.
For example if your form contains:
my_field = forms.IntegerField(
widget=PlainTextWidgetWithHiddenCopy,
initial=12345)
then this field will render as the following html:
12345<input type='hidden' name='my_field' value='12345'>
When the form is submitted the POST data received by the corresponding view will include:
{'my_field': '12345'}
This means that if the view now re-renders the form then the value of my_field is again set to 12345 just as if the request had been a GET.
As Matthew correctly points out in his subsequent comment, this approach provides no significant protection against a user changing the data that’s sent back in the hidden field. “All user input is evil”.