Django-Admin: CharField as TextArea - django

I have
class Cab(models.Model):
name = models.CharField( max_length=20 )
descr = models.CharField( max_length=2000 )
class Cab_Admin(admin.ModelAdmin):
ordering = ('name',)
list_display = ('name','descr', )
# what to write here to make descr using TextArea?
admin.site.register( Cab, Cab_Admin )
how to assign TextArea widget to 'descr' field in admin interface?
upd:
In Admin interface only!
Good idea to use ModelForm.

You will have to create a forms.ModelForm that will describe how you want the descr field to be displayed, and then tell admin.ModelAdmin to use that form. For example:
from django import forms
class CabModelForm( forms.ModelForm ):
descr = forms.CharField( widget=forms.Textarea )
class Meta:
model = Cab
class Cab_Admin( admin.ModelAdmin ):
form = CabModelForm
The form attribute of admin.ModelAdmin is documented in the official Django documentation. Here is one place to look at.

For this case, the best option is probably just to use a TextField instead of CharField in your model. You can also override the formfield_for_dbfield method of your ModelAdmin class:
class CabAdmin(admin.ModelAdmin):
def formfield_for_dbfield(self, db_field, **kwargs):
formfield = super(CabAdmin, self).formfield_for_dbfield(db_field, **kwargs)
if db_field.name == 'descr':
formfield.widget = forms.Textarea(attrs=formfield.widget.attrs)
return formfield

Ayaz has pretty much spot on, except for a slight change(?!):
class MessageAdminForm(forms.ModelForm):
class Meta:
model = Message
widgets = {
'text': forms.Textarea(attrs={'cols': 80, 'rows': 20}),
}
class MessageAdmin(admin.ModelAdmin):
form = MessageAdminForm
admin.site.register(Message, MessageAdmin)
So, you don't need to redefine a field in the ModelForm to change it's widget, just set the widgets dict in Meta.

You don't need to create the form class yourself:
from django.contrib import admin
from django import forms
class MyModelAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
kwargs['widgets'] = {'descr': forms.Textarea}
return super().get_form(request, obj, **kwargs)
admin.site.register(MyModel, MyModelAdmin)
See ModelAdmin.get_form.

You can subclass your own field with needed formfield method:
class CharFieldWithTextarea(models.CharField):
def formfield(self, **kwargs):
kwargs.update({"widget": forms.Textarea})
return super(CharFieldWithTextarea, self).formfield(**kwargs)
This will take affect on all generated forms.

If you are trying to change the Textarea on admin.py, this is the solution that worked for me:
from django import forms
from django.contrib import admin
from django.db import models
from django.forms import TextInput, Textarea
from books.models import Book
class BookForm(forms.ModelForm):
description = forms.CharField( widget=forms.Textarea(attrs={'rows': 5, 'cols': 100}))
class Meta:
model = Book
class BookAdmin(admin.ModelAdmin):
form = BookForm
admin.site.register(Book, BookAdmin)
If you are using a MySQL DB, your column length will usually be autoset to 250 characters, so you will want to run an ALTER TABLE to change the length in you MySQL DB, so that you can take advantage of the new larger Textarea that you have in you Admin Django site.

Instead of a models.CharField, use a models.TextField for descr.

You can use models.TextField for this purpose:
class Sample(models.Model):
field1 = models.CharField(max_length=128)
field2 = models.TextField(max_length=1024*2) # Will be rendered as textarea

Wanted to expand on Carl Meyer's answer, which works perfectly till this date.
I always use TextField instead of CharField (with or without choices) and impose character limits on UI/API side rather than at DB level. To make this work dynamically:
from django import forms
from django.contrib import admin
class BaseAdmin(admin.ModelAdmin):
"""
Base admin capable of forcing widget conversion
"""
def formfield_for_dbfield(self, db_field, **kwargs):
formfield = super(BaseAdmin, self).formfield_for_dbfield(
db_field, **kwargs)
display_as_charfield = getattr(self, 'display_as_charfield', [])
display_as_choicefield = getattr(self, 'display_as_choicefield', [])
if db_field.name in display_as_charfield:
formfield.widget = forms.TextInput(attrs=formfield.widget.attrs)
elif db_field.name in display_as_choicefield:
formfield.widget = forms.Select(choices=formfield.choices,
attrs=formfield.widget.attrs)
return formfield
I have a model name Post where title, slug & state are TextFields and state has choices. The admin definition looks like:
#admin.register(Post)
class PostAdmin(BaseAdmin):
list_display = ('pk', 'title', 'author', 'org', 'state', 'created',)
search_fields = [
'title',
'author__username',
]
display_as_charfield = ['title', 'slug']
display_as_choicefield = ['state']
Thought others looking for answers might find this useful.

Related

Creating a generic search view in Django

I am struggling to create my custom generic view in django to easily create search pages for certain models. I'd like to use it like this:
class MyModelSearchView(SearchView):
template_name = 'path/to/template.html'
model = MyModel
fields = ['name', 'email', 'whatever']
which will result in a view that returns a search form on GET and both form and results on POST.
The fields specifies which fields of MyModel will be available for a user to search.
class SearchView(FormView):
def get_form(self, form_class=None):
# what I'v already tried:
class SearchForm(ModelForm):
class Meta:
model = self.model
fields = self.fields
return SearchForm()
def post(self, request, *args, **kwargs):
# perform searching and return results
The problem with the code above is that form will not be submitted if certain fields are not be properly filled. User should be allowed to provide only part of fields to search but with the code I provided the form generated with ModelForm prevents that (for example because a field in a model cannot be blank).
My questions are:
Is it possible to generate a form based on a model to omit this behaviour?
Or is there any simpler way to create SearchView class?
I don't want to manually write forms if it's possible.
One way to accomplish this is to set blank=True on the field in MyModel, as indicated in the docs:
If the model field has blank=True, then required is set to False on the form field. Otherwise, required=True.
But for this to be a generic solution, you can't count on being able to modify the model fields. You can instead set the fields' required attribute to False immediately after the instance is created:
class SearchForm(ModelForm):
class Meta:
model = self.model
fields = self.fields
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for (field_name, field) in self.fields.items():
field.required = False
Since you're using the ModelForm for searching, you should set all the fields as required=False, by overriding the __init__ method:
def get_form(self, form_class=None):
# what I'v already tried:
class SearchForm(ModelForm):
class Meta:
model = self.model
fields = self.fields
def __init__(self, *args, **kwargs):
super(SearchForm, self).__init__(*args, **kwargs)
for field in self.fields:
self.fields[field].required = False
return SearchForm()
Though I suggest you should user django-filter, which makes it easier and cleaner to filter your searches. First you need to install it:
pip install django-filter
Then add it to your INSTALLED_APPS. After that you can create a filters.py file in your app:
# myapp/filters.py
import django_filters as filters
from .models import MyModel
MyModelFilterSet(filters.FilterSet):
class Meta:
model = MyModel
fields = ['name', 'email', 'whatever']
By default it's going to filter with the __exact lookup. You can change this in a couple of ways, just take a look here and here. To know which lookups you can use, take a look here.
After creating your filters.py file you can add it to a View, like a ListView:
# myapp/views.py
from django.views.generic import ListView
from .filters import MyModelFilterSet
from .models import MyModel
class MyModelSearchView(ListView):
template_name = 'path/to/template.html'
model = MyModel
def get_queryset(self):
qs = self.model.objects.all()
filtered_model_list = MyModelFilterSet(self.request.GET, queryset=qs)
return filtered_model_list.qs
There's a lot more you can do with django-filter. Here's the full documentation.

Add `help_text` to Django Admin field

I'm attempting to display an image when editing a user on the admin panel, but I can't figure out how to add help text.
I'm using this Django Admin Show Image from Imagefield code, which works fine.
However the short_description attribute only names the image, and help_text doesn't seem to add an text below it.
How can I add help_text to this field like normal model fields?
EDIT:
I would like to have help_text on the image like the password field does in this screenshot:
Use a custom form if you don't want change a model:
from django import forms
class MyForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(MyForm, self).__init__(*args, **kwargs)
self.fields['image'].help_text = 'My help text'
class Meta:
model = MyModel
exclude = ()
#admin.register(MyModel)
class MyModelAdmin(admin.ModelAdmin):
form = MyForm
# ...
It took me a while to figure out. If you've defined a custom field in your admin.py only and the field is not in your model. Use a custom ModelForm:
class SomeModelForm(forms.ModelForm):
# You don't need to define a custom form field or setup __init__()
class Meta:
model = SomeModel
help_texts = {'avatar': "User's avatar Image"}
exclude = ()
And in the admin.py:
class SomeModelAdmin(admin.ModelAdmin):
form = SomeModelForm
# ...
If you don't want to create a custom model form class :
class MyModelAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, change=False, **kwargs):
form = super().get_form(request, obj=obj, change=change, **kwargs)
form.base_fields["image"].help_text = "Some help text..."
return form

Django Admin - Filter Choice Field based on current object being edited

I am attempting to exclude the current object being edited from a choice field to select a parent object from the same model. For example:
from django import forms
from django.contrib import admin
class RelationForm(forms.ModelForm):
parent = forms.ModelChoiceField(queryset=Ingredient.objects.exclude(id=current_ingredient_id))
def save(self, commit=True):
parent = self.cleaned_data.get('parent', None)
# ...do something with extra_field here...
return super(RelationForm, self).save(commit=commit)
class Meta:
model = IngredientRelations
exclude = ['description']
#admin.register(Ingredient)
class IngredientAdmin(admin.ModelAdmin):
form = RelationForm
fieldsets = (
(None, {
'fields': ('name', 'slug', 'description', 'parent',),
}),
)
The difficult comes from getting the current object being edited and then getting its primary key in the RelationForm for the queryset argument.
I have tried using ModelAdmin.formfield_for_foreignkey and ModelAdmin.formfield_for_choice_field in IngredientAdmin with no luck.
Any ideas?
The canonical method to do this is by updating the queryset in the __init__ method using currently edited instance id.
class RelationForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(RelationForm, self).__init__(*args, **kwargs)
if self.instance.id:
self.fields['parent'].queryset = Ingredient.objects.exclude(id=self.instance.id)
class Meta:
model = IngredientRelations
exclude = ['description']
originally seen in this answer: https://stackoverflow.com/a/1869917/484127

Hidden property in Django 1.7 admin BaseInlineFormSet

I have a model in my project which is rendered as an inline of an other model.
The inline model has a property (not a field) that I need in my template for a particular check, but I don't want it shown as a field. I can't find a way to make such field rendered as hidden, so that the entire <TD> disappears.
I've both tried with Meta class:
class MyFormset(forms.models.BaseInlineFormSet):
class Meta:
widgets = {
'to_be_hidden_property': forms.HiddenField,
}
and with add_fields method:
class MyFormset(forms.models.BaseInlineFormSet):
def add_fields(self, form, index):
super(MyFormset, self).add_fields(form, index)
form.fields["to_be_hidden_property"].widget = forms.HiddenInput()
but both failed. First attempt simply gets ignored, while second one leads to a KeyError exception (field doesn't exist).
I'm open to suggestions, as I'd really hate to have to hide it via JS.
EDIT (adding admin.TabularInline code):
class AssegnamentoCommessaConsulenzaInline(admin.TabularInline):
formset = AssegnamentoCommessaConsulenzaFormset
model = AssegnamentoCommessaConsulenza
template = 'admin/commessa/edit_inline/assegnamentocommessaconsulenza_tabular.html'
extra = 0
readonly_fields = (
'get_fase',
'get_configuration_link_inline',
'numero_erogazioni_effettuate', # Field to hide
)
class Media:
js = ('useless things', )
css = {'all': ('even less useful things', )}
EDIT (to explain what I've tried after #Alasdair's suggestion):
from django.forms.models import inlineformset_factory
class MyFormAbstract(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(MyFormAbstract, self).__init__(*args, **kwargs)
self.fields['to_be_hidden_property'] = forms.CharField(
widget=forms.HiddenInput(),
initial=self.instance.to_be_hidden_property
)
class Meta:
model = MyModel
fields = '__all__'
MyFormset = inlineformset_factory(
MyMainModel,
MyInlineModel,
form=MyFormAbstract
)
This whole thing seems to get silently ignored. There are no errors, but the property is still there.
Instead of subclassing BaseInlineFormSet, you might find it easier to customize the model form.
class MyForm(forms.ModelForm):
class Meta:
model = MyModel
def __init__(self, *args, **kwargs):
super(MyForm, self).__init__(*args, **kwargs)
self.fields['property_field'] = forms.CharField(widget=forms.HiddenInput(), initial=self.instance.property_field)
MyInlineFormset = inlineformset_factory(MyMainModel, MyModel, form=MyForm)
Note that all this does is add the hidden field to the form, you will have to add any additional processing you want.

Django TextField max_length validation for ModelForm

Django does not respect the max_length attribute of TextField model field while validating a ModelForm.
So I define a LimitedTextField inherited from the models.TextField and added validation bits similar to models.CharField:
from django.core import validators
class LimitedTextField(models.TextField):
def __init__(self, *args, **kwargs):
super(LimitedTextField, self).__init__(*args, **kwargs)
self.max_length = kwargs.get('max_length')
if self.max_length:
self.validators.append(validators.MaxLengthValidator(self.max_length))
def formfield(self, **kwargs):
defaults = {'max_length': self.max_length}
defaults.update(kwargs)
return super(LimitedTextField, self).formfield(**defaults)
But this still has no affect on ModelForm validation.
What am I missing?
As of Django 1.2 this can be done by validators at model level, as explained here:
https://docs.djangoproject.com/en/stable/ref/validators/
from django.core.validators import MaxLengthValidator
class Comment(models.Model):
comment = models.TextField(validators=[MaxLengthValidator(200)])
Since Django 1.7, you can use max_length which is only enforced in client side. See here
You can enforce a max length for a TextField by defining a CharField with a Textarea widget like this:
class MyClass(models.Model):
textfield = models.TextField()
class MyForm(forms.ModelForm):
textfield = forms.CharField(
max_length = 50,
widget = forms.Textarea
)
class Meta:
model = MyClass
fields = ('textfield',)
No need to import MaxLengthValidator from validators for Django 2.x
from django.db import models
class Comment(models.Model):
comment = models.TextField(max_length=200)