How to add custom fields to InlineFormsets? - python

I'm trying to add custom fields to an InlineFormset using the following code, but the fields won't show up in the Django Admin. Is the InlineFormset too locked down to allow this? My print "ding" test fires as expected, I can print out the form.fields and see them all there, but the actual fields are never rendered in the admin.
admin.py
from django.contrib import admin
import models
from django.forms.models import BaseInlineFormSet
from django import forms
from forms import ProgressForm
from django.template.defaultfilters import slugify
class ProgressInlineFormset(BaseInlineFormSet):
def add_fields(self, form, index):
print "ding"
super(ProgressInlineFormset, self).add_fields(form, index)
for criterion in models.Criterion.objects.all():
form.fields[slugify(criterion.name)] = forms.IntegerField(label=criterion.name)
class ProgressInline(admin.TabularInline):
model = models.Progress
extra = 8
formset = ProgressInlineFormset
class ReportAdmin(admin.ModelAdmin):
list_display = ("name", "pdf_column",)
search_fields = ["name",]
inlines = (ProgressInline,)
admin.site.register(models.Report, ReportAdmin)

I did it another way:
forms.py:
from django import forms
class ItemAddForm(forms.ModelForm):
my_new_field = forms.IntegerField(initial=1, label='quantity')
class Meta:
model = Item
admin.py:
from django.contrib import admin
from forms import *
class ItemAddInline(admin.TabularInline):
form = ItemAddForm
fields = (..., 'my_new_field')
This works so far, I only need to override somehow the save method to handle this new field. See this: http://docs.djangoproject.com/en/dev/ref/contrib/admin/#form . It says that by default Inlines use BaseModelForm, which is send to formset_factory. It doesn't work for me, tried to subclass BaseModelForm with errors (no attribute '_meta'). So I use ModelForm instead.

You can do it by another way (Dynamic forms):
admin.py
class ProgressInline(admin.TabularInline):
model = models.Progress
extra = 8
def get_formset(self, request, obj=None, **kwargs):
extra_fields = {'my_field': forms.CharField()}
kwargs['form'] = type('ProgressForm', (forms.ModelForm,), extra_fields)
return super(ProgressInline, self).get_formset(request, obj, **kwargs)

model = models.Progress
In the admin there will be only the fields defined in this Progress model. You have no fields/fieldsets option overwriting it.
If you want to add the new ones, there are two options:
In the model definition, add those new additional fields (make them optional!)
In the admin model (admin.TabularInline), add something something like:
fields = ('newfield1', 'newfield2', 'newfield3')
Take a look at fields, fieldsets.

Related

I dont see models in django-admin which created from drf

I create and save user from django-rest-framework, but it do when i am anonymous user. Now i dont see those model in django-admin. How to add model to django-admin?
admin.py
from django.contrib import admin
from possible_blacklist.models import PossibleBlacklist
class PossibleBlacklistAdmin(admin.ModelAdmin):
list_display = [field.name for field in PossibleBlacklist._meta.get_fields()]
admin.site.register(PossibleBlacklist, PossibleBlacklistAdmin)
serializers.py
from rest_framework import serializers
from possible_blacklist.models import PossibleBlacklist
class PossibleBlacklistSerializer(serializers.ModelSerializer):
class Meta:
model = PossibleBlacklist
fields = '__all__'
def create(self, validated_data):
return PossibleBlacklist.objects.create(**validated_data)
def validate_mobile_phone(self, data):
if data.startswith('0'):
raise serializers.ValidationError("Номер должен начинаться на +380")
return data
I had this problem and when I research, I've found this good documentation which will add the model automatically to your Django admin interface.
and no need to touch your admin with every single model which are you adding.
https://medium.com/hackernoon/automatically-register-all-models-in-django-admin-django-tips-481382cf75e5

Django ModelMultipleChoiceField lazy loading of related m2m objects

