I'm building a webshop using Cartridge/Mezzanine. By default however the checkout form (cartridge.shop.forms.OrderForm) contains fields for both first names and last names. I want a form where the customer just has to provide a name. Further, I don't want to use the fields for Country and State/Region.
So far I've created a CustomOrderForm that subclasses the OrderForm and added a 'name'-field. I also attempted to override the init method using this:
class CustomOrderForm(OrderForm):
name = forms.CharField(label=("Navn"))
def __init__(self, *args, **kwargs):
super(OrderForm, self).__init__(*args, **kwargs)
self.fields.pop('billing_detail_country')
This doesn't really do me any good except the 'name'-field is displayed in the template. But I still need to take the input from this field, split it into first name and last name and populate the relevant fields with it.
How do I do this?
This is how I removed the fields Country and State/Regions from my Order Form:-
class CustomOrderForm(OrderForm):
class Meta:
model = Order
fields = ([f.name for f in Order._meta.fields if
f.name.startswith("billing_detail") or
f.name.startswith("shipping_detail")
]
+
["additional_instructions", "discount_code"])
def __init__(
self, request, step, data=None, initial=None, errors=None,
**kwargs):
res = super(CustomOrderForm, self).__init__(request, step, data, initial, errors, **kwargs)
del self.fields["billing_detail_country"]
del self.fields["shipping_detail_country"]
return res
Since it's a dictionary I used the del method. Hope so this is useful as you are being answered after such a long time.
Related
I'm trying to create a form wizard that would contain variable number of questionnaires, depending on how many of them are present in the database and marked as active. For every one of them I am using this form, and the same template. This is my form:
class QuestionnaireForm(forms.Form):
def __init__(self, slug='', *args, **kwargs):
super(QuestionnaireForm, self).__init__(*args, **kwargs)
degrees = Questionnaire.objects.get(slug=slug).degrees
VALUES = ()
for i in range(1,degrees+1):
VALUES += ((i,i),)
items = Item.objects.filter(Q(scales__questionnaire__slug=slug)|Q(scales__slug=slug)).order_by('ord_number')
for item in items:
self.fields[unicode(item.id)] = forms.ChoiceField(
choices=VALUES,
required=True,
widget=RadioSelect,
error_messages={'required': 'Bro, you have to answer that.'},
label = item.name)
Now, since I need to provide a slug to this form in order to get different questionnaire every time, I tried something like this:
class Testing(SessionWizardView):
form_list = [QuestionnaireForm(slug=questionnaire.slug) for questionnaire in Questionnaire.objects.filter(active=True)]
template_name = 'index.html'
success_url = '/'
It gives me the following error: issubclass() arg 1 must be a class. Obviously, I'm not passing slug in the proper place, but I'm not sure where I should be passing it. My guess is that I should be overriding some SessionWizardView method, but I'm not having any luck (skill?) so far.
After doing some more research, I finally managed to do what I wanted. Solution proposed here did the trick, even though I'm not sure why is this working.
So, here's my modified code. Class generator from the cited answer:
def class_generator(cls, **additionalkwargs):
class ClassWithKwargs(cls):
def __init__(self, *args, **kwargs):
kwargs.update(additionalkwargs)
super(ClassWithKwargs, self).__init__(*args, **kwargs)
return ClassWithKwargs
Which I then applied like this:
class Testing(SessionWizardView):
form_list = [class_generator(QuestionnaireForm(slug=questionnaire.slug)) for questionnaire in Questionnaire.objects.filter(active=True)]
template_name = 'index.html'
success_url = '/'
Since I'm not a pro (more of like an advancing noob), if there's someone who could clarify what's happening here, that would be nice for the sake of future readers.
It seems like if a ModelForm is given an instance, it ignores any values you provide for initial and instead sets it to the value of the instance -- even if that instance is an empty model record.
Is there any way to create a form with an instance and have it set initial data?
I need it because I'm saving related records and they don't appear to save correctly unless the ModelForm is given an instance when created.
I'm sure the answer to this is straightforward and I'm just missing something obvious.
Here is the relevant code:
in the view:
form = form_class(person=person, conference=conference, initial=initial, instance=registration)
where form_class is RegistrationForm and then in the registration form:
class RegisterForm(forms.ModelForm):
... fields here ...
def __init__(self, *args, **kwargs):
... other code ...
self.person = kwargs.pop('person')
super(RegisterForm, self).__init__(*args, **kwargs)
for key, in self.fields.keys():
if hasattr(self.person, key):
self.fields[k].initial = getattr(self.person, key)
Then when I call the field, the related fields are empty.
Figured this out after a little bit of googling.
You have to set the initial value before calling super.
So instead of looping through self.fields.keys(), I had to type out the list of fields that I wanted and looped through that instead:
class RegisterForm(forms.ModelForm):
... fields here ...
initial_fields = ['first_name', 'last_name', ... ]
def __init__(self, *args, **kwargs):
... other code ...
self.person = kwargs.pop('person')
for key in self.initial_fields:
if hasattr(self.person, key):
self.fields[k].initial = getattr(self.person, key)
super(RegisterForm, self).__init__(*args, **kwargs)
#Daria rightly points out that you don't have self.fields before calling super. I'm pretty sure this will work:
class RegisterForm(forms.ModelForm):
... fields here ...
initial_fields = ['first_name', 'last_name', ... ]
def __init__(self, *args, **kwargs):
... other code ...
initial = kwargs.pop('initial', {})
self.person = kwargs.pop('person')
for key in self.initial_fields:
if hasattr(self.person, key):
initial[key] = initial.get(key) or getattr(self.person, key)
kwargs['initial'] = initial
super(RegisterForm, self).__init__(*args, **kwargs)
In this version, we use the initial argument to pass the values in. It's also written so that if we already have a value in initial for that field, we don't overwrite it.
Sounds to me that you may be looking for a bound form. Not entirely sure, I'm trying to unpick a similar issue:
Django forms can be instantiated with two arguments which control this kind of thing. As I understand it:
form = MyForm(initial={...}, data={...}, ...)
initial will set the possible values for the fields—like setting a queryset—data will set the actual (or selected) values of a form and create a bound form. Maybe that is what you want. Another, tangental, point you might find interesting is to consider a factory method rather than a constructor, I think the syntax is more natural:
class MyForm(forms.ModelForm):
...
#staticmethod
def makeBoundForm(user):
myObjSet = MyObject.objects.filter(some_attr__user=user)
if len(myObjSet) is not 0:
data = {'myObject': myObjSet[0]}
else:
raise ValueError()
initial = {'myObject': myObjSet}
return MyForm(initial=initial, data=data)
You can also pass extra variables to the class when initializing it. The values you pass can then override initial or POST data.
class RegisterForm(forms.ModelForm):
... fields here ...
def __init__(self, person, conference, *args, **kwargs):
... other code ...
super(RegisterForm, self).__init__(*args, **kwargs)
self.fields['person'] = person
self.fields['conference'] = conference
form = RegisterForm(person, conference, initial=initial, instance=registration)
Use ModelAdmin.get_changeform_initial_data. For example, if you add initial data for form field "report_datetime"
def get_changeform_initial_data(self, request):
initial_data = super().get_changeform_initial_data(request)
initial_data.update(report_datetime=<my_initial_datetime>)
return initial_data
Works for 3.2+. I'm not sure about older versions.
See django docs
I am writing an Edit form, where some fields already contain data. Example:
class EditForm(forms.Form):
name = forms.CharField(label='Name',
widget=forms.TextInput(),
initial=Client.objects.get(pk=??????)) #how to get the id?
What I did for another form was the following (which does not work for the case of the previous EditForm):
class AddressForm(forms.Form):
address = forms.CharField(...)
def set_id(self, c_id):
self.c_id = c_id
def clean_address(self):
# i am able to use self.c_id here
views.py
form = AddressForm()
form.set_id(request.user.get_profile().id) # which works in the case of AddressForm
So what is the best way to pass an id or a value to the form, and that could be used in all forms for that session/user?
Second: is it right to use initial to fill in the form field the way I am trying to do it?
You need to override the __init__ method for your form, like so:
def __init__(self, *args, **kwargs):
try:
profile = kwargs.pop('profile')
except KeyError:
super(SelectForm, self).__init__(*args, **kwargs)
return
super(SelectForm, self).__init__(*args, **kwargs)
self.fields['people'].queryset = profile.people().order_by('name')
and, obviously, build your form passing the right parameter when needed :)
I need to pass an instance variable (self.rank) to be used by a class variable (provider) (see the commented out line below).
Commented out, the code below works. But I'm pretty sure I shouldn't be trying to pass an instance variable up to a class variable anyway. So I'm dumbfounded as to how to accomplish my goal, which is to dynamically filter down my data in the ModelChoiceField.
As you can see, I already overrided ModelChoiceField so I could beautify the usernames. And I also subclassed my basic SwapForm because I have several other forms I'm using (not shown here).
Another way of saying what I need ... I want the value of request.user in my Form so I can then determine the rank of that user and then filter out my Users by rank to build a smaller ModelChoiceField (that looks good too). Note that in my views.py, I call the form using:
form = NewSwapForm(request.user)
or
form = NewSwapForm(request.user, request.POST)
In forms.py:
from myapp.swaps.models import Swaps
from django.contrib.auth.models import User
class UserModelChoiceField(forms.ModelChoiceField):
""" Override the ModelChoiceField to display friendlier name """
def label_from_instance(self, obj):
return "%s" % (obj.get_full_name())
class SwapForm(forms.ModelForm):
""" Basic form from Swaps model. See inherited models below. """
class Meta:
model = Swaps
class NewSwapForm(SwapForm):
# Using a custom argument 'user'
def __init__(self, user, *args, **kwargs):
super(NewSwapForm, self).__init__(*args, **kwargs)
self.rank = User.objects.get(id=user.id).firefighter_rank_set.get().rank
provider = UserModelChoiceField(User.objects.all().
order_by('last_name').
filter(firefighter__hirestatus='active')
### .filter(firefighter_rank__rank=self.rank) ###
)
class Meta(SwapForm.Meta):
model = Swaps
fields = ['provider', 'date_swapped', 'swap_shift']
Thanks!
You can't do it that way, because self doesn't exist at that point - and even if you could, that would be executed at define time, so the rank would be static for all instantiations of the form.
Instead, do it in __init__:
provider = UserModelChoiceField(User.objects.none())
def __init__(self, user, *args, **kwargs):
super(NewSwapForm, self).__init__(*args, **kwargs)
rank = User.objects.get(id=user.id).firefighter_rank_set.get().rank # ??
self.fields['provider'].queryset = User.objects.order_by('last_name').filter(
firefighter__hirestatus='active', firefighter_rank__rank=rank)
I've put a question mark next to the rank line, because rank_set.get() isn't valid... not sure what you meant there.
I have a Django admin form.
And now I want to fill it's initial field with data based on my model. So I tried this:
class OrderForm(forms.ModelForm):
class Meta:
model = Order
email = CharField(initial="null", widget=Textarea(attrs={'rows': 30, 'cols': 100}))
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
products = kwargs['instance'].products.all()
self.message = purchase_message % (
"".join(["<li>" + p.name + ": " + str(p.price) + "</li>" for p in products]),
reduce(lambda x, y:x + y.price, products, 0)
)
# and here I have a message in self.message variable
super(OrderForm, self).__init__(*args, **kwargs)
At this point i don't know how to access email field to set it's initial value before widget is rendered. How can i do this?
Assuming the value is based on 'request' you should use this:
class MyModelAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
form = super(MyModelAdmin, self).get_form(request, obj, **kwargs)
form.base_fields['my_field_name'].initial = 'abcd'
return form
Since Django 1.7 there is a function get_changeform_initial_data in ModelAdmin that sets initial form values:
def get_changeform_initial_data(self, request):
return {'name': 'custom_initial_value'}
EDIT: Apart from that, #Paul Kenjora's answer applies anyway, which might be useful if you already override get_form.
In case of inline (InlineModelAdmin) there is no get_changeform_initial_data. You can override get_formset and set formset.form.base_fields['my_field_name'].initial.
I'm not too sure what you need to set email to, but You can set the initial values in lots of different places.
Your function def init() isn't indented correctly which i guess is a typo? Also, why are you specifically giving the email form field a TextInput? It already renders this widget by default
You can set the email's initial value in your form's initialized (def __ init __(self))
(self.fields['email'].widget).initial_value = "something"
or in the model.py
email = models.CharField(default="something")
or as you have in forms.py
email = models.CharField(initial="something")
I needed the first solution of pastylegs since the other ones overwrite the whole Widget including, for example, the help text. However, it didn't work for me as he posted it. Instead, I had to do this:
self.fields['email'].initial = 'something'
In my case, I was trying to do a personalized auto-increment(based on current data and not a simple default) in a field of a django admin form.
This code is worked for me (Django 1.11):
from django import forms
class MyAdminForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.initial['field_name'] = 'initial_value'