In Django, how to pass a value into form class - python

Some of my mistakes here are to do with my very limited python knowledge as much as anything, however I have been searching for half a day now.
class AssignmentForm(forms.ModelForm):
def __init__(self, *args, **kwargs, m_id):
super(AssignmentForm, self).__init__(*args, **kwargs)
# Making name required
self.fields['title'].required = True
self.fields['title'].help_text = "Title Required"
self.fields['description'].required = True
self.fields['description'].help_text= "Description Optional"
self.fields['module_id'].required = False
self.fields['module_id'] = m_id
class Meta:
# Provide an association between the ModelForm and a model
model = Assignment
fields = ('title', 'description', 'module_id')
This is my view
def add_assignment(request, m_id):
template = loader.get_template('add_assignment.html')
if request.method == 'POST':
form = AssignmentForm(request.POST, m_id)
if form.is_valid():
form.save(commit=True)
return module(request, m_id)
else:
print form.errors
else:
form = AssignmentForm()
# Bad form (or form details), no form supplied...
# Render the form with error messages (if any).
context = RequestContext(request,
{
'form':form,
})
return HttpResponse(template.render(context))
And this my URL mapping
url(r'^courses/(?P<m_id>\d+)/new/$', add_assignment, name='add_assignment'),
I'm currently getting an error because I mustn't be meant to pass values into init however I don't know how else to get the 'module_id' in.
Thanks again and I realise I might be structuring the whole thing terribly.

Related

Django Formset: Is there any chance to add missing field content in the post() method of a CreateView?

I've created a CreateView with a modelformset, added some js to add or remove additional forms. It is a view to create a booking from a bank booking entry. And as the 'from_account' is obvious (the booking is launched from a certain bank booking entry and handing over the pk from the bank booking in the url), I do not want to show this (selection-) field in the form. So I put the field as a hidden field in the formset with the objective to fill it in the post.
All this works as expected until submit.
Now, in the post() method, I see in formset.error correctly the errors for the missing fields 'bank_account_ta' and 'from_account'. Since I don't know the total_forms number (it might have been changed using js), I cannot prefill it while instantiating the formset class.
Working around in the template with js is possible, but not the proper way...
I would rather using the formset.clean() method but whatever I try, the formset.error remains as before which leads to not formset.is_valid().
My questions: What is the easiest way to complete data (cleaned_data) of a formset in post()? Another point which is still hurting me: Is it necessary or helpful to define self.object in a formset and if yes, how? I put it to None as I receive a missing object error if I leave it undefined.
Thank you in advance.
views.py
class TransactionMulitCreateView(LoginRequiredMixin, CreateView):
model = Transaction
template_name = 'accounting/transaction_formset.html'
extra_context = {'title': model._meta.verbose_name, }
success_url = reverse_lazy('finance:ta_groups')
TaFormset = modelformset_factory(Transaction,
formset=TransactionBaseFormset,
exclude=('validity_period_start', 'validity_period_end', 'ta_group'),
extra=1)
def get(self, request, *args, **kwargs):
self.object = None
ba = BankTransaction.objects.get(pk=self.kwargs['bank_ta_pk'])
form = self.TaFormset(queryset=Transaction.objects.none(), initial=[
{
# commented out, as it was the js approach.
# 'bank_account_ta': ba,
# 'from_account': ba.account.ca_account,
'posting_text': ba.booking_string(),
'valuta': ba.valuta,
'entry_date': ba.entry_date,
'value': ba.amount,
},
], ba=ba)
helper = TransactionMultiFormsetHelper()
return self.render_to_response(self.get_context_data(form=form, helper=helper, bank_ta=ba))
def post(self, request, *args, **kwargs):
self.object = None
ba = BankTransaction.objects.get(pk=self.kwargs['bank_ta_pk'])
# pass in initial to be able to detect changes, see https://docs.djangoproject.com/en/3.1/topics/forms/formsets/
formset = self.TaFormset(request.POST, request.FILES, initial=[
{
# commented out, as it was the js approach.
# 'bank_account_ta': ba,
# 'from_account': ba.account.ca_account,
'posting_text': ba.booking_string(),
'valuta': ba.valuta,
'entry_date': ba.entry_date,
'value': ba.amount,
},
], ba=ba)
formset.clean()
# formset = self.TaFormset()
if formset.is_valid():
instances = formset.save(commit=False)
split = TransactionGroup.objects.create(create_by=self.request.user, update_by=self.request.user)
for instance in instances:
instance.ta_group = split
instance.save()
# BankTransaction.objects.filter(pk=self.kwargs['bank_ta_pk']).update(is_booked=True)
ba.is_booked = True
ba.save()
return HttpResponseRedirect(reverse_lazy('finance:bank_tas'))
# return self.form_valid(formset)
else:
helper = TransactionMultiFormsetHelper()
return self.render_to_response(self.get_context_data(form=formset, helper=helper, bank_ta=ba))
# return self.form_invalid(formset)
forms.py
class TransactionBaseFormset(forms.BaseModelFormSet):
def __init__(self, *args, **kwargs):
# self.ba_pk = kwargs.pop('ba_pk')
try:
self.ba = kwargs.pop('ba')
except KeyError:
self.ba = None
super().__init__(*args, **kwargs)
def clean(self):
data = None
for form in self.forms:
data = form.cleaned_data
data['bank_account_ta'] = self.ba
data['from_account'] = self.ba.account.ca_account
return data
Got it!
The simple solution is to exclude all enforced fields and fill them either in the clean() or in the is_valid() function.
TaFormset = modelformset_factory(Transaction,
formset=TransactionBaseFormset,
exclude=(
'validity_period_start',
'validity_period_end',
'ta_group',
'bank_account_ta',
'from_account',
),
extra=1)
However, I would still like to know which role plays (or might play) self.object in a formset (or modelformset)?

