Django, set ChoiceField in form, after creation - django

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

Related

How to load a form with options from a queryset in Django

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)

Modify model data before inserting into a form in Django

Is there a way to modify the data obtained from the model before inserting it in the form?
Here's my model:
class SomeData(models.Model):
Some_No = models.ForeignKey('SomeDef', on_delete=models.PROTECT)
Some_Val = models.DecimalField(max_digits=10, decimal_places=3, verbose_name=_('Some_Val'))
And here's my form:
#autostrip
class SomeForm(forms.ModelForm):
class Meta:
model = models.SomeData
fields = ('Some_Val', 'Some_No')
def __init__(self, *args, **kwargs):
super(SomeForm, self).__init__(*args, **kwargs)
self.fields['Some_No'].label = _(' ')
def clean_some(self):
some = None
some_id = self.cleaned_data['some']
some = models.SomeDef.objects.get(Some_No=some_id)
return some
def save(self, something, *args, **kwargs):
orig_commit = kwargs.get('commit', True)
kwargs['commit'] = False
ri = super(SomeForm, self).save(*args, **kwargs)
ri.Some_No = something
if orig_commit:
try:
ri.save()
except ValidationError as e:
raise ValidationError
return ri
The data saved inside of the models is a bit different from what I want to show in the forms when these are populated with data. However, I cannot figure out how to do it in a smart way.
Using the pre_save signal. Signals
You can set the initial value in the __init__ method:
def __init__(self, *args, **kwargs):
super(SomeForm, self).__init__(*args, **kwargs)
self.fields['Some_No'].initial = f(self.instance)
You can pass an initial data dictionary argument to your form, when you instantiate it into your view.

Binding data to django's form choicefield

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)

Django ModelForm with field overwrite does not post data

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)

Django custom selectfield with initial

I have a selectfield which I override a charfield in my model. I can't use a foreignkey at all on the field.
class AliasForm(forms.ModelForm):
account = forms.ModelChoiceField(queryset=Account.objects.all(), widget=forms.HiddenInput())
domain = forms.ModelChoiceField(queryset=Domain.objects.all(), widget=forms.HiddenInput())
end = forms.ModelChoiceField(queryset=Mailbox.objects.all())
def __init__(self, *args, **kwargs):
self.account = kwargs.pop('account', None)
super(AliasForm, self).__init__(*args, **kwargs)
if self.account:
self.fields['end'].queryset = Mailbox.objects.filter(account=self.account)
How can I make end get passed in a value where it is autoselected?
Ok figured it out.
I needed to pass in initial but with instance.id and not instance
form = AliasForm(initial={'end': mailbox.id})
I was doing just 'end': mailbox