Django get_form_kwargs() not being called - python

I have a view that has 3 sections. The top section is a search area. The bottom left section shows a list of objects and the right section shows the details of the selected object.
My view inherits from ListView and FormView.
class SearchViewMixin(FormView):
template = 'template here'
form_class = forms.SearchForm
def get_form_kwargs(self):
print 'This is not being called'
kwargs = super(SearchViewMixin, self).get_form_kwargs()
kwargs['search-units'] = self.request.search.unit_set.all()
return kwargs
class ListView(ListView, SearchViewMixin):
def get_queryset()...
def get_context_data()....
The form view is this:-
class ReceiptSearchForm(BaseForm):
obj_id = forms.IntegerField(required=False)
unit = forms.ModelChoiceField(queryset=None, required=False, empty_label='Select a unit',
widget=core.SelectPickerSearch)
def __init__(self, *args, **kwargs):
self.units = kwargs.pop('search-units', None)
super(ReceiptSearchForm, self).__init__()
self.fields['unit'].queryset = self.units
The problem is the get_form_kwargs method is not being called. The print statement is not getting executed
Since I am not writing or updating any data, I am just using a get method for the form.

Related

Django form with ModelMultipleChoiceField rendered empty

I have trouble setting up a form with a ModelMultipleChoiceField where the queryset depends on the user. My goal is to implement an export function.
My view looks like this:
class ExportView(FormView):
template_name = 'ExportTemplate.html'
def get(self, request, *args, **kwargs):
self.form_class = ExportForm(user = request.user)
return render(request, self.template_name, {'form': self.form_class})
def get_success_url(self):
return '/addrbook/'
def form_valid(self, form):
# This method is called when valid form data has been POSTed.
# It should return an HttpResponse.
return super().form_valid(form)
form:
class ExportForm(forms.Form):
def __init__(self, user, *args, **kwargs):
usersContacts = ContactManager().getAllUsersContacts()
self.contactList = forms.ModelMultipleChoiceField(queryset = usersContacts[str(user)])
print(usersContacts[str(user)])
super(ExportForm, self).__init__(*args, **kwargs)
I verified that the queryset is not empty, it contains a list of model objects.
My template looks like this:
<form method="post">{% csrf_token %}
{{ form }}
<input type="submit">
</form>
the only thing that gets rendered is the submit button.
Another thing that left me completely unsure of python basics is that this code:
class ExportForm(forms.Form):
contactList = forms.ModelMultipleChoiceField(queryset = [])
def __init__(self, user, *args, **kwargs):
usersContacts = ContactManager().getAllUsersContacts()
self.contactList.queryset = usersContacts[str(user)]
print(usersContacts[str(user)])
super(ExportForm, self).__init__(*args, **kwargs)
returned the runtime error:
'ExportForm' object has no attribute 'contactList'
How is it possible? the contactList member is part of the ExportForm class definition and 'self' should point to an object of that class.
Could someone explain to me why the form field is rendered empty and/or point me to a better way to pass the user to the form?
Edit: here are the changes i made to the answer to get it working, although i now stumbled upon a different problem(the field expects a queryset, and not a list of model objects):
View:
class ExportView(FormView):
template_name = 'ExportTemplate.html'
form_class = ExportForm
def get_form_kwargs(self):
kwargs = super(ExportView, self).get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
form:
class ExportForm(forms.Form):
contactList = forms.ModelMultipleChoiceField(queryset = Contact.objects.none())
def __init__(self, *args, **kwargs):
user = kwargs['user']
kwargs.pop('user', None)
super(ExportForm, self).__init__(*args, **kwargs)
usersContacts = ContactManager().getAllUsersContacts()
self.fields['contactList'].queryset = usersContacts[str(user)]
print(self.fields['contactList'].queryset)
First of all, you should pass the user to the form every time you instantiate it, not just in the get method. The way to do this with FormView is to override get_form_kwargs.
def get_form_kwargs(self):
kwargs = super(ExportForm, self).get_form_kwargs()
kwargs[user] = self.request.user
return kwargs
You can then remove your get() method.
Then, in your form class, you should use the none() method instead of an empty list to get an empty queryset. In the __init__ method you can pop the user from kwargs, and then call super() before you edit the fields. You edit the contactList field via self.fields instead of self.contactList. Note that the recommended style for field names in Django is contact_list instead of contactList.
class ExportForm(forms.Form):
contactList = forms.ModelMultipleChoiceField(queryset=YourModel.objects.none())
def __init__(self, *args, **kwargs):
user = kwargs.pop('user')
super(ExportForm, self).__init__(*args, **kwargs)
usersContacts = ContactManager().getAllUsersContacts()
self.fields['contactList'].queryset = usersContacts[str(user)]
You haven't shown the ContactManager() code, but using str(user) as the dictionary key looks fragile. It would probably be better to use user.pk or user.username instead.

