Django: Dynamically generate ChoiceField values from another model object - python

I want to implement a Django form with dynamically generated ChoiceField options.
In my views.py I have defined the following (relevant) methods:
from .forms import OptionForm
def get_choice():
return # like this [(q.optionA, q.optionA), (q.optionB, q.optionB), (q.optionC, q.optionC), (q.optionD, q.optionD)]
# how can I pass q to this __init__ method
class OptionForm(forms.Form):
options= forms.ChoiceField(choices= [])
def __init__(self, *args, **kwargs):
super(OptionForm, self).__init__(*args, **kwargs)
self.fields['options'].choices = get_choice()
def viewtest(request, test_pk):
# get the test object containing all the questions
# each question contains four options using which I want to generate ChoiceField options corresponding to each question
# each option is available as q.optionA, q.optionB q.optionC & q.optionD
test= get_object_or_404(Test, pk= test_pk)
options= []
for q in test.questions.all():
opform= OptionForm() # how do I pass q.optionA, q.optionB q.optionC & q.optionD here to dynamically provide ChoiceField options?
options.append(opform)
return render(request, 'tests/test.html', {'test': test, 'options': options})

To get choices from model, ModelChoiceField is a better option. Find more details here: https://docs.djangoproject.com/en/3.1/ref/forms/fields/#django.forms.ModelChoiceField

Related

Fill a ChoiceField in Django template with external data

I'm new to Django and I'm having a hard time understanding forms when the data to choose from are not taken from the database nor user input that they're generated on the go.
I currently have a template with a single ChoiceField. The data inside this field aren't fixed and they're calculated on the go once the page is requested. To calculate it I need the username of the User who is logged in. Basically, the calculation returns a list of lists in the form of ((title, id),(title,id),(title,id)), etc. that I need to put into the ChoiceField to make the User choose from one of the options.
Now, I'm not understanding how to pass the calculated list of lists to the form. I've tried to add the calculations inside the form as below but it is clearly the wrong way.
The main issue is that, to calculate my list of lists, I need the request value, and I don't know how to access it from the form.
Another idea was to add the generate_selection function inside the init but then I don't know how to pass main_playlist to being able to add it to ChoiceField
Below my not working forms.py
forms.py
class ChoosePlaylistForm(forms.Form):
playlists = forms.ChoiceField(choices=HERE_SHOULD_GO_main_playlist)
def generate_selection(self):
sp_auth, cache_handler = spotify_oauth2(self.request)
spotify = spotipy.Spotify(oauth_manager=sp_auth)
user_playlists = spotify.current_user_playlists(limit=10)
main_playlist = []
for playlists in user_playlists["items"]:
playlists_list = []
playlists_list.append(playlists['name'])
playlists_list.append(playlists['id'])
main_playlist.append(playlists_list)
return main_playlist
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
super(ChoosePlaylistForm, self).__init__(*args, **kwargs)
class Meta:
model = User
fields = ('playlists',)
The views should be something like below so I'm able to pass the request
views.py
form = ChoosePlaylistForm(request=request)
Maybe overriding the field choices in the form constructor would work:
class ChoosePlaylistForm(forms.Form):
playlists = forms.ChoiceField(choices=())
class Meta:
model = User
fields = ('playlists',)
def __init__(self, *args, request=None, **kwargs):
super(ChoosePlaylistForm, self).__init__(*args, **kwargs)
self.request = request
self.fields['playlists'].choices = self.generate_selection()
def generate_selection(self):
sp_auth, cache_handler = spotify_oauth2(self.request)
spotify = spotipy.Spotify(oauth_manager=sp_auth)
user_playlists = spotify.current_user_playlists(limit=10)
choices = []
for playlist in user_playlists["items"]:
playlist_choice = (playlist["name"], playlist["id"])
choices.append(playlist_choice)
return choices

How to fill a Django custom multiplechoicefield with choices

