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)
...
Related
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).
Let's say we have an app called Closet and it has some models:
# closet.models.py
class Outfit(models.Model):
shirt = models.ForeignKey(Shirt)
pants = models.ForeignKey(Trouser)
class Shirt(models.Model):
desc = models.TextField()
class Trouser(models.Model):
desc = models.TextField()
class Footwear(models.Model):
desc = models.TextField
Using generic detail view, it's easy to make the URL conf for details on each of those:
#urls.py
urlpatterns = patterns('',
url(r'^closet/outfit/(?P<pk>\d+)$', DetailView(model=Outfit), name='outfit_detail'),
url(r'^closet/shirt/(?P<pk>\d+)$', DetailView(model=Shirt), name='shirt_detail'),
url(r'^closet/trouser/(?P<pk>\d+)$', DetailView(model=Trouser), name='trouser_detail'),
url(r'^closet/footwear/(?P<pk>\d+)$', DetailView(model=Footwear), name='footwear_detail'),
)
What I'd like to do next is define the views that will create a new object of each type. I would like to do this with an extended version of CreateView which will be able to handle data on pre-populated fields.
Specifically, I want the following behavior:
If I visit /closet/outfit/new I want to get a standard ModelForm for the Outfit model with everything blank and everything editable.
If I visit /closet/outfit/new/?shirt=1 I want to see all the fields I saw in case 1) but I want the shirt field to be pre-populated with the shirt with pk=1. Additionally, I want the shirt field to be displayed as un-editable. If the form is submitted and is deemed to be invalid, when the form is redisplayed I want the shirt field to continue to be un-editable.
If I visit /closet/outfit/new/?shirt=1&trouser=2 I want to see all the fields I saw in case 1) but now both the shirt and trouser fields should be preopoulated and uneditable. (I.e. only the footwear field should be editable.)
In general, is this possible? I.e. can the querystring modify the structure of the displayed form in this way? I want to accomplish this in the DRYest way possible. My gut tells me this should be doable with class based views and perhaps would involve model_form_factory but I can't get the logic straight in my mind. In particular, I wasn't sure whether it was possible to have the class-based-view access the request.REQUEST (i.e. the request.POST or request.GET parameters) at the time that the ModelForm is being constructed.
Perhaps its possible only if I use different querystring keywords for the locked fields. I.e. perhaps the URL's need to be: /closet/outfit/new/?lock_shirt=1 and /closet/outfit/new?lock_shirt=1&lock_trouser=2. Perhaps if its done that way the POST handler would be handed both a list of locked fields (for the purposes of form display in the browser) along with a regular list of all the model fields for the purpose of actually creating the object.
Why do I want this: In the template for the footwear_detail I would want to be able to make a tag like
<a href="{% url outfit_new %}?footwear={{object.pk}}>Click to create a new outfit with this footwear!</a>
In general, it would be really useful to be able to make links to forms whose "structure" (not just values) changes depending on the querystring passed.
Responding to the great suggestion from Berislav Lopac:
So I went ahead and did:
class CreateViewWithPredefined(CreateView):
def get_initial(self):
return self.request.GET
This gets me 90% of what I need. But let me flesh out the situation a bit more. Say I add two fields to the Outfit model: headgear = models.ManyToManyField('headgear') and awesomeness_rating = models.FloatField().
Two problems:
If I visit /closet/outfit/new/?awesomeness_rating=10 then my form pre-fills with [u'10'] instead of just filling with 10. Is there a filter I should use in my template or a bit of processing I can add to my view to make the formatting more appropriate?
If I want to pre-specify a few pieces of headwear, what is the right format to pass what feels like a python list in through a query string? I.e. should I do /closet/outfit/new/?headgear=1,2,3? If so, will Django correctly figure out that I'd like to pre-select the 3 pieces of headgear with those ID's?
Continuing to work on this...
class CreateViewWithPredefined(CreateView):
def get_initial(self):
initial = super(CreateView, self).get_initial()
for k, v in self.request.GET.iterlists():
if len(v) > 1:
initial.update({ k : v })
else:
initial.update({ k : v[0] })
return initial
This seems to kill 2 birds with one stone: numerical data gets coerced from unicode to numerical and it flattens lists when possible (as intended). Need to check if this works on multi-valued fields.
It's self.request, anywhere in a CBV. :-)
OK, let me make this answer more comprehensive. Basically, what you want is the get_initial method, which is contributed by the FormMixin. Override it to populate the initial values for your fields.
I want write a custom form field (and possibly widget too) and I'm not sure about how the form instances are shared between requests. For example, if I render a form with data from a model instance, is that instance still available when I am validating data? If so, does that mean that there is another database hit to look up the model again between requests?
Similarly, if I write a custom field that takes in a list of data to display in its __init__ method, will that list of data be available to validate against when the user POSTs the data?
It would be really helpful if someone could point me to parts of the django source where this occurs. I've been looking at the models.py, forms.py, fields.py and widgets.py from django.forms, but I'm still not 100% sure how it all works out.
Eventually, what I want to do is have a field that works something like this (the key part is the last line):
class CustomField(ChoiceField):
def __init__(self, data_dict, **kwargs):
super(CustomField, self).__init__(**kwargs)
self.data_dict = data_dict
self.choices = data_dict.keys()
def validate(self, value):
if value not in self.data_dict:
raise ValidationError("Invalid choice")
else:
return self.data_dict[value]
Will that data_dict be available on the next request? If I create a custom forms.Form and initialize it with the data_dict, will that be available on the next request? (e.g. with a factory method or something...).
Side note: I'm doing this because I want to (eventually) use something like Bootstrap's typeahead and I'd like to pass it "pretty values" which I then convert server-side (basically, like how option values in a select can have a different submitted value). I've done this with client-side javascript in the past, but it would be nice to consolidate it all into a form field.
There's nothing magical about forms. Like everything else in Django (or just about any web framework), objects don't persist between requests, and need to be reinstantiated each time. This happens in the normal view pattern for form handling: you instantiate it once for a POST, and a separate time for a GET. If you have data associated with the form, it would need to be passed in each time.
I have a many to many link between Foos and Bars. I don't particularly like the multi select widget so was thinking of having a widget which allows selecting a single Bar and a button to add more choice fields.
From what I've been reading formsets may be the answer. Is it valid to have a formset made up of a form with only one choice field or is there a better way to get the behaviour I'm looking for?
I wouldn't worry about the quantity of fields in the form. If your 'child' model only has the one field, then I'd say it's perfectly valid to use an formset with single field forms in this application.
You should take a look at inline formsets, they should help with exactly what you need. Although I'm not 100% sure they work for M2M...
additional thought: If it doesn't inline forsmet doesn't work directly with M2M, you can just use a model formset, and manually save the relationship in your view after using formset.save(commit=False). docs: formset saving
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.