I have a simple form,
class Compose(forms.Form):
CHOICES = ()
recepient = forms.ChoiceField(choices=CHOICES)
subject = forms.CharField(max_length=100)
message = forms.CharField(widget=forms.Textarea)
Chocies are generated as
def mychoiceview(request):
subscribed_lists, other_lists = getListOfLists(request.user.email)
for lst in subscribed_lists:
# list name and list address
CHOICES = CHOICES + ((lst[1],lst[0]),)
#Bind data to form and render
Basically, the user is subscribed to certain lists (from a superset of lists) and can choose (via dropdown) which list he/she wants to send the message to.
The problem is that I cannot find how to bind the "CHOICES" to the django form.
Some solutions online include using models.. but I don't have a queryset... just a dynamically generated tuple of ids I want the choicefield to render.
Any ideas ?
Thanks !
#nnmware's solution is correct, but a little too complex/confusing. Below is a more succinct version that works just as well:
class DynamicBindingForm(forms.Form):
def __init__(self, *args, **kwargs):
super(DynamicBindingForm, self).__init__(*args, **kwargs)
self.fields['recipient'] = forms.ChoiceField(choices=db_lookup_choices())
where db_lookup_choices is a call to some database or other set of dynamic data and returns a list of pairs for choices: [('val', 'Label'),('val2', 'Label2')]
If you using Class-based view, then:
in view make mixin for sending request in form
class RequestToFormMixin(object):
def get_form_kwargs(self):
kwargs = super(RequestToFormMixin, self).get_form_kwargs()
kwargs.update({'request': self.request})
return kwargs
class YouView(RequestToFormMixin, CreateView):
model = YouModel
# etc
in form make mixin for receive request from view
class RequestMixinForm(forms.Form):
def __init__(self, *args, **kwargs):
request = kwargs.pop('request')
super(RequestMixinForm, self).__init__(*args, **kwargs)
self._request = request
class Compose(RequestMixinForm):
subject = forms.CharField(max_length=100)
message = forms.CharField(widget=forms.Textarea)
def __init__(self, *args, **kwargs):
super(Compose, self).__init__(*args, **kwargs)
subscribed_lists, other_lists = getListOfLists(self._request.user.email)
for lst in subscribed_lists:
# list name and list address
CHOICES = CHOICES + ((lst[1],lst[0]),)
self.fields['recipient'] = forms.ChoiceField(choices=CHOICES)
Related
I am trying to load a form with user payment options, so this is needing a query set from the users profile.
I have tried initializing the form (below code) with user being required. The issue is if I make self.options when I am initializing. I have also tried creating the choice_field
class ListPaymentOptionsForm(forms.Form):
choice_field = forms.ChoiceField(widget=forms.RadioSelect, choices=options)
def __init__(self, user, *args, **kwargs):
self.options = list(UserPaymentOption.objects
.values_list('last_four', 'last_four')
.filter(user=user, active=True))
super(ListPaymentOptionsForm, self).__init__(self, *args, **kwargs)
The above code gives this error:
choice_field = forms.ChoiceField(widget=forms.RadioSelect, choices=options)
NameError: name 'options' is not defined
Then I have tried adding the options on the view instead like this
form = ListPaymentOptionsForm(user=request.user)
form.fields['choice_field'].choices = list(UserPaymentOption.objects
.values_list('id', 'last_four')
.filter(user=request.user, active=True))
This causes an error with the form being used on post, it seems like because it is trying to validate the value provided is a choice but in the actual form the choice is not set. The reason I believe this is the problem is this is what the form returns as
form=ListPaymentOptionsForm(request.POST)
print(form)
This returns: Choice field:Select a valid choice. 54 is not one of the available choices.
Any input on this would be very appreciated. Thanks.
Nearly there!
Try doing the fields['choice_field'].choices in the constructor.
class ListPaymentOptionsForm(forms.Form):
def __init__(self, user, *args, **kwargs):
super().__init__(*args, **kwargs) # assuming python 3 constructor
self.options = list(UserPaymentOption.objects.values_list('last_four', 'last_four').filter(user=user, active=True))
self.fields['choice_field'] = forms.ChoiceField(widget=forms.RadioSelect, choices=self.options)
Maybe consider having a look at ModelChoiceField instead however, that way you can specify a queryset instead of having to worry about creating a list:
class ListPaymentOptionsForm(forms.Form):
choice_field = forms.ModelChoiceField(widget=forms.RadioSelect, queryset=UserPaymentOption.objects.none())
def __init__(self, user, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['choice_field'].queryset = UserPaymentOption.objects.filter(user=user, active=True)
EDIT based on comments we can use the kwargs to pass the user which may be better:
class ListPaymentOptionsForm(forms.Form):
choice_field = forms.ModelChoiceField(widget=forms.RadioSelect, queryset=UserPaymentOption.objects.none())
def __init__(self, *args, **kwargs):
user = kwargs.pop('user') # this must be done before super()
super().__init__(*args, **kwargs)
self.fields['choice_field'].queryset = UserPaymentOption.objects.filter(user=user, active=True)
Then instantiate the form to handle POST data:
form = ListPaymentOptionsForm(request.POST, user=user)
I'm trying to override concept queryset in my child form, to get a custom list concepts based on the area got from request.POST, here is my list of concepts, which i need to filter based on the POST request, this lists is a fk of my child form (InvoiceDetail). is it possible to have these filters?
after doing some test when I pass the initial data as the documentation says initial=['concept'=queryset_as_dict], it always returns all the concepts, but i print the same in the view and its ok the filter, but is not ok when i render in template, so I was reading that I need to use some BaseInlineFormset. so when I test I obtained different errors:
django.core.exceptions.ValidationError: ['ManagementForm data is missing or has been tampered with']
'InvoiceDetailFormFormSet' object has no attribute 'fields'
so here is my code:
models.py
class ConceptDetail(CreateUpdateMixin): # here, is custom list if area='default' only returns 10 rows.
name = models.CharField(max_length=150)
area = models.ForeignKey('procedure.Area')
class Invoice(ClusterableModel, CreateUpdateMixin): # parentForm
invoice = models.SlugField(max_length=15)
class InvoiceDetail(CreateUpdateMixin): # childForm
tax = models.FloatField()
concept = models.ForeignKey(ConceptDetail, null=True, blank=True) # fk to override using custom queryset
invoice = models.ForeignKey('Invoice', null=True, blank=True)
views.py
class CreateInvoiceProcedureView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
template_name = 'invoice/invoice_form.html'
model = Invoice
permission_required = 'invoice.can_check_invoice'
def post(self, request, *args, **kwargs):
self.object = None
form = InvoiceForm(request=request)
# initial initial=[{'tax': 16, }] removed
invoice_detail_form = InvoiceDetailFormSet(request.POST, instance=Invoice,
request=request)
return self.render_to_response(
self.get_context_data(
form=form,
invoice_detail_form=invoice_detail_form
)
)
forms.py
class BaseFormSetInvoice(BaseInlineFormSet):
def __init__(self, *args, **kwargs):
# call first to retrieve kwargs values, when the class is instantiated
self.request = kwargs.pop("request")
super(BaseFormSetInvoice, self).__init__(*args, **kwargs)
self.queryset.concept = ConceptDetail.objects.filter(
Q(area__name=self.request.POST.get('area')) | Q(area__name='default')
)
class InvoiceForm(forms.ModelForm):
class Meta:
model = Invoice
fields = ('invoice',)
class InvoiceDetailForm(forms.ModelForm):
class Meta:
model = InvoiceDetail
fields = ('concept',)
InvoiceDetailFormSet = inlineformset_factory(Invoice, InvoiceDetail,
formset=BaseFormSetInvoice,
form=InvoiceDetailForm,
extra=1)
How can i fix it?, what do i need to read to solve this problem, I tried to debug the process, i didn't find answers.
i try to do this:
def FooForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(FooForm, self).__init__(*args, **kwargs)
self.fields['concept'].queryset = ConceptDetail.objects.filter(area__name='default')
In a inlineformset_factory how can do it?.
After a lot of tests, my solution is override the formset before to rendering, using get_context_data.
def get_context_data(self, **kwargs):
context = super(CreateInvoiceProcedureView, self).get_context_data(**kwargs)
for form in context['invoice_detail_form']:
form.fields['concept'].queryset = ConceptDetail.objects.filter(area__name=self.request.POST.get('area'))
return context
I want to display a form with some customized user data in it. More specifically I want to fill a forms.ChoiceField with different data for each user.
This is my Form:
class WallPostForm(forms.Form):
text = forms.CharField(label=u'', widget=TinyMCE(attrs={'cols': 70, 'rows': 5}))
relates_to = forms.ChoiceField(label=u'Relates to', choices=[], widget=forms.Select(), required=False)
def __init__(self, data):
self.fields['relates_to'] = forms.ChoiceField(label=u'Relates to', choices=data, widget=forms.Select(), required=False)
super(WallPostForm, self).__init__()
And this is how I am calling this:
user = get_object_or_404(User, username=username)
data = UserTopics.objects.filter(user=user, result=0).values('id', 'topic__name')[:10]
form = WallPostForm(data)
I get a 'WallPostForm' object has no attribute 'fields' error.
What am I doing wrong?
As an addition to Jack's answer, you're probably better off just replacing the choices attribute, rather than the whole field:
def __init__(self, *args, **kwargs):
relates_to_choices = kwargs.pop('relates_to_choices')
super(WallPostForm, self).__init__(*args, **kwargs)
self.fields['relates_to'].choices = relates_to_choices
(I renamed the variable, it won't be a queryset.)
Django sets up the form's fields property in the __init__.
So just swap your code:
def __init__(self, data):
super(WallPostForm, self).__init__()
self.fields['relates_to'] = forms.ChoiceField(label=u'Relates to', choices=data, widget=forms.Select(), required=False)
Though, you probably shouldn't override a Form's __init__ like that. Django's form system expects the data arg in init to contain the data for the form, not a queryset you're using for a choices field.
I'd override it differently:
def __init__(self, *args, **kwargs):
relates_to_queryset = kwargs.pop('relates_to_queryset')
super(WallPostForm, self).__init__(*args, **kwargs)
self.fields['relates_to'] = forms.ChoiceField(label=u'Relates to', choices=relates_to_queryset, widget=forms.Select(), required=False)
Then call it:
form = WallPostForm(request.POST or None, relates_to_queryset=data)
You can use "initial" parameter, see the docs: https://docs.djangoproject.com/en/dev/ref/forms/api/#django.forms.Form.initial
Suppose I have a set of records which I know to be unique based on some other record and an e-mail, thusly:
class Signup(models.Model):
activity = models.ForeignKey(Activity, related_name='activities')
name = models.CharField(max_length=100)
email = models.EmailField()
# many addtional fields here not relevant to question
Then, I have a model form:
class SignupForm(forms.ModelForm):
class Meta:
model = Signup
exclude = [ 'activity' ]
def __init__(self, *args, **kwargs):
self.activity = kwargs.pop('activity')
def save(self, *args, **kwargs):
kwargs['commit'] = False
m = super(SignupForm, self).save(*args, **kwargs)
m.activity = self.activity
m.save()
return m
Suppose a user goes in a fills out the form under the activity, then realizes they made an error in the form, clicks the back button, makes changes, then clicks submit again.
Without any modifications to the code above, a duplicate record for that activity and email would be created.
What I want to know is how I can force the form to update, rather than create, a record if it finds a match for the entered e-mail.
I tried this code:
class SignupForm(forms.ModelForm):
class Meta:
model = Signup
exclude = [ 'activity' ]
def __init__(self, *args, **kwargs):
self.activity = kwargs.pop('activity')
def save(self, *args, **kwargs):
kwargs['commit'] = False
try:
self.instance = Signup.objects.get(email=self.cleaned_data['email'], activity=self.activity)
except Signup.DoesNotExist:
pass
m = super(SignupForm, self).save(*args, **kwargs)
m.activity = self.activity
m.save()
return m
However, looks like this causes the form to ignore all new information for some reason (I have debug toolbar running and examining the query confirms that none of the fields are being changed!)
Is there an accepted way of handling this?
Further request
Is there any way to do this while still using the ModelForm's built-in save function? So far the answers seem to suggest that this is impossible, which is, I'm sorry, ridiculous.
Replace
try:
self.instance = Signup.objects.get(email=self.cleaned_data['email'], activity=self.activity)
except Signup.DoesNotExist:
pass
With:
obj, created = Signup.objects.get_or_create(\
email=self.cleaned_data['email'],
activity=self.activity)
if created:
print 'its a new one, hooray!'
else:
print 'the object exists!'
More information on get_or_create.
So, I have the following form:
class DesignItemForm (forms.ModelForm):
def __init__(self, *args, **kwargs):
super(DesignItemForm, self).__init__(*args, **kwargs)
CHOICES=[(i,i) for i in range(MAX_DESIGN_ITEM_QUANTITY)]
self.fields['quantity'] = forms.ChoiceField(choices=CHOICES)
class Meta:
model = DesignItem
fields = ('quantity','trackable',)
My view:
d = Design.object.get(slug=fromInput)
....
DesignItemInlineFormSet = inlineformset_factory(Design, DesignItem, fk_name="design", form=DesignItemForm,)
if request.method == "POST":
formset = DesignItemInlineFormSet(request.POST, request.FILES, instance=d)
if formset.is_valid():
formset.save()
DesignItemInlineFormSet(instance=d)
As you can tell, in my form, I overwrote the quantity field to be a drop down instead of an integer field.
For some reason, when I submit the form, the data is not updated in the database. However, if I change the form to the following, it works (of course, it doesn't have the dropdowns I want, but it posts to the db). Why is this, and how do I fix it?
class DesignItemForm (forms.ModelForm):
def __init__(self, *args, **kwargs):
super(DesignItemForm, self).__init__(*args, **kwargs)
# CHOICES=[(i,i) for i in range(MAX_DESIGN_ITEM_QUANTITY)]
# self.fields['quantity'] = forms.ChoiceField(choices=CHOICES)
class Meta:
model = DesignItem
fields = ('quantity','trackable',)
EDIT: Here is the DesignItem model:
class DesignItem(models.Model):
"""Specifies how many of an item are in a design."""
design = models.ForeignKey(Design, related_name="items")
quantity = models.PositiveIntegerField(default=1)
trackable = models.ForeignKey(Trackable, related_name="used")
have you tried just overriding the widget instead of the whole field?
i guess you want a select widget
def __init__(self, *args, **kwargs):
super(DesignItemForm, self).__init__(*args, **kwargs)
CHOICES=[(i,i) for i in range(MAX_DESIGN_ITEM_QUANTITY)]
self.fields['quantity'].widget = forms.Select(choices=CHOICES)