I tried to use django.forms.ModelMultipleChoiceField in one of the model's admin page in order to update m2m relation through form. I also specified django.contrib.admin.widgets.FilteredSelectMultiple as a widget for form's field and achieved my purpose successfully. The problem is, since I have over 10K related objects, all of them are included as an option in form page and that makes page very severely slow. For illustrating this problem I am adding here analogy of this case's appliance on django.contrib.auth.models.Group model and related 'users'.
admin.py
from django.contrib import admin
from django.contrib.auth.models import Group
from forms import GroupAdminForm
admin.site.unregister(Group) # get rid of default one
class GroupAdmin(admin.ModelAdmin):
form = GroupAdminForm
filter_horizontal = ['permissions']
admin.site.register(Group, GroupAdmin)
forms.py
from django import forms
from django.contrib import admin
from django.contrib.auth import get_user_model
from django.contrib.admin.widgets import FilteredSelectMultiple
from django.contrib.auth.models import Group
User = get_user_model()
class GroupAdminForm(forms.ModelForm):
class Meta:
model = Group
exclude = []
users = forms.ModelMultipleChoiceField(
queryset=User.objects.all(),
required=False,
widget=FilteredSelectMultiple('users', False)
)
def __init__(self, *args, **kwargs):
super(GroupAdminForm, self).__init__(*args, **kwargs)
if self.instance.pk:
self.fields['users'].initial = self.instance.user_set.all()
def save_m2m(self):
self.instance.user_set.set(self.cleaned_data['users'])
def save(self, *args, **kwargs):
instance = super(GroupAdminForm, self).save()
self.save_m2m()
return instance
In the codes above, default admin change page for Group is disabled and implemented again that also includes input for group users. This is how it looks after I added 1000 test user objects:
As you see, all of 1000 users are included as an option. My question is that, how can I achieve to disable displaying them initially and display as the user types something in filter box?
Note: Please don't suggest me to use admin.ModelAdmin.raw_id_fields, since that is not user-friendly. Also, it would be better to solve this issue without help of any third-party package.

How to add an extra field to a Django ModelForm?

So I'm working on an admin page. I'm registering the form with admin.site.register. And I want to add an extra field to the form, which will let me populate a TextField with a file contents.
Therefore I need to add an extra FileInput to upload the file and populate the TextField with its contents. I am trying this:
class PersonForm(forms.ModelForm):
extra_field = forms.FileInput()
class Meta:
model = Person
fields = '__all__'
but the field is not showing. Any ideas?
Also I have no clue where to access the file contents and populate the TextField with that before saving the model.
Thanks in advance.
My problem was in this line:
extra_field = forms.FileInput()
I solved the problem changing the line to:
extra_field = forms.FileField()
Thanks to all willing to help.
Try to do it in the constructor.
class PersonForm(forms.ModelForm):
class Meta:
model = Person
fields = '__all__'
def __init__(self, *args, **kwargs):
super(PersonForm, self ).__init__(*args, **kwargs)
self.fields['extra_field'] = forms.FileInput()
And since you are using the django admin, you need to change the form in the admin too.
What you've done is ok according to the documentation, read note here - https://docs.djangoproject.com/en/2.0/topics/forms/modelforms/#overriding-the-default-fields
To register it in the admin you should add something like this to your admin.py:
from django.contrib import admin
from .forms import PersonForm
#admin.register(Person)
class PersonAdmin(admin.ModelAdmin):
form = PersonForm
Example from here - https://docs.djangoproject.com/en/2.0/ref/contrib/admin/#admin-custom-validation
EDIT: it is necessary to actually register custom ModelAdmin, there are two equivalent ways: using decorator, as in the example above, or use admin.site.register(Person, PersonAdmin).
Documentation for ModelAdmin registration - https://docs.djangoproject.com/en/2.0/ref/contrib/admin/#the-register-decorator
Registration source code - https://github.com/django/django/blob/master/django/contrib/admin/sites.py#L85
Register the model in admin as
admin.site.register(UserProfile)
where UserProfile is a OnetoOnemodel that extends django's builtin User Model then after every changes in models run
python manage.py makemigrations
python manage.py migrate

Django form not selecting options