KeyError when passing kwargs to ModelForm

I'm trying to create a custom field for my M2M field in my ModelForm. ConnectedTo is the many to many field. Code below:
views:
def addPartForm_Create(request, site, subtype):
siteselected = site
sitenumber = str(site)
print(sitenumber)
subtypeselected = Subtype.objects.get(SubtypeID = subtype)
if request.method == 'POST':
form = addPartForm(request.POST, sitenum=sitenumber)
if form.is_valid():
obj = form.save(commit=False)
obj.SiteID = Site.objects.get(SiteID = siteselected)
obj.Subtype = subtypeselected
obj.save()
form.save_m2m()
return redirect('/sites/'+str(site))
else:
form = addPartForm()
return render(request, 'myproj/addPart.html', {'form': form, 'SiteNo': Site.objects.get(SiteID = siteselected).SiteID, 'subtype': subtypeselected})
forms:
class addPartForm(forms.ModelForm):
class Meta:
model = Part
fields = ('Comment', 'Location', 'ConnectedTo', 'BatchNo', 'Manufacturer', 'Length', 'InspectionPeriod')
labels = {"BatchNo": "Batch Number", "InspectionPeriod": "Inspection Period"}
def __init__(self, *args, **kwargs):
super(addPartForm, self).__init__(*args, **kwargs)
sitenum = kwargs.pop('sitenum')
self.fields["ConnectedTo"].widget = forms.CheckboxSelectMultiple()
self.fields["ConnectedTo"].queryset = Part.objects.filter(SiteID = sitenum)
I get KeyError when I try to pass sitenum from view to form. I know I could set a default value None but I don't want it to ever display none. I need to always have a sitenum sent here. Am I passing this wrong?
You need to pass the kwarg sitenum to all instances of your form, you aren't doing this when the request is GET. I'd also move all kwargs.pop() above the super calls to ensure it doesn't conflict with kwargs that the super might be expecting.

Preserve checked checkboxes after post in django

