How to populate initial values of form from queryset - django

I have a FormView with a get_initial method which I am trying to use to populate the form. I am trying to get the EmployeeTypes of the receiver of the memo as values in the form.
def get_initial(self):
initial = super(NotificationView, self).get_initial()
users = Memo.objects.filter(id=self.kwargs['pk']).values('receiver__employee_type')
initial['receiving_groups'] = users
return initial
There are 2 issues here..
This returns a Queryset which looks like: <QuerySet [{'receiver__employee_type': 'Bartender'}, {'receiver__employee_type': 'Supervisor'}]> when I really need the fields in the form to be the EmployeeType itself.
Most importantly - the form isn't even rendering these fields.
Here is the form just in case:
class MemoNotificationForm(forms.Form):
class Meta:
fields = [
'receiving_groups'
]
receiving_groups = forms.MultipleChoiceField(
required=False,
widget=forms.CheckboxSelectMultiple)
How do I populate the fields of the form?
EDIT:
class Memo(models.Model):
receiver = models.ManyToManyField(EmployeeType, related_name='memos_receiver')
class EmployeeType(models.Model):
"""Stores user employee type."""
employee_type = models.CharField(
max_length=32,
unique=True)

Having a Meta on a forms.Form doesn't do anything, this is used for ModelForms
If receiving_groups should be choices of EmployeeType then it should be a ModelMultipleChoiceField
class MemoNotificationForm(forms.Form):
receiving_groups = forms.ModelMultipleChoiceField(
EmployeeType.objects.all(),
widget=forms.CheckboxSelectMultiple
)
Then you should be passing instances, or a queryset of the model in the initial
def get_initial(self):
initial = super(NotificationView, self).get_initial()
initial['receiving_groups'] = EmployeeType.objects.filter(memo__id=self.kwargs['pk'])
return initial
EDIT:
As a ModelForm this could look like so
class MemoNotificationForm(forms.ModelForm):
class Meta:
model = Memo
fields = ('receiver', )
View:
class NotificationView(FormView);
form_class = MemoNotificationForm
def get_form_kwargs(self):
kwargs = super(NotificationView, self).get_form_kwargs()
kwargs['instance'] = get_object_or_404(Memo, id=self.kwargs['pk'])
return kwargs

While #lain Shelvington is correct in the process he used to produce the form results, I had to do a little editing to make the code operate correctly...
def get_initial(self):
initial = super(NotificationView, self).get_initial()
receivers = Memo.objects.filter(id=self.kwargs['pk']).values_list('receiver')
initial['receiving_groups'] = EmployeeType.objects.filter(employee_type=receivers)
return initial

Related

Create form to change relationship from related model's form

I have two models:
class Thing(forms.ModelForm):
class Owner(forms.ModelForm):
thing = models.OneToOneField(Thing)
I want to add a form to change the owner in Thing's UpdateView. I can do it like this:
class ThingForm(forms.ModelForm):
owner = forms.ModelChoiceField(
queryset=Owner.objects.all(),
)
class Meta:
model = Thing
fields = '__all__'
And then process the result inside form_valid() method. But isn't there a more direct approach for this, where i just add this to the fields of the form?
UPDATE
So I ended up doing it like this:
class ThingUpdateView(UpdateView):
model = Thing
form_class = ThingForm
def get_initial(self):
initial = super(ThingUpdateView, self).get_initial()
try:
initial['owner'] = self.object.owner
except Thing.owner.RelatedObjectDoesNotExist:
pass
return initial
def form_valid(self, form):
self.object = form.save(commit=False)
owner = form.cleaned_data['owner']
owner.thing = self.object
owner.save(update_fields=['thing'])
self.object.save()
return redirect(self.object.get_absolute_url())
Maybe there's a better way.

Can I specify or change fields to use in a ModelFrom from within my Views.py?

