Django - ForeignKey field initial value definition in Admin - django

I have a Person model, which has a ForeignKey field to itself, called mother.
When the user goes to the 'add' admin form, I want to define an initial value for mother, in case there is a GET('mother') parameter, or leave it blank, in case there is not.
I have actually 2 questions:
How to access request inside ModelAdmin?
How to define initial value for a ForeignKey field?
In models.py:
class Person(models.Model):
name=models.CharField()
mother=models.ForeignKey('self')
In admin.py:
class PersonAdminForm(forms.ModelForm):
class Meta:
model = Person
class PersonAdmin(admin.ModelAdmin):
mother = request.GET.get('mother','') #don`t know how to access request
if mother != '':
form = PersonAdminForm
form.initial={'mother':Person.objects.get(id=mother)}
Well, this ain't working. Even if I only try to define a hardcoded initial value, it doesn`t work.
What am I doing wrong?
PS.: Of course, I may be asking the wrong questions, so I appreciate any help that solves the problem.

My solution:
class PersonAdmin(admin.ModelAdmin):
form = PersonAdminForm
# ...
def get_form(self, request, obj=None, *args, **kwargs):
form = super(PersonAdmin, self).get_form(request, *args, **kwargs)
# Initial values
form.base_fields['mother'].initial = None
if obj and obj.mother:
form.base_fields['mother'].initial = obj.mother
return form

Oh, it happens to be a lot easier than I thought.
If you pass a GET parameter with the name of the field as key to a Django`s add form, the GET parameters value will be set as initial value for that field.
In my case, I just needed to redirect to
localhost/admin/my_app/person/add/?&mother=< id >
There was no need for manipulating admin or anything.

Try overriding the get_form() method on ModelAdmin:
class PersonAdmin(admin.ModelAdmin):
form = PersonAdminForm
def get_form(self, request, *args, **kwargs):
form = super(PersonAdmin, self).get_form(request, *args, **kwargs)
mother = request.GET.get('mother', None)
if mother:
form.initial = {'mother': Person.objects.get(id=mother)}
return form

Related

django admin dynamically set changeform initial data

I'm implementing a m2m relationship through an intermediate model using the default m2m widget. I have the Person and Project models related using the Membership model.
So far I've succeeded at displaying the default m2m widget in the Person change form and creating the intermediate model instances correctly, but my problem is populating the widget when a Person is being modified.
This is the form class I'm using with the PersonAdmin:
class PersonForm(forms.ModelForm):
projects = forms.ModelMultipleChoiceField(models.Project.objects.all(),
widget=widgets.FilteredSelectMultiple(
verbose_name="Projects",
is_stacked=False,
attrs={'rows':'10'}))
projects.required = False
class Meta:
model = models.Person
fields = ['name', 'last_name', 'personal_id_number',
'personal_id_type', 'administrative_viability',
'observations']
def save(self, commit=True):
ret = super(PersonForm, self).save(commit)
for p in self.cleaned_data['projects']:
models.Membership.objects.create(person=self.instance, project=p)
return ret
And the PersonAdmin itself:
class PersonAdmin(admin.ModelAdmin):
form = PersonForm
def get_changeform_initial_data(self, request):
initial = super(PersonAdmin, self).get_changeform_initial_data(request)
initial['projects'] = models.Person.get(pk=initial['person']).project_set.all()
return initial
I tried setting the initial value of projects in the method get_changeform_initial_data like that, but it doesn't work. Overall it looks like it's being ignored, as if I'm not overriding it properly.
Any help will be greatly appreciated!
This question gave me the idea of overriding the __init__ method of my PersonForm:
def __init__(self, *args, **kwargs):
if 'instance' in kwargs:
person = kwargs['instance']
initial = {'projects': person.project_set.all()}
kwargs['initial'] = initial
super(PersonForm, self).__init__(*args, **kwargs)
I still don't know why overriding get_changeform_initial_data wasn't working.
get_changeform_initial_data is only called if it's not a change. I know this makes no sense. I suspect it's a bug.
See django/contrib/admin/options.py from line 1573, which is the only call to this method in the whole of Django:
if add:
initial = self.get_changeform_initial_data(request)
form = ModelForm(initial=initial)
formsets, inline_instances = self._create_formsets(request, form.instance, change=False)
else:
form = ModelForm(instance=obj)
formsets, inline_instances = self._create_formsets(request, obj, change=True)
Update: Looks like it's deliberate. I'll ask the developers why it works like this.

How to set initial data for Django admin model add instance form?

How can I set an initial value of a field in the automatically generated form for adding a Django model instance, before the form is displayed? I am using Django 1.3.1.
My model is the following:
class Foo(models.Model):
title = models.CharField(max_length=50)
description = models.TextField()
and the current admin form is really nothing special
class FooAdmin(admin.ModelAdmin):
ordering = ('title',)
When I use the admin page to add a new instance of Foo, I get a nice form with empty fields for title and description. What I would like is that the description field is set with a template that I obtain by calling a function.
My current best attempt at getting there is this:
def get_default_content():
return 'this is a template for a Foo description'
class FooAdminForm(django.forms.ModelForm):
class Meta:
model = Foo
def __init__(self, *args, **kwargs):
kwargs['initial'].update({'description': get_default_content()})
super(FooAdminForm, self).__init__(self, *args, **kwargs)
class FooAdmin(admin.ModelAdmin):
ordering = ('title',)
form = FooAdminForm
but if I try this I get this Django error:
AttributeError at /admin/bar/foo/add/
'FooForm' object has no attribute 'get'
Request Method: GET
Request URL: http://localhost:8000/admin/bar/foo/add/
Django Version: 1.3.1
Exception Type: AttributeError
Exception Value: 'FooForm' object has no attribute 'get'
Exception Location: /www/django-site/venv/lib/python2.6/site-packages/django/forms/widgets.py in value_from_datadict, line 178
I don't know what is wrong here, and what I should do to make it work. What I also find strange about this error (apart from the fact that I see it at all) is that there is no FooForm in my code at all?
Alasdair's approach is nice but outdated. Radev's approach looks quite nice and as mentioned in the comment, it strikes me that there is nothing about this in the documentation.
Apart from those, since Django 1.7 there is a function get_changeform_initial_data in ModelAdmin that sets initial form values:
def get_changeform_initial_data(self, request):
return {'name': 'custom_initial_value'}
You need to include self as the first argument in your __init__ method definition, but should not include it when you call the superclass' method.
def __init__(self, *args, **kwargs):
# We can't assume that kwargs['initial'] exists!
if 'initial' not in kwargs:
kwargs['initial'] = {}
kwargs['initial'].update({'description': get_default_content()})
super(FooAdminForm, self).__init__(*args, **kwargs)
Having said that, a model field can take a callable for its default, so you may not have to define a custom admin form at all.
class Foo(models.Model):
title = models.CharField(max_length=50)
description = models.TextField(default=get_default_content)
More then 3 years later,
But actually what you should do is override admin.ModelAdmin formfield_for_dbfield .. like this:
class FooAdmin(admin.ModelAdmin):
def formfield_for_dbfield(self, db_field, **kwargs):
field = super(FooAdmin, self).formfield_for_dbfield(db_field, **kwargs)
if db_field.name == 'description':
field.initial = 'My initial description'
elif db_field.name == 'counter':
field.initial = get_counter() + 1
return field
Cheers;
When adding new objects, it is convenient to use get_changeform_initial_data() as suggested by Wtower.
However, when changing existing objects, that does not work (see source).
In that case, you could extend ModelAdmin.get_form() as follows (using the OP's example):
def get_form(self, request, obj=None, change=False, **kwargs):
if obj and not obj.description:
obj.description = get_default_content()
return super().get_form(request, obj, change, **kwargs)

Django send key or value from the view to the form class

I am writing an Edit form, where some fields already contain data. Example:
class EditForm(forms.Form):
name = forms.CharField(label='Name',
widget=forms.TextInput(),
initial=Client.objects.get(pk=??????)) #how to get the id?
What I did for another form was the following (which does not work for the case of the previous EditForm):
class AddressForm(forms.Form):
address = forms.CharField(...)
def set_id(self, c_id):
self.c_id = c_id
def clean_address(self):
# i am able to use self.c_id here
views.py
form = AddressForm()
form.set_id(request.user.get_profile().id) # which works in the case of AddressForm
So what is the best way to pass an id or a value to the form, and that could be used in all forms for that session/user?
Second: is it right to use initial to fill in the form field the way I am trying to do it?
You need to override the __init__ method for your form, like so:
def __init__(self, *args, **kwargs):
try:
profile = kwargs.pop('profile')
except KeyError:
super(SelectForm, self).__init__(*args, **kwargs)
return
super(SelectForm, self).__init__(*args, **kwargs)
self.fields['people'].queryset = profile.people().order_by('name')
and, obviously, build your form passing the right parameter when needed :)

Django AdminForm field default value

I have a Django admin form.
And now I want to fill it's initial field with data based on my model. So I tried this:
class OrderForm(forms.ModelForm):
class Meta:
model = Order
email = CharField(initial="null", widget=Textarea(attrs={'rows': 30, 'cols': 100}))
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
products = kwargs['instance'].products.all()
self.message = purchase_message % (
"".join(["<li>" + p.name + ": " + str(p.price) + "</li>" for p in products]),
reduce(lambda x, y:x + y.price, products, 0)
)
# and here I have a message in self.message variable
super(OrderForm, self).__init__(*args, **kwargs)
At this point i don't know how to access email field to set it's initial value before widget is rendered. How can i do this?
Assuming the value is based on 'request' you should use this:
class MyModelAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
form = super(MyModelAdmin, self).get_form(request, obj, **kwargs)
form.base_fields['my_field_name'].initial = 'abcd'
return form
Since Django 1.7 there is a function get_changeform_initial_data in ModelAdmin that sets initial form values:
def get_changeform_initial_data(self, request):
return {'name': 'custom_initial_value'}
EDIT: Apart from that, #Paul Kenjora's answer applies anyway, which might be useful if you already override get_form.
In case of inline (InlineModelAdmin) there is no get_changeform_initial_data. You can override get_formset and set formset.form.base_fields['my_field_name'].initial.
I'm not too sure what you need to set email to, but You can set the initial values in lots of different places.
Your function def init() isn't indented correctly which i guess is a typo? Also, why are you specifically giving the email form field a TextInput? It already renders this widget by default
You can set the email's initial value in your form's initialized (def __ init __(self))
(self.fields['email'].widget).initial_value = "something"
or in the model.py
email = models.CharField(default="something")
or as you have in forms.py
email = models.CharField(initial="something")
I needed the first solution of pastylegs since the other ones overwrite the whole Widget including, for example, the help text. However, it didn't work for me as he posted it. Instead, I had to do this:
self.fields['email'].initial = 'something'
In my case, I was trying to do a personalized auto-increment(based on current data and not a simple default) in a field of a django admin form.
This code is worked for me (Django 1.11):
from django import forms
class MyAdminForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.initial['field_name'] = 'initial_value'

adding new form fields dynamically in admin

I am trying to add dynamically new form fields (I used this blog post), for a form used in admin interface :
class ServiceRoleAssignmentForm(forms.ModelForm):
class Meta:
model = ServiceRoleAssignment
def __init__(self, *args, **kwargs):
super(ServiceRoleAssignmentForm, self).__init__(*args, **kwargs)
self.fields['test'] = forms.CharField(label='test')
class ServiceRoleAssignmentAdmin(admin.ModelAdmin):
form = ServiceRoleAssignmentForm
admin.site.register(ServiceRoleAssignment, ServiceRoleAssignmentAdmin)
However, no matter what I try, the field doesn't appear on my admin form ! Could it be a problem related to the way admin works ? Or to ModelForm ?
Thank for any help !
Sébastien
PS : I am using django 1.3
When rendering your form in template, fields enumerating from fieldsets variable, not from fields. Sure you can redefine fieldsets in your AdminForm, but then validations will fail as original form class doesn't have such field. One workaround I can propose is to define this field in form definition statically and then redefine that field in form's init method dynamically. Here is an example:
class ServiceRoleAssignmentForm(forms.ModelForm):
test = forms.Field()
class Meta:
model = ServiceRoleAssignment
def __init__(self, *args, **kwargs):
super(ServiceRoleAssignmentForm, self).__init__(*args, **kwargs)
# Here we will redefine our test field.
self.fields['test'] = forms.CharField(label='test2')
I actually have a the same issue which I'm working through at the moment.
While not ideal, I have found a temporary workaround that works for my use case. It might be of use to you?
In my case I have a static name for the field, so I just declared it in my ModelForm. as normal, I then override the init() as normal to override some options.
ie:
def statemachine_form(for_model=None):
"""
Factory function to create a special case form
"""
class _StateMachineBaseModelForm(forms.ModelForm):
_sm_action = forms.ChoiceField(choices=[], label="Take Action")
class Meta:
model = for_model
def __init__(self, *args, **kwargs):
super(_StateMachineBaseModelForm, self).__init__(*args, **kwargs)
actions = (('', '-----------'),)
for action in self.instance.sm_state_actions():
actions += ((action, action),)
self.fields['_sm_action'] = forms.ChoiceField(choices=actions,
label="Take Action")
if for_model: return _StateMachineBaseModelForm
class ContentItemAdmin(admin.ModelAdmin):
form = statemachine_form(for_model=ContentItem)
Now as I mentioned before, this is not entirely 'dynamic', but this will do for me for the time being.
I have the exact same problem that, if I add the field dynamically, without declaring it first, then it doesn't actually exist. I think this does in fact have something to do with the way that ModelForm creates the fields.
I'm hoping someone else can give us some more info.
Django - Overriding get_form to customize admin forms based on request
Try to add the field before calling the super.init:
def __init__(self, *args, **kwargs):
self.fields['test'] = forms.CharField(label='test')
super(ServiceRoleAssignmentForm, self).__init__(*args, **kwargs)