Django: Pass URL parameter to context_data in generic.CreateView

I have created two url paths.
path('addhours/<int:jobNr>/', views.AddHoursView.as_view(), name='addhours'),
path('addhours/', views.AddHoursView.as_view(), name='addhours'),
And a CreateView for these paths.
class AddHoursView(generic.CreateView):
template_name = "worktime/addhours.html"
form_class = AddHoursForm
success_url = reverse_lazy('worktime:myjobs')
def get_form_kwargs(self):
# pass "jobNr" keyword argument from the current url to form
kwargs = super(AddHoursView, self).get_form_kwargs()
kwargs['jobNr'] = self.kwargs.pop(JOB_PARAM, None)
return kwargs
And a form, where the number of fields depends on if the parameter exists in the URL.
class AddHoursForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
#Add jobworker field to from Worktime model if any jobNr pass in url
#When in url will be parameter. Job foreignkey will be set by automat.
self.jobNr = kwargs.pop('jobNr', None)
super(AddHoursForm, self).__init__(*args, **kwargs)
if not self.jobNr:
self.fields['jobWorker'] = forms.ModelChoiceField(queryset=Job.objects.all())
class Meta:
model = WorkTime
fields = ['date', 'hours', 'description']
widgets = {
'date': forms.SelectDateWidget(empty_label=("Choose Year",
"Choose Month",
"Choose Day"))
}
Now I want to have in AddHoursView context["jobNr"] = "bar" where the URL is /url/bar
In AddHoursView's def context_data(self, **kwargs) I tried:
self.request.GET gives me empty QueryDict
self.kwargs['jobNr'] give me a key error
I need access to the url parameter if it exist, to pass this jobNr to template, so override form_valid is not what I need.
At the moment you are popping the value from self.kwargs, which removes it. That means it is no longer there when you try to access it in get_context_data. Since the argument is optional, you can use get() instead.
Change the method to:
def get_form_kwargs(self):
kwargs = super(AddHoursView, self).get_form_kwargs()
kwargs['jobNr'] = self.kwargs.get('JOB_PARAM')
return kwargs
Then in get_context_data you can do:
def get_context_data(self, **kwargs):
kwargs = super(AddHoursView, self).get_context_data(**kwargs)
kwargs['jobNr'] = self.kwargs.get('JOB_PARAM')
return kwargs

Python. Django. Change feeds of form from get_context_data in CreateView

I have an issue with my Create view. I initialise it like this:
class OutputCreateView(LoginRequiredMixin, generic.CreateView):
template_name = 'rcapp/common_create_update.html'
form_class = OutputForm
model = Output
def get_context_data(self, **kwargs):
context = super(OutputCreateView, self).get_context_data(**kwargs)
# self.form_class.fields['activity_ref'].queryset = Activity.objects.filter(rc_ref=ResultsChain.objects.get(pk=self.kwargs['rc']).pk)
context['is_authenticated'] = self.request.user.is_authenticated
return context
def form_valid(self, form):
# code code code
return redirect("/portal/edit/" + str(self.kwargs['rc']) + "/#outputs-table")
I have a ForeignKey Field in my model and I wanted to filter options for current view.
My form is set like this:
class OutputForm(forms.ModelForm):
class Meta:
model = Output
fields = ['value', 'activity_ref']
widgets = {
'value': forms.Select(choices=(#Choises here
,), attrs={"onChange":'select_changed()', 'class':'selector'})
}
I need to change a queryset for the activity_ref field.
You can see a commented line in get_context_data, it's where I tried to do this. But it didn't work. How can I get what I need?
You need to pass the choices / queryset to your form.
in OutputCreateView
def get_form_kwargs(self, *args, **kwargs)
filter_key = self.kwargs['rc']).pk
return {'filter_key': key}
Like this, it will give an error in when your form gets created, because of the unexpected argument. To get around that and to make use of it, override the init method.
In your OutputForm
def __init__(self, *args, **kwargs)
kwargs.pop('filter_key')
super()._init__(*args, **kwargs)
self.fields['value'] = forms.Select(queryset=Activity.objects.filter(rc_ref=ResultsChain.objects.get(pk=self.kwargs['rc']).pk),
attrs={"onChange":'select_changed()', 'class':'selector'})
You don't need to set the widgets value, as it is being done in the init method.

Django: Using get_form_kwarg to pass url pramater into form __init__ for ModelChoiceField selection filiter

