KeyError when passing kwargs to ModelForm - python

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.

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)?

Not passing **kwargs to django-import-export resources from custom view/form

When I use this resources.py inside Django admin everything works fine. However, when I do it on my custom view page there is an issue that popped with the **kwargs user auto-populate.
The error must be in my view as it's not passing the **kwargs but I'm not sure how to solve it. Where should I be passing this information?
KeyError at /import/
'user'
C:\Users\winkl\tj3\venv\lib\site-packages\import_export\resources.py in import_row
self.after_import_instance(instance, new, **kwargs) …
C:\Users\winkl\tj3\portfolios\resources.py in after_import_instance
instance.created_by = kwargs['user']
resources.py
class EntryResource(resources.ModelResource):
symbol = fields.Field(
attribute="symbol",
column_name="symbol",
widget=SymbolWidget(Symbol, 'name'),
)
date = fields.Field(
attribute="date",
column_name="date",
widget=widgets.DateTimeWidget(format="%Y-%m-%d %H:%M:%S"),
)
class Meta:
model = Entry
fields = ('symbol', 'date', 'id', 'amount', 'price', 'fee', 'entry_type', 'reg_fee',)
import_order = fields
skip_unchanged = False
report_skipped = True
def after_import_instance(self, instance, new, row_number=None, **kwargs):
print(f' Kwargs: {kwargs}')
instance.created_by = kwargs['user']
def after_save_instance(self, instance, using_transactions, dry_run):
pass
view.py
#login_required
def import_data(request):
if request.method == 'POST':
trade_resource = EntryResource()
dataset = Dataset()
new_trades = request.FILES['importData']
imported_data = dataset.load(new_trades.read().decode('utf-8'),format='csv')
result = trade_resource.import_data(dataset, dry_run=True, raise_errors=True)
if result.has_errors():
messages.error(request, 'Uh oh! Something went wrong...')
else:
# Import now
trade_resource.import_data(dataset, dry_run=False)
messages.success(request, 'Your words were successfully imported')
return render(request, 'dashboard/import.html')
Obviously you are getting the error because there is no entry with key 'user' in the kwargs dict.
If you look at the source, you can see that the kwargs get passed down the call stack into after_import_instance().
In your case, at no point is there ever a user entry in the kwargs dict.
However, if you pass this value in to the import_data() call then it can be retrieved and used:
your_user = load_user() # implement this as required
result = trade_resource.import_data(dataset, dry_run=True, raise_errors=True, user=your_user)

How can I pass current logged in user while form submission in django?

I am trying to create a form that allows current logged in user to submit data.
The form comprises of the field - amount, rate(interest), timestamp(automatically picked up), and currently logged in user.
Data is not passing into the database and giving an error like - The view investors.views.InvestView didn't return an HttpResponse object. It returned None instead.
views.py
def InvestView(request):
if request.method == 'GET':
investment_form = InvestorsForm(request.user)
context = {'investment_form': investment_form}
return render(request, 'investors/form.html', context)
if request.method == 'POST':
investment_form = InvestorsForm(request.POST or None, instance=request.user)
if investment_form.is_valid():
amount = investment_form.cleaned_data['amount']
interest = investment_form.cleaned_data['rate']
saving = investment_form.save(commit=False)
# Passing Logged in user
investor = request.user
print(investor)
saving.investor = request.user.id
saving.save()
messages.success(request, f'New Investment Done!')
return redirect('/myinvest/')
forms.py
class InvestorsForm(forms.ModelForm):
class Meta :
model = Investment
fields = ['amount', 'rate']
def __init__(self, user, *args, **kwargs):
self.user = user
super(InvestorsForm, self).__init__(*args, **kwargs)
models.py
class Investor(models.Model):
name = models.CharField(max_length=99)
user = models.ForeignKey(User, on_delete=models.CASCADE)
def __str__(self):
return self.name
class Investment(models.Model):
amount = models.FloatField(blank=False)
rate = models.FloatField(blank=False)
timestamp = models.DateField(default=datetime.now)
investor = models.ForeignKey(Investor, on_delete=models.CASCADE)
def __str__(self):
return str(self.investor)
Data must be stored into the database and redirect the page to myinvest section simultaneously.
This issue has nothing to do with adding the user. It is because you do nothing in case the validation fails.
You should turn your function the other way round so that the render is hit in all cases.
def InvestView(request):
if request.method == 'POST':
investment_form = InvestorsForm(request.POST)
if investment_form.is_valid():
saving = investment_form.save(commit=False)
saving.investor.user = request.user
saving.save()
messages.success(request, f'New Investment Done!')
return redirect('/myinvest/')
else:
investment_form = InvestorsForm()
context = {'investment_form': investment_form}
return render(request, 'investors/form.html', context)
Note the indentation.
The actual failure to validate is because you have changed the signature of the form to accept a user parameter first, instead of the expected data. However it is not clear why you have done this as you do not use that value, but instead set the user in the view. You should remove that __init__ method.
Another error: your form's __init__ method takes a user as its first argument:
def __init__(self, user, *args, **kwargs):
In the case of a GET request, you pass it correctly:
investment_form = InvestorsForm(request.user)
But then with a POST request, you forget it:
investment_form = InvestorsForm(request.POST or None, instance=request.user)
Passing request.user as the first argument there as well should help.
You need to replace saving.investor = request.user.id with saving.investor.user = request.user.

Django Form Error: Select a valid choice. ... is not one of the available choices

I am trying to create a dynamic choice field. I have a view that creates a list of tuples. The first value of the tuple is the primary key of the object ServiceWriter while the second value is the name of the ServiceWriter. The list then gets passed into the form class. When I make the selection and submit the page the form is decided to be not valid and the following form error is printed in the shell: "Select a valid choice. (First value of tuple. ie 1,2,3..) is not one of the available choices."
forms.py
class CreateAdvancedRO(forms.Form):
service_writer = forms.ChoiceField()
def __init__(self, writer_choices, *args, **kwargs):
super(CreateAdvancedRO, self).__init__(*args, **kwargs)
self.fields['service_writer'].choices = writer_choices
self.helper = FormHelper()
self.helper.form_id = 'id-create-advanced-ro'
self.helper.form_method = 'post'
self.helper.add_input(Submit('submit', 'Open Repair Order'))
Note: I am not using a ModelForm.
views.py
class CreateAdvancedRO(View):
form_class = CreateAdvancedRO
writer_form = CreateServiceWriter
add_line_form = AddJobLine
def post(self, request):
writer_choices = []
form = self.form_class(writer_choices, request.POST)
print(form.errors)
if form.is_valid():
'''Do something'''
else:
writer_choices = []
try:
writers = ServiceWriter.objects.filter(user=request.user)
for writer in writers:
writer_choices.append((str(writer.id), writer.name))
except ObjectDoesNotExist:
pass
form = self.form_class(writer_choices, request.POST)
writer_form = self.writer_form()
add_line_form = self.add_line_form()
return render(request, 'free/advanced_create.html', {'form': form, 'writer_form': wri
'add_line_form': add_line_form})
I have tried both of the following in the view:
writer_choices.append((str(writer.id), writer.name)) and
writer_choices.append((writer.id, writer.name))
Here is the ServiceWriter model, just in case.
class ServiceWriter(models.Model):
user = models.ForeignKey(User)
name = models.CharField(max_length=20, blank=False)
def __str__(self):
return str(self.name)
Any thoughts?
Thanks for the help.
It looks like you're trying to validate the form against an empty list of choices. Have you tried populating writer_choices before instantiating or attempting to validate the form?

In Django, how to pass a value into form class

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.

Categories