How to make a Django admin readonly textarea field - django

In Django admin, if I have a model field that's a TextField and set it as readonly using readonly_fields, then it's displayed as text in a <p> tag.
I'd like it to still be displayed as a textarea field, but with its disabled attribute set.
What's the simplest way to accomplish this?

use a form field
somefield = forms.CharField(
widget=forms.TextInput(attrs={'readonly':'readonly'})
)

A bit late, but here's an idea (inspired by #cinoch`s answer and this answer) that does the trick for me, with a minimum of code:
do not add the name of your TextField to the readonly_fields in your ModelAdmin subclass (otherwise step 2 has no effect)
instead, do add the following to your ModelAdmin subclass:
formfield_overrides = {
TextField: dict(widget=Textarea(attrs=dict(readonly=True)))
}
Note this requires some imports:
from django.db.models import TextField
from django.forms import Textarea
The TextField will now show up on the admin page as a scrollable Textarea instead of plain text, and its content will now be read-only, as desired.
Downside is that this applies to all TextFields in the model. If that's a problem, you should probably use a custom form as suggested by #cinoch and described in more detail here or here.
Also, this has no effect if ModelAdmin.has_change_permission() returns False.

The readonly_fields can take method names as well as field names. You could write a method that renders the value of the field in a disabled textarea.
Make sure you exclude the field from the model admin, since it will no longer be in readonly_fields.

#alasdair's answer is actually quite clever, but, unfortunately, it does not provide an example.
Here's my attempt to clarify, based on the docs for readonly_fields.
Assuming a model like so:
class MyModel(models.Model):
my_textfield = models.TextField()
The admin could look like this, using format_html to create a readonly textarea:
class MyModelAdmin(admin.ModelAdmin):
exclude = ['my_textfield']
readonly_fields = ['display_my_textfield']
#admin.display(description='my textfield')
def display_my_textfield(self, obj):
return format_html(
'<textarea cols="40" rows="10" readonly>{}</textarea>',
obj.my_textfield)
This also works if ModelAdmin.has_change_permission() returns False.

Related

In Django, when a UrlField is made read only, it prevents it from rendering as a clickable link. Why?

In Django (we are currently using 1.9), when we add an UrlField to a model, the Admin site correctly renders the UrlField value as a clickable link on edit views.
If we were to mark this UrlField as readonly (through the ModelAdmin  readonly_fields attribute), the value is then displayed as non-clickable plain text.
What is a rationale for this behaviour ?
Is there a way to work around it without changing the widget for the associated form field ?
I think it's just that readonly_fields displays the raw content (using the __str__() method) without any widget.
To work it around you might do something like this:
class MyAdmin (ModelAdmin):
readonly_fields = ['myurl_link']
def myurl_link(self, instance):
return format_html('<a href="{url}" target=_blank>{url}</a>', url=instance.myurl)
myurl_link.short_description = _("Website")

django admin list_display textfield

I've a Textfield defined in model.py
In the changelist data are shown as single line instead
in the change view of the object, the data are rendered in a:
vLargeTextField
the break lines are mantained as in the user input.
es.
hi,
nice to meet you,
I need a break
Is there something special to allow the list_display to show the data as in the change view?
You can render breaks as html (the linebreaks templatetag make it easy, or surround with the html pre tag i.e. <pre>... (your_text) ...</pre>) and set the allow_tags property to True for this field within your admin class definition.
admin.py
class CustomAdmin(admin.ModelAdmin):
model = YourModel
list_display = ['your_large_text_field__custom_rendering']
def your_large_text_field__custom_rendering(self, obj):
return "<pre>%s</pre>" % (obj.your_large_text_field,)
your_large_text_field__custom_rendering.allow_tags = True
admin.site.register(YourModel, CustomAdmin)

Django forms.ChoiceField without validation of selected value