I am building a FAQ app.
Model flow Topic -> Section -> Article.
Article has a FK to Section which has a FK to Topic.
In my create article from I want to take in the Topic_Pk so when the user selects a Section the choice selection is limited to just the Sections attached under the Topic.
I am using get_from_kwarg to pass the Topic_Pk from the url to __init__ in the form. I keep getting a TypeError __init__() got an unexpected keyword argument 'topic_pk'. I do not want to pop the data or set topic_pk=None in the __init__ parameters as this would invalidate the whole point.
What is it I am missing to allow me to use this variable?
Url:
url(r'^ironfaq/(?P<topic_pk>\d+)/article/create$', ArticleCreateView.as_view()),
View:
class ArticleCreateView(CreateView):
model = Article
form_class = CreateArticleForm
template_name = "faq/form_create.html"
success_url = "/ironfaq"
def get_form_kwargs(self):
kwargs = super(ArticleCreateView,self).get_form_kwargs()
kwargs.update(self.kwargs)
return kwargs
Form:
class CreateArticleForm(forms.ModelForm):
section = forms.ModelChoiceField(queryset=Section.objects.none())
def __init__(self, *args, **kwargs):
super(CreateArticleForm, self).__init__(*args, **kwargs)
self.fields['section'].queryset = Section.objects.filter(topic_pk=self.kwargs['topic_pk'])
class Meta:
model = Article
widgets = {
'answer': forms.Textarea(attrs={'data-provide': 'markdown', 'data-iconlibrary': 'fa'}),
}
fields = ('title','section','answer')
Model:
class Article(Audit):
title = models.CharField(max_length=255)
sort = models.SmallIntegerField()
slug = models.SlugField()
section = models.ForeignKey(Section,on_delete=models.CASCADE)
answer = models.TextField()
vote_up = models.IntegerField()
vote_down = models.IntegerField()
view_count = models.IntegerField(default=0)
class Meta:
verbose_name_plural = "articles"
def __str__(self):
return self.title
def total_votes(self):
return self.vote_up + self.vote_down
def percent_yes(self):
return (float(self.vote_up) / self.total_votes()) * 100
def get_absolute_url(self):
return ('faq-article-detail',(), {'topic__slug': self.section.topic.slug,
'section__slug': self.section.slug, 'slug': self.slug})
For your current __init__ signature, you must pop topic_pk from kwargs before you call super(), otherwise you'll get the TypeError.
In your question, you say that popping the value would 'invalidate the whole point', but I think you're mistaken. You can still use the topic_pk value after calling super().
class CreateArticleForm(forms.ModelForm):
section = forms.ModelChoiceField(queryset=Section.objects.none())
def __init__(self, *args, **kwargs):
topic_pk = kwargs.pop('topic_pk')
super(CreateArticleForm, self).__init__(*args, **kwargs)
self.fields['section'].queryset = Section.objects.filter(topic_pk=topic_pk)
Another approach would be to use topic_pk as a named argument. Note that this changes the signature of the __init__ method, so it might break other code (for example if you had CreateArticleForm(request.POST) somewhere else).
def __init__(self, topic_pk=None, *args, **kwargs):
super(CreateArticleForm, self).__init__(*args, **kwargs)
self.fields['section'].queryset = Section.objects.filter(topic_pk=topic_pk)

Django, passing information when modifying queryset for ModelForm

Database:
Document has many Sections, Sections has many Comments
On each document page, there is a comment form that lets you pick the section (using a ModelChoiceField). The problem is that the ModelChoiceField will contain ALL sections for all documents.
So to limit them I do this:
class CommentForm(ModelForm):
def __init__(self, *args, **kwargs):
super(CommentForm, self).__init__(*args, **kwargs)
if self.instance:
logger.debug(self.instance.document_id) # Prints "None"
self.fields['section'].queryset = Section.objects.filter(document=self.instance.document)
# ^^^ Throws DoesNotExist as self.instance.document is None
and my view is just:
form = CommentForm()
How do I pass CommentForm a document id?
Edit: Tried in my view:
d = Document.objects.get(id=id)
c = Comment(d)
form = CommentForm(c)
but document_id is still None in CommentForm
You can pass the document id when initialising the form:
class CommentForm(ModelForm):
def __init__(self, doc_id=None, *args, **kwargs):
if doc_id:
self.fields['section'].queryset = Section.objects.filter(document__id=doc_id)
and in the view
def my_view(request):
...
doc = Document.objects(...)
form = CommentForm(doc_id = doc.id)
EDIT
I edited the second line of the view, which I think deals with your comment? (make doc.id) a keyword arguement

Categories

Resources