Let's say I have the following ModelForm:
forms.py
class JournalEntryForm(ModelForm):
def clean_date(self):
data = self.cleaned_data['date']
# Some validation...
return data
class Meta:
model = JournalEntry
fields = ['user','date','description']
widgets = {'date': DateTypeInput()}
I want to reuse the above modelform in several views. But the different views require different fields to be used from the model form. Is there a way I can "over ride" the fields in the ModelForm meta?
I tried this:
#login_required
def entries_update(request, pk):
journal_entry = get_object_or_404(JournalEntry, pk=pk)
if request.method == 'POST':
journal_entry_form = JournalEntryForm(request.POST, instance=journal_entry)
journal_entry_form.fields = ['date','description'] # Just testing !
if journal_entry_form.is_valid():
journal_entry_form.save()
messages.success(request, "Journal entry successfully updated.")
return HttpResponseRedirect(reverse('journal:entries_show_detail', kwargs={'pk': journal_entry.id}) )
return render(request, 'journal/entries_update.html',{'journal_entry': journal_entry, 'journal_entry_form': journal_entry_form, })
I get an error:
Exception Value: 'list' object has no attribute 'items'
The end of the traceback:
C:\Users\Philip\CodeRepos\Acacia2\venv\lib\site-packages\django\forms\forms.py in _clean_fields
for name, field in self.fields.items(): …
▼ Local vars
Variable Value
self
<JournalEntryForm bound=True, valid=True, fields=(date;description)>
I think there are a number of ways to achieve what your are aiming at. The easiest approach would be to define a BaseForm class with different Meta and pick the appropriate Form in your views:
class JournalEntryForm(ModelForm):
def clean_date(self):
data = self.cleaned_data['date']
# Some validation...
return data
class Meta:
model = JournalEntry
fields = ['user','date','description']
widgets = {'date': DateTypeInput()}
class JournalEntryForm2(JournalEntryForm):
class Meta:
model = JournalEntry
fields = ['date','description']
widgets = {'date': DateTypeInput()}
Another quite nice approach is defining a function to create your required form at runtime as described here:
def get_journal_form(myfields):
class JournalEntryForm(ModelForm):
class Meta:
model = JournalEntry
fields = myfields
return JournalEntryForm
and create the form in your view based on whatever condition
FormClass = get_journal_form(['date', 'description'])
A third approach could be overidng the '__init__' method of your form, passing your unwanted fields to the __init__
class JournalEntryForm(ModelForm):
class Meta:
model = JournalEntry
fields = ['user', 'date', 'description']
def __init__(self, *args, **kwargs):
unwanted_fields = kwargs.pop('unwanted_fields')
super().__init__(*args, **kwargs)
for f in unwanted_fields:
self.fields.pop(f)

django fill form field automatically from context data

I have a form attached to a DetailedView and its working fine when saved. I would like the form field(position) to be prepopulated with the value coming from the slug of the detailed view(e.g jobs/human-resource-manager). The Model of the form field has a Foreignkey to the JobPost model. Need help. Part of my view looks like this
class JobsDetailView(DetailView):
model = JobPost
template_name = 'job_post-detail.html'
def get_context_data(self, **kwargs):
context = super(JobsDetailView, self).get_context_data(**kwargs)
context['position'] = JobPost.objects.order_by('position')
context['job_app_form'] = JobsForm()
return context
foms.py
from django import forms
from job_post.models import JobsApplied
class JobsForm(forms.ModelForm):
class Meta:
model = JobsApplied
fields = '__all__'
def form_valid(self, form):
form.instance.customuser = self.request.user
return super().form_valid(form)
I'm assuming you do not want your users to be able to interact with or change these prefilled values.
I'm making a comments/review model and I want it to automatically link reviews to the people they are about
models.py
class Review(models.Model):
person = models.ForeignKey(Person, on_delete=models.CASCADE)
...
I hide the person field in the ReviewsForm to prevent user input by either omitting it from the 'fields' or adding it to an 'exclude'.
forms.py
class ReviewsForm(forms.ModelForm):
class Meta:
model = Review
fields = ('rating', 'summary', 'review_text')
Then, when processing the form in the view, I use commit=False so I can manipulate field values before saving to the database.
Include prefilled values, save and then redirect the user wherever is ideal
views.py
def person(request, area_slug, category_slug, person_id):
...
if form.is_valid():
pending_review = form.save(commit=False)
pending_review.person = Person.objects.get(pk = person_id)
pending_review.save()
return HttpResponseRedirect(...)
django fill form field automatically from context data for django form and django formsets
For formsets in forms.py
StoreRequestAccessoryUpdateFormSet = forms.modelformset_factory(StoreRequestAccessory, form=StoreRequestAccessoryUpdateForm, exclude=["storeRequestId"], can_delete=True)
In get_context_data you can add it as you like for django
class StoreRequestUpdateView(LoginRequiredMixin, UpdateView):
template_name = "Inventory/Stock/StoreRequest/StoreRequestUpdateView.html"
model = StoreRequest
fields = ["fromStoreId", "toStoreId", "reference", "status", "remark"]
def get_context_data(self, **kwargs):
context = super(StoreRequestUpdateView, self).get_context_data(**kwargs)
print(self.object.pk)
context.update({
# "StoreRequestForm": context.get("form"),
"StoreRequestForm": StoreRequestUpdateForm(instance=StoreRequest.objects.get(id=self.object.pk)),
"StoreRequestAccessoryForm": StoreRequestAccessoryUpdateFormSet(
queryset=StoreRequestAccessory.objects.filter(storeRequestId=self.object.pk),
prefix="storereq_accessory_form"),
})
return context

How can I change the queryset of one of the fields in the form I'm passing to inlineformset_factory

