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.
Related
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.
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.
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 have a Django form that allows a user to change their password. I find it confusing on form error for the fields to have the *'ed out data still in them.
I've tried several methods for removing form.data, but I keep getting a This QueryDict instance is immutable exception message.
Is there a proper way to clear individual form fields or the entire form data set from clean()?
Regarding the immutable QueryDict error, your problem is almost certainly that you have created your form instance like this:
form = MyForm(request.POST)
This means that form.data is the actual QueryDict created from the POST vars. Since the request itself is immutable, you get an error when you try to change anything in it. In this case, saying
form.data['field'] = None
is exactly the same thing as
request.POST['field'] = None
To get yourself a form that you can modify, you want to construct it like this:
form = MyForm(request.POST.copy())
Someone showed me how to do this. This method is working for me:
post_vars = {}
post_vars.update(request.POST)
form = MyForm(post_vars, auto_id='my-form-%s')
form.data['fieldname'] = ''
form.data['fieldname2'] = ''
If you need extra validation using more than one field from a form, override the .clean() method. But if it's just for one field, you can create a clean_field_name() method.
http://docs.djangoproject.com/en/1.1/ref/forms/validation/#ref-forms-validation
I just created the form again.
Just try:
form = AwesomeForm()
and then render it.
Django has a widget that does that:
https://docs.djangoproject.com/en/1.8/ref/forms/widgets/#passwordinput
now if you are not working with passwords you can do something like this:
class NonceInput(Input):
"""
Hidden Input that resets its value after invalid data is entered
"""
input_type = 'hidden'
is_hidden = True
def render(self, name, value, attrs=None):
return super(NonceInput, self).render(name, None, attrs)
Of course you can make any django widget forget its value just by overriding its render method (instead of value I passed None in super call.)
Can't you just delete the password data from the form's cleaned_data during validation?
See the Django docs for custom validation (especially the second block of code).
I guess you need to use JavaScript to hide or remove the text from the container.