Django form instance is missing a field - django

So I have a problem. I have a model form:
class TeamForm(forms.ModelForm):
class Meta:
model = Team
fields = ['name','category','association','division','gender','logo','season']
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user')
super(TeamForm, self).__init__(*args, **kwargs)
instance = getattr(self, 'instance', None)
utils = CPUtils()
season_utils = SeasonUtils()
if instance and instance.pk is None:
self.fields['division'].initial = 1
self.fields['season'].initial = season_utils.getCurrentSeason().id
if user_role != 'admin':
self.fields['division'].widget.attrs['disabled'] = True
self.fields['division'].required = False
self.fields['season'].widget.attrs['disabled'] = True
self.fields['season'].required = False
So I am setting the initial of the form for the field season.
On my form, it is showing just fine.
So now in my clean method, I want to run some validation and I need to get the instance season.
In my clean method:
if cleaned_data.get('season') is None:
cleaned_data['season'] = self.instance.season
But it says that self.instance.season: DoesNotExist: Team has no season.
I have been trying to figure this out for a while now and I have no idea what's going on...
EDIT:
Here my model:
name = models.CharField(max_length=25,verbose_name=_("name"))
slug = AutoSlugField(unique=True,populate_from='name')
season = models.ForeignKey(Season,verbose_name=_("season"),blank=False)
association = models.ForeignKey(Association,blank=False,null=True,verbose_name=_("association"),on_delete=models.SET_NULL)
category = models.ForeignKey(Category,verbose_name=_("category"),blank=False,null=True,default=1,on_delete=models.SET_NULL)
division = models.ForeignKey(Division,verbose_name=_("division"),blank=False,null=True,default=1,on_delete=models.SET_NULL)
Also, the instance does not have season only during create and not during update...
Thanks,
Ara

The reason it says:
self.instance.season: DoesNotExist
Is that you are accessing self. Self here is actually the Form, and you are asking the Form for an attribute called instance, and then asking the instance attribute if it has a season attribute. The Form doesn't have this attribute.
What you really want to do is get the Model instance based on the Season ID, not the Form instance. The Form instance only holds the data temporarily while you clean it before you use the Model to save it to the database.
If you are trying to clean() the season, then you would want to access it this way if Season is an ID in your Form:
class TeamForm(forms.ModelForm):
# form logic here
def clean(self):
cleaned_data = super(TeamForm, self).clean()
# now get the Season Object from the cleaned_data dictionary
# if 'season' == season_id
try:
season_id = cleaned_data['season']
season = Season.object.get(pk=season_id)
except KeyError:
# season id does not exist, so do something here
pass

Related

copy values of multiple ModelMutipleChoiceField into one after Post using MPTT