I want to keep the form fields preserved after submitting the form. I can preserve all the fields except the checked checkboxes
Here is my form action view:
def student_submit_info(request):
form_values = {}
if request.method == 'POST':
form = StudentForm(request.POST)
print form.is_valid()
form_values = getDetails(request)
if form.is_valid():
print request.user.username
student = Student.objects.get(user=request.user)
// saving student to DB
saveDetails(request, student, form_values)
return render_to_response('student.html', form_values, context_instance=RequestContext(request))
def getDetails(request):
#fetch input data
form_values = {}
form_values['first_name'] = request.POST['first_name']
form_values['last_name'] = request.POST['last_name']
form_values['skills'] = request.POST.getlist('skills[]')
return form_values
I would want to preserve skills (checkbox), here's a part of my template
skill1<input type="checkbox" name="skills[]" checked value="skill1"/>
skill2<input type="checkbox" name="skills[]" value="skill2"/>
skill3<input type="checkbox" name="skills[]" value="skill3"/>
You should make a custom django Forms to handle easily forms, example of a post view:
def post_view(request):
form = MyForm(request.POST)
if form.is_valid():
do_something()
render(request, 'your_temp', {'MyForm': MyForm'})
With this kind of snippet, data in form will always be those sent.
You can override is_valid() for empty skill checkboxes.
Perhaps, use CBV's and ModelForms to make the process simpler.
Combine a listview (or similar) and add your form via context.
Then in get_context_data of your CBV:
form_values = {}
#Simpler code below for accessing post dictionary
for key, value in self.request.POST.items():
if value == 'on':
form_values[key] = value
context = super().get_context_data(**kwargs)
# You need to add form_values to make it accessible in the forms.py
context['form'] = YourFormName(form_values)
Then, in your forms.py (I'm hoping you use modelforms):
def __init__(self, filters, *args, **kwargs):
super(YourFormName, self).__init__(*args, **kwargs)
if len(form_values) > 0:
for field_name in self.fields:
if field_name in filters:
self.fields[field_name].widget = forms.CheckboxInput(attrs={'checked': True})
I hope this helps.

Django form fields do not get populated by POST data

I have a model form with custom constructor. It checks if a file is required and if false, it deletes the file field. It also has two hidden fields, which I initialize at my views. The form class is as follows:
class SubmitTask(forms.ModelForm):
task = forms.ModelChoiceField(queryset=Task.objects.all(), widget=forms.HiddenInput())
student = forms.ModelChoiceField(queryset=UserProfile.objects.all(), widget=forms.HiddenInput())
def __init__(self, file_required=False, *args, **kwargs):
super(SubmitTask, self).__init__(*args, **kwargs)
if file_required is True:
file = forms.FileField(
label='Select a file',
help_text='max: 2.5 MB'
)
else:
del self.fields['file']
class Meta:
model = Submission
exclude = ('date_submitted',)
My problem is that the hidden fields are populated when I initialize the form (I have confirmed that its values are indeed initialized by viewing the HTML code). However, the values of the hidden fields are not populated during POST request. I have confirmed during POST request that the form is indeed bound and I also confirmed on Firebug that POST also contains the task and student values. This is the involved method in my views.py
def view_task(request, id):
task = Task.objects.get(pk=id)
profile = request.user.get_profile()
data = {}
data['classroom'] = task.get_classroom()
data['description'] = task.get_description()
teacher_classrooms = Classroom.objects.filter(teacher=profile)
teacher_tasks = Task.objects.filter(classroom__in=teacher_classrooms)
if not submission and task not in teacher_tasks:
form = SubmitTask(file_required=task.file_required, initial={'task':task.pk, 'student':profile.pk})
data['form'] = form
if request.method == 'POST':
form = SubmitTask(request.POST, request.FILES)
if form.is_valid():
form.save()
return render_to_response('classrooms/view_task.html',
data, context_instance=RequestContext(request))
Your view function looks incorrect to me:
Where does the submission variable come from, when initializing the posted form you are missing the file_required parameter and the form processing should perhaps be reorganized to something like:
if request.method == 'POST':
form = SubmitTask(task.file_required, request.POST, request.FILES)
...
else:
form = SubmitTask(task.file_required, ...)
data['form'] = form

Django set default form values

I have a Model as follows:
class TankJournal(models.Model):
user = models.ForeignKey(User)
tank = models.ForeignKey(TankProfile)
ts = models.IntegerField(max_length=15)
title = models.CharField(max_length=50)
body = models.TextField()
I also have a model form for the above model as follows:
class JournalForm(ModelForm):
tank = forms.IntegerField(widget=forms.HiddenInput())
class Meta:
model = TankJournal
exclude = ('user','ts')
I want to know how to set the default value for that tank hidden field. Here is my function to show/save the form so far:
def addJournal(request, id=0):
if not request.user.is_authenticated():
return HttpResponseRedirect('/')
# checking if they own the tank
from django.contrib.auth.models import User
user = User.objects.get(pk=request.session['id'])
if request.method == 'POST':
form = JournalForm(request.POST)
if form.is_valid():
obj = form.save(commit=False)
# setting the user and ts
from time import time
obj.ts = int(time())
obj.user = user
obj.tank = TankProfile.objects.get(pk=form.cleaned_data['tank_id'])
# saving the test
obj.save()
else:
form = JournalForm()
try:
tank = TankProfile.objects.get(user=user, id=id)
except TankProfile.DoesNotExist:
return HttpResponseRedirect('/error/')
You can use Form.initial, which is explained here.
You have two options either populate the value when calling form constructor:
form = JournalForm(initial={'tank': 123})
or set the value in the form definition:
tank = forms.IntegerField(widget=forms.HiddenInput(), initial=123)
Other solution: Set initial after creating the form:
form.fields['tank'].initial = 123
If you are creating modelform from POST values initial can be assigned this way:
form = SomeModelForm(request.POST, initial={"option": "10"})
https://docs.djangoproject.com/en/1.10/topics/forms/modelforms/#providing-initial-values
I had this other solution (I'm posting it in case someone else as me is using the following method from the model):
class onlyUserIsActiveField(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(onlyUserIsActiveField, self).__init__(*args, **kwargs)
self.fields['is_active'].initial = False
class Meta:
model = User
fields = ['is_active']
labels = {'is_active': 'Is Active'}
widgets = {
'is_active': forms.CheckboxInput( attrs={
'class': 'form-control bootstrap-switch',
'data-size': 'mini',
'data-on-color': 'success',
'data-on-text': 'Active',
'data-off-color': 'danger',
'data-off-text': 'Inactive',
'name': 'is_active',
})
}
The initial is definded on the __init__ function as self.fields['is_active'].initial = False
As explained in Django docs, initial is not default.
The initial value of a field is intended to be displayed in an HTML . But if the user delete this value, and finally send back a blank value for this field, the initial value is lost. So you do not obtain what is expected by a default behaviour.
The default behaviour is : the value that validation process will take if data argument do not contain any value for the field.
To implement that, a straightforward way is to combine initial and clean_<field>():
class JournalForm(ModelForm):
tank = forms.IntegerField(widget=forms.HiddenInput(), initial=123)
(...)
def clean_tank(self):
if not self['tank'].html_name in self.data:
return self.fields['tank'].initial
return self.cleaned_data['tank']
If you want to add initial value and post other value you have to add the following :
or None after request.POST
form = JournalForm(request.POST or None,initial={'tank': 123})
If you want to add files or images also
form = JournalForm(request.POST or None,request.FILES or None,initial={'tank': 123})
I hope this can help you:
form.instance.updatedby = form.cleaned_data['updatedby'] = request.user.id
I also encountered the need to set default values in the form during development. My solution is
initial={"":""}
form=ArticleModel(request.POST)
if form.has_changed():
data = {i: form.cleaned_data[i] for i in form.changed_data}
data.update({key: val for key, val in init_praram.items() if key not in form.changed_data})
use form.has_changed ,if form.fields is required you can use this method
How I added the initial to the form:
I read #Sergey Golovchenko answer.
So I just added it to the form in if request.method == 'POST':.
But that's not where you place it, if you want to see what value it got before posting the form.
You need to put it in the form where the else is.
Example here from views.py
def myForm(request):
kontext = {}
if request.method == 'POST':
# You might want to use clean_data instead of initial here. I found something on a stack overflow question, and you add clean data to the Forms.py, if you want to change the post data. https://stackoverflow.com/questions/36711229/django-forms-clean-data
form = myModelForm(request.POST, initial={'user': request.user})
if form.is_valid():
form.save()
return redirect('/')
else:
# you need to put initial here, if you want to see the value before you post it
form = myModelForm(initial={'user': request.user})
kontext['form'] = form
return render(request, 'app1/my_form.html', kontext)

Categories