Django: Admin: changing the widget of the field in Admin - django

I have a model with a boolean value like that:
class TagCat(models.Model):
by_admin = models.BooleanField(default=True)
This appears as a checkbox in admin.
How could I use this as a radio button in admin?
Also, how do I make it be always with a certain selected value in admin?
Also, I want the default value to be the opposite, when a non-admin user adds a TagCat. This field should be hidden from him.
Can someone tell me how to do this? Django documentation doesn't seem to go in such details.

UPDATE 1: Code that gets me done with 1) (don't forget tot pass CHOICES to the BooleanField in the model)
from main.models import TagCat
from django.contrib import admin
from django import forms
class MyTagCatAdminForm(forms.ModelForm):
class Meta:
model = TagCat
widgets = {
'by_admin': forms.RadioSelect
}
fields = '__all__' # required for Django 3.x
class TagCatAdmin(admin.ModelAdmin):
form = MyTagCatAdminForm
admin.site.register(TagCat, TagCatAdmin)
The radio buttons appear ugly and displaced, but at least, they work
I solved with following info in MyModel.py:
BYADMIN_CHOICES = (
(1, "Yes"),
(0, "No"),
)
class TagCat(models.Model):
by_admin = models.BooleanField(choices=BYADMIN_CHOICES,default=1)

There is another way to do this that is, IMO much easier if you want every field of the same type to have the same widget. This is done by specifying a formfield_overrides to the ModelAdmin. For example:
from django.forms.widgets import Textarea
class MyModelAdmin(admin.ModelAdmin):
formfield_overrides = {
models.TextField: {'widget': Textarea},
}
More in the docs: https://docs.djangoproject.com/en/1.4/ref/contrib/admin/#django.contrib.admin.ModelAdmin.formfield_overrides

Here is a more dynamic extension of mgPePe's response:
class MyAdminForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(MyAdminForm, self).__init__(*args, **kwargs)
self.fields['by_admin'].label = 'My new label'
self.fields['by_admin'].widget = forms.RadioSelect()
class Meta:
model = TagCat
class MyAdmin(admin.ModelAdmin):
fields = ['name', 'by_admin']
form = MyAdminForm
This way you get full control over the fields.

Related

GeoDjango: Can I use OSMGeoAdmin in an Inline in the User Admin?

Profile contains a PointField. I've used OSMGeoAdmin in the ProfileAdmin, here:
class ProfileAdmin(admin.OSMGeoAdmin):
model = Profile
But can't figure out how to use it in an inline for display in the UserAdmin. I currently have this set up as below:
# User Admin, with Profile attached
class ProfileInline(admin.StackedInline):
model = Profile
can_delete = False
verbose_name_plural = 'Profile' # As only one is displayed in this view
class UserAdmin(UserAdmin):
inlines = (
ProfileInline,
)
admin.site.unregister(User)
admin.site.register(User, UserAdmin)
Is it possible to use class OSMGeoAdmin in this situation?
This would be a good feature to request I guess.
As a workaround, you can take advantage of the fact that an InlineModelAdmin is quite similar to a ModelAdmin. Both extend BaseModelAdmin.
Inheriting from both StackedInline and ModelAdmin should not clash too much.
The only issue is that both __init__() methods take 2 positional arguments and call super().__init__() without arguments. So whatever the inheritance order, it will fail with TypeError: __init__() missing 2 required positional arguments: 'parent_model' and 'admin_site'
Fortunately, the InlineModelAdmin.__init__() method, the one we are interested in, is not really verbose nor complex (not too much super().__init__() calls in cascade).
Here is what it looks like in Django 1.9:
def __init__(self, parent_model, admin_site):
self.admin_site = admin_site
self.parent_model = parent_model
self.opts = self.model._meta
self.has_registered_model = admin_site.is_registered(self.model)
super(InlineModelAdmin, self).__init__()
if self.verbose_name is None:
self.verbose_name = self.model._meta.verbose_name
if self.verbose_name_plural is None:
self.verbose_name_plural = self.model._meta.verbose_name_plural
And here is what its parent (BaseModelAdmin) looks like in Django 1.9
def __init__(self):
overrides = FORMFIELD_FOR_DBFIELD_DEFAULTS.copy()
overrides.update(self.formfield_overrides)
self.formfield_overrides = overrides
Now let's put it all together:
from django.contrib.admin.options import FORMFIELD_FOR_DBFIELD_DEFAULTS
# User Admin, with Profile attached
class ProfileInline(OSMGeoAdmin, admin.StackedInline):
model = Profile
can_delete = False
verbose_name_plural = 'Profile' # As only one is displayed in this view
def __init__(self, parent_model, admin_site):
self.admin_site = admin_site
self.parent_model = parent_model
self.opts = self.model._meta
self.has_registered_model = admin_site.is_registered(self.model)
overrides = FORMFIELD_FOR_DBFIELD_DEFAULTS.copy()
overrides.update(self.formfield_overrides)
self.formfield_overrides = overrides
if self.verbose_name is None:
self.verbose_name = self.model._meta.verbose_name
if self.verbose_name_plural is None:
self.verbose_name_plural = self.model._meta.verbose_name_plural
class UserAdmin(UserAdmin):
inlines = (
ProfileInline,
)
admin.site.unregister(User)
admin.site.register(User, UserAdmin)
It's not really a satisfying solution as it requires to copy/paste some code from django, which may be different within the version of Django you use, and might be a pain to maintain when upgrading Django. However it should work until it is included in Django as a mix-in or as an InlineModelAdmin.
Note: the code snippets above are taken from Django 1.9, you should browse github tags to find the snippets corresponding to your version.
Since Django admin fields use widgets, you can override the widget that's automatically set for a PointField using formfield_overrides. In this case, you can override all PointField instances to use the OSMWidget class like so:
from django.contrib.gis.forms.widgets import OSMWidget
class ProfileInline(admin.StackedInline):
model = Profile
can_delete = False
verbose_name_plural = 'Profile' # As only one is displayed in this view
formfield_overrides = {
PointField: {"widget": OSMWidget},
}

Django - set filter field label or verbose_name

I'm displaying a table of data using django-tables2.
For filtering I'm using the solution from here:
How do I filter tables with Django generic views?
My problem is only that I can't set the labels for the filter form. This is also imposible to google as words "django, form, filter, label" are quite general :(
My filter class:
import django_filters as filters
from models import Sale
class SaleFilter(filters.FilterSet):
class Meta:
model = Sale
fields = ['CompanyProductID', 'CompanySellerID', 'CompanyRegisterID']
labels = {
'CompanyProductID': 'Article',
'CompanySellerID': 'Seller',
'CompanyRegisterID': 'Cash register'
} #THIS IS NOT WORKING
To set custom labels you can do it this way. Not sure if it is a new functionality.
import django_filters as filters
from models import Sale
class SaleFilter(filters.FilterSet):
CompanyProdutID = filters.CharFilter(label='Article')
CompanySellerID = filters.CharFilter(label='Seller')
CompanyRegisterID = filters.CharFilter(label='Cash register')
class Meta:
model = Sale
fields = ['CompanyProductID', 'CompanySellerID', 'CompanyRegisterID']
Use the filter that you want for each field.
docs
Note:
for some reason
import django_filters as filters
filters.CharField(...)
is not working for me. I have to use it like this:
from django_filters import CharFilter
CharFilter(...)
Previous answer will duplicate the filter fields. Here is how to do it:
def __init__(self, *args, **kwargs):
super(SaleFilter, self).__init__(*args, **kwargs)
self.filters['CompanyProductID'].label="Article"
self.filters['CompanySellerID'].label="Seller"
self.filters['CompanyRegisterID'].label="Cash register"
class ProductFilter(django_filters.FilterSet):
class Meta:
model = Product
fields = ['manufacturer']
def __init__(self, *args, **kwargs):
super(ProductFilter, self).__init__(*args, **kwargs)
self.filters['manufacturer'].extra.update(
{'empty_label': 'All Manufacturers'})
I actually believe the op was asking about the "Label name". Not the field name. In order to do this simply do something like the following.
class Name_of_Filter(django_filters.FilterSet):
#example of how to set custom labels
your_field_name = django_filters.WhateverFilterYouWantHere(label='Whatever you want')
class Meta:
model = Your_Model_Here
fields = ['your_field_name']
#could also do something like '__all__' to get all the fields for that table just have to refer to your models to get the field name

Better ArrayField admin widget?

Is there any way to make ArrayField's admin widget allow adding and deleting objects? It seems that by default, it is instead displayed just a text field, and uses comma separation for its values.
Besides being inconvenient, AFAICT in the case the base field of the array is a Char/TextField, this doesn't allow any way of including commas in any of the texts in the array.
I take no credit for this (original source), but if you are using PostgreSQL as the database and are happy to use the Postgres-specific ArrayField implementation there is an even easier option: subclass ArrayField on the model and override the default admin widget. A basic implementation follows (tested in Django 1.9, 1.10, 1.11, 2.0, 2.1 & 2.2):
models.py
from django import forms
from django.db import models
from django.contrib.postgres.fields import ArrayField
class ChoiceArrayField(ArrayField):
"""
A field that allows us to store an array of choices.
Uses Django's Postgres ArrayField
and a MultipleChoiceField for its formfield.
"""
def formfield(self, **kwargs):
defaults = {
'form_class': forms.MultipleChoiceField,
'choices': self.base_field.choices,
}
defaults.update(kwargs)
# Skip our parent's formfield implementation completely as we don't
# care for it.
# pylint:disable=bad-super-call
return super(ArrayField, self).formfield(**defaults)
FUNCTION_CHOICES = (
('0', 'Planning'),
('1', 'Operation'),
('2', 'Reporting'),
)
class FunctionModel(models.Model):
name = models.CharField(max_length=128, unique=True)
function = ChoiceArrayField(
base_field=models.CharField(max_length=256, choices=FUNCTION_CHOICES),
default=list)
For OP, or anyone out there looking, between these helpful bits you should be good to go:
1. Extending SelectMultiple or CheckboxSelectMultiple widget to parse arrayfield and
2. Creating or extending admin form to display the arrayfield using the widget above
This is a better version of an already accepted solution. Using "CheckboxSelectMultiple" makes it more usable in the admin page.
class ChoiceArrayField(ArrayField):
def formfield(self, **kwargs):
defaults = {
'form_class': forms.TypedMultipleChoiceField,
'choices': self.base_field.choices,
'coerce': self.base_field.to_python,
'widget': forms.CheckboxSelectMultiple,
}
defaults.update(kwargs)
return super(ArrayField, self).formfield(**defaults)
The Django better admin ArrayField package provides exactly this functionality. The advantage over the solutions above is that it allows you to add new entries dynamically instead of relying on pre-defined choices.
See the documentation here: django-better-admin-arrayfield
It has a drop-in replacement for the ArrayField and a simple mixin to add to the admin model.
# models.py
from django_better_admin_arrayfield.models.fields import ArrayField
class MyModel(models.Model):
my_array_field = ArrayField(models.IntegerField(), null=True, blank=True)
# admin.py
from django_better_admin_arrayfield.admin.mixins import DynamicArrayMixin
#admin.register(MyModel)
class MyModelAdmin(admin.ModelAdmin, DynamicArrayMixin):
...
This would show something like:
This is another version using the Django Admin M2M filter_horizontal widget, instead of the standard HTML select multiple.
We use Django forms only in the Admin site, and this works for us, but the admin widget FilteredSelectMultiple probably will break if used outside the Admin. An alternative would be overriding the ModelAdmin.get_form to instantiate the proper form class and widget for the array field. The ModelAdmin.formfields_overrides is not enough because you need to instantiate the widget setting the positional arguments as shown in the code snippet.
from django.contrib.admin.widgets import FilteredSelectMultiple
from django.contrib.postgres.fields import ArrayField
from django.forms import MultipleChoiceField
class ChoiceArrayField(ArrayField):
"""
A choices ArrayField that uses the `horizontal_filter` style of an M2M in the Admin
Usage::
class MyModel(models.Model):
tags = ChoiceArrayField(
models.TextField(choices=TAG_CHOICES),
verbose_name="Tags",
help_text="Some tags help",
blank=True,
default=list,
)
"""
def formfield(self, **kwargs):
widget = FilteredSelectMultiple(self.verbose_name, False)
defaults = {
"form_class": MultipleChoiceField,
"widget": widget,
"choices": self.base_field.choices,
}
defaults.update(kwargs)
# Skip our parent's formfield implementation completely as we don't
# care for it.
return super(ArrayField, self).formfield(**defaults)
django-select2 offers a way to render the ArrayField using Select2. In their documentation, the example is for ArrayField:
http://django-select2.readthedocs.io/en/latest/django_select2.html#django_select2.forms.Select2TagWidget
To render the already selected values:
class ArrayFieldWidget(Select2TagWidget):
def render_options(self, *args, **kwargs):
try:
selected_choices, = args
except ValueError: # Signature contained `choices` prior to Django 1.10
choices, selected_choices = args
output = ['<option></option>' if not self.is_required and not self.allow_multiple_selected else '']
selected_choices = {force_text(v) for v in selected_choices.split(',')}
choices = {(v, v) for v in selected_choices}
for option_value, option_label in choices:
output.append(self.render_option(selected_choices, option_value, option_label))
return '\n'.join(output)
def value_from_datadict(self, data, files, name):
values = super().value_from_datadict(data, files, name)
return ",".join(values)
To add the widget to your form:
class MyForm(ModelForm):
class Meta:
fields = ['my_array_field']
widgets = {
'my_array_field': ArrayFieldWidget
}
write a form class for your model and use forms.MultipleChoiceField for ArrayField:
class ModelForm(forms.ModelForm):
my_array_field = forms.MultipleChoiceField(
choices=[1, 2, 3]
)
class Meta:
exclude = ()
model = Model
use ModelForm in your admin class:
class ModelAdmin(admin.ModelAdmin):
form = ModelForm
exclude = ()
fields = (
'my_array_field',
)

Django admin: How to display a field that is marked as editable=False' in the model?

Even though a field is marked as 'editable=False' in the model, I would like the admin page to display it. Currently it hides the field altogether.. How can this be achieved ?
Use Readonly Fields. Like so (for django >= 1.2):
class MyModelAdmin(admin.ModelAdmin):
readonly_fields=('first',)
Update
This solution is useful if you want to keep the field editable in Admin but non-editable everywhere else. If you want to keep the field non-editable throughout then #Till Backhaus' answer is the better option.
Original Answer
One way to do this would be to use a custom ModelForm in admin. This form can override the required field to make it editable. Thereby you retain editable=False everywhere else but Admin. For e.g. (tested with Django 1.2.3)
# models.py
class FooModel(models.Model):
first = models.CharField(max_length = 255, editable = False)
second = models.CharField(max_length = 255)
def __unicode__(self):
return "{0} {1}".format(self.first, self.second)
# admin.py
class CustomFooForm(forms.ModelForm):
first = forms.CharField()
class Meta:
model = FooModel
fields = ('second',)
class FooAdmin(admin.ModelAdmin):
form = CustomFooForm
admin.site.register(FooModel, FooAdmin)
Add the fields you want to display on your admin page.
Then add the fields you want to be read-only.
Your read-only fields must be in fields as well.
class MyModelAdmin(admin.ModelAdmin):
fields = ['title', 'author', 'published_date', 'updated_date', 'created_date']
readonly_fields = ('updated_date', 'created_date')
You could also set the readonly fields as editable=False in the model (django doc reference for editable here). And then in the Admin overriding the get_readonly_fields method.
# models.py
class MyModel(models.Model):
first = models.CharField(max_length=255, editable=False)
# admin.py
class MyModelAdmin(admin.ModelAdmin):
def get_readonly_fields(self, request, obj=None):
return [f.name for f in obj._meta.fields if not f.editable]
With the above solution I was able to display hidden fields for several objects but got an exception when trying to add a new object.
So I enhanced it like follows:
class HiddenFieldsAdmin(admin.ModelAdmin):
def get_readonly_fields(self, request, obj=None):
try:
return [f.name for f in obj._meta.fields if not f.editable]
except:
# if a new object is to be created the try clause will fail due to missing _meta.fields
return ""
And in the corresponding admin.py file I just had to import the new class and add it whenever registering a new model class
from django.contrib import admin
from .models import Example, HiddenFieldsAdmin
admin.site.register(Example, HiddenFieldsAdmin)
Now I can use it on every class with non-editable fields and so far I saw no unwanted side effects.
You can try this
#admin.register(AgentLinks)
class AgentLinksAdmin(admin.ModelAdmin):
readonly_fields = ('link', )

in django admin, can we have a multiple select based on choices

http://docs.djangoproject.com/en/dev/ref/models/fields/#choices
i've read through the documentation and this implies using a database table for dynamic data, however it states
choices is meant for static data that doesn't change much, if ever.
so what if i want to use choices, but have it select multiple because the data i'm using is quite static, e.g days of the week.
is there anyway to achieve this without a database table?
ChoiceField is not really suitable for multiple choices, instead I would use a ManyToManyField. Ignore the fact that Choices can be used instead of ForeignKey for static data for now. If it turns out to be a performance issue, there are ways to represent this differently (one being a binary mask approach), but they require way more work.
This worked for me:
1) create a Form class and set an attribute to provide your static choices to a MultipleChoiceField
from django import forms
from myapp.models import MyModel, MYCHOICES
class MyForm(forms.ModelForm):
myfield = forms.MultipleChoiceField(choices=MYCHOICES, widget=forms.SelectMultiple)
class Meta:
model = MyModel
2) then, if you're using the admin interface, set the form attribute in your admin class so tit will use your customized form
from myapp.models import MyModel
from myapp.forms import MyForm
from django.contrib import admin
class MyAdmin(admin.ModelAdmin):
form = MyForm
admin.site.register(MyModel, MyAdmin)
Try following configuration. In models.py
class MyModel(models.Model):
my_choices = models.TextField(help_text="It's a good manners to write it")
in forms.py
CHOICES = ((1,1), (2,2))
class MyForm(forms.ModelForm):
my_choices = forms.MultipleChoiceField(choices=CHOICES)
def __init__(self, *args, **kwargs):
super(MyForm, self).__init__(*args, **kwargs)
# maybe you can set initial with self.fields['my_choices'].initial = initial
# but it doesn't work wity dynamic choices
obj = kwargs.get('instance')
if obj:
initial = [i for i in obj.my_choices.split(',')]
self.initial['my_choices'] = initial
def clean_lead_fields(self):
return ','.join(self.cleaned_data.get('my_choices', []))
in admin.py
#admin.register(MyModel)
class MyModelAdmin(admin.ModelAdmin):
form = MyModelForm