I've been struggling with this issue all day and hope someone can help.
I have all my hierarchies classified by category in the same table.
during the form creation, I want to separate each hierarchy by category and render it using a ModelMutipleChoiceField his way not all hierarchies are displayed together.
The problem comes when the form is submitted, as I need to go through each ModelMutipleChoiceField field and get the selected values and copy these to the model field before saving the form. however, I am not able to iterate through the ModelMutipleChoiceField and get the selected values. I also don't know how to set these values on the ModelField
NOTE: The number of hierarchies can vary.
here is my code:
I'm using Django MPTT and create my hierarchy structure using 2 models.
one is the category(Hierarchy) and the other is the nodes of the hierarchy (HierarchyNode_MPTT).
Then I created a separate model that has ManyToManyField pointing to the HierarchyNode_MPTT.
Models.py
class Hierarchy(models.Model):
ID = kp.ObjectIDField()
name = kp.ObjectNameField()
ext_hierarchy = kp.ObjectTechnicalID()
seq_no = kp.SeqNoField(unique=True)
mptt_seq_no = models.PositiveIntegerField()
class HierarchyNode_MPTT(MPTTModel):
id = kp.ObjectIDField()
name = kp.ObjectNameField()
description = kp.ObjectDescriptionField()
ext_node_id = kp.ObjectShortNameField()
parent = TreeForeignKey('self', on_delete=models.CASCADE, null=True, blank=True, related_name='children')
hierarchy = models.ForeignKey(Hierarchy, on_delete=models.CASCADE, null=True, blank=True, related_name='children')
class Configuration(models.Model):
uuid = kp.ObjectIDField()
name = kp.ObjectNameField()
description = kp.ObjectDescriptionField()
hierarchy_nodes = models.ManyToManyField(HierarchyNode_MPTT)
Then I created the form and implement the init method to automatically create as many hierarchies as I need.
form.py
class ConfigurationCreateForm(forms.ModelForm):
class Meta:
model = ForecastConfiguration
exclude = ['uuid', 'hierarchy_nodes']
def __init__(self, user, *args, **kwargs):
super().__init__(*args, **kwargs)
hierarchies = Hierarchy.objects.all()
for hierarchy in hierarchies:
field_name = 'hierarchy_%s' % (hierarchy.mptt_seq_no,)
self.fields[field_name] = TreeNodeMultipleChoiceField(queryset=HierarchyNode_MPTT.objects.all().filter(hierarchy=hierarchy),label=hierarchy.name, required=True)
try:
self.initial[field_name] = HierarchyNode_MPTT.objects.root_node(tree_id=hierarchy.mptt_seq_no)
except IndexError:
self.initial[field_name] = ''
def copy_hierarchies(self, *args, **kwargs):
hierarchies = Hierarchy.objects.all()
choice_list = list()
for hierarchy in hierarchies:
field_name = 'hierarchy_%s' % (hierarchy.mptt_seq_no,)
selected_values = self.cleaned_data.get(field_name)
for selection in selected_values:
choice_list.append(selection)
self.initial['hierarchy_nodes'] = choice_list
Finally, the idea was to implement the post method on the View to loop over the created hierarchies and then assign the value to the model field called 'hierarchy_nodes'
view.py
class ConfigurationCreateView(CreateView):
model = Configuration
form_class = ConfigurationCreateForm
template_name = 'frontend/base/config_create.html'
def get(self, request, *args, **kwargs):
form = ConfigurationCreateForm(user=request.user)
return render(request, self.template_name, {'form': form})
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST)
form.copy_hierarchies(*args, **kwargs)
if form.is_valid():
fcc_form = form.save(commit=True)
messages.add_message(self.request, messages.INFO, 'Your Forecast Configurations has been saved')
return redirect(reverse('planning_detail', kwargs={'uuid': self.fcc_form.uuid}))
else:
messages.add_message(self.request, messages.ERROR, 'Error when creating the Forecast Configuration')
return render(request, self.template_name, {'form': form})
As you can see I created a method in my form called copy_hierarchies which is where I was planning to copy the hierarchy values, this is the method where I'm having problems.
if there is an easier way to perform this using Javascript, I'm open to these options.
Thanks in advance.
I wasn't able to solve this using multi-choice field, however, the following is the solution for a ChoiceField (single selection)
1) Changed my view.py post method to save the object.
2) After the model is saved I loop over the request input filed and append the values to the created instance.
3) Save the instance.
4) delete my copy_hierarchies method in forms.py
here is the code snippet created in views.py
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST)
if form.is_valid():
fcc = form.save()
for key in self.request.POST:
# check only the ones w/ 'hierarchy_#'
if key.startswith('hierarchy_'):
# get form field object
id = self.request.POST[key]
node = HierarchyNode_MPTT.objects.get(id=id)
# add to object instance
fcc.hierarchy_nodes.add(node)
fcc.save()

Django Model form with a hidden field won't pass validation

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

Django: __init__() got an unexpected keyword argument 'first_name'

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...

Setting value of Django field inside of Form class

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.

ModelForm save fails

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)