Help needed with pre-populating Django forms: I have a form that updates a UserProfile model, when the form is loaded I want it pre-populated with the existing UserProfile data. Simple enough, the instance can be passed to the form. However, it seems that fields with choices (which come out as select drop-down elements in HTML) are not pre-populated with the instance data and instead default to '-----------'.
I've tried manually setting the initial value for a specific form (e.g. country) but it doesn't pull through to the HTML.
form = UserProfileForm(instance=user_profile)
form.fields['country'].initial = 'GBR'
I'm sure I could create a convoluted work around to get the current country selected in the front-end but it feels like it should be possible in Django. I couldn't see any solutions in other questions.
You can dynamically set default for a form field as:
form = UserProfileForm(instance=user_profile, initial={'country': 'GBR'})
If country field should be populated from user_profile data, then you can define form as following (assuming country is a field in UserProfile class)
class UserProfileForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(UserProfileForm, self).__init__(*args, **kwargs)
if self.instance:
self.initial['country'] = instance.country
Why what you are doing isn't working?
You are using:
form.fields['country'].initial = 'GBR'
This will not work on for a bounded form. form = UserProfileForm(instance=user_profile) will return a bounded form.
From docs:
The initial argument lets you specify the initial value to use when rendering this Field in an unbound Form.
To specify dynamic initial data, see the Form.initial parameter.
Related
I have a Model with 5 fields. I create a ModelForm on top of that model, now I intialize the form with request data containing 4 fields.
The reason of 4 fields in request data is the checkbox on frontend that isn't passed using jquery FormData when unchecked.
Now the problem is, I want to set a default value of the 5th field at the time of form initialization if checkbox is not passed in request data.
What would be the best thing to do, set default value in ModelForm or Form or use default value from Model or I can set the value of a specific field.
Sugesstions are welcome. TIA.
You can mess around with request.POST before binding it to the form. If you are using CBV FormView you can subclass get_form_kwargs. Something like
# UNTESTED CODE
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
if self.request.method in ('POST', 'PUT'):
if 'foo' not in kwargs['data']:
#this starts as request.POST which is an immutable QueryDict
data = kwargs['data'].copy() # mutable copy
data['foo'] = 'whatever' #supply the missing default value
kwargs['data'] = data
return kwargs
Consult the Django documentation w.r.t. QueryDict. They are tricky beasts that are directory-like but the differences can be enough to catch you out. It's easy to create one at the shell prompt and play with it to make sure you get this right.
I need to know why may I need to use instance into ModelForm I read about it in Django document what I understand that it can replace with save() method somehow if that is correct why I have to use it if not how can I use it and why?
Suppose you have a record in your models whose pk=1. So first you fetch the instance, and then you can create a form in your views by passing the instance argument. So if changes are made to some fields, the same record will be updated.
In your views -
a = ModelName.objects.get(pk=1) //Fetching the record you want to update
form = ModelFormName((request.POST, instance=a) or None)
if form.is_valid():
record = form.save(commit=False)
//Modify the records fields which you get from form
record.save()
// if not valid send it to template via context
Use this form in your templates as you would do in a normal form.
Background: I would like to enhance a page instance during an admin page view with some admin request related information (some pre-population in general). Basically I would need some function like "get_queryset", but not for list view, just for edit view.
In my older question related to a similar problem: Wagtail - how to preopulate fields in admin form? I was provided with instructions to use something called
CreatePageView
However, I cannot import it. Furthermore, I cannot even found any mention about that in google if I search:
Wagtail +CreatePageView
The closest thing I found is https://docs.wagtail.io/en/v2.1.1/reference/contrib/modeladmin/create_edit_delete_views.html but the page also states:
NOTE: modeladmin only provides ‘create’, ‘edit’ and ‘delete’
functionality for non page type models (i.e. models that do not extend
wagtailcore.models.Page). If your model is a ‘page type’ model,
customising any of the following will not have any effect
I am quite confused. What should I do if I need to customize the admin view for Page model extension?
I studied the wagtail source codes for Model.admin and Page and I have not found any way. Any ideas?
The related code simplified:
wagtail hooks:
class ItemAdmin(ModelAdmin):
pass
# some function override here maybe?
models:
class ItemPage(Page):
pass
# override for a function that gives data to the admin view maybe here?
Edit
As suggested in comments, it is possible to modify the admin page form during creation:
from wagtail.admin.forms import WagtailAdminPageForm
class ItemPageForm(WagtailAdminPageForm):
def __init__(self, data=None, files=None, parent_page=None, *args, **kwargs):
super().__init__(data, files, *args, **kwargs)
class ItemPage(Page):
base_form_class = ItemPageForm
however, acquiring the "request" in the WagtailAdminPageForm constructor does not seem possible.
This question is a bit ambiguous, so it is not super clear exactly what you need.
Interpreted question: When crediting (or editing) a page, I need access to the request to modify the initial values of some fields in the page form.
Potential Approach
Note: This may not be best practice and could be fragile depending on future changes to Wagtail.
First, we need a custom EditHandler, these are the way Wagtail builds up forms and even Panels within the editing interface. An EditHandler's job is to manage the form to return based on the model and even the current request.
As a first step, it would be good to get your page create form showing correctly by following the instructions on using a custom tabbed interface. From here, you can replace the TabbedInterface with your custom class (e.g. CustomTabbedInterface) and add some functionality to this which will allow for a dynamic form_class to be returned.
get_form_class should return the form_class, however, we can modify it to return a function that, when called, will instantiate the class with custom information based on the request.
There may be some issues with this approach below in edit views or scenarios not considered by this example so validate this fully before using.
Example Code
from wagtail.admin.edit_handlers import TabbedInterface, ObjectList
from wagtail.core.models import Page
class CustomTabbedInterface(TabbedInterface):
def get_form_class(self):
form_class = super().get_form_class()
request = self.request
if request and request.method != 'POST':
# check request is available to ensure this instance has been bound to it
user = self.request.user
def initiate_class(**kwargs):
# instead of returning the class, return a function that returns the instantiated class
# here we can inject a kwarg `initial` into the generated form
# important: this gets called for edit view also and initial will override the instance data
# kwarg['instance'] will be the `Page` instance and can be inspected as needed
kwargs['initial'] = {'introduction': user.first_name}
return form_class(**kwargs)
return initiate_class
return form_class
class StandardPage(Page):
# ... field etc
edit_handler = CustomTabbedInterface([
ObjectList(content_panels, heading='Content'),
ObjectList(Page.promote_panels, heading='Promote'),
ObjectList(Page.settings_panels, heading='Settings', classname="settings"),
])
Explanation
wagtail/admin/views/pages.py contains the create view, which will use the edit_handler, bind it to the model and the request and then call its get_form_class.
The form_class is used for the response here form = form_class(instance=page, parent_page=parent_page)
It gets called with the instance and the parent_page kwargs
Our custom get_form_class response takes those kwargs and injects an additional initial kwarg.
initial is used by Django forms to add any initial data - https://docs.djangoproject.com/en/3.0/ref/forms/api/#dynamic-initial-values
Finally, the Django form will merge the instance field values with the intial kwarg to generate the final pre-filled data for the form. You can see how this works in Django's BaseModelForm.
Be careful to consider what will happen on an update view, you likely do not want to override existing values with your initial values when a user has already entered something in the field.
I have a django form with 2 required fields and some optional fields which filters the data presented in a view.
This view uses some GET parameters apart from the ones for the filter form and I'm initialising my filter from like form = MyFilterForm(request.GET or None) [see (1) in the code below].
I'm finding when my view is first loaded and there are no GET parameters this works fine as request.GET is falsey so the form doesn't get bound (and therefore we use the initial values for the required fields). If the filter form gets used then request.GET gets populated with the form parameters and all works well again. However if one of my other GET parameters (namely one used to sort the resulting data table) gets passed without the filter form getting used then request.GET is truthy but doesn't have any of the parameters that correspond to the form and so the form gets bound and errors as invalid because my required fields have not been given a value.
What should happen here is that data table should be sorted and the form should continue using the initial ('default') values, just like when it is first loaded.
This is a little tricky to explain and might be clearer with some code...
I've googled extensively as this feels like a problem other people must have too but haven't had any luck finding anything.
I'm using django-tables2 to present the data in a table for the user but this question is more around the form, the only thing you need to know about the table is that it allows a user to sort the data presented in the table by clicking a table heading - this then adds a GET parameter to the request with the column to sort by I.E ?sort=start_date.
class MyFilterForm(forms.Form):
date_from = DateTimeField(required=True)
date_to = DateTimeField(required=True)
user = ModelChoiceField(required=False, queryset=User.objects.all())
def __init__(self, *args, **kwargs):
super(MyFilterForm, self).__init__(*args, **kwargs)
# dates in correct timezone and at start/end of day
# initial values set in __init__ so that they aren't fixed at import time
week_start, week_end = get_start_end_week(timezone.now())
self.fields['date_from'].initial = week_start
self.fields['date_to'].initial = week_end
And in my view
import django-tables2 as tables
#login_required
def view_with_filter_form_and_table(request):
form = MyFilterForm(request.GET or None) # (1) The form gets bound when the table is sorted here as request.GET is truthy
if form.is_bound and form.is_valid():
date_from = self.cleaned_data['date_from']
date_to = self.cleaned_data['date_to']
else:
# use defaults if not bound or not valid
date_from = form.fields['date_from'].initial
date_to = form.fields['date_to'].initial
user = form.cleaned_data.get('user') if form.is_bound else None
query = Action.objects.all()
if date_from:
query = query.filter(date__gte=date_from)
if date_to:
query = query.filter(date__lte=date_to)
if user:
query = query.filter(user=user)
table = MyTable(query)
tables.RequestConfig(request, paginate=False).configure(table)
return render(request, 'my_form_and_table.html', {'form': form, 'table': table})
What I've considered
Checking that all the required fields are in request.GET before binding to the form. Apart from being a bit of a code smell because I'll need to instantiate an unbound form to iterate over all the fields and check if the required ones are in request.GET to even know if I should instantiate a bound form. The problem here is that if the form is edited and one of the required fields is set to empty by a user I still want validation errors to be shown, aka This field is required..
I've currently worked around the issue by adding the below code under the code labeled (1) but would love it if there was a cleaner solution.
for name, field in form.fields.items():
if field.required and name not in request.GET:
form = MyFilterForm(None) # unbound form
break
I'm have a model that has some fields that should never be edited, such as "date created." I want to display a form that has some text fields with the extra information, probably just the data itself in a <span>. A person can edit a few fields and at the same time they can see the extra information about the instance of the model, such as created, last modified, or even related objects in the database for this object. Is there a way to do this using Django's built in framework, or will I have to create my own custom form by passing the whole object to a template?
If this needs more clarification, feel free to ask.
The best way I know to do this is to initialize the fields before you pass the form to the template by passing an initial dictionary to the form or by passing a instance object to the form.
You should then make sure that the fields are disabled, or you should make them hidden fields and then display the fields as regular text.
Most importantly, if you're passing data to the client that will then be sent back in a form, you should make sure that the data coming in is the same as the data that went out (for security's sake). Do this with at clean_[field] function on the Form. It should look like the following.
class MyForm(forms.ModelForm):
class Meta:
model = MyModel
def clean_date_created(self):
if self.cleaned_fields['date_created'] != self.instance.date_created:
raise ValidationError, 'date_created has been tampered'
self.cleaned_fields['date_created']
[Edit/Addendum] Alternatively, you can pass the data directly to your template to render separately, and then tack on the data to your form after you get it back into your view. It should go something like this:
def recieve_form(request, ...):
...
f = MyForm(request.POST, instance=a)
new_model_instance = f.save(commit=False)
new_model_instance.date_created = <whatever>
new_model_instance.save()
To do that I usually customize the form in order for the widget to be read only, like the following:
class MyForm(forms.ModelForm):
class Meta:
model = MyModel
widgets = {
'my_field': forms.TextInput(attrs={'disabled':'disabled'}),
}
This will output a read only text field but I guess you can also create a Widget that will just output a <div> or a <p> (or whatever you need)