I have a django project, in the project i have a django forms.py which contain a field call category which select box input element that is generated as a result of a query on the database.
If I choose an option from the select dropdown from the database I keep getting the error:
**strong text**Select a valid choice. That choice is not one of the available choices
Below is the code:
Forms.py
from django import forms
#from django.contrib.auth.models import User
from signer.models import CreateSingleSigner
class CreateSingleSignerForm(forms.ModelForm):
category = forms.ModelChoiceField(
required = True,
help_text = 'category',
queryset=CreateSingleSigner.objects.all().values_list(
'category', flat=True
).distinct()
)
my views.
from django.shortcuts import render
from django.http import HttpResponse, HttpResponseRedirect
from signer.models import CreateSingleSigner
from signer.forms import CreateSingleSignerForm
from django.template import RequestContext
def singlesigner(request):
context = {}
if request.method == 'POST':
createsinglesigner_form = CreateSingleSignerForm(data=request.POST)
if createsinglesigner_form.is_valid():
createsinglesigner.category = request.POST['category']
createsinglesigner_form.save()
else:
print createsinglesigner_form.errors
else:
# context['createsinglesigner'] = CreateSingleSigner()
createsinglesigner_form =CreateSingleSignerForm()
return render(request, "signer/singlesigner.html", {"createsinglesigner_form":createsinglesigner_form}, RequestContext(request))
my models.py
from django.db import models
class CreateSingleSigner(models.Model):
category = models.CharField(max_length = 32)
Can someone tell me where I am going wrong?
Try one of ways below to fix problem:
Try to define conversion of unicode in your CreateSingleSigner model:
class CreateSingleSignerForm(forms.Form):
def __unicode__(self):
return self. category
Explanations:
ModelChoiceField will use __unicode__ representation of specified fields for displaying and validating your fields.
Try to set choices in __init__ method of your form
class CreateSingleSignerForm(forms.Form):
category = forms.ChoiceField(choices=[], required=False)
def __init__(self, *args, **kwargs):
super(CreateSingleSignerForm, self).__init__(*args, **kwargs)
self.fields['category'].choices = CreateSingleSigner.objects.all().values_list('category', flat=True).distinct()
Explanations: The queryset parameter for ModelChoiceField cannot be values_list, because it's going to save the relationships, so django have to use complete model objects, not certain values of model objects.

Add custom form fields that are not part of the model (Django)