I have a form with a search box that uses jQuery to fill a multipleChoiceField. I need to use a custom multipleChoiceField so I can control the validation and only check if the choice exists, not if it was one of the original choices as a modelMultipleChoiceField with a queryset would. However, the custom multipleChoiceField renders on the page as empty until you enter something in the search box to fill it with choices via jQuery. I would like it to render with a few choices to begin with instead.
class ArticleMultipleChoiceField(forms.MultipleChoiceField):
def __init__(self, *args, **kwargs):
super(ArticleMultipleChoiceField, self).__init__(*args, **kwargs)
include_articles = [article.id for article in Article.objects.order_by('-sub_date')[:5]]
self.choices = Article.objects.filter(id__in=include_articles).order_by('-sub_date')
In this form, I get the error "Article object is not iterable". I have also tried changing that self.choices to self.data, self.queryset, and self.initial, and in all those 3 cases, I keep getting an empty multiple choice field instead.
How can I use a queryset to provide the initial set of choices here?
Here is the form it is used in:
class StorylineAddArticleForm(forms.Form):
articleSearchBox = forms.CharField(label="Search to narrow list below:")
include_articles = [article.id for article in Article.objects.order_by('-sub_date')[:5]]
articles = ArticleMultipleChoiceField()
def __init__(self, *args, **kwargs):
super(StorylineAddArticleForm, self).__init__(*args, **kwargs)
self.fields['articleSearchBox'].required = False
self.helper = FormHelper(self)
self.helper.layout = Layout(
Field('articleSearchBox'),
Field('articles'),
ButtonHolder(
Submit('submit', 'Add', css_class='button white')
)
)
Also, this is being rendered by Crispy Forms.
choices doesn't accept a QuerySet as an argument, it needs a list or tuple of two-tuples with acceptable values. See the documentation on choices here: https://docs.djangoproject.com/en/2.0/ref/models/fields/#field-choices .
In this case you need to turn your Article queryset into a list or tuple of the above format.

Set instance of each form in Django model formset

When working with Django model forms, I often do something like this:
def my_view(request):
new_entry = MyModel(name='a')
form = MyModelForm(instance=new_entry)
...
I want to do something similar with a modelformset. Something like this would be ideal:
def my_view(request):
MyFormSet = modelformset_factory(MyModel, form=MyModelForm)
new_entries = [MyModel(name='a'), MyModel(name='b')]
formset = MyFormSet(instances=new_entries) # off course this does not work
...
Since the items are not stored in the database yet, I can't set the instances using a queryset. If I want to use initial I have to declare the fields in the form, which is not ideal and seems a bit hacky.
Any suggestions how I can set the instances of each modelform in a modelformset?
Ok, I think I've found a solution.
class FormSetWithInstances(BaseFormSet):
def __init__(self, *args, **kwargs):
self.instances = kwargs.pop('instances')
super(FormSetWithInstances, self).__init__(*args, **kwargs)
def get_form_kwargs(self, index):
form_kwargs = super(FormSetWithInstances, self).get_form_kwargs(index)
if index < len(self.instances):
form_kwargs['instance'] = self.instances[index]
return form_kwargs
Be careful when using this modelformsets or inlinemodelformsets, as the queryset will override the instance you set.
An alternative approach:
class FormSetWithInstances(BaseFormSet):
def get_form_kwargs(self, index):
kwargs = super(FormSetWithInstances, self).get_form_kwargs(index)
instances = kwargs.pop('instances')
try:
kwargs.update({'instance': instances[index]})
except IndexError:
pass
return kwargs
Then, when creating instances of FormSetWithInstances, you pass in a list of instances as a form kwarg:
form_set = FormSetWithInstances(form_kwargs={'instances': [...]})
I personally prefer this method because it takes advantage of existing class infrastructure instead of defining custom class members in an overridden __init__(). Also, it's in the docs.
I'm not aware of an easy way to pass a list of instances as you are trying to do. Here's a couple of options that might work depending on your use case.
You can provide initial data for the model formset. This should be a list of dictionaries, not model instances:
initial = [{'name': 'a'}, {'name': 'b'}]
formset = MyFormSet(
queryset=MyModel.objects.none(),
initial=initial,
)
Note that I have set the queryset to an empty queryset. If you didn't do this, then the formset would display existing instances, and the initial data would be used for new instances.
If you have initial values for fields that you do not wish to include in the form, then you could be able to set those values when you [save the formset].
instances = formset.save(commit=False)
names = ['a', 'b']
for instance, name in zip(instances, names):
instance.name = name
instance.save()