Django ChoiceField "Validates that the given value exists in the list of choices."
I want a ChoiceField (so I can input choices in the view) but I don't want Django to check if the choice is in the list of choices. It's complicated to explain why but this is what I need. How would this be achieved?
You could create a custom ChoiceField and override to skip validation:
class ChoiceFieldNoValidation(ChoiceField):
def validate(self, value):
pass
I'd like to know your use case, because I really can't think of any reason why you would need this.
Edit: to test, make a form:
class TestForm(forms.Form):
choice = ChoiceFieldNoValidation(choices=[('one', 'One'), ('two', 'Two')])
Provide "invalid" data, and see if the form is still valid:
form = TestForm({'choice': 'not-a-valid-choice'})
form.is_valid() # True
Best way to do this from the looks of it is create a forms.Charfield and use a forms.Select widget. Here is an example:
from django import forms
class PurchaserChoiceForm(forms.ModelForm):
floor = forms.CharField(required=False, widget=forms.Select(choices=[]))
class Meta:
model = PurchaserChoice
fields = ['model', ]
For some reason overwriting the validator alone did not do the trick for me.
As another option, you could write your own validator
from django.core.exceptions import ValidationError
def validate_all_choices(value):
# here have your custom logic
pass
and then in your form
class MyForm(forms.Form):
my_field = forms.ChoiceField(validators=[validate_all_choices])
Edit: another option could be defining the field as a CharField but then render it manually in the template as a select with your choices. This way, it can accept everything without needing a custom validator

Django Admin: Add text at runtime next to a field

I want to add a text next to a field of the django admin interface.
The warning needs to created at runtime inside a python method. I know python and the django ORM well, but I don't know how to get the text next the field.
The text should be a warning. Raising ValidationError in clean() is not a solution, since
the user can't edit the page any more. It should be just a warning message.
You can use custom ModelForm subclass for the admin, adding help_text attribute for the field in question at its initialization, and style it appropriately.
# forms.py
class YourModelForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(YourModelForm, self).__init__(*args, **kwargs)
self.fields['field_in_question'].help_text = generate_warning()
# admin.py
class YourModelAdmin(admin.ModelAdmin):
form = forms.YourModelForm
# And here you can specify custom CSS / JS which would make
# `help_text` for that particular field look like a warning.
# Or you can make it generic--say, style (and maybe reposition w/js) all tags
# like <span class="warning"> that occur within the help text of any field.
class Media:
css = {"all": ("admin_warning.css", )}
js = ("admin_warning.js", )
If you want to do it in changelist view, you can write in model method, which returns string in format you want, and put name of that method in list_display in admin.
class MyModel(models.Model):
myfield = models.CharField(max_length=100)
def myfield_with_warning(self):
return '%s - %s' % (self.myfield, '<span class="warn">My warning message</p>'
myfield_with_warning.short_description = 'My field verbose name'
myfield_with_warning.allow_tags = True
class MyModelAdmin(models.ModelAdmin):
list_display = ('myfield_with_warning',)
If it's not what you need, write more precisely, where do you want to display warning message.
I think the simplest way would be to override the specific admin page for that model. This is described here in the Django documentation. The template you need to override is probably change_form.html. Within these template displayed object is available in the template variable original.
I would add a method or property to you model, that generates and returns the error message and call this method from the template.
Edit: Have a look at contrib/admin/templates/admin/change_form.html there is a include for includes/fieldset.html that displays the the fields of the admin site. You could put some code there that chckes if the model has some special named attribute and if so it is displayed. You could them simply override that change_form.html for all models with your custom one.

Customising Django admin TabularInline default field

I have a TabularInline admin layout, all works fine except I'd like to have it show something other than the Obj.__unicode__ value on the top left of each row.
My TabularInline is a photologue ImageModel model, so I'd like it to show me the thumbnail instead of the regular __unicode__ result.
I tried to change __unicode__ to output the thumbnail, which works, except the HTML is escaped so I get <img src="XXX"...... etc
Is there an easy way to mark my __unicode__ method as a safe string? Or a way to override the property the admin chooses to display?
I've tried this:
__unicode__.is_safe = True
But that doesn't work.
You can customize the template for you TabularInline to make it look the way you want. I think it's a better idea then hacking __unicode__:
class PhotoInline(admin.TabularInline):
model = Photo
template = 'photologue/photoinline.html'
The easiest way to create your is to copy and customize the default django/contrib/admin/templates/admin/edit_inline/tabular.html template.