I have a model registered on the admin site. One of its fields is a long string expression. I'd like to add custom form fields to the add/update pages of this model in the admin. Based on the values of these fields I will build the long string expression and save it in the relevant model field.
How can I do this?
I'm building a mathematical or string expression from symbols. The user chooses symbols (these are the custom fields that are not part of the model) and when they click save then I create a string expression representation from the list of symbols and store it in the DB. I don't want the symbols to be part of the model and DB, only the final expression.
Either in your admin.py or in a separate forms.py you can add a ModelForm class and then declare your extra fields inside that as you normally would. I've also given an example of how you might use these values in form.save():
from django import forms
from yourapp.models import YourModel
class YourModelForm(forms.ModelForm):
extra_field = forms.CharField()
def save(self, commit=True):
extra_field = self.cleaned_data.get('extra_field', None)
# ...do something with extra_field here...
return super(YourModelForm, self).save(commit=commit)
class Meta:
model = YourModel
To have the extra fields appearing in the admin just:
Edit your admin.py and set the form property to refer to the form you created above.
Include your new fields in your fields or fieldsets declaration.
Like this:
class YourModelAdmin(admin.ModelAdmin):
form = YourModelForm
fieldsets = (
(None, {
'fields': ('name', 'description', 'extra_field',),
}),
)
UPDATE:
In Django 1.8 you need to add fields = '__all__' to the metaclass of YourModelForm.
It it possible to do in the admin, but there is not a very straightforward way to it. Also, I would like to advice to keep most business logic in your models, so you won't be dependent on the Django Admin.
Maybe it would be easier (and maybe even better) if you have the two seperate fields on your model. Then add a method on your model that combines them.
For example:
class MyModel(models.model):
field1 = models.CharField(max_length=10)
field2 = models.CharField(max_length=10)
def combined_fields(self):
return '{} {}'.format(self.field1, self.field2)
Then in the admin you can add the combined_fields() as a readonly field:
class MyModelAdmin(models.ModelAdmin):
list_display = ('field1', 'field2', 'combined_fields')
readonly_fields = ('combined_fields',)
def combined_fields(self, obj):
return obj.combined_fields()
If you want to store the combined_fields in the database you could also save it when you save the model:
def save(self, *args, **kwargs):
self.field3 = self.combined_fields()
super(MyModel, self).save(*args, **kwargs)
Django 2.1.1
The primary answer got me halfway to answering my question. It did not help me save the result to a field in my actual model. In my case I wanted a textfield that a user could enter data into, then when a save occurred the data would be processed and the result put into a field in the model and saved. While the original answer showed how to get the value from the extra field, it did not show how to save it back to the model at least in Django 2.1.1
This takes the value from an unbound custom field, processes, and saves it into my real description field:
class WidgetForm(forms.ModelForm):
extra_field = forms.CharField(required=False)
def processData(self, input):
# example of error handling
if False:
raise forms.ValidationError('Processing failed!')
return input + " has been processed"
def save(self, commit=True):
extra_field = self.cleaned_data.get('extra_field', None)
# self.description = "my result" note that this does not work
# Get the form instance so I can write to its fields
instance = super(WidgetForm, self).save(commit=commit)
# this writes the processed data to the description field
instance.description = self.processData(extra_field)
if commit:
instance.save()
return instance
class Meta:
model = Widget
fields = "__all__"
You can always create new admin template, and do what you need in your admin_view (override the admin add URL to your admin_view):
url(r'^admin/mymodel/mymodel/add/$','admin_views.add_my_special_model')
If you absolutely only want to store the combined field on the model and not the two seperate fields, you could do something like this:
Create a custom form using the form attribute on your ModelAdmin. ModelAdmin.form
Parse the custom fields in the save_formset method on your ModelAdmin. ModelAdmin.save_model(request, obj, form, change)
I never done something like this so I'm not completely sure how it will work out.
The first (highest score) solution (https://stackoverflow.com/a/23337009/10843740) was accurate, but I have more.
If you declare fields by code, that solution works perfectly, but what if you want to build those dynamically?
In this case, creating fields in the __init__ function for the ModelForm won't work. You will need to pass a custom metaclass and override the declared_fields in the __new__ function!
Here is a sample:
class YourCustomMetaClass(forms.models.ModelFormMetaclass):
"""
For dynamically creating fields in ModelForm to be shown on the admin panel,
you must override the `declared_fields` property of the metaclass.
"""
def __new__(mcs, name, bases, attrs):
new_class = super(NamedTimingMetaClass, mcs).__new__(
mcs, name, bases, attrs)
# Adding fields dynamically.
new_class.declared_fields.update(...)
return new_class
# don't forget to pass the metaclass
class YourModelForm(forms.ModelForm, metaclass=YourCustomMetaClass):
"""
`metaclass=YourCustomMetaClass` is where the magic happens!
"""
# delcare static fields here
class Meta:
model = YourModel
fields = '__all__'
This is what I did to add the custom form field "extra_field" which is not the part of the model "MyModel" as shown below:
# "admin.py"
from django.contrib import admin
from django import forms
from .models import MyModel
class MyModelForm(forms.ModelForm):
extra_field = forms.CharField()
def save(self, commit=True):
extra_field = self.cleaned_data.get('extra_field', None)
# Do something with extra_field here
return super().save(commit=commit)
#admin.register(MyModel)
class MyModelAdmin(admin.ModelAdmin):
form = MyModelForm
You might get help from my answer at :
my response previous on multicheckchoice custom field
You can also extend multiple forms having different custom fields and then assigning them to your inlines class like stackedinline or tabularinline:
form =
This way you can avoid formset complication where you need to add multiple custom fields from multiple models.
so your modeladmin looks like:
inlines = [form1inline, form2inline,...]
In my previous response to the link here, you will find init and save methods.
init will load when you view the page and save will send it to database.
in these two methods you can do your logic to add strings and then save thereafter view it back in Django admin change_form or change_list depending where you want.
list_display will show your fields on change_list.
Let me know if it helps ...
....
class CohortDetailInline3(admin.StackedInline):
model = CohortDetails
form = DisabilityTypesForm
...
class CohortDetailInline2(admin.StackedInline):
model = CohortDetails
form = StudentRPLForm
...
...
#admin.register(Cohort)
class CohortAdmin(admin.ModelAdmin):
form = CityInlineForm
inlines = [uploadInline, cohortDetailInline1,
CohortDetailInline2, CohortDetailInline3]
list_select_related = True
list_display = ['rto_student_code', 'first_name', 'family_name',]
...

Categories