I am writing a class-based view that lets the employees setup their profile. Because the employee model has a few foreign key field (e.g. employer is a forieng key referencing company model), I decided not to use ModelForm and resort to good old forms so the user can enter the name of the company they work in rather than 32.
Here is my code:
class Employee_ProfileSetting(forms.Form):
first_name = forms.CharField(label = 'First Name', max_length = 30)
last_name = forms.CharField(label = 'Last Name', max_length = 30)
email = forms.EmailField()
employer = forms.CharField(max_length = 50)
cell = forms.CharField(max_length = 20)
driver_license_num = forms.CharField(max_length=20)
birth_year = forms.IntegerField()
start_date = forms.IntegerField(help_text = 'Year you started with the company')
title = forms.CharField(max_length = 30)
def __init__(self, *args, **kwargs):
if not args: # args is empty, meaning a fresh object
super(Employee_ProfileSetting, self).__init__(*args, **kwargs)
else:
# Retrieving the form's information
self.first_name = args[0].get('first_name')
self.last_name = args[0]['last_name']
self.email = args[0]['email']
self.cell = args[0]['cell']
self.driver_license_num = args[0]['driver_license_num']
self.birth_year = args[0]['birth_year']
self.start_year = args[0]['start_date']
self.title = args[0]['title']
super(Employee_ProfileSetting, self).__init__(*args, **kwargs)
The constructor would then allow me to do this in my class-based view:
# Inside class AdminSetting(View):
def post(self, request):
form = self.form_class(request.POST)
if form.is_valid():
cd = form.cleaned_data
employee_profile = Employee_ProfileSetting(**cd) # Calling the constructor
employee_profile.save() # The save function is overridden
admin, created = Employee.objects.get_or_create(**cd) # If a matching employee exists, it gets that object. Otherwise, it creates it.
if created: # Object was not found, and so it was created
return HttpResponseRedirect('success.html')
When I run it, it gives me the error:
__init__() got an unexpected keyword argument 'first_name'
So, my problem is two-fold:
1) What is wrong with the code? and what does that error mean?
2) Is there a better way to let the user fill in all the fields, including the foreign key fields, in a form and save fields accordingly? e.g. the field corresponding to a foreign key is saved in its respective table first (company1.employee_set.create() and then saving the other fields. Can a ModelForm be used?
Constructor of model can't take any arguments that have no matching field inside model. So if you try to pass first_name into model that doesn't have first_name field, you will get that exception.
To do it properly, you can create multiple forms and use them. Form should ignore extra POST data and take only what it needs. Also you can pass commit=False into ModelForm.save method, so created object won't be saved. That way you can pass some additional data, for example Employee_ProfileSettings ID can be passed into Employee before saving it.
The way you define the init is very strange. It should be something like this:
def __init__(self, data, *args, **kwargs):
if not data: # data is empty, meaning a fresh object
super(Employee_ProfileSetting, self).__init__(*args, **kwargs)
else:
# Retrieving the form's information
self.first_name = data[0].get('first_name')
self.last_name = data[0]['last_name']
self.email = data[0]['email']
self.cell = data[0]['cell']
self.driver_license_num = data[0]['driver_license_num']
self.birth_year = data[0]['birth_year']
self.start_year = data[0]['start_date']
self.title = data[0]['title']
super(Employee_ProfileSetting, self).__init__(*args, **kwargs)
And second thing is how you call you class:
employee_profile = Employee_ProfileSetting(**cd)
Which expands all the clean_data list. This expands to:
employee_profile = Employee_ProfileSetting(first_name=cleaned_data[first_name], ...)
Instead you should call it like this:
employee_profile = Employee_ProfileSetting(form.cleaned_data)
So that the dictionary instance is not expanded.
Finally, I don't think I understood why you cannot use a model form...
Related
I want to have a select drop down form. In this example, i want to have a drop down of all division but it can include one more extra record (which is default=0).
How can I generate a list for select and append an additional item ?
models:
class Employee(models.Model):
username = models.CharField(max_length=30, blank=False)
division = models.ForeignKey(Division,null=True)
class Division(models.Model):
name_of_division = models.CharField(max_length=30)
forms.py
class EmployeeEditForm(forms.ModelForm):
username = forms.CharField(max_length=30, required=True)
division = forms.ChoiceField(widget=forms.Select, choices=XXXXWHATSHOULDIPUTHEREXXX)
class Meta:
model = Employee
fields = ("username","division")
for this variable, I want the list to be something like this but Im not sure how it should be coded:
XXXXWHATSHOULDIPUTHEREXXX = (0, Default) + Division.object.all()
For example it should generate something like this:
division
0 - default (this is not in the Division table) <-- i want to add this to the select list
1 - corporate
4 - human resource
7 - engineering
You are looking for ModelChoiceField instead of just ChoiceField:
class EmployeeEditForm(forms.ModelForm):
username = forms.CharField(max_length=30, required=True)
division = forms.ModelChoiceField(widget=forms.Select, queryset=Division.object.all(), empty_label="(Default)")
class Meta:
model = Employee
fields = ("username","division")
If you need to pass additional value to form - forms by itself doesn't have access to request object and therefore can't identify which user is currently logged. Your view should pass current user username instead:
def index(request):
# ...
form = EmployeeEditForm(request.POST or None, company_id=request.user.employee.company.id)
# ...
class EmployeeEditForm(forms.ModelForm):
username = forms.CharField(max_length=30, required=True)
division = forms.ModelChoiceField(widget=forms.Select, queryset=None, empty_label="(Default)")
class Meta:
model = Employee
fields = ("username","division")
def __init__(self, *args, **kwargs):
company_id = kwargs.pop('company_id')
super().__init__(*args, **kwargs)
self.fields['division'].queryset = Division.objects.filter(company_id=company_id)
Do not pass serialized object instances to form - use their id or pk instead and filter queryset in form by that id.
I'm not sure if this is what you're after but within the for __init__ you can specify the empty label.
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['division'].empty_label = 'default'
When I have a field which is hidden but specified in the modelform, it fails to pass validation. The below form fails to pass validation for the postcode field, even though I pass in the postcode data in the constructor. How do I attach data to it to pass validation correctly so that it can save?
eg
class SupplyAddressForm(forms.ModelForm):
full_address = forms.ChoiceField()
def __init__(self, postcode, *args, **kwargs):
super().__init__(*args, **kwargs)
raw_addresses_data = get_full_address(postcode)
addresses = raw_addresses_data['data']['addresses']
...........
self.fields['postcode'].initial = postcode
def save(self, commit=False):
address = super().save(commit=False)
cd = self.cleaned_data
full_address = cd['full_address']
full_address = json.loads(full_address)
......
return address
class Meta:
model = Address
fields = [
'supply_months',
'supply_years',
'postcode',
'residential_status',
]
widgets = {
'postcode': forms.HiddenInput
}
Read this documentaion to understand why Initial is not suitable for your puposes.
Instead, override the 'is_valid' method.
In 'is_valid', change the value of the hidden field AFTER the form is submitted. I got this solution here
I defined a model that has some foreign keys to other models. Such that I have the following in models.py:
class Appelation(models.Model):
appelation = models.CharField(max_length=100,
verbose_name=_('Appelation'),
validators=[non_numeric],
blank=True,
unique=True
)
class Wine(models.Model):
appelation = models.ForeignKey(ForeignKeyModel, null=True, blank=True, verbose_name=_('Appelation'))
forms.py
class WineForm(ModelForm):
class Meta:
model = Wine
appelation= CharField(widget=TextInput)
views.py
class WineCreateView(WineActionMixin, LoginRequiredMixin, CreateView):
model = Wine
form_class = WineForm
action = 'created'
def post(self, *args, **kwargs):
self.request.POST = self.request.POST.copy() # makes the request mutable
appelationForm = modelform_factory(Appelation, fields=('appelation',))
form_dict = {
'appelation': appelationForm
}
for k, modelForm in form_dict.iteritems():
model_class = modelForm.Meta.model
log.debug('current model_class is: %s' % model_class)
log.debug('request is %s' % self.request.POST[k])
try:
obj = model_class.objects.get( **{k: self.request.POST[k]} )
log.debug("object exists. %s pk from post request %s " % (model_class,obj.pk))
self.request.POST[k] = obj.id
except ObjectDoesNotExist as e:
log.error('Exception %s' % e)
f = modelForm(self.request.POST)
log.debug('errors %s' % f.errors)
if f.is_valid():
model_instance = f.save()
self.request.POST[k] = model_instance.pk
return super(WineCreateView,self).post(self.request, *args, **kwargs)
Basically, what the view code does is, it tries to create a new Appelation model instance ( which is a fk to Wine) if the one we passed does not exist. and it returns the pk in the field, since we expect a pk, not a string as input.
I want to create appelationForm, because I have some custom validators I need to apply to validate the foreignKey input.
The limitations I see now, Is that I don't see how I can attach the validation errors from appelationForm to the ones of the main form so that they are displayed instead of the ones we would typically have from a foreignKey field.
To see the full example code:
https://github.com/quantumlicht/django-wine/blob/master/project/corewine/models.py
https://github.com/quantumlicht/django-wine/blob/master/project/corewine/forms.py
https://github.com/quantumlicht/django-wine/blob/master/project/corewine/views.py
What you should do is write a clean_appelation method on your WineForm, which comprehensively validates the input according to your criteria, i.e. either an existing Appelation id, or a new Appelation name, and raises the appropriate errors. Then in your view, you can assume the form data is valid and will work. This should give you something to start off with:
class WineForm(ModelForm):
...
appelation= CharField(widget=TextInput)
def clean_appelation(self):
data = self.cleaned_data['appelation']
if data.isdigit():
# assume it's an id, and validate as such
if not Appelation.objects.filter(pk=data):
raise forms.ValidationError('Invalid Appelation id')
else:
# assume it's a name
if ...:
raise forms.ValidationError('Invalid Appelation name')
return data
I'm trying to set the value of a Django field inside of the Form class. Here is my model
class Workout(models.Model):
user = models.ForeignKey(User , db_column='userid')
datesubmitted = models.DateField()
workoutdate = models.DateField();
bodyweight = models.FloatField(null=True);
workoutname = models.CharField(max_length=250)
Here is the form class, in which i am attempting to achieve this:
class WorkoutForm(forms.ModelForm):
class Meta:
model = Workout
def __init__(self,*args, **kwargs):
# this is obviously wrong, I don't know what variable to set self.data to
self.datesubmitted = self.data['datesubmitted']
Ok, sorry guys. I'm passing the request.POST data to the WorkoutForm in my view like this
w = WorkoutForm(request.POST)
However, unfortunately the names of the html elements have different names then the names of the model. For instance, there is no date submitted field in the html. This is effectively a time stamp that is produced and saved in the database.
So I need to be able to save it inside the form class some how, I think.
That is why I am trying to set the datesubmitted field to datetime.datetime.now()
Basically I am using the form class to make the verification easier, and I AM NOT using the form for it's html output, which I completely disregard.
You have to do that in the save method of your form
class WorkoutForm(forms.ModelForm):
class Meta:
model = Workout
def __init__(self,*args, **kwargs):
super(WorkoutForm, self).__init__(*args, **kwargs)
def save(self, *args, **kw):
instance = super(WorkoutForm, self).save(commit=False)
instance.datesubmitted = datetime.now()
instance.save()
How ever you can set that in your model also to save the current datetime when ever a new object is created:
datesubmitted = models.DateTimeField(auto_now_add=True)
You can set some extra values set in form as:
form = WorkOutForm(curr_datetime = datetime.datetime.now(), request.POST) # passing datetime as a keyword argument
then in form get and set it:
def __init__(self,*args, **kwargs):
self.curr_datetime = kwargs.pop('curr_datetime')
super(WorkoutForm, self).__init__(*args, **kwargs)
You should not be using a ModelForm for this. Use a normal Form, and either in the view or in a method create a new model instance, copy the values, and return the model instance.
I am trying to save a modelform that represents a bank account but I keep getting a ValueError even though the form appears to validate. The models I have to use are:
class Person(models.Model):
name = models.CharField()
class Bank(models.Model):
bsb = models.CharField()
bank_name = models.CharField()
def __unicode__(self):
return '%s - %s', (self.bank_name, self.bsb)
def _get_list_item(self):
return self.id, self
list_item = property(-get_list_item)
class BankAccount(models.Model):
bank = models.ForignKey(Bank)
account_name = models.CharField()
account_number = models.CharField()
class PersonBankAcc(models.Model):
person = models.ForeignKey(Person)
The ModelForm for the personBankAcc;
def PersonBankAccForm(forms.ModelForm):
bank = forms.ChoiceField(widget=SelectWithPop)
class Meta:
model = PersonBankAcct
exclude = ['person']
def __init__(self, *args, **kwargs):
super(PersonBankAccForm, self).__init__(*args, **kwargs)
bank_choices = [bank.list_item for banks in Bank.objects.all()]
bank_choices.isert(0,('','------'))
self.fields['bank'].choices = bank_choices
The view is:
def editPersonBankAcc(request, personBankAcc_id=0):
personBankAcc = get_object_or_404(PersonBankAcc, pk=personBankAcc_id)
if request.method == 'POST':
form = PersonBankAccForm(request.POST, instance=personBankAcc )
if form.is_valid():
print 'form is valid'
form.save()
return HttpResponseRedirect('editPerson/' + personBankAcc.person.id +'/')
else:
form = PersonBankAccForm(instance=personBankAcc )
return render_to_response('editPersonBankAcc', {'form': form})
When I try to save the form I get the a VlaueError exception even though it gets passed the form.is_valid() check, the error I get is:
Cannot assign "u'26'": PersonBankAcc.bank must be a "bank" instance
I know the issue is arising because of the widget I am using in the PersonBankAccForm:
bank = forms.ChoiceField(widget=SelectWithPop)
because if I remove it it works. But all that does is gives me the ability to add a new bank to the database via a popup and then inserts it into the select list, similar to the admin popup forms. I have checked the database and the new bank is added. But it fails even if I don't change anything, if I call the form and submit it, I get the same error.
I don't understand why it does not fail at the is_valid check.
Any help would be greatly appreciated.
Thanks
Andrew
better yet, i don't think it really needs to be in the init function...
def PersonBankAccForm(forms.ModelForm):
bank = forms.ModelChoiceField(queryset=Bank.objects.all(),widget=SelectWithPop(),empty_label='-----')
class Meta:
model = EmpBankAcct
exclude = ['person']
def __init__(self, *args, **kwargs):
super(PersonBankAccForm, self).__init__(*args, **kwargs)