Django 1.9.7
Updating and creation are more or less similar operations. Sometimes I would like to add JavaScript events as widget attrs. For example, users select a gender. Depending on that I'd like to show or hide maiden name. This is just an example of some operation common to UpdateView and CreateView.
I tried to organize it as a mixin, but failed: UpdateView and CreateView both use FormMixin. If I organize another mixin inheriting from FormMixin, I get a clash.
Could you help me understand how to cope without repeating the code?
class PersonUpdateView(UpdateView):
model = Person
fields = ['gender', 'last_name']
class PersonCreate(CreateView):
model = Person
fields = ['gender', 'last_name']
redirect_name = "people"
field_attrs = {'gender': {'onchange':"alert('G')"},
'last_name': {'onclick': "alert('LN')"},
}
def get_form(self, form_class=None):
form = super(PersonCreate, self).get_form(form_class)
for key, value in self.field_attrs.items():
form.fields[key].widget.attrs = value;
return form
Your mixin should be a simple class:
class CreateUpdateMixin(object):
model = Person
fields = ['gender', 'last_name']
field_attrs = {'gender': {'onchange':"alert('G')"},
'last_name': {'onclick': "alert('LN')"},
}
Then you use it like so:
class PersonUpdateView(CreateUpdateMixin, UpdateView):
pass
class PersonCreate(CreateUpdateMixin, CreateView):
redirect_name = "people"
def get_form(self, form_class=None):
form = super(PersonCreate, self).get_form(form_class)
for key, value in self.field_attrs.items():
form.fields[key].widget.attrs = value;
return form
Related
I`m trying to make a CreateView form that takes the UID of the object as a foreign key from the previous page.
Here I got DetailView of Plan model:
class PlanDetailView(IsStaffPermissionMixin, DetailView):
model = Plan
template_name = "backoffice/plans/plan_detail.html"
context_object_name = 'plan'
def get_object(self):
uid = self.kwargs.get("uid")
return get_object_or_404(Plan, uid=uid)
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super().get_context_data(**kwargs)
practice_sessions = PracticeSession.objects.all().filter(plan__uid=self.kwargs.get("uid"))
context['practice_sessions'] = practice_sessions
return context
And here I got a PracticeSession CreateView:
class PracticeSessionCreateView(IsStaffPermissionMixin, CreateView):
model = PracticeSession
template_name = "backoffice/practice_session/practice_session_create.html"
fields = ['uid', 'title', 'plan', 'snippet', 'welcome_content', 'done_content', 'order_index', ]
success_url = reverse_lazy('practice-sessions')
As you understand, PracticalSession contains Foreign Key for the Plan model
Now I want that when I click on the button to create a PracticalSession (the picture below), I create a form in which the plan field already contains a "uid" of Plan from the page of which I create a new PracticalSession
My Form:
class PracticeSessionCreateForm(ModelForm):
class Meta:
model = PracticeSession
fields = '__all__'
Big THANK YOU In advance !!!
Here's my scenario:
I have two models:
class Person(models.Model):
# --- model fields ---
class Qualification(models.Model):
owner = models.ForeignKey(Person, on_delete=models.CASCADE)
# --- other fields ---
And Model forms:
class PersonalForm(forms.ModelForm):
class Meta:
model = Person
fields = ['first_name', 'last_name', 'email', 'id_number', 'date_of_birth']
class IsQualifiedForm(forms.ModelForm):
class Meta:
model = Person
fields = ['is_qualified']
class QualificationForm(forms.ModelForm):
class Meta:
model = Qualification
fields = ['level', 'course_name', 'attainment']
And finally my wizard view:
class Wizard(SessionWizardView):
template_name = 'demo/wizard_test.html'
form_list = [
("personal", PersonalForm),
("is_qualified", IsQualifiedForm),
("qualifications", QualificationForm),
]
def get_form_instance(self, step):
return self.instance_dict.get(step, None)
def done(self, form_list, **kwargs):
# What is the exact logic to be applied here to save the model forms concurrently?
return redirect('home')
I'm trying to save the form but I run into errors:
When I try to call:
for form in form_list:
form.save()
in the done() method, I get an error because the is_qualified is intercepted as null in the first step.
Plus, how do I get to set the owner field's value to the currently created person?
Any help would be appreciated.
If is_qualified is not nullable in your Person model, validation will always fail. What you can do is save both PersonalForm and IsQualifiedForm in one go, since they refer to the same model anyways. To do this, set the values of one form in the other. For example:
def done(self, form_list, **kwargs):
person = form_list[0].save(commit=False)
person.is_qualified = form_list[1].cleaned_data['is_qualified']
person.save()
return redirect('home')
Some notes:
You should probably use named steps instead of relying on form index
If your case is as simple as the form you provided, you should just make the first two forms a single form
I have a form that is used to edit (update) a record, and the Author field is automatically a dropdown, which is great, but how do you filter this list?
For example, the dropdown is populated with the entire user list. How can I filter this list so that it only shows the items where isDevice == True?
accounts/models.py
from django.contrib.auth.models import AbstractUser
class CustomUser(AbstractUser):
isDevice = models.BooleanField(default = False)
...
builds/models.py
class BuildQueue(models.Model):
Author = models.ForeignKey(CustomUser,blank=True, null= True, on_delete=models.CASCADE)
...
forms.py
class BuildQueueEditForm(forms.ModelForm):
class Meta:
model = BuildQueue
fields = ['Author','project', 'customer',]
views.py
class buildQueueEdit(LoginRequiredMixin,UpdateView):
model = BuildQueue
form_class = BuildQueueEditForm
template_name = 'buildQueue_edit.html'
Since UpdateView inherited also from FormMixin, in your buildQueueEdit you can override get_form, where form is instantiated and exactly where you can modify the form's field's queryset.
class buildQueueEdit(LoginRequiredMixin,UpdateView):
model = BuildQueue
form_class = BuildQueueEditForm
template_name = 'buildQueue_edit.html'
def get_form(self, form_class=None):
form = super().get_form(form_class)
form.fields['Author'].queryset = CustomUser.objects.filter(isDevice=True)
return form
UPDATE
If you want to change text displayed in your dropdown you can override choises instead of queryset. It worked for me.
form.fields['Author'].choices = [(item.id, item.equipmentName) for item in CustomUser.objects.filter(isDevice=True)]
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
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/