I have a CharField field in my model. I want to display it as a MultipleChoiceField widget in the Django admin site. models.py:
class Product(models.Model):
...
categories = models.CharField()
...
I've created a custom form widget in forms.py:
from django import forms
CATEGORIES_LIST = [
('for_him', 'For Him'),
('for_her', 'For Her'),
('for_kids', 'For Kids'),
]
class Categories(forms.Form):
categories = forms.MultipleChoiceField(
required=False,
widget=forms.CheckboxSelectMultiple,
choices=CATEGORIES_LIST,
)
Not quite sure what to do next. How do I connect this widget with Django Admin for my Product model? Thanks in advance for your help!
What you are trying to do is not that straight forward using a CharField and putting multiple options in it so you must first find a way to serialize and restore your data to the model take a look here.
if you are on a postgres database you could do it this way (thanks to postgres array fields you don't have the serializing and restoring data headache)
from django.contrib.postgres.fields import ArrayField
class ChoiceArrayField(ArrayField):
"""
A field that allows us to store an array of choices.
Uses Django 1.9's postgres ArrayField
and a MultipleChoiceField for its formfield.
Usage:
choices = ChoiceArrayField(models.CharField(max_length=..., choices=(...,)), default=[...])
"""
def formfield(self, **kwargs):
defaults = {
'form_class': forms.MultipleChoiceField,
'choices': self.base_field.choices,
}
defaults.update(kwargs)
return super(ArrayField, self).formfield(**defaults)
and then you can use ChoiceArrayField on your model
Update:
So to use this on your model you can do it like this:
class Product(models.Model):
categories = ChoiceArrayField(max_length=8, choices=CATEGORIES_LIST, default=['for_him', 'for_her'])
Use MultipleChoiceField in Admin Form
in your forms.py
class Categories(forms.Form):
categories = forms.MultipleChoiceField(
required=False,
widget=forms.CheckboxSelectMultiple,
choices=CATEGORIES_LIST,
)
def __init__(self, *args, **kwargs):
super(Categories, self).__init__(*args, **kwargs)
if kwargs.get('instance'):
self.initial['categories'] = eval(self.initial['categories'])
It'll detect your choices and auto-fill the boxes use select after update the page.
Related
I am trying to have a custom form on django admin for my ModelB, with fields taken from other ModelA.
models.py
class ModelA(models.Model):
source = models.CharField(max_length=80)
keys = ArrayField(
models.CharField(max_length=50)
)
class ModelB(models.Model):
characteristic_keys = JSONField()
forms.py
class ModelBForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
queryset = ModelA.objects.all()
dynamic_fields = [(x.source, x.keys) for x in queryset]
# New fields to be shown on admin =>
# Field name => "source" from modelA
# Field type => multiple choice with options => "keys" from modelA
for field in dynamic_fields:
self.fields[field[0]] = forms.MultipleChoiceField(widget=forms.CheckboxSelectMultiple,
choices=field[1])
def save(self, commit=True):
# ...do something with extra_field here...
return super().save(commit=commit)
class Meta:
model = Workflow
fields = "__all__"
admin.py
class ModelBAdmin(admin.ModelAdmin):
form = ModelBForm
admin.site.register(ModelB, ModelBAdmin)
I want a single form for ModelB on django admin, with dynamic "source" fields takes from ModelA, with multiple choice options from their corresponding "key" values in modelB.
I have tried to keep information clear and understandable, please let me know if I have missed any information that might be needed to understand the problem. Any ideas to deal this problem would be a great help!
I have a model called Keyword and a model called Statement, and I've been customizing the form for adding or changing statements. Each Keyword object has an m2m (many to many) relationship with Statement objects, and I wanted users to be able to select keywords to associate. The default widget for m2m fields isn't useful in my case because there are many Keyword objects so I needed something better than that. I used the FilteredSelectMultiple widget in order to get the adjustments I needed.
Here's the code for that.
In admin.py
class KeywordInline(admin.TabularInline):
model = Keyword.statement.through
class StatementAdmin(admin.ModelAdmin):
list_display = ('statement_id', 'title', 'author', 'released_by', 'issue_date', 'access', 'full_text',)
list_filter = (StatementListFilter, 'released_by', 'issue_date', 'access',)
search_fields = ('statement_id', 'title', 'author', 'issue_date',)
inlines = [ KeywordInline,]
in forms.py
class StatementForm(forms.Modelform):
statement_keywords = forms.ModelMultipleChoiceField(
queryset=Keyword.objects.all(),
required=False,
widget=FilteredSelectMultiple(
verbose_name='Keywords Associated with Statement',
is_stacked=False
)
)
class Meta:
model = Statement
def __init__(self, *args, **kwargs):
super(StatementForm, self).__init__(*args, **kwargs)
if self.instance.pk:
self.fields['statement_keywords'].initial = self.instance.keyword_set.all()
def save(self, commit=True):
statement = super(StatementForm, self).save(commit=False)
if commit:
statement.save()
if statement.pk:
statement.keyword_set = self.cleaned_data['keyword']
self.save_m2m()
return statement
So now I have a filter_horizontal menu for my inline, just like I wanted. But there's one problem: There's no plus sign to add new keywords.
I know that the RelatedFieldWidgetWrapper is necessary to resolve this, and I've found tons of examples of people using it. However, I haven't been able to find one that suits my situation. The most immediate problem I'm having right now is trying to insert something into the "rel" parameter. The "rel" parameter typically defines "a relation of the two models involved," going off of this popular example implementation: http://dashdrum.com/blog/2012/07/relatedfieldwidgetwrapper/
I don't know what to indicate for this relation nor how to indicate it because I'm working with an inline. So I'm not actually working with a field called "keywords," I am doing a reverse look up of the m2m relationship between Keyword and Statement. So I don't know what the name could be to describe the relationship.
All of the examples I've found haven't really talked about what to do in this situation. Most examples easily get the field of interest from one of the models and then get its type or relationship, but with an inline model and a reverse relation I can't necessarily do that.
I managed to make ends meet with a many to many relation and a custom widget of an inline model, like the one you are describing.
Inspired from this answer and this post here is the result using my models because you do not provide a models.py in your question and you also have extra -unnecessary for this occasion- information in your code.
models.py
class MasterModel(models.Model):
pass
class ThirdModel(models.Model):
pass
class InlineModel(models.Model):
'''
It should also be working with a ForeignKey
but I have not tested it.
'''
master_key = models.OneToOneField(MasterModel)
the_field = models.ManyToManyField(ThirdModel)
forms.py
from django.contrib.admin import site as admin_site
from django.contrib.admin.widgets import RelatedFieldWidgetWrapper
class InlineModelForm(forms.Modelform):
the_field = forms.ModelMultipleChoiceField(
queryset=ThirdModel.objects.all(),
required=False,
widget=(
<the_custom_widget with attributes etc>
)
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['the_field'].widget = (
RelatedFieldWidgetWrapper(
self.fields['the_field'].widget,
self.instance._meta.get_field('the_field').rel,
admin_site,
)
)
class Meta:
model = InlineModel
fields = '__all__'
admin.py:
class InlineModelAdminInline(admin.TabularInline):
model = InlineModel
form = InlineModelForm
#admin.register(MasterModel)
class MasterModelAdmin:
inlines = (InlineModelAdminInline, )
#admin.register(InlineModel)
class InlineModelAdmin:
form = InlineModelForm
#admin.register(ThirdModel)
class ThirdModelAdmin:
pass
ive added a custom widget to my admin form that just displays data and when i try save the form i get field is required on my custom widget, how can i set this to not required?
Thanks
forms.py
class TemplateVariablesWidget(forms.Widget):
template_name = 'networks/config_variables.html'
def render(self, name, value, attrs=None):
c_vars = ConfigVariables.objects.all()
context = {
'ConfigVariables' : c_vars
}
return mark_safe(render_to_string(self.template_name, context))
class VariableForm(forms.ModelForm):
variables = forms.CharField(widget=TemplateVariablesWidget)
class Meta:
model = ConfigVariables
fields = "__all__"
admin.py
class ConfigTemplateAdmin(admin.ModelAdmin):
form = VariableForm
list_display = ('device_name', 'date_modified')
admin.site.register(ConfigTemplates, ConfigTemplateAdmin)
error:
Take a look at the Django docs at the Form fields section.
class VariableForm(forms.ModelForm):
variables = forms.CharField(widget=TemplateVariablesWidget, required=False)
class Meta:
model = ConfigVariables
fields = "__all__"
But, take care, check if this field is not required in models.
I have a DateTimeField field in my model. I wanted to display it as a checkbox widget in the Django admin site. To do this, I created a custom form widget. However, I do not know how to use my custom widget for only this one field.
The Django documentation explains how to use a custom widget for all fields of a certain type:
class StopAdmin(admin.ModelAdmin):
formfield_overrides = {
models.DateTimeField: {'widget': ApproveStopWidget }
}
This is not granular enough though. I want to change it for only one field.
Create a custom ModelForm for your ModelAdmin and add 'widgets' to its Meta class, like so:
class StopAdminForm(forms.ModelForm):
class Meta:
model = Stop
widgets = {
'field_name': ApproveStopWidget(),
}
fields = '__all__'
class StopAdmin(admin.ModelAdmin):
form = StopAdminForm
Done!
Documentation for this is sort of non-intuitively placed in the ModelForm docs, without any mention to it given in the admin docs. See: Creating forms from models
After digging into the admin, model field and form field code, I believe the only way to carry out what I want is by creating a custom model field:
models.py
from django.db import models
from widgets import ApproveStopWidget
class ApproveStopModelField(models.DateTimeField):
pass
class Stop(models.model):
# Other fields
approve_ts = ApproveStopModelField('Approve place', null=True, blank=True)
admin.py
from widgets import ApproveStopWidget
from models import ApproveStopModelField
class StopAdmin(admin.ModelAdmin):
formfield_overrides = {
ApproveStopModelField: {'widget': ApproveStopWidget }
}
It gets the job done.
For the time being, I'll leave the question unanswered because I have the habit of missing the obvious. Perhaps some Django smartypants has a better solution.
Override formfield_for_dbfield like thus:
class VehicleAdmin(admin.ModelAdmin):
search_fields = ["name", "colour"]
def formfield_for_dbfield(self, db_field, **kwargs):
if db_field.name == 'colour':
kwargs['widget'] = ColourChooserWidget
return super(VehicleAdmin, self).formfield_for_dbfield(db_field,**kwargs)
(credit to http://www.kryogenix.org/days/2008/03/28/overriding-a-single-field-in-the-django-admin-using-newforms-admin/ )
Django's ModelAdmin.get_changelist_form(self, request, **kwargs) will do the trick for the case of list_editable
class StopAdminForm(forms.ModelForm):
class Meta:
model = Stop
widgets = {
'approve_ts': ApproveStopWidget(),
}
class StopAdmin(admin.ModelAdmin):
form = StopAdminForm
#just return the ModelForm class StopAdminForm
def get_changelist_form(self, request, **kwargs):
return StopAdminForm
Refer to Django Official documentation on this topic
I hope this will help
You can change the widget for only one field by assigning your widget to a field in a custom form and assigning the custom form to an admin as shown below:
# "admin.py"
class ProductForm(forms.ModelForm):
class Meta:
model = Product
widgets = {
'price': PriceWidget(),
}
fields = '__all__'
#admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
form = ProductForm
A Django autofield when displayed using a formset is hidden by default. What would be the best way to show it?
At the moment, the model is declared as,
class MyModel:
locid = models.AutoField(primary_key=True)
...
When this is rendered using Django formsets,
class MyModelForm(ModelForm):
class Meta:
model = MyModel
fields = ('locid', 'name')
it shows up on the page as,
<input id="id_form-0-locid" type="hidden" value="707" name="form-0-locid"/>
Thanks.
Edit
I create the formset like this -
LocFormSet = modelformset_factory(MyModel)
pformset = LocFormSet(request.POST, request.FILES, queryset=MyModel.objects.order_by('name'))
Second Edit
Looks like I'm not using the custom form class I defined there, so the question needs slight modification..
How would I create a formset from a custom form (which will show a hidden field), as well as use a custom queryset?
At the moment, I can either inherit from a BaseModelFormSet class and use a custom query set, or I can use the ModelForm class to add a custom field to a form. Is there a way to do both with a formset?
Third Edit
I'm now using,
class MyModelForm(ModelForm):
def __init__(self, *args, **kwargs):
super(MyModelForm, self).__init__(*args, **kwargs)
locid = forms.IntegerField(min_value = 1, required=True)
self.fields['locid'].widget.attrs["type"] = 'visible'
self.queryset = MyModel.objects.order_by('name')
class Meta:
model = MyModel
fields = ('locid', 'name')
LocFormSet = modelformset_factory(MyModel, form = MyModelForm)
pformset = LocFormSet()
But this still doesn't
Show locid
Use the custom query that was specified.
Try changing the default field type:
from django import forms
class MyModelForm(ModelForm):
locid = forms.IntegerField(min_value=1, required=True)
class Meta:
model = MyModel
fields = ('locid', 'name')
EDIT: Tested and works...
As you say, you are not using the custom form you have defined. This is because you aren't passing it in anywhere, so Django can't know about it.
The solution is simple - just pass the custom form class into modelformset_factory:
LocFormSet = modelformset_factory(MyModel, form=MyModelForm)
Edit in response to update 3:
Firstly, you have the redefinition for locid in the wrong place - it needs to be at the class level, not inside the __init__.
Secondly, putting the queryset inside the form won't do anything at all - forms don't know about querysets. You should go back to what you were doing before, passing it in as a parameter when you instantiate the formset. (Alternatively, you could define a custom formset, but that seems like overkill.)
class MyModelForm(ModelForm):
locid = forms.IntegerField(min_value=1, required=True)
def __init__(self, *args, **kwargs):
super(MyModelForm, self).__init__(*args, **kwargs)
self.fields['locid'].widget.attrs["type"] = 'visible'
class Meta:
model = MyModel
fields = ('locid', 'name')
LocFormSet = modelformset_factory(MyModel, form = MyModelForm)
pformset = LocFormSet(request.POST, request.FILES,
queryset=MyModel.objects.order_by('name')))
Okay, none of the approaches above worked for me. I solved this issue from the template side, finally.
There is a ticket filed (http://code.djangoproject.com/ticket/10427), which adds a "value" option to a template variable for a form. For instance, it allows,
{{form.locid.value}}
to be shown. This is available as a patch, which can be installed in the SVN version of django using "patch -p0 file.patch"
Remember, the {{form.locid.value}} variable will be used in conjunction with the invisible form - otherwise, the submit and save operations for the formset will crash.
This is Not the same as {{form.locid.data}} - as is explained in the ticket referred to above.
The reason that the autofield is hidden, is that both BaseModelFormSet and BaseInlineFormSet override that field in add_field. The way to fix it is to create your own formset and override add_field without calling super. Also you don't have to explicitly define the primary key.
you have to pass the formset to modelformset_factory:
LocFormSet = modelformset_factory(MyModel,
formset=VisiblePrimaryKeyFormSet)
This is in the formset class:
from django.forms.models import BaseInlineFormSet, BaseModelFormSet, IntegerField
from django.forms.formsets import BaseFormSet
class VisiblePrimaryKeyFormset(BaseModelFormSet):
def add_fields(self, form, index):
self._pk_field = pk = self.model._meta.pk
if form.is_bound:
pk_value = form.instance.pk
else:
try:
pk_value = self.get_queryset()[index].pk
except IndexError:
pk_value = None
form.fields[self._pk_field.name] = IntegerField( initial=pk_value,
required=True) #or any other field you would like to display the pk in
BaseFormSet.add_fields(self, form, index) # call baseformset which does not modify your primary key field