Django key error "request" when using inlineformset - django

I created a Class Based(CreateView) with in inlineformset. I need to pass the request.user to the form to enable a filter function on one of the form fields. I however get a Key Error: request on the line:
self.request = kwargs.pop('request') in the def __init__(self, *args, **kwargs): of the form. Assistance will be appreciated.
Tips on my programming also welcome.
models.py:
class ttransactions(models.Model):
transaction_type = models.CharField(max_length=10, choices=tx_choices)
description = models.CharField(max_length=50, null=False, blank=False, default='Description')
transaction_date = models.DateField(default=datetime.today, db_index=True)
company = models.ForeignKey(tcompany, on_delete=models.PROTECT, db_index=True)
def __str__(self):
return self.description
class ttransaction_lines(models.Model):
transaction = models.ForeignKey(ttransactions, on_delete=models.CASCADE, db_index=True)
sequence = models.IntegerField()
transaction_type = models.CharField(max_length=6, choices=debit_credit)
ledger_account = models.ForeignKey(tledger_account, on_delete=models.PROTECT, db_index=True)
amount = models.DecimalField(max_digits=14, decimal_places=2, default=0.0)
quantity = models.IntegerField(blank=True, null=True)
posted = models.BooleanField(default=True)
forms.py:
class TransactionLinesForm(forms.ModelForm):
class Meta:
model = ttransaction_lines
fields = ['transaction_type', 'ledger_account', 'amount']
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request')
super(TransactionLinesForm, self).__init__(*args, **kwargs)
user = self.request.user
current_company = user.current_company
self.fields['ledger_account'].queryset = tledger_account.objects.filter(
company=current_company)
TransactionLineFormset = inlineformset_factory(ttransactions,
ttransaction_lines,
# fields=['transaction_type', 'ledger_account', 'amount'] ,
form=TransactionLinesForm,
can_order=True, can_delete=True)
views.py:
class JournalCreateView(LoginRequiredMixin, CreateView):
template_name = 'accounting/journal.html'
model = ttransactions
transaction_lines_form = TransactionLineFormset
form_class = TransactionsForm
success_url = '/accounting/transaction_list'
def get_form_kwargs(self):
kwargs = super(JournalCreateView, self).get_form_kwargs()
kwargs['request'] = self.request
return kwargs
def get(self, request, *args, **kwargs):
self.object = None
form_class = self.get_form_class()
form = self.get_form(form_class)
transaction_lines_form = TransactionLineFormset()
return self.render_to_response(
self.get_context_data(form=form, transaction_lines_form=transaction_lines_form))
def post(self, request, *args, **kwargs):
extra_forms = 1
if 'additems' in request.POST and request.POST['additems'] == 'true':
formset_dictionary_copy = self.request.POST.copy()
formset_dictionary_copy['form-TOTAL_FORMS'] = \
int(formset_dictionary_copy['form-TOTAL_FORMS']) + extra_forms
transaction_lines_form = TransactionLinesFormSet(formset_dictionary_copy)
return self.render_to_response(
self.get_context_data(form=form,
transaction_lines_form=transaction_lines_form))
self.object = None
form_class = self.get_form_class()
form = self.get_form(form_class)
transaction_lines_form = TransactionLineFormset(self.request.POST)
if (form.is_valid() and transaction_lines_form.is_valid()):
return self.form_valid(form, transaction_lines_form)
else:
return self.form_invalid(form, transaction_lines_form)
def form_valid(self, form, transaction_lines_form):
form.instance.company = self.request.user.current_company
self.object = form.save()
sequence = 1
for line in transaction_lines_form:
line.instance.sequence = sequence
sequence += 1
transaction_lines_form.instance = self.object
transaction_lines_form.save()
return super().form_valid(form)
def form_invalid(self, form, transaction_lines_form):
return self.render_to_response(
self.get_context_data(form=form,
transaction_lines_form=transaction_lines_form))

Your form may be initialized at multiple places. It is difficult to find where the error happened without seeing the Traceback.
So, it is better to keep the filtering logic in the view rather than passing 'request' to form. Remove __init__ method in form and try the below code in the view
def get(self, request, *args, **kwargs):
self.object = None
form_class = self.get_form_class()
form = self.get_form(form_class)
transaction_lines_form = TransactionLineFormset()
transaction_lines_form.form.base_fields['ledger_account'].queryset = \
tledger_account.objects.filter(company=request.user.current_company)
return self.render_to_response(
self.get_context_data(form=form, transaction_lines_form=transaction_lines_form))

