I am working on an UpdateView of a form. I am having hard time displaying the initial value of grades per subject. I can display the subjects fine in the template, however I can't display the grades using the DecimalField. If change DecimalField to ModelChoiceField in forms.py, I can view the grades in a drop down menu in template but this is not what I want. I want the user to be able to edit using DecimalField.
forms.py
class GradeUpdateForm(CrispyFormMixin, forms.ModelForm):
s_name = forms.ModelChoiceField(queryset=Subject.objects.none(), empty_label=None)
final_grade = forms.DecimalField(widget=forms.NumberInput(attrs={'style':'width:80px'}), decimal_places=2, max_digits=5,)
class Meta:
model = SGrade
fields = [
's_name',
'final_grade',
]
views.py
class SchoolDashboardGradesUpdateView(SchoolStudentMixin, UpdateView):
template_name = 'education/dashboard/grades_update.html'
model = SubjectGrade
form_class = GradeUpdateForm
# def get_object(self):
# return get_object_or_404(Recipient, pk=self.kwargs['pk'])
def get(self, request, *args, **kwargs):
self.object = None
form_class = self.get_form_class()
form = self.get_form(form_class)
form.fields['subject_name'].queryset = Subject.objects.filter(
sgrade__recipient__id=self.kwargs.get('pk'),
sgrade__status='approved').values_list('name', flat=True)
form.fields['final_grade'].queryset = SGrade.objects.filter(
recipient__id=self.kwargs.get('pk'),
status='approved').values_list('final_grade', flat=True)
student = Student.objects.get(id=self.kwargs.get('pk')
for x in student.sgrade_set.all():
if x.status == 'approved':
forms.fields['final_grade'].initial = x.final_grade
template
<tbody>
{% for instance in form.subject_name.field.choices %}
<tr>
<td>{{instance.1}}</td>
<td>{{form.final_grade}}</td>
</tr>
{% endfor %}
</tbody>
Any suggestions on how to approach this? I am a complete beginner.
Related
Im trying to add a field called, interested_fields inside my personalInfo model which users can choose from and the choices themselves come from another models' objects with the help of ManyToMany relation between the two models. Here are my models.py codes(I simplified my personal model by removing some other fields like name, age, etc in order to make it more readable for you):
class Field(models.Model):
id = models.AutoField(primary_key=True)
slug = models.CharField(max_length=16, default='default')
title = CharField(max_length=32)
class PersonalInfo(models.Model):
id = models.AutoField(primary_key=True)
interested_fields = models.ManyToManyField(Field, blank=True)
then, I created a ModelForm like this:
class InterestedFieldsForm(forms.ModelForm):
interested_fields = forms.MultipleChoiceField(widget=forms.CheckboxSelectMultiple, choices=Field.objects.all(), required=False)
class Meta:
model = PersonalInfo
fields = ['interested_fields']
and created a get and post functions inside my views like this:
class PersonalView(View):
template_name = 'reg/personal.html'
def get(self, request, *args, **kwargs):
context = {}
context['fields'] = Field.objects.all()
return render(request, self.template_name, context=context)
def post(self, request, *args, **kwargs):
user = request.user
if request.method == 'POST':
form = InterestedFieldsForm(request.POST)
if form.is_valid():
profile = form.save(commit=False)
profile.user = request.user
profile.save()
else:
form = InterestedFieldsForm()
return render(request, 'reg/done.html', context={'form': form})
and finally in template, inside the form I added this for loop:
{% for field in fields %}
<label class="containerq ant-col ant-col-md-6 ant-col-xs-8" >
<span>
<input type="checkbox" name="interested_fields" {% if field.slug in user.personalInfo.interested_fields %} checked="checked" {% endif %} value="{{field.title}}">
<span style="margin-left:7px" class="checkmark"></span>
</span>
<span>{{field.title}}</span>
</label>
{% endfor %}
when I submit the form it gives me this error:
cannot unpack non-iterable Field object
Im new to django so I really dont know what am I doing wrong. thank you for your answers
You should use a ModelMultipleChoiceField
interested_fields = forms.ModelMultipleChoiceField(widget=forms.CheckboxSelectMultiple, queryset=Field.objects.all(), required=False).
I got two models:
Project:
class Project(Model):
name = CharField(max_length=50)
members = ManyToManyField("accounts.User", through='ProjectUser')
organization = ForeignKey(Organization, related_name="projects", on_delete=CASCADE)
def __str__(self):
return self.name
and Task:
class Task(Model):
task = CharField(max_length=100)
project = ForeignKey(Project, on_delete=CASCADE)
class Meta:
db_table = 'task'
I got a UpdateView class:
class ProjectUpdateView(UpdateView):
form_class = ProjectUpdateForm
template_name = 'projects/project_edit.html'
success_url = reverse_lazy('projects:list')
How can I allow a user to add tasks (through an inline formset) on the same page as where they'd edit a Project instance?
E.g one consolidated form where the user can edit the Project name, and add / remove Task instances, all in one place
Form/Formset:
First, create a form and a formset for your Task model
class TaskForm(ModelForm):
class Meta:
model = Task
fields = ['task']
def __init__(self, *args, **kwargs):
super(TaskForm, self).__init__(*args, **kwargs)
class TaskBaseFormSet(BaseInlineFormSet):
def __init__(self, *args, **kwargs):
super(TaskBaseFormSet, self).__init__(*args, **kwargs)
TaskFormset = inlineformset_factory(
Project, # parent_model
Task, # model
form=TaskForm,
formset=TaskBaseFormSet
)
Or maybe all that you need to do to create a TaskFormset if you dont need a TaskForm class is this
TaskFormset = inlineformset_factory(Project, Task, fields=('task',))
View:
I see you're using a UpdateView class for your view, so you can do this to get a TaskFormset in your context_data, so now you can use the TaskFormset in the template that you declared in the 'template_name' property of your UpdateView class
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
if self.request.POST:
context['task_formset'] = forms.TaskFormset(self.request.POST)
else:
context['task_formset'] = forms.TaskFormset()
return context
# In the form_valid method of your UpdateView class you can validate the data
# and assign the Project instance to all the tasks that were create by the formsets
def form_valid(self, form):
task_formset = context['task_formset']
# you can validate formset data like this
if not task_formset.is_valid():
return self.form_invalid(form)
project = form.save()
# Here you assign the Project instance to the Tasks
task_formset.instance = project
task_formset.save()
return super().form_valid(form)
Template:
Now all that you need to do is to print the management_form and each form from the formset using a loop as you can see in the code below
<form method="post">
<!-- Your ProjectUpdateForm form here... -->
{{ task_formset.management_form }}
<table>
{% for form in task_formset %}
{{ form }}
{% endfor %}
</table>
</form>
Hope this can help! There are some links to the official Django documentation that you may find useful:
https://docs.djangoproject.com/en/3.1/topics/forms/formsets/#using-a-formset-in-views-and-templates
https://docs.djangoproject.com/en/3.1/topics/forms/modelforms/#inline-formsets
https://docs.djangoproject.com/en/3.1/ref/forms/models/#inlineformset-factory
I'm trying to create Car Rental website similar to Hyrecar. I created a form according to the Django tutorial "Working with forms", but I don't know how to render information I got from the POST forms. I want to make information(Booking Name,rental price ...etc) that I got from POST show up the car and its detail which is booked. Car is foreign key field in booking.i want to redirect it to the page showing that car which is booked .
for eg
Booking name : xyz
Rental Price : 123
CAr : carimage.jpg
4 .
I want to redirect it to the page pop. if the user booked a car and post the form . after that redirect it to the pop.html page and show the booking detail that the user posted now .
Forms.py
class BookingForm(ModelForm):
class Meta:
model = Booking
widgets = {
'times_pick': forms.TimeInput(attrs={'class':'timepicker'}),
}
fields = ('booking_name','rental_price','book_car','customer_name','times_pick',)
urls.py
[
url(r'^booking/',views.BookingView.as_view(),name='car_booking'),
url(r'^pop/$',views.PopView.as_view(),name='pop'),
]
views.py
class CarDetailView(DetailView):
context_object_name = 'car_details'
model = models.Car
template_name = 'buggy_app/car_detail.html'
class BookingView(FormView):
template_name = 'buggy_app/booking.html'
form_class = BookingForm
models = Booking
def form_valid(self, form):
form.save()
return super(BookingView, self).form_valid(form)
success_url = reverse_lazy('index')
def get_context_data(self, **kwargs):
# kwargs['car'] is the car booking now!
try:
kwargs['car'] = Car.objects.get(id=self.request.GET.get('car', ''))
except (Car.DoesNotExist, ValueError):
kwargs['car'] = None
return super(BookingView, self).get_context_data(**kwargs)
def get_initial(self):
initial = super(BookingView, self).get_initial()
if 'car' in self.request.GET:
try:
initial['book_car'] = Car.objects.get(id=self.request.GET['car'])
except (Car.DoesNotExist, ValueError):
pass
return initial
booking.html
<form method="POST">
{% csrf_token %}
{% bootstrap_form form %}
<input type="submit" class='btn btn-primary' value="Submit">
</form>
Try like this
def save_form(request):
args = {}
form = BookCarForm(request.POST)
if form.is_valid():
book = form.save(commit=False)
book.book_car_mark = request.POST.get('car_mark')
book.book_car_mmodel = request.POST.get('car_model')
book.book_car_year = request.POST.get('car_year')
book.book_car_mark = request.POST.get('car_mark')
form.save()
try:
args['book'] = Book.objects.get(id=book.id)
except:
args['book'] = None
if args['book'] is not None:
return render(request, 'your_template.html', args)
else:
return HttpResponseRedirect('/your/url/to-booking-form/')
Name of fields as name of models and templates are abstract, so it's just a working mechanism scheme
I am having problem saving inline forms. It does save default forms. But whenever i add new inline forms, It doesnt save. What am i missing? can anyone show me the mistake? Thank you.
models.py
class Student(models.Model):
name = models.CharField(max_length=20)
def __unicode__(self):
return self.name
class Course(models.Model):
student = models.ForeignKey(Student)
course = models.CharField(max_length=18)
forms.py
class StudentForm(forms.ModelForm):
class Meta:
model = Student
class CourseForm(forms.ModelForm):
class Meta:
model = Course
CourseFormset = inlineformset_factory(Student,Course, extra=1)
views.py
class CourseCreateView(View):
def post(self,request, *args, **kwargs):
form = StudentForm(request.POST)
if form.is_valid():
std = form.save(commit=False)
formset = CourseFormset(request.POST, instance=std)
if formset.is_valid():
std.save()
formset.save()
return HttpResponseRedirect("/course/list")
def get(self, request,*args, **kwargs):
studentform = StudentForm()
formset = CourseFormset()
return render(request,'example/course_form.html', {'formset': formset, 'studentform': studentform})
and the jquery-formset.js
https://dpaste.de/sVPT0/
Well I can't see any mistakes, but maybe you can use easier solution to add new form, so that you won't have to use jquery formset at all.
Formset class has nice attribute called empty_form:
https://docs.djangoproject.com/en/1.4/topics/forms/formsets/#empty-form
You can pass it as "empty_form" context variable and add this script inside template:
<script type="text/template" id="row-template">
<tr>
{% for field in empty_form %}
<td>{{ field }}</td>
{% endfor %}
</tr>
</script>
<script type="text/javascript">
var formset = {};
$(function() {
$('.btn-add-extra-form').click(function() {
formset.$total = $('#id_rows-TOTAL_FORMS');
formset.$initial = $('#id_rows-INITIAL_FORMS');
formset.templateRowStr = $('#row-template').html();
formset.newTotal = parseInt(formset.$total.val());
formset.appendRowStr = formset.templateRowStr.replace(/__prefix__/g, formset.newTotal);
formset.$total.val(formset.newTotal + 1);
$('.table-inline-rows tbody').append(formset.appendRowStr);
});
});
</script>
There.. no need to use jquery formset :) and that's the only changes I'm making, I'm not adding any extra code, django takes care of everything.
When I read the documentation about modelforms and widgets it looks like you can use any widget on any modelform but that there are certain default widgets used for form fields corresponding to modelform fields. I'm able to render a radio input using a form field but not with a modelform field.
I have tried many different things, but I just can't seem to render a RadioSelect widget of a modelform field which comes from a model field. Is this even possible?
Btw, my goal is to let the initial value of the radio input correspond with the current value of the model field(A boolean).
Attempt 1:
# views.py
class SettingsView(FormView):
template_name = 'settings.html'
success_url = 'settings/saved/'
form_class = NicknameForm
def post(self, request, *args, **kwargs):
profile = request.user.get_profile()
if request.POST['show_nickname'] == 'False':
profile.show_nickname = False
profile.save()
elif request.POST['show_nickname'] == 'True':
profile.show_nickname = True
profile.save()
return super(NicknameFormView, self).post(request, *args, **kwargs)
def get_context_data(self, **kwargs):
"""
To be able to use 'show_nickname_form' instead of plain 'form' in the template.
"""
context = super(NicknameFormView, self).get_context_data(**kwargs)
context["show_nickname_form"] = context.get('form')
return context
# models.py
from django.db import models
from django.contrib.auth.models import User
class Profile(models.Model):
user = models.OneToOneField(User,
unique=True,
verbose_name='user',
related_name='profile')
show_nickname = models.BooleanField(default=True)
# forms.py
from django import forms
from models import Profile
CHOICES = (('shows_nickname', 'Yes'), ('hides_nickname', 'No'))
class NicknameForm(forms.ModelForm):
class Meta:
model = Profile
fields = ('show_nickname',)
widgets = {
'show_nickname': forms.RadioSelect(attrs={'choices': CHOICES}),
}
Part from my template:
<form action='' method="post">
{{ show_nickname_form.as_ul }} {% csrf_token %}
<input type="submit" value="Save setttings">
</form>
The form that is rendered from {{ show_nickname_form.as_ul }}:
<li><label for="id_show_nickname_0">show nickname:</label>
<ul></ul>
</li>
<div style='display:none'><input type='hidden' name='csrfmiddlewaretoken' value='1BqD6HJbP5e01NVwLtmFBqhhu3Y1fiOw' /></div>`
Attempt 2:
# forms.py
from django import forms
from models import Profile
class NicknameForm(forms.ModelForm):
class Meta:
model = Profile
fields = ('show_nickname',)
widgets = {
'show_nickname': forms.RadioSelect(),
}
Attempt 3
# forms.py
CHOICES = ((True, 'On',),
(False, 'Off',))
class NicknameForm(ModelForm):
show_nickname = ChoiceField(widget=RadioSelect, choices=CHOICES, initial=True , label='')
class Meta:
model = Profile
fields = ('show_nickname',)
This renders the radio input fine but I need it to take the initial value of the corresponding model field show_nickname instead of the constant True.
I am using Django 1.4 btw.
You need to make it into a ChoiceField instead of a BooleanField with a choice for each and a RadioWidget in order for it to display radio buttons.
https://docs.djangoproject.com/en/dev/ref/forms/fields/#django.forms.ChoiceField
If you want to keep the boolean field, you will most likely have to do some hacking to create your own field/widget.
# views.py
class SettingsView(FormView):
template_name = 'settings.html'
success_url = 'settings/saved/'
form_class = NicknameForm
def get_form(self, form_class):
"""
Returns an instance of the form to be used in this view.
"""
form = super(SettingsView, self).get_form(form_class)
if 'show_nickname' in form.fields:
profile = self.request.user.get_profile()
form.fields['show_nickname'].initial = profile.show_nickname
return form
def post(self, request, *args, **kwargs):
profile = request.user.get_profile()
if request.POST['show_nickname'] == 'False':
profile.show_nickname = False
profile.save()
elif request.POST['show_nickname'] == 'True':
profile.show_nickname = True
profile.save()
return super(NicknameFormView, self).post(request, *args, **kwargs)
def get_context_data(self, **kwargs):
"""
To be able to use 'show_nickname_form' instead of plain 'form' in the template.
"""
context = super(NicknameFormView, self).get_context_data(**kwargs)
context["show_nickname_form"] = context.get('form')
return context