Add django-filter to class-based-view to allow user filter results on frontend

Unfortunatelly Django doesn't have super-magic Drupal's analog for Views module https://www.drupal.org/project/views (by the way other cms also doesn't have it) so we all need write views in code and add content filters like everyone see in Django Admin by hand.
I need to add filters with dropdowns for Charfield and datepopup widget for DateTime field in my class-based-view, i found django-filter for this http://django-filter.readthedocs.org/en/latest/usage.html
But in docs no example how to setup it with CBW, only with function views.
views.py:
class VkwallpostListView(ListView):
model = Vkwallpost
context_object_name = "vk_list"
def get_template_names(self):
return ["vk_list.html"]
def get_context_data(self, **kwargs):
articles = Vkwallpost.objects.order_by("-date_created")[:5]
videos = Fbpagepost.objects.order_by("-date_created")[:5]
items = list(articles) + list(videos)
items.sort(key=lambda i: i.date_created, reverse=True)
return {"vk_fb_list": items[:5]}
def get_queryset(self):
wallposts = Vkwallpost.objects
if 'all_posts' not in self.request.GET:
pass
elif 'all' in self.request.GET:
pass
else:
success = False
criteria = {}
if 'sentiment' in self.request.GET:
criteria['sentiment'] = self.request.GET['sentiment']
print(criteria)
wallposts = wallposts.filter(**criteria)
return wallposts
And i want to easily add this filters:
import django_filters
class VkwallpostFilter(django_filters.FilterSet):
class Meta:
model = Vkwallpost
fields = ['sentiment', 'date_created']
How to achieve this?
Try to use Django Form with ModelChoiceField or ModelMultipleChoiceField.
Its all that you need.

How to extend CheckboxInput for a list of checkboxes

I am currently using a MultiCheckboxField like this:
class MultiCheckboxField(SelectMultipleField):
"""
A multiple-select, except displays a list of checkboxes.
Iterating the field will produce subfields, allowing custom rendering of
the enclosed checkbox fields.
"""
widget = widgets.ListWidget(prefix_label=False)
option_widget = widgets.CheckboxInput()
to generate a list of checkboxes. I would like to extend this list in such a way as to allow some of the list entries to have an associated TextInput field. When the box is checked, the corresponding text input is required.
I am new to Flask and WTForms, and I am having some trouble trying to figure out just how to attack the problem. I would be grateful for any suggestions that might provide some kind of direction.
See the FieldList and FormField with custom widgets
http://wtforms.readthedocs.org/en/latest/fields.html#field-enclosures
You can use a custom validator like this:
class RequiredIfChoice(validators.DataRequired):
# a validator which makes a field required if
# another field is set and has a truthy value
def __init__(self, other_field_name, desired_choice, *args, **kwargs):
self.other_field_name = other_field_name
self.desired_choice = desired_choice
super(RequiredIfChoice, self).__init__(*args, **kwargs)
def __call__(self, form, field):
other_field = form._fields.get(self.other_field_name)
if other_field is None:
raise Exception('no field named "%s" in form' % self.other_field_name)
for value, label, checked in other_field.iter_choices():
if label == self.desired_choice and checked:
super(RequiredIfChoice, self).__call__(form, field)
and in your form:
class MyForm(Form):
"""
Your form.
"""
multi = MultiCheckboxField('Multibox', choices=[(1, 'First'), (2, 'Second')], coerce=int)
multitext = StringField('SubText', [RequiredIfChoice('multi', 'Second')])
For a slightly similar question look at this Q&A.

Categories

Resources