Related

How to get rid of redundant requests ord django

class Post(models.Model):
cat_post = models.ForeignKey(Category, on_delete=models.CASCADE, blank=True,null=True)
top_post = models.ForeignKey(TopicsCategory, on_delete=models.CASCADE, blank=True,null=True)
sub_post = models.ForeignKey(SubTopicsCategory, on_delete=models.CASCADE, blank=True,null=True)
class CreatePostView(CreateView):
model = Post
template_name = 'blog/create.html'
form_class = CreatePostForm
def get_context_data(self, *args, **kwards):
print(self.kwargs)
context = super(CreatePostView, self).get_context_data(**kwards)
context['btn'] = 'Add'
return context
def form_valid(self, form, *args, **kwargs):
if self.kwargs.get('category_slug') and len(self.kwargs) == 1:
category = Category.objects.get(slug=self.kwargs['category_slug'])
form.instance.cat_post = category
return super(CreatePostView, self).form_valid(form)
# передача в форму kwargs view
def get_form_kwargs(self):
kwargs = super(CreatePostView, self).get_form_kwargs()
kwargs.update({'view_kwargs': self.kwargs})
return kwargs
def get_success_url(self):
return reverse('topics_category_list', kwargs={'category_slug': self.kwargs['category_slug'], })
class CreatePostForm(forms.ModelForm):
class Meta:
model = Post
fields = ['name', 'text', 'discussion']
# widgets = {
# 'cat_post': forms.HiddenInput(),
# }
def __init__(self, *args, **kwargs):
self.request = kwargs.pop("view_kwargs")
super(CreatePostForm, self).__init__(*args, **kwargs)
def clean_name(self):
name = self.cleaned_data['name']
if self.request.get('category_slug') and len(self.request) == 1:
category = Category.objects.get(slug=self.request['category_slug'])
unique = Post.objects.filter(slug=slugify(self.cleaned_data['name']), cat_post=category.pk,discussion=False).exists()
if unique:
raise ValidationError(f'Post is not unique')
return name
**
I have duplicate sources in db here form_valid and clean_name.
How do I pass the form in the view form form_valid class instance that I got from the database to clean_name.
There will be posts for 2 models and requests will increase

MultiValueDictKeyError hen adding lines with inlineformset