I'm using django extra views:
# views.py
from django.forms.models import inlineformset_factory
from extra_views import (CreateWithInlinesView, UpdateWithInlinesView,
InlineFormSet, )
class LinkInline(InlineFormSet):
model = Link
form = LinkForm
extra = 1
def get_form(self):
return LinkForm({})
def get_formset(self):
return inlineformset_factory(self.model, self.get_inline_model(), form=LinkForm, **self.get_factory_kwargs())
class TargetCreateView(BaseSingleClient, CreateWithInlinesView):
model = Target
form_class = TargetForm
inlines = [LinkInline, ]
template_name = 'clients/target_form.html'
I want this 'keywords' field to change based on the pk I pass to the view through the url.
# forms.py
class LinkForm(forms.ModelForm):
keywords = forms.ModelMultipleChoiceField(queryset=ClientKeyword.objects.filter(client__pk=1))
class Meta:
model = Link
I could manage to overwrite the form's init, however:
I don't have access to self.kwargs inside LinkInline
Even if I did, I'm not sure I can pass an instantiated form to inlineformset_factory()
Ok, if any poor soul needs an answer to how to accomplish this... I managed to do it by overwriting construct_inlines() (which is part of extra_views.advanced.ModelFormWithInlinesMixin) and modifying the field's queryset there.
class TargetCreateView(BaseSingleClient, CreateWithInlinesView):
model = Target
form_class = TargetForm
inlines = [LinkInline, ]
template_name = 'clients/target_form.html'
def construct_inlines(self):
'''I need to overwrite this method in order to change
the queryset for the "keywords" field'''
inline_formsets = super(TargetCreateView, self).construct_inlines()
inline_formsets[0].forms[0].fields[
'keywords'].queryset = ClientKeyword.objects.filter(
client__pk=self.kwargs['pk'])
return inline_formsets
def forms_valid(self, form, inlines):
context_data = self.get_context_data()
# We need the client instance
client = context_data['client_obj']
# And the cleaned_data from the form
data = form.cleaned_data
self.object = self.model(
client=client,
budget=data['budget'],
month=data['month']
)
self.object.save()
for formset in inlines:
f_cd = formset.cleaned_data[0]
print self.object.pk
link = Link(client=client,
target=self.object,
link_type=f_cd['link_type'],
month=self.object.month,
status='PEN',
)
# save the object so we can add the M2M fields
link.save()
for kw in f_cd['keywords'].all():
link.keywords.add(kw)
return HttpResponseRedirect(self.get_success_url())

How to pass a queryset to a ModelChoiceField using a self.field_value in Django ModelForms

I could explain the whole thing to you but I guess a code speaks clearer than words so:
class Skills(models.Model):
skill = models.ForeignKey(ReferenceSkills)
person = models.ForeignKey(User)
class SkillForm(ModelForm):
class Meta:
model = Skills
fields = ( 'person', 'skill')
(???)skill = forms.ModelChoiceField(queryset= SkillsReference.objects.filter(person = self.person)
I'm just guessing at how I can do it. But I hope you guys understand what I'm trying to do.
You can ovverride a form structure before you create an instance of the form like:
class SkillForm(ModelForm):
class Meta:
model = Skills
fields = ( 'person', 'skill')
In your view:
SkillForm.base_fields['skill'] = forms.ModelChoiceField(queryset= ...)
form = SkillForm()
You can override it anytime you want in your view, impottant part is, you must do it before creating your form instance with
form = SkillForm()
Assuming you are using class-based views, you can pass the queryset in your form kwargs and then replace it on form init method:
# views.py
class SkillUpdateView(UpdateView):
def get_form_kwargs(self, **kwargs):
kwargs.update({
'skill_qs': Skills.objects.filter(skill='medium')
})
return super(self, SkillUpdateView).get_form_kwargs(**kwargs)
# forms.py
class SkillForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
qs = kwargs.pop('skill_ks')
super(self, SkillForm).__init__(*args, **kwargs)
self.fields['skill'].queryset = qs
But, personally I prefer this second approach. I get the form instance on the View and than replace the field queryset before django wrap it on the context:
# views.py
class SkillsUpdateView(UpdateView):
form_class = SkillForm
def get_form(self, form_class=None):
form = super().get_form(form_class=self.form_class)
form.fields['skill'].queryset = Skills.objects.filter(skill='medium')
return form
Your code looks almost ok. Try this SkillForm:
class SkillForm(ModelForm):
skill = forms.ModelChoiceField(queryset= SkillsReference.objects.filter(person = self.person)
class Meta:
model = Skills
fields = ( 'person', 'skill')
The difference is that skill is a form's field, should not be in Meta class
EDITED
The above solution is incorrect, but this link describes how to achieve what you want:
http://www.zoia.org/blog/2007/04/23/using-dynamic-choices-with-django-newforms-and-custom-widgets/