How to add placeholder text to a Django Admin field - python

I'd like to add placeholder text to a field in the Django Admin change form. In a regular ModelForm you can do this by overriding the field's widget or by modifying self.fields['my_field'].widget in the ModelForm __init__() method. How do I do something similar for a Django Admin?

The documented way is to override get_form():
The base implementation uses modelform_factory() to subclass form,
modified by attributes such as fields and exclude.
If you look at the docs for modelform_factory you'll see that you can pass widgets as kwarg. So this should work:
class MyModelAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
kwargs['widgets'] = {
'name': forms.TextInput(attrs={'placeholder': 'e.g. John Doe'})
}
return super().get_form(request, obj, **kwargs)
or, if you want to be sure you're not overriding any widgets (if you're inheriting from a subclass of ModelAdmin):
kwargs['widgets'] = kwargs.get('widgets', {})
kwargs['widgets'].update({'name': ...})

Override the render_change_form() method on your ModelAdmin, which provides access to the form instance:
class Address(model.Model):
street = models.CharField(max_length=50)
class AddressAdmin(admin.ModelAdmin):
def render_change_form(self, request, context, *args, **kwargs):
form_instance = context['adminform'].form
form_instance.fields['street'].widget.attrs['placeholder'] = 'Your street'
return super().render_change_form(request, context, *args, **kwargs)
This approach would be the same for other field attributes like attributes like autocomplete, autofocus, min, max, required, type or pattern. You also have access to context["original"] which provides the model instance, in case you'd like to change the behavior based on the model instance.
The source code is the best reference for this:
https://docs.djangoproject.com/en/2.2/_modules/django/contrib/admin/options/#ModelAdmin

This is a way of doing it without having to manually add placeholder text to each field:
admin.py
from django import forms
class MyModelAdmin(admin.ModelAdmin):
def render_change_form(self, request, context, *args, **kwargs):
form_instance = context['adminform'].form
for key, field in form_instance.fields.items():
if isinstance(field.widget, (forms.TextInput, forms.EmailInput)):
field.widget.attrs.update({'placeholder': field.label})
return super().render_change_form(request, context, *args, **kwargs)

Another way to do this is:
class MyModelAdmin(model.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
#--> Get form
form = super().get_form(request, obj, **kwargs)
#--> Add placeholder for field
form.base_fields['the_field_name'].widget.attrs['placeholder'] = "My_Place_Holder_Text"
#--> Return form
return form
#---
#---
This is similar to the answer of dirkgroten. The advantage here is that there is no need to worry about the widget used for the field.

Related

simplest way to override Django admin inline to request formfield_for_dbfield for each instance

I would like to provide different widgets to input form fields for the same type of model field in a Django admin inline.
I have implemented a version of the Entity-Attribute-Value paradigm in my shop application (I tried eav-django and it wasn't flexible enough). In my model it is Product-Parameter-Value (see Edit below).
Everything works as I want except that when including an admin inline for the Parameter-Value pair, the same input formfield is used for every value. I understand that this is the default Django admin behaviour because it uses the same formset for each Inline row.
I have a callback on my Parameter that I would like to use (get_value_formfield). I currently have:
class SpecificationValueAdminInline(admin.TabularInline):
model = SpecificationValue
fields = ('parameter', 'value')
readonly_fields = ('parameter',)
max_num = 0
def get_formset(self, request, instance, **kwargs):
"""Take a copy of the instance"""
self.parent_instance = instance
return super().get_formset(request, instance, **kwargs)
def formfield_for_dbfield(self, db_field, **kwargs):
"""Override admin function for requesting the formfield"""
if self.parent_instance and db_field.name == 'value':
# Notice first() on the end -->
sv_instance = SpecificationValue.objects.filter(
product=self.parent_instance).first()
formfield = sv_instance.parameter.get_value_formfield()
else:
formfield = super().formfield_for_dbfield(db_field, **kwargs)
return formfield
formfield_for_dbfield is only called once for each admin page.
How would I override the default behaviour so that formfield_for_dbfield is called once for each SpecificationValue instance, preferably passing the instance in each time?
Edit:
Here is the model layout:
class Product(Model):
specification = ManyToManyField('SpecificationParameter',
through='SpecificationValue')
class SpecificationParameter(Model):
"""Other normal model fields here"""
type = models.PositiveSmallIntegerField(choices=TUPLE)
def get_value_formfield(self):
"""
Return the type of form field for parameter instance
with the correct widget for the value
"""
class SpecificationValue(Model):
product = ForeignKey(Product)
parameter = ForeignKey(SpecificationParameter)
# To store and retrieve all types of value, overrides CharField
value = CustomValueField()
The way I eventually solved this is using the form = attribute of the Admin Inline. This skips the form generation code of the ModelAdmin:
class SpecificationValueForm(ModelForm):
class Meta:
model = SpecificationValue
def __init__(self, instance=None, **kwargs):
super().__init__(instance=instance, **kwargs)
if instance:
self.fields['value'] = instance.parameter.get_value_formfield()
else:
self.fields['value'].disabled = True
class SpecificationValueAdminInline(admin.TabularInline):
form = SpecificationValueForm
Using standard forms like this, widgets with choices (e.g. RadioSelect and CheckboxSelectMultiple) have list bullets next to them in the admin interface because the <ul> doesn't have the radiolist class. You can almost fix the RadioSelect by using AdminRadioSelect(attrs={'class': 'radiolist'}) but there isn't an admin version of the CheckboxSelectMultiple so I preferred consistency. Also there is an aligned class missing from the <fieldset> wrapper element.
Looks like I'll have to live with that!

Django default queryset and widget for ModelForm custom field

I have a Site model, and I am trying to create a SiteSelectorField that extends django.forms.ModelMultipleChoiceField, that uses my custom SiteSelectorWidget and Site.objects.all() as the queryset
Without the custom form field, my forms.py code looks like this (and works):
sites = forms.ModelMultipleChoiceField(queryset=Site.objects.all(), widget=SiteSelectorWidget())
I would like to limit the arguments passed, so I can do this
sites = SiteSelectorField()
But when I create the SiteSelectorField class, as below, Django tells me "SiteSelectorField' object has no attribute 'validators"
class SiteSelectorField(forms.ModelMultipleChoiceField):
queryset = Site.objects.all()
widget = SiteSelectorWidget()
def __init__(self, *args, **kwargs):
pass
How can I specify a default queryset and widget for this field so they don't need to be passed?
Delete the def __init__ method and code. By putting "pass" inside there, you're overriding the default functionality of ModelMultipleChoiceField, which your class inherits from, that would utilize the queryset.
Edit:
Re-structure your __init__ method like so:
def __init__(self, *args, **kwargs):
if not 'queryset' in kwargs:
kwargs['queryset'] = Site.objects.all()
return super(SiteSelectorField, self).__init__(*args, **kwargs)

Django ModelAdmin get_form() doesn't set fields attribute

I've overriden get_form() in my ModelAdmin class:
def get_form(self, request, obj=None, **kwargs):
form = super(ModelAdmin, self).get_form(request, obj, **kwargs)
Now, if I add this line:
print form.fields
I get an:
AttributeError: type object 'FilerImageForm' has no attribute 'fields'
Why is this happening? Should the call to the super get_form() set the fields attribute on the form? If I am mistaken, how can I access fields on a form in a ModelAdmin class?
get_form returns class not instance and fields attribute is instance attribute. So, you have to instantiate form before accessing fields.
Definition from django/contrib/admin/options.py:
def get_form(self, request, obj=None, **kwargs):
"""
Returns a Form class for use in the admin add view. This is used by
add_view and change_view.
"""
update:
I need to intercept form field creation, not the view. I need to
change a field's value, not mess with a template's context. I don't
think add_view() is the appropriate place for this.
I think you can do it by overriding formfield_for_dbfield method:
def formfield_for_dbfield(self, db_field, **kwargs):
"""
Hook for specifying the form Field instance for a given database Field
instance.
If kwargs are given, they're passed to the form Field's constructor.
"""
formfield = super(MyModelAdmin, self).formfield_for_dbfield(db_field, **kwargs)
if db_field.name == "field_you_are_looking_for":
# change formfield somehow here
# (or above, by passing modified kwargs in 'super' call)
return formfield

How do I override this method on a Django Model Field

I'm trying to modify an existing Django Mezzanine setup to allow me to blog in Markdown. Mezzanine has a "Core" model that has content as an HtmlField which is defined like so:
from django.db.models import TextField
class HtmlField(TextField):
"""
TextField that stores HTML.
"""
def formfield(self, **kwargs):
"""
Apply the class to the widget that will render the field as a
TincyMCE Editor.
"""
formfield = super(HtmlField, self).formfield(**kwargs)
formfield.widget.attrs["class"] = "mceEditor"
return formfield
The problem comes from the widget.attrs["class"] of mceEditor. My thoughts were to monkey patch the Content field on the Blog object
class BlogPost(Displayable, Ownable, Content):
def __init__(self, *args, **kwargs):
super(BlogPost, self).__init__(*args, **kwargs)
self._meta.get_field('content').formfield = XXX
My problems are my python skills aren't up to the task of replacing a bound method with a lambda that calls super.
formfield is called by the admin when it wants to create a field for display on the admin pages, so I need to patch that to make the BlogPost widget objects NOT have the class of mceEditor (I'm trying to leave mceEditor on all the other things)
How do you craft the replacement function? I'm pretty sure I attach it with
setattr(self._meta.get_field('content'), 'formfield', method_i_dont_know_how_to_write)
You could change the used formfield in the admin's method formfield_for_dbfield:
class BlogAdmin(admin.ModelAdmin):
def formfield_for_dbfield(self, db_field, **kwargs):
field = super(BlogAdmin, self).formfield_for_dbfield(db_field, **kwargs)
if db_field.name == 'content':
field.widget = ....
field.widget.attrs['class'] = ...
return field
If you really want to do the monkey-patching, it should be something like that:
class BlogPost(Displayable, Ownable, Content):
def __init__(self, *args, **kwargs):
super(BlogPost, self).__init__(*args, **kwargs)
def formfield_new(self, *args, **kwargs):
# do here what you would like to do
return formfield
instancemethod = type(self._meta.get_field('content').formfield)
self._meta.get_field('content').formfield = instancemethod(formfield_new,
self, BlogPost)
I realize this question was answered several months ago, but just in case anyone else comes across it, Mezzanine now provides the ability to completely modify the WYSIWYG editor field. Take a look a the docs for it here:
http://mezzanine.jupo.org/docs/admin-customization.html#wysiwyg-editor

How do I access the request object or any other variable in a form's clean() method?

I am trying to request.user for a form's clean method, but how can I access the request object? Can I modify the clean method to allow variables input?
The answer by Ber - storing it in threadlocals - is a very bad idea. There's absolutely no reason to do it this way.
A much better way is to override the form's __init__ method to take an extra keyword argument, request. This stores the request in the form, where it's required, and from where you can access it in your clean method.
class MyForm(forms.Form):
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
super(MyForm, self).__init__(*args, **kwargs)
def clean(self):
... access the request object via self.request ...
and in your view:
myform = MyForm(request.POST, request=request)
For what it's worth, if you're using Class Based Views, instead of function based views, override get_form_kwargs in your editing view. Example code for a custom CreateView:
from braces.views import LoginRequiredMixin
class MyModelCreateView(LoginRequiredMixin, CreateView):
template_name = 'example/create.html'
model = MyModel
form_class = MyModelForm
success_message = "%(my_object)s added to your site."
def get_form_kwargs(self):
kw = super(MyModelCreateView, self).get_form_kwargs()
kw['request'] = self.request # the trick!
return kw
def form_valid(self):
# do something
The above view code will make request available as one of the keyword arguments to the form's __init__ constructor function. Therefore in your ModelForm do:
class MyModelForm(forms.ModelForm):
class Meta:
model = MyModel
def __init__(self, *args, **kwargs):
# important to "pop" added kwarg before call to parent's constructor
self.request = kwargs.pop('request')
super(MyModelForm, self).__init__(*args, **kwargs)
UPDATED 10/25/2011: I'm now using this with a dynamically created class instead of method, as Django 1.3 displays some weirdness otherwise.
class MyModelAdmin(admin.ModelAdmin):
form = MyCustomForm
def get_form(self, request, obj=None, **kwargs):
ModelForm = super(MyModelAdmin, self).get_form(request, obj, **kwargs)
class ModelFormWithRequest(ModelForm):
def __new__(cls, *args, **kwargs):
kwargs['request'] = request
return ModelForm(*args, **kwargs)
return ModelFormWithRequest
Then override MyCustomForm.__init__ as follows:
class MyCustomForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
super(MyCustomForm, self).__init__(*args, **kwargs)
You can then access the request object from any method of ModelForm with self.request.
The usual aproach is to store the request object in a thread-local reference using a middleware. Then you can access this from anywhere in you app, including the Form.clean() method.
Changing the signature of the Form.clean() method means you have you own, modified version of Django, which may not be what you want.
Thank middleware count look something like this:
import threading
_thread_locals = threading.local()
def get_current_request():
return getattr(_thread_locals, 'request', None)
class ThreadLocals(object):
"""
Middleware that gets various objects from the
request object and saves them in thread local storage.
"""
def process_request(self, request):
_thread_locals.request = request
Register this middleware as described in the Django docs
For Django admin, in Django 1.8
class MyModelAdmin(admin.ModelAdmin):
...
form = RedirectForm
def get_form(self, request, obj=None, **kwargs):
form = super(MyModelAdmin, self).get_form(request, obj=obj, **kwargs)
form.request = request
return form
I ran into this particular problem when customizing the admin. I wanted a certain field to be validated based on the particular admin's credentials.
Since I did not want to modify the view to pass the request as an argument to the form, the following is what I did:
class MyCustomForm(forms.ModelForm):
class Meta:
model = MyModel
def clean(self):
# make use of self.request here
class MyModelAdmin(admin.ModelAdmin):
form = MyCustomForm
def get_form(self, request, obj=None, **kwargs):
ModelForm = super(MyModelAdmin, self).get_form(request, obj=obj, **kwargs)
def form_wrapper(*args, **kwargs):
a = ModelForm(*args, **kwargs)
a.request = request
return a
return form_wrapper
The answer by Daniel Roseman is still the best. However, I would use the first positional argument for the request instead of the keyword argument for a few reasons:
You don't run the risk of overriding a kwarg with the same name
The request is optional which is not right. The request attribute should never be None in this context.
You can cleanly pass the args and kwargs to the parent class without having to modify them.
Lastly, I would use a more unique name to avoid overriding an existing variable. Thus, My modified answer looks like:
class MyForm(forms.Form):
def __init__(self, request, *args, **kwargs):
self._my_request = request
super(MyForm, self).__init__(*args, **kwargs)
def clean(self):
... access the request object via self._my_request ...
You can't always use this method (and its probably bad practice), but if you are only using the form in one view you could scope it inside the view method itself.
def my_view(request):
class ResetForm(forms.Form):
password = forms.CharField(required=True, widget=forms.PasswordInput())
def clean_password(self):
data = self.cleaned_data['password']
if not request.user.check_password(data):
raise forms.ValidationError("The password entered does not match your account password.")
return data
if request.method == 'POST':
form = ResetForm(request.POST, request.FILES)
if form.is_valid():
return HttpResponseRedirect("/")
else:
form = ResetForm()
return render_to_response(request, "reset.html")
fresh cheese from cheesebaker#pypi: django-requestprovider
I have another answer to this question as per your requirement you want to access the user into the clean method of the form.
You can Try this.
View.py
person=User.objects.get(id=person_id)
form=MyForm(request.POST,instance=person)
forms.py
def __init__(self,*arg,**kwargs):
self.instance=kwargs.get('instance',None)
if kwargs['instance'] is not None:
del kwargs['instance']
super(Myform, self).__init__(*args, **kwargs)
Now you can access the self.instance in any clean method in form.py
When you want to access it through "prepared" Django class views like CreateView there's a small trick to know (= the official solution doesn't work out of the box). In your own CreateView you'll have to add code like this:
class MyCreateView(LoginRequiredMixin, CreateView):
form_class = MyOwnForm
template_name = 'my_sample_create.html'
def get_form_kwargs(self):
result = super().get_form_kwargs()
result['request'] = self.request
return result
= in short this is the solution to pass request to your form with Django's Create/Update views.

Categories