I am trying to add more lines on the inline formset factory using the same methodology that I used before on a formset factory but is getting an error:
MultiValueDictKeyError form-TOTAL_FORMS'
models.py:
class ttransactions(models.Model):
transaction_type = models.CharField(max_length=10, choices=tx_choices)
description = models.CharField(max_length=50, null=False, blank=False, default='Description')
transaction_date = models.DateField(default=datetime.today, db_index=True)
company = models.ForeignKey(tcompany, on_delete=models.PROTECT, db_index=True)
def __str__(self):
return self.description
class ttransaction_lines(models.Model):
transaction = models.ForeignKey(ttransactions, on_delete=models.PROTECT, db_index=True)
sequence = models.IntegerField()
transaction_type = models.CharField(max_length=6, choices=debit_credit)
ledger_account = models.ForeignKey(tledger_account, on_delete=models.PROTECT, db_index=True)
amount = models.DecimalField(max_digits=14, decimal_places=2, default=0.0)
vat_amount = models.DecimalField(max_digits=14, decimal_places=2, default=0.0)
vat_code = models.ForeignKey(tvat, on_delete=models.PROTECT, blank=True, null=True)
quantity = models.IntegerField(blank=True, null=True)
posted = models.BooleanField(default=True)
forms.py:
class TransactionsForm(forms.ModelForm):
transaction_date = forms.DateField(widget=forms.SelectDateWidget(years=year_range), initial=datetime.today)
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request')
super(TransactionsForm, self).__init__(*args, **kwargs)
class Meta:
model = ttransactions
fields = ['description',
'transaction_date']
class TransactionLinesForm(forms.ModelForm):
class Meta:
model = ttransaction_lines
fields = ['transaction_type', 'ledger_account', 'amount']
class BaseTransactionLinesFormSet(BaseModelFormSet):
def clean(self):
super(BaseTransactionLinesFormSet, self).clean()
# Check errors dictionary first, if there are any error, no point in validating further
if any(self.errors):
return
balance = 0
for form in self.forms:
if form.cleaned_data['DELETE'] == True or form.cleaned_data['DELETE'] == '':
continue
if form.cleaned_data['transaction_type']=='Debit':
balance = balance + form.cleaned_data['amount']
else:
balance = balance - form.cleaned_data['amount']
if balance != 0:
message = 'Transactions not balanced (excluding deleting lines)'
raise forms.ValidationError(message)
TransactionLineFormset = inlineformset_factory(ttransactions,
ttransaction_lines,
form=TransactionLinesForm,
can_order=True, can_delete=True)
views.py
class JournalCreateView(LoginRequiredMixin, CreateView):
template_name = 'accounting/journal.html'
model = ttransactions
formset = TransactionLineFormset
form_class = TransactionsForm
success_url = '/accounting/transaction_list'
def get_form_kwargs(self):
kwargs = super(JournalCreateView, self).get_form_kwargs()
kwargs['request'] = self.request
return kwargs
def get(self, request, *args, **kwargs):
self.object = None
form_class = self.get_form_class()
form = self.get_form(form_class)
formset = TransactionLineFormset(queryset=ttransaction_lines.objects.none())
formset.form.base_fields['ledger_account'].queryset = \
tledger_account.objects.filter(company=request.user.current_company)
return self.render_to_response(
self.get_context_data(form=form, formset=formset))
def post(self, request, *args, **kwargs):
extra_forms = 1
if 'additems' in request.POST and request.POST['additems'] == 'true':
formset_dictionary_copy = self.request.POST.copy()
formset_dictionary_copy['form-TOTAL_FORMS'] = \
int(formset_dictionary_copy['form-TOTAL_FORMS']) + extra_forms
formset = TransactionLineFormSet(formset_dictionary_copy)
return self.render_to_response(
self.get_context_data(form=form,
formset=formset))
self.object = None
form_class = self.get_form_class()
form = self.get_form(form_class)
formset = TransactionLineFormset(self.request.POST)
if (form.is_valid() and formset.is_valid()):
return self.form_valid(form, formset)
else:
return self.form_invalid(form, formset)
def form_valid(self, form, formset):
form.instance.company = self.request.user.current_company
self.object = form.save()
sequence = 1
for line in formset:
line.instance.sequence = sequence
sequence += 1
formset.instance = self.object
formset.save()
return super().form_valid(form)
def form_invalid(self, form, formset):
return self.render_to_response(
self.get_context_data(form=form,
formset=formset))
I get the error on the line that dictionary copy line. The code for adding the line is added in the post function. I am not sure if that is the correct place to add this code. Help will be appreciated.
This error occurs if the form submitted is missing the key.
The .get() method allows for a default when accessing a missing key in a dict.
formset_dictionary_copy['form-TOTAL_FORMS'] = \
int(formset_dictionary_copy.get('form-TOTAL_FORMS', 1)) + extra_forms
It appears that, the formset is not getting all it's hidden values or a different form is submitted.
I would consider a frontend solution or sending a single initial formset with the add button values.
A frontend / js example:
https://www.brennantymrak.com/articles/django-dynamic-formsets-javascript

How do I get my Django generic view to work

Am I going about this the right way? Having never used 'generic views' I am tying to use Django's generic.UpdateView view. When I 'hit' the 'update' button on the form, I get an invalid form response with message 'Library with this Slide name already exists'
Grateful for any help.
View:
class Slideview(generic.UpdateView):
model = Library
template_name = 'app1/slide_update.html'
fields = ['slide_name', 'reference_value','esd',
'current_mean', 'counts_averaged', 'status']
context_object_name = 'qc_slide'
#def get_queryset(self):
#slide_id = self.kwargs['pk']
#return Library.objects.filter(slide_name=slide_id)
def get_success_url(self):
return reverse('Slideview', args=[self.kwargs['pk']])
def get_context_data(self, **kwargs):
context = super(Slideview, self).get_context_data(**kwargs)
#form = self.get_form(self.get_form_class())
#context['form'] = form
return context
def post(self, request, *args, **kwargs):
print("Im in post")
form = self.get_form(self.get_form_class())
if form.is_valid():
#Code will go here which will query a second model
#perform a series of math calculations and then
#return the updated information
self.object = self.get_object()
self.object.save()
return self.form_valid(form)
else:
print("Form not valid")
self.object = self.get_object()
return self.form_invalid(form)
Model:
class Library(models.Model):
slide_name = models.CharField(max_length=5, primary_key=True)
reference_value = models.DecimalField(max_digits=5, decimal_places=2, default=Decimal(0))
esd = models.DecimalField(max_digits=5, decimal_places=2, default=Decimal(0))
current_mean = models.DecimalField(max_digits=5, decimal_places=2, default=Decimal(0))
counts_averaged = models.IntegerField(default=0)
status = models.CharField(max_length=9)
def __str__(self):
return self.slide_name

django forms and __init__ function: unexpected keyword argument 'user'

I follow kenneth love crash course and I get unexpected error which I can not solve:
"init() got an unexpected keyword argument 'user'"
here is the form class:
class TalkTalkListForm(forms.ModelForm):
class Meta:
model = models.Talk
fields = ('talk_list',)
def __init__(self, *args, **kwargs):
super(TalkTalkListForm, self).__init__(*args, **kwargs)
self.fields['talk_list'].queryset = (
self.instance.talk_list.user.lists.all())
self.helper = FormHelper()
self.helper.layout = Layout(
'talk_list',
ButtonHolder(
Submit('move', 'Move', css_class='btn-primary')
)
)
and here is Talk model:
class Talk(models.Model):
ROOM_CHOICES = (
('BS1_O', 'BS1_O'),
('BS1TC', 'BS1TC'),
('BS3_O', 'BS3_O'),
('BS3TC', 'BS3TC'),
)
talk_list = models.ForeignKey(TalkList, related_name='talks')
name = models.CharField(max_length=255)
slug = models.SlugField(max_length=255, blank=True)
when = models.DateTimeField()
room = models.CharField(max_length=5, choices=ROOM_CHOICES)
host = models.CharField(max_length=255)
talk_rating = models.IntegerField(blank=True, default=0)
speaker_rating = models.IntegerField(blank=True, default=0)
notes = models.TextField(blank=True, default='')
notes_html = models.TextField(blank=True, default='', editable=False)
class Meta:
ordering = ('when', 'room')
unique_together = ('talk_list', 'name')
def __unicode__(self):
return self.name
def save(self, *args, **kwargs):
self.slug = slugify(self.name)
self.notes_html = mistune.markdown(self.notes)
super(Talk, self).save(*args, **kwargs)
def get_absolute_url(self):
return reverse('talks:talks:detail', kwargs={'slug': self.slug})
#property
def overall_rating(self):
if self.talk_rating and self.speaker_rating:
return (self.talk_rating + self.speaker_rating) / 2
return 0
I can see Talk has not got an attribute 'user' but I pass 'user' attribute to init somehow. Is it the reason of the problem ?
I know kwargs are named arguments so a named argument not expected will throw an error but I do not know how to get rid off the error.
UPDATE:
class TalkDetailView(views.LoginRequiredMixin, generic.DetailView):
http_method_names = ['get', 'post']
model = models.Talk
def get_queryset(self):
return self.model.objects.filter(talk_list__user=self.request.user)
def get_context_data(self, **kwargs):
context = super(TalkDetailView, self).get_context_data(**kwargs)
obj = context['object']
rating_form = forms.TalkRatingForm(self.request.POST or None,instance=obj)
list_form = forms.TalkTalkListForm(self.request.POST or None,instance=obj)
context.update({
'rating_form': rating_form,
'list_form': list_form
})
return context
def post(self, request, *args, **kwargs):
self.object = self.get_object()
if 'save' in request.POST:
talk_form = forms.TalkRatingForm(request.POST or None,
instance=self.object)
if talk_form.is_valid():
talk_form.save()
if 'move' in request.POST:
list_form = forms.TalkTalkListForm(request.POST or None,
instance=self.object,
user=request.user)
if list_form.is_valid():
list_form.save()
return redirect(self.object)
I don't understand why do you put request.user in init if you have already filtered queryset by request.user == return self.model.objects.filter(talk_list__user=self.request.user)
Anyway ...
1) If you want access kwarg in form __init__ you should pop it before call super...
def __init__(self, *args, **kwargs):
user = kwargs.pop('user', False)
# now you have removed it from kwargs and `super` call will work as expected
super(TalkTalkListForm, self).__init__(*args, **kwargs)
# do smth with `user`
2) If you want save request.user to object do smth like this in your view:
if list_form.is_valid():
obj = list_form.save()
obj.talk_list.user = request.user
obj.save()

Django UpdateView with related model

I have the following situation and I don't know how to do an update:
#models.py
class Task(models.Model):
creation_date = models.DateField(
default=None,
)
name = models.CharField(
max_length=255,
)
description = models.TextField(
max_length=500,
blank=True,
null=True,
)
class TaskDetails(models.Model):
PEND = 1
COMP = 2
TASK_STATUS = (
(PEND, 'pending'),
(COMP, 'completed'),
)
task = models.OneToOneField(
Task,
primary_key=True,
on_delete=models.CASCADE
)
solution = models.CharField(
max_length=255,
)
due_date = models.DateField(
null=True,
default=None,
blank=True,
)
status = models.PositiveSmallIntegerField(
default=1,
choices=TASK_STATUS,
)
And now my view
#views.py
class TaskUpdate(UpdateView):
model = Task
second_model = TaskDetails
form_class = TaskForm
second_form_class = TaskDetailsForm
pk_url_kwarg = 'task_id'
def get_context_data(self, **kwargs):
context = super(TaskUpdate, self).get_context_data(**kwargs)
if self.request.method == 'POST':
details_form = self.second_form_class(self.request.POST, prefix='details')
else:
details_object = self.second_model.objects.get(pk=self.kwargs.get(self.pk_url_kwarg))
details_form = self.second_form_class(instance=details_object, prefix='details')
context['details_form'] = details_form
return context
def post(self, request, *args, **kwargs):
self.object = self.get_object()
form = self.form_class(request.POST)
details_form = self.second_form_class(request.POST, prefix='details')
if form.is_valid() and details_form.is_valid():
return self.form_valid(form, details_form)
else:
return self.form_invalid(form, details_form)
def form_valid(self, form, details_form):
form.instance.creation_date = datetime.now().date()
self.object = form.save()
details_form.instance.task = self.object
details_form.save()
return HttpResponseRedirect(self.success_url)
def form_invalid(self, form, details_form):
return self.render_to_response(self.get_context_data(form=form, details_form=details_form))
I also have the ModelForms: TaskForm and TaskDetailsForm. Not relevant here.
The 2 forms are displayed and submitted at once.
But instead of updating the existing record in Task and TaskDetails tables, it creates a new one in both tables
I think my problem is in form_valid. What should I put there?
Thanks a lot
In the post method forms are created without instance. You have to pass you instances there.
def post(self, request, *args, **kwargs):
# get current task
obj = self.get_object()
#initiate the task form with this object as instance
form = self.form_class(request.POST, instance=obj)
#get realted details object or None.
#I can't check if this string works, but it should.
details_obj = getattr(object, 'taskdetails', None)
#initiate the details_form with this details_object as instance
details_form = self.second_form_class(request.POST, prefix='details',
instance=details_obj)
if form.is_valid() and details_form.is_valid():
return self.form_valid(form, details_form)
else:
return self.form_invalid(form, details_form)
def form_valid(self, form, details_form):
#save object
obj = form.save(commit=False)
obj.creation_date = datetime.now().date()
obj.save()
#save details_object
details_obj = details_form.save(commit=False)
details_obj.task = obj
details_obj.save()
return HttpResponseRedirect(self.success_url)
I think this should work. You don't need the form_valid and form_invalid methods in this case,
def post(self, request, *args, **kwargs):
response = super(TaskUpdate, self).post(request, *args, **kwargs)
details_form = self.second_form_class(self.request.POST, prefix='details')
if details_form.is_valid():
task = self.get_object()
self.second_model.objects.filter(task=task)
.update(**details_form.cleaned_data)
return response
return render(request, self.template_name, {
'form': self.get_form(self.get_form_class()),
'details_form': details_form,
})
PS: put related_name=task_details for the OneToOneField and auto_now_add=True for the creation date of your task
Nevertheless, why don't you include the Task Details into task and stop using this OneToOneKey?