New to using WTForms, and I can't figure out how to do expose additional form fields as shown here: https://css-tricks.com/exposing-form-fields-radio-button-css/
Because radio fields have the choices parameter where a list of fields is provided, is there any way to customize each of those choices? Like have something different be exposed when different choices are selected?
This is going to be a mix of python and javascript code.
There are 2 common solutions:
Everything is already in the webpages and the unused fields are hidden until there is an action from the user (on the radio button).
Make API calls to get dynamic custom choices.
You can create a route:
#app.route("/api/choices", methods=["GET"])
def my route:
custom_choices = get_custom_choices()
return jsonify(custom_choices)
Then in your javascript code, when the user clicks on a button, you make a request to the this route to get the custom choices.
Then when you check/validate the sent form, populate the choices of the field with the custom choices (in the example, get_custom_choice) as described in the WTForms documentation here:
Note that the choices keyword is only evaluated once, so if you want
to make a dynamic drop-down list, you’ll want to assign the choices
list to the field after instantiation. Any submitted choices which are
not in the given choices list will cause validation on the field to
fail. If this option cannot be applied to your problem you may wish to
skip choice validation (see below).
Select fields with dynamic choice values:
class UserDetails(Form):
group_id = SelectField('Group', coerce=int)
def edit_user(request, id):
user = User.query.get(id)
form = UserDetails(request.POST, obj=user)
form.group_id.choices = [(g.id, g.name) for g in Group.query.order_by('name')]
Note we didn’t pass a choices to the SelectField constructor, but
rather created the list in the view function. Also, the coerce keyword
arg to SelectField says that we use int() to coerce form data. The
default coerce is str().
Source
The second solution requires more work, but it's lighter depending on how many different possibilities ou have for the choice. (And in my opinion: cleaner).
Related
I am using materializecss to give my django site some material elements. I have put together a form (the 'old' way using html) but now realised I need to use a django form instead. The problem is, these forms don't play well with materialises built in column system (they use classes to determine rows and column spacing). Here is an example of the layout I set up so far. However when defining the form through form.py, it spits out one input per layer.
My question is: what can I do to either a) get django to work with the html-defined form or b) make a 'form template' to give the input fields the appropriate classes?
If you want to see the code I can post some but I'm quite a new coder so it's messy.
Thanks!
There are three ways I can think of off the top of my head.
If you want full control over the HTML form, in a Django template or HTML form, simply map the names of your fields to match the underlying field names in the Django form. This way, when POSTed back to your view, Django will automatically link up the POSTed fields with the Django form fields.
For example, if you have a field username in your Django form (or Django model if using ModelForm), you could have an element <input type="text" name="username" maxlength="40"> (that you can style any way you need) on your HTML form that Django will happily parse into your Django form field, assuming your view is plumbed correctly. There is an example of this method in the Django documentation.
Another way is to customize the Django form field widgets in your Django form definition. The Django documentation talks a little bit about how to do this. This is great for one offs, but is probably not the best approach if you expect to reuse widgets.
The final approach would be to subclass Django form field widgets to automatically provide whatever attributes you need. For example, we use Bootstrap and have subclassed nearly all of the widgets we use to take advantage of Bootstrap classes.
class BootstrapTextInput(forms.TextInput):
def __init__(self, attrs=None):
final_attrs = {'class': 'form-control'}
if attrs is not None:
final_attrs.update(attrs)
super().__init__(attrs=final_attrs)
Then it's simply a matter of letting the Django form know which widget to use for your form field.
class UsernameForm(forms.ModelForm):
class Meta:
model = auth.get_user_model()
fields = ['username']
widgets = {'username': BootstrapTextInput()}
Hope this helps. Cheers!
Say I have a model User, which has a credits field (IntegerField). When a user registers, I will set the credits field to 0, and I will update the credits for certain events.
I don't want the user know there is a field like this in the db table.
What attribute should I set to the field?
To accomplish the defaulting to 0 part, you can simply use the default argument of the model field.
For the part where you don't want your users to know about the field, you have a couple choices.
Solution 1: Field.editable
Defining your field as follows will cause the field to never show up in a model form.
credits = models.IntegerField(default=0, editable=False)
Downsides
You won't be able to edit the field's value in the admin
Form validation will never take this field into account (e.g., def clean_credits(self): won't run)
Solution 2: ModelForm.exclude|fields
Creating a ModelForm for the model is something you're going to be doing. You can define an exclude attribute on the form's Meta class, and add "credits" to the list. See the docs linked above. You can instead define fields on the Meta class, and omit "credits". The latter of the two options is considered a better practice, particularly when pertaining to security, and is known as a whitelist.
Downsides
You have to remember to define exclude or fields on every exposed form
Updating the "secret" field
The proper way to handle specifying a "secret" field's value when the field isn't in the form is:
# Inside your view's post method (or FormView.form_valid, if you're using generic views)
instance = form.save(commit=False) # Does everything except INSERT into the database
instance.credits = <however many credits you feel like giving the user>
instance.save()
If you didn't do that, and instead just saved the form as-is, the value specified by default would be set to the instance's credits field.
You'll want to use an IntegerField with default=0: credits = models.IntegerField(default=0). Just take care not to show this field to the user in any forms or when displaying the user.
E.g., if you had a ModelForm for User, do not include credits in the fields field of Meta
I am trying to work out the best way to implement a form in django that has two choice fields on it, one of which affects the choices available in the other. An example - form field one is a radio button (can choose only one option) called 'cuisine', and the second is a multichoice field called 'menu'. If you choose 'french' from 'cuisine' then you get french dishes in the menu list, but if you choose 'chinese' you get a different selection.
How do I work this server-side in the form validation process. How do I 'bind' the two controls so that only dishes related to the cuisine option are accepted?
And how do I render this - should I pass in a ModelForm for each type of cuisine, or have a single menu ModelForm that has everything in it, and just show/hide stuff on the client-side?
All of the menu options are stored in the db and loaded in as fixtures, and the cuisines are hard-coded into the app:
CUISINE = ((0,'French'),(1,'Chinese'),(2,'Italian'))
class MenuItem(models.Model):
description = models.CharField(max_length=200)
cuisine = models.IntegerField('Cuisine', choices=CUISINE)
For rendering, you can use django-selectable or django-autocomplete-light,
For server side validation, django has it completely documented.
I'd like to create a confirmation page for selected objects before a change is made to them (outside the admin). The objects can be of different models (but only one model a time).
This is much like what is done in administration before deletion. But the admin code is complex and I haven't grasped how it is done there.
First I have severall forms that filter the objects differently and then I pass the queryset to the action / confirmation page. I have created a form factory so that I can define different querysets depending on model (as seen in another similiar question here at Stackoverflow):
def action_factory(queryset):
''' Form factory that returns a form that allows user to change status on commissions (sale, lead or click)
'''
class _ActionForm(forms.Form):
items = forms.ModelMultipleChoiceField(queryset = queryset, widget=forms.HiddenInput())
actions = forms.ChoiceField(choices=(('A', 'Approve'), ('D' ,'Deny'), ('W' ,'Under review'), ('C' ,'Closed')))
return _ActionForm
Which I use in my view:
context['form']=action_factory(queryset)()
The problem is that the items field wont be displayed at all in the html-code when it is hidden. When I remove the HiddenInput widget it displays the form correctly.
I don't want to display the choice field since there can be thousands of objects. All I want to have is something like "Do you want to change the status of 1000 objects" and a popdown and a submit button. A simple enough problem it seems, but I can't get it to work.
If someone has a solution to my current attempt I would be glad to hear how they have done it. Even better would be if there is a cleaner and better solution.
I used the wrong widget. It should be MultipleHiddenInput not HiddenInput.
Here is what I've been struggling for a day...
I have a Message model in which recipients is a ManyToManyField to the User model.
Then there is a form for composing messages. As there are thousands of users, it is not convenient to display the options in a multiple select widget in the form, which is the default behavior. Instead, using FcbkComplete jquery plugin, I made the recipients field look like an input field where the user types the recipients, and it WORKS.
But...
Although not visible on the form page, all the user list is rendered into the page in the select field, which is something I don't want for obvious reasons.
I tried overriding the ModelChoiceField's behavior manipulating validation and queryset, I played with the MultipleChoice widget, etc. But none of them worked and felt natural.
So, what is the (best) way to avoid having the whole list of options on the client side, but still be able to validate against a queryset?
Have you seen django-ajax-selects? I've never used it, but it's in my mental grab bag for when I come across a problem like what it sounds like you're trying to solve...
I would be trying one of two ways (both of which might be bad! I'm really just thinking out aloud here):
Setting the field's queryset to be empty (queryset = Model.objects.none()) and having the jquery tool use ajax views for selecting/searching users. Use a clean_field function to manually validate the users are valid.
This would be my preferred choice: edit the template to not loop through the field's queryset - so the html would have 0 options inside the select tags. That is, not using form.as_p() method or anything.
One thing I'm not sure about is whether #2 would still hit the database, pulling out the 5k+ objects, just not displaying them in the html. I don't think it should, but... not sure, at all!
If you don't care about suggestions, and is OK to use the ID, Django Admin comes with a raw_id_field attribute for these situations.
You could also make a widget, that uses the username instead of the ID and returns a valid user. Something among the lines of:
# I haven't tested this code. It's just for illustration purposes
class RawUsernameField(forms.CharField):
def clean(self, value):
try:
return User.objects.get(username=value)
except User.DoesNotExist:
rause forms.ValidationError(u'Invalid Username')
I solve this by overriding the forms.ModelMultipleChoiceField's default widget. The new widget returns only the selected fields, not the entire list of options:
class SelectMultipleUserWidget(forms.SelectMultiple):
def render_options(self, choices, selected_choices):
choices = [c for c in self.choices if str(c[0]) in selected_choices]
self.choices = choices
return super(SelectMultipleUserWidget,
self).render_options([], selected_choices)
class ComposeForm(forms.Form):
recipients = forms.ModelMultipleChoiceField(queryset=User.objects.all(),
widget=SelectMultipleUserWidget)
...