I am trying to generate a list to populate choices for a form. This list will be dynamic and will depend on what choices have been created by other users. Here is an example of the model the list should derive from:
#models.py
class User(models.Model):
brewery_you_work_for = models.CharField(choises=ALL_THE_BREWERIES_IN_THE_WORLD)
username = models.CharField()
I want the form to be something like this:
#forms.py
class BestBrewery(forms.Form):
vote = forms.ChoiceField(choices=BREWERIES_A_USER_WORKS_FOR)
What I want to do is have a list of all the breweries in the world for some users to select as the brewery they work for. Then I want to generate a list for other users to vote as the best brewery.
Lets say I have 3 users that claim they work the Sierra Nevada, 2 users that claim they work for Budweiser, and 5 users that claim they work for Coors.
I want to generate a list of breweries that would look like this:
(
'Budweiser',
'Coors,
'Sierra Nevada',
)
Note the alphabetical order and no repeated brewery listings.
You need to pass an extra user argument when initialising the form. Normally like this if you are using view functions:
form = BestBrewery(request.user)
If you are using Class Based Views you should override the get_form_kwargs method in your view:
def get_form_kwargs(self):
kwargs = super(MyView, self).get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
Your form should look like this:
class BestBrewery(forms.Form):
def __init__(self, user, *args, **kwargs):
super(BestBrewery, self).__init__(*args, **kwargs)
self.fields['vote'].choices = (
user.brewery_you_work_for,
user.get_brewery_you_work_for_display()
)
Just note that to override the choices field you need to provide tuples containing the value for the form and the text to be shown in the dropdown.
Well, I think u can set "choices" as result of function.
I tried like that:
def yolo():
# there we will get stats from db and return choices depends on stats
first_query = 1 # there U will use more complicated logic :)
second_query = 3
if first_query > second_query:
return (1, 2, 3)
else:
return (1, 3)
class Event(models.Model):
date = models.DateField(primary_key=True)
count_of_updates = models.SmallIntegerField(default=0)
yolos = models.CharField(max_length=255, choices=yolo())
And, for being sure U can check ur choices with something ugly:
choices = s._meta.get_field_by_name('yolos')[0].choices
print choices
Order and other things u can set in func logic :)
In your Form, you can change the choices of your field in the init.
class BestBrewery(forms.Form):
vote = forms.ChoiceField()
def __init__(self, *args, **kwargs):
super(BestBrewery, self).__init__(*arg, **kwargs)
# get the choices from where you need
choices = (...)
self.fields['vote'].choices = choices
Related
I am using django-filter and need to add a ChoiceFilter with choices dependent on the request that I receive. I am reading the docs for ChoiceFilter but it says: This filter matches values in its choices argument. The choices must be explicitly passed when the filter is declared on the FilterSet.
So is there any way to get request-dependent choices in the ChoiceFilter?
I haven't actually written the code but the following is what I want -
class F(FilterSet):
status = ChoiceFilter(choices=?) #choices depend on request
class Meta:
model = User
fields = ['status']
I've been looking too hard that I found two different ways of doing it! (both by overriding the __init__ method). Code inspired from this question.
class LayoutFilterView(filters.FilterSet):
supplier = filters.ChoiceFilter(
label=_('Supplier'), empty_label=_("All Suppliers"),)
def __init__(self, *args, **kwargs):
super(LayoutFilterView, self).__init__(*args, **kwargs)
# First Method
self.filters['supplier'].extra['choices'] = [
(supplier.id, supplier.id) for supplier in ourSuppliers(request=self.request)
]
# Second Method
self.filters['supplier'].extra.update({
'choices': [(supplier.id, supplier.name) for supplier in ourSuppliers(request=self.request)]
})
The function ourSuppliers is just to return a QuerySet to be used as choices
def ourSuppliers(request=None):
if request is None:
return Supplier.objects.none()
company = request.user.profile.company
return Supplier.objects.filter(company=company)
If for example, I have a class Summary,
each Instance of the class Summary, can be one subject:
subjects = (
('english', 'אנגלית'),
('bible', 'תנ"ך'),
('history', 'היסטוריה'),
('civics', 'אזרחות'),
('language', 'לשון'),
('literature', 'ספרות'),
)
class Summary(models.Model):
...
subject = models.CharField(max_length=20, choices=subjects)
...
Now I've decided I want to hardcode some topics for each subject, so if Summary.subject = "literature" I want to add a field
subtopic = models.CharField(choices=literature_subtopics)
and make the choices equal to:
literature_subtopics = (
('poems', 'שירה'),
('short_stories', 'סיפורים קצרים'),
('plays', 'מחזות'),
('novels', 'נובלות'),
)
If the subject was English then english_subtopics would be used for the choices field.
I want to hard-code all these divisions because they will not change more than once every few years if at all, storing them in a database makes zero sense.
I need to somehow set up all these divisions for each subject, and make Django set the choices field for the subtopic appropriately.
can I override the init method to accomplish this somehow? I heard that's a bad idea and can break things.
Even if the data doesn't change often, it seems most natural to put data in the database and Python in Python files. Your proposed solution seems like you're fighting the way Django wants to do things.
What do think of a database solution?
class Subject(models.Model):
parent = models.ForeignKey("self")
name = models.CharField(max_length=100)
hebrew_name = models.CharField(max_length=100)
class Summary(models.Model):
...
subject = models.ForeignKey("Subject")
...
class SubjectForm(forms.Form):
subject = forms.ModelChoiceField(queryset=Subject.objects.none())
...
def __init__(self, *args, **kwargs): # see http://stackoverflow.com/a/4880869/1477364
sub = kwargs.pop('parent_subject')
super(SubjectForm, self).__init__(*args, **kwargs)
parent_subject = Subject.objects.get(name=sub)
sub_subjects = Subject.objects.filter(parent=parent_subject)
self.fields['subject'].queryset = forms.ModelChoiceField(queryset=sub_subjects)
Note that the code implies there will always be a parent Subject passed to SubjectForm. (You'll need a "root" Subject.)
You could set the choices dynamically using a function.
# models.py
def get_choices():
choices_tuple_list = []
... # your logic to define choices
return choices_tuple_list
class Summary(models.Model):
... # Summary definition
subject = models.CharField(max_length=20, blank=True)
def __init__(self, *args, **kwargs):
super(Summary, self).__init__(*args, **kwargs)
self._meta.get_field_by_name('subject')[0]._choices = get_choices()
See docs
I feel like this must be really simple, but after a couple of days of trying I'm officially clueless.
I have a dictionary where the keys are objects and the values are lists of objects. Here's how I want to use that info to construct a form:
for object in dictionary:
name_of_field = object.slug
name_of_field = forms.ModelMultipleChoiceField(widgets=forms.CheckboxSelectMultiple, queryset=dictionary[object])
Of course, just putting name_of_field in there twice doesn't work to generate dynamically named fields. What this actually does is create a single field called "name_of_field" using the final object it iterates over. I wish it would create a field for every key in the dictionary, named using the key object's slug and with a choice set of that key's values.
Is there a way to loop through this dictionary and create the form fields I want? I feel like the answer lies in superclassing __init__, but I still can't wrap my head around how to get multiple fields with different names.
You don't say where you are using this code. You should be putting it into the form's __init__ method, from where you can reference self.fields:
class DynamicForm(forms.Form):
def __init__(self, *args, **kwargs):
dynamic_fields = kwargs.pop('dynamic_fields')
super(DynamicForm, self).__init__(*args, **kwargs)
for key, value in dynamic_fields:
self.fields[key.slug] = forms.ModelMultipleChoiceField(widget=forms.CheckboxSelectMultiple, queryset=value)
I feel like I want to contribute to this question although it is really old, because I could not solve my question with the answer alone.
For a given model with the form:
class Product(models.Model):
data = models.JSONField()
store = models.ForeignKey(Store, on_delete = models.CASCADE)
number = models.PositiveIntegerField()
a dynamically created form can be created (careful, this is a simplified case, where all products of the same store share the same keys in the JSONField):
class ProductForm(forms.Form):
def __init__(self, first_product, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["number"] = forms.IntegerField(required = True)
for key in first_product.data.keys():
self.fields[key] = forms.CharField(required = False)
class Meta:
model = Product
fields = ["number", "data"]
The form must be called by handing it the first_product variable in the view:
class SomeView(TemplateView):
template_name = "appName/sometemplatename.html"
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(**kwargs)
store = Store.objects.get(user = self.request.user)
tmp = Product.objects.filter(store = store).first()
context["testform"] = ProductForm(first_product = tmp)
return context
This is a vastly general approach to start from.
All,
Is there a straightforward way for me to display a form that uses a modelchoicefield for models that have yet to be saved?
class Foo(models.Model):
name = models.CharField(max_length=42)
class Bar(models.Model):
name = models.CharField(max_length=42)
foo = models.ForeignKey("Foo",blank=True,null=True)
class BarForm(ModelForm):
class Meta:
model = Bar
pass
def create_new_bar(request):
foos = [Foo(name='foo1'), Foo(name='foo2'), Foo(name='foo3')]
bar = Bar(name='bar',foo=foos[0])
form = BarForm(instance=bar)
return render_to_response('bar_template.html',{"form" : form},context_instance=RequestContext(request))
But the "select" widget that gets rendered in the form has empty content. Presumably, this is because there is nothing to bind the queryset to for the ModelChoiceField that Bar's ForeignKey to Foo uses. (Since nothing in "foos" has been saved and therefore does not exist in the database).
Is there another way to deal with this besides writing a custom formfield/widget?
Thanks.
You can add additional field to BarForm with predefined choices - fooempty for example. That field will be shown in case if Foo table is empty. Try this (not tested):
class BarForm(ModelForm):
FOO_CHOICES = (
(0, 'Choice1'),
(1, 'Choice2'),
(2, 'Choice3'),
)
fooempty = forms.ChoiceField(required=False, label='lbl', choices=FOO_CHOICES)
class Meta:
model = Bar
def __init__(self, *args, **kwargs):
super(BarForm, self).__init__(*args, **kwargs)
if self.fields['foo'].queryset.count(): # Foo empty?
self.fields['fooempty'].widget = forms.HiddenInput()
else:
self.emptyflag
self.fields['foo'].widget = forms.HiddenInput()
def save(self, commit=True):
bar = super(BarForm, self).save(commit=False)
if self.emptyflag:
bar.foo_id = self.cleaned_data['fooempty']
bar.save()
return bar
EDIT: actually, now that I think about it. Those Foos won't have pks. So there is no way that could ever work. The form needs to know the pk of the Foos. You really just need to save them first. How would django know which one is which when it posts back the form? IGNORE ANSWER BELOW.
I think you can change the choices variable on the field. You will need to that in the form. So pass the list of Foos to the init of the form. Something like this:
class BarForm(ModelForm):
class Meta:
model = Bar
def __init__(self, foo_list, *args, **kwargs):
super(BarForm, self).__init__(*args, **kwargs)
self.fields['foo'].choices = foo_list
Then in the view:
foos = [Foo(name='foo1'), Foo(name='foo2'), Foo(name='foo3')]
bar = Bar(name='bar', foo=foos[0])
form = BarForm(foos, instance=bar)
I know I've done something like this before. I just can't remember the exact way. Something like this approach should work though.
I have some models and I want to generate a multi-selection form from this data.
So the form would contain an entry for each category and the choices would be the skills in that category.
models.py
class SkillCategory(models.Model):
name = models.CharField(max_length=50)
class Skill(models.Model):
name = models.CharField(max_length=50)
category = models.ForeignKey(SkillCategory)
Is there a way to auto-generate the form fields?
I know I can manually add a 'SkillCategory' entry in the form for each SkillCategory, but the reason to have it as a model is so skills and skillcategories can be edited freely.
I want to do something like this:
(I tried this, but didn't get it to work, don't remember the exact error...)
forms.py
class SkillSelectionForm(forms.Form):
def __init__(*args, **kwargs):
super(SkillSelectionForm, self).__init__(*args, **kwargs)
for c in SkillCategory.objects.all():
category_skills = [(pk, s.name) for s in c.skill_set.all()]
setattr(self, c.name, forms.MultipleChoiceField(choices=category_skills, widget=forms.CheckboxSelectMultiple))
SOLUTION
This creates a form field entry using the SkillCategory.name and assigns choices as those in Skill. field_name/display_name are used to avoid issues with non-ascii category names.
forms.py
def get_categorized_skills():
skills = {}
for s in Skill.objects.values('pk', 'name', 'category__name').order_by('category__name'):
if s['category__name'] not in skills.keys():
skills[s['category__name']] = []
skills[s['category__name']].append((s['pk'], s['name']))
return skills
class SkillSelectionForm(forms.Form):
def __init__(self, *args, **kwargs):
super(SkillSelectionForm, self).__init__(*args, **kwargs)
skills = get_categorized_skills()
for idx, cat in enumerate(skills.keys()):
field_name = u'category-{0}'.format(idx)
display_name = cat
self.fields[field_name] = forms.MultipleChoiceField(choices=skills[cat], widget=forms.CheckboxSelectMultiple, label=display_name)
Okay so you can't set fields like that on forms.Form, for reasons which will become apparent when you see DeclarativeFieldsMetaclass, the metaclass of forms.Form (but not of forms.BaseForm). A solution which may be overkill in your case but an example of how dynamic form construction can be done, is something like this:
base_fields = [
forms.MultipleChoiceField(choices=[
(pk, s.name) for s in c.skill_set.all()
]) for c in SkillCategory.objects.all()
]
SkillSelectionForm = type('SkillSelectionForm', (forms.BaseForm,), {'base_fields': base_fields})
Take a look at creating dynamic forms in Django, from b-list.org and uswaretech.com. I've had success using these examples to dynamically create form content from models.
What you want is a Formset. This will give you a set of rows, each of which maps to a specific Skill.
See the Formset documentation and the page specifically on generating formsets for models.