I’m having difficulty implementing initial data in my django formset.
For context, I’m building an attendance app where a list of students exist and need to be assessed for attendance every day.
What I’m trying to do is have an administrator click on a link which has a date listed on it. They will then be taken to data grid where each row represents the number of students in the system along with 4 columns (student name, date, present/absent drop down, a notes field). The goal is to have the student name field be pre-populated with the the list of students in the Student model, the date field be pre-populated with the date on the link the user clicked and the attendance and notes fields be user inputs.
Any help would be much appreciated
Thanks!
—
Student model
class Student(models.Model):
first_name = models.CharField(max_length=255)
last_name = models.CharField(max_length=255)
GENDER_CHOICES = (
('male', 'male'),
('female', 'female'),
)
gender = models.CharField(max_length=12, choices=GENDER_CHOICES, null=False, blank=False)
date_of_birth = models.DateField(auto_now=False, auto_now_add=False)
#property
def full_name(self):
return ''.join([self.first_name, '_', self.last_name])
def __unicode__(self):
return self.full_name
Attendance model
class Attendance(models.Model):
student = models.ForeignKey('students.Student')
attendance_date = models.DateField(auto_now=False, auto_now_add=False)
STATUS_CHOICES = (
('present', ‘1’),
('absent', ‘0’),
)
status = models.CharField(max_length=12, choices=STATUS_CHOICES, null=False, blank=False)
notes = models.CharField(max_length=300, null=True, blank=True)
class Meta:
unique_together = ("student", "attendance_date")
def __unicode__(self):
return self.student
Attendance form
class AttendanceForm(forms.ModelForm):
class Meta:
model = Attendance
fields = ["student","attendance_date", "status", "notes"]
View
def add_attendance(request):
s = Student.objects.all().values()
AttendanceFormSet = formset_factory(AttendanceForm, extra=0)
formset = AttendanceFormSet(request.POST or None, initial=s)
if formset.is_valid():
try:
instance = form.save(commit=False)
instance.save()
return HttpResponseRedirect('/')
except:
return HttpResponseRedirect('/')
context = {
"formset": formset,
}
return render(request, "group_attendance.html", context)
Template
<table id="formset" class="form">
{{ formset.management_form }}
{% for form in formset.forms %}
{% if forloop.first %}
<thead><tr>
{% for field in form.visible_fields %}
<th>{{ field.label|capfirst }}</th>
{% endfor %}
</tr></thead>
{% endif %}
<tr>
{% for field in form.visible_fields %}
<td>
{# Include the hidden fields in the form #}
{% if forloop.first %}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{% endif %}
{{ field.errors.as_ul }}
{{ field }}
</td>
{% endfor %}
</tr>
{% endfor %}
</table>
I'm not really sure, if I understood your question the intended way. I think you're looking for a way to generate dynamic forms, which were pre-filled with data from the db.
A really helpful article about that, can be found here https://jacobian.org/writing/dynamic-form-generation/
You don't have a fixed numbers of students (-> fields in form), so you have to generate as many fields as needed. So you have to iterate over the students and create a form field for every single one.
You can find this in the article above. Just down below is a code snippet and an explanation of it:
class UserCreationForm(forms.Form):
username = forms.CharField(max_length=30)
password1 = forms.CharField(widget=forms.PasswordInput)
password2 = forms.CharField(widget=forms.PasswordInput)
def __init__(self, *args, **kwargs):
extra = kwargs.pop('extra')
super(UserCreationForm, self).__init__(*args, **kwargs)
for i, question in enumerate(extra):
self.fields['custom_%s' % i] = forms.CharField(label=question)
Related
I have two querysets: type and age_group.
type queryset:
<QuerySet [<Type: Cat>, <Type: Dog>, <Type: Other>]>
age_group queryset:
<QuerySet [<AgeGroup: Young>, <AgeGroup: Baby>, <AgeGroup: Adult>, <AgeGroup: Senior>]>
I loop through these from within my template form so that I can grab the pk when one has been selected, but I cannot capture the variable from within the for loop. How do I capture a variable from within a for loop when using Django? I want to capture pk for type and pk for age_group and then use both to filter the model Animal and return a filtered list that matches the user's preferences. A directory search function, essentially.
Template:
{% extends 'base.html' %}
{% block content %}
<h1>Animal Search</h1>
<form class="form-inline" action= '.' method="post">
{% csrf_token %}
<select name= "TypeSearch" class="custom-select my-1 mr-sm-2" id="animal_list_type">
<label class="sr-only type" for="animal_list_type">SEARCH</label>
{% for i in animal_type_list %}
<option value="{{i.pk}}">{{i}}</option> #how to capture the selected pk??
{% endfor %}
</select>
<select name="AgeSearch" class="custom-select my-1 mr-sm-2" id="animal_list_ageGroup">
<label class="sr-only ageLabel" for="animal_list_ageGroup">SEARCH</label>
{% for j in age_group_list %}
<option value="{{j.pk}}">{{j}}</option> #how to capture the selected pk??
{% endfor %}
</select>
<input type="submit" value="SEARCH" onclick="window.location='{% url 'animals:matches_list' pk=4 %}'; return false;">
<input type="submit" onclick="window.location='{% url 'animals:animals' %}'; return false;" value="Cancel">
</form>
{% endblock %}
views.py
class VisitorSearchView(View):
def get(self, request, pk=None):
#first tried ModelForm but couldn't figure out how to capture and iterate through one field of value options at a time
animalList = Animal.type.get_queryset()
animalList2 = Animal.ageGroup.get_queryset()
context = {
"animal_type_list": animalList,
"age_group_list": animalList2
}
return render(request, "animals/landing.html", context)
def post(self, request, pk=None):
theForm1 = AnimalSearchForm(request.POST)
success_url = reverse_lazy('animals:matches_list')
print(pk)
print(theForm1)
filler_for_now = Animals.objects.all()
context = {
'theMatches': filler_for_now
}
return render(request, success_url, context)
model.py
class Animal(models.Model):
name = models.CharField(max_length=500, blank=False, null=False)
type = models.ForeignKey(Type, on_delete=models.SET_NULL, blank=False, null=True)
ageGroup = models.ForeignKey(AgeGroup, max_length=300, on_delete=models.SET_NULL, blank=False, null=True)
age = models.PositiveIntegerField(blank=False, null=False)
sex = models.CharField(max_length=100, choices=SEX, blank=False, null=False, default='NA')
breedGroup = models.ManyToManyField(BreedGroup, blank=False)
breed = models.ManyToManyField(Breed, blank=False)
tagLine = models.CharField(max_length=300, blank=False, null=False)
goodWithCats = models.BooleanField(blank=False, null=False, default='Not Enough Information')
goodWithDogs = models.BooleanField(null=False, blank=False, default='Not Enough Information')
goodWKids = models.BooleanField(null=False, blank=False, default='Not Enough Information')
urls.py
app_name = 'animals'
urlpatterns = [
path('', views.AnimalListView.as_view(), name='animals'),
path('landing/', views.VisitorSearchView.as_view(), name='landing'),
path('matches/<int:pk>', views.VisitorSearchView.as_view(), name='matches_list'),
]
forms.py #(originally tried to use ModelForm but couldn't figure out how to grab the pk for both chooseType and chooseAge fields so chose to try to just use querysets from view)
class AnimalSearchForm(ModelForm):
chooseType = ModelChoiceField(queryset=Animal.objects.values_list('type', flat=True).distinct(),empty_label=None)
chooseAge = ModelChoiceField(queryset=Animal.objects.values_list('ageGroup', flat=True).distinct(), empty_label=None)
class Meta:
model = Animal
exclude = '__all__'
Outside of Django, this would be a simple problem to solve. How do I capture a variable from within a for loop when using Django? I have tried to instantiate a variable outside the for-loop and then update that based off selection from within, but it seems that this cannot be done via the template...?
Well the real issue here is that you really should be using FormView to display a form together with DetailView to display model data, in this particular case you should do something like this:
views.py
from django.views.generic import FormView, DetailView
class VisitorSearchView(FormView, DetailView):
model = Animal
template_name = 'animals/landing.html'
form_class = AnimalSearchForm
def form_valid(self, form):
data = form.cleaned_data # Dict of submitted data
# handle form validation and redirect
def get_context_data(self, request):
context = super(VisitorSearchView, self).get_context_data(**kwargs)
animals = Animal.objects.all() # or use a custom filter
context['animals'] = animals
return context
Then in your landing.html
where you want a list of animal types:
{% for animal in animals %}
{{ animal.type }}
{% endfor %}
and where you want a list of animal ages:
{% for animal in animals %}
{{ animal.age }}
{% endfor %}
declare your form normally as you would.
I think you need to remove the dot from the action attribute. Empty string in action use the current URL for form submission. Form opening line will be like
<form class="form-inline" action= '' method="post">
I have two models with one being a foreign key to another. A user can only submit an answer. am trying to use the if statement to check if an answer exit for a user then the submit answer button should change the update button the template.
class Assignment(models.Model):
title = models.CharField(max_length=120)
slug = models.SlugField(max_length=500)
course = models.ForeignKey(Course, on_delete=models.CASCADE)
class_or_level = models.ForeignKey(StudentClass, on_delete=models.CASCADE)
teacher = models.ForeignKey(Teacher, on_delete=models.CASCADE)
file = models.FileField(upload_to='assignment', blank=True, null=True)
Text = models.TextField()
date_added = models.DateTimeField(auto_now_add=True)
date_expire = models.DateTimeField()
class Answer(models.Model):
slug = models.SlugField(max_length=500)
assignment = models.ForeignKey(Assignment, on_delete=models.CASCADE)
student = models.ForeignKey(User, on_delete=models.CASCADE)
file = models.FileField(upload_to='assignment')
date_added = models.DateTimeField(auto_now_add=True)
My View
class AssignmentView(LoginRequiredMixin, ListView):
template_name = 'assignment.html'
context_object_name = 'all_couses'
now = timezone.now()
queryset = Course.objects.all()
def get_context_data(self, **kwargs):
now = timezone.now()
context = super(AssignmentView, self).get_context_data(**kwargs)
context.update({
'assignment_list': Assignment.objects.filter(class_or_level=self.request.user.student.class_or_level, date_expire__gte=now).order_by('-date_expire'),
})
return context
this the template> What the users submitted answer to show if he does submit one else show the form like to submit answer
{% for assignment in assignment_list %}
<h4>{{ assignment.title|truncatewords:12 }}</h4>
{% if assignment.answer %}
{{ assignment.answer.file }}
<button> Update Answer</button>
{% else %}
<button> Summit Answer</button>
{% endif %}
{% endfor %}
You can change your view or create a template tag to identify if an assignment has a response of a specific user, I decided to change the view and add a new variable to the assignment object that will have the information of if the user who requested the page has an answer to that assignment:
class AssignmentView(LoginRequiredMixin, ListView):
template_name = 'assignment.html'
context_object_name = 'all_couses'
now = timezone.now()
queryset = Course.objects.all()
def get_context_data(self, **kwargs):
now = timezone.now()
context = super(AssignmentView, self).get_context_data(**kwargs)
assignment_list = Assignment.objects.filter(
class_or_level=self.request.user.student.class_or_level,
date_expire__gte=now
).order_by('-date_expire')
for assignment in assignment_list:
try:
assignment.student_answer = Answer.objects.get(
assignment=assignment,
student=self.request.user
)
except Answer.DoesNotExist:
pass
context.update({
'assignment_list': assignment_list,
})
return context
Then, inside of your template you can do the following:
{% for assignment in assignment_list %}
<h4>{{ assignment.title|truncatewords:12 }}</h4>
{% if assignment.student_answer %}
{{ assignment.student_answer.file }}
<button> Update Answer</button>
{% else %}
<button> Summit Answer</button>
{% endif %}
{% endfor %}
By doing that you'll achieve what you're looking for.
I am using Django to build a help desk and I want to allow the client to upload multiple files when they submit a ticket.
I am trying to do this by using a formset.
I have found many questions related to a similar problem, but I still have not been able to get my form working. I will appreciate a pointer in the right direction.
I have posted the relevant code below:
# models.py
class Ticket(models.Model):
PRIORITY_CHOICES = (
(1, 'Critical'),
(2, 'High'),
(3, 'Normal'),
(4, 'Low'),
(5, 'Very Low'),
)
STATUS_CHOICES = (
(1, 'Open'),
(2, 'Reopened'),
(3, 'Resolved'),
(4, 'Closed'),
(5, 'Duplicate'),
)
ticket_number = models.CharField(max_length=50, blank=True,
null=True, unique=True)
client = models.ForeignKey(settings.AUTH_USER_MODEL,
editable=True, on_delete=models.CASCADE,
related_name="tickets")
title = models.CharField("Summary",
max_length=200,
help_text='Provide a brief description of your request.')
description = models.TextField(blank=True,
help_text='Provide as much detail as possible to help us resolve this ticket as quickly as possible.')
due_date = models.DateField(blank=True, null=True)
assigned_to = models.ForeignKey(settings.AUTH_USER_MODEL,
related_name="assigned",
blank=True, null=True,
on_delete=models.CASCADE)
priority = models.IntegerField(choices=PRIORITY_CHOICES,
editable=True, default=3,
help_text='Please select a priority carefully. If unsure, leave it as "Normal".',
blank=True, null=True)
status = models.IntegerField(choices=STATUS_CHOICES,
editable=True,
default=1, blank=True, null=True)
closing_date = models.DateField(blank=True, null=True)
closing_notes = models.TextField(blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
upload = models.FileField(upload_to='uploads/%Y/%m/%d/', blank=True, null=True)
def get_absolute_url(self):
return reverse('tickets:ticket-detail', args=[str(self.id)])
def __str__(self):
return str(self.ticket_number)
class TicketFile(models.Model):
attachment = models.FileField(upload_to='attachments/%Y/%m/%d', blank=True, null=True)
ticket = models.ForeignKey(Ticket, editable=True, on_delete=models.CASCADE)
description = models.CharField(max_length=200, blank=True)
def get_image_filename(self):
if self.docfile:
return media_url + str(self.docfile)
def __str__(self):
return str(self.attachment)
# forms.py
class TicketFormIsStaff(ModelForm):
class Meta:
model = Ticket
exclude = ()
class TicketFileForm(forms.ModelForm):
attachment = forms.FileField(label='Attachment')
class Meta:
model = TicketFile
fields = ('attachment', )
TicketAttachmentFormset = modelformset_factory(Ticket,
form=TicketFileForm, extra=2)
# views.py
class TicketCreateView(CreateView):
model = Ticket
fields = ['ticket_number', 'client', 'title', 'description', 'status']
def get_context_data(self, **kwargs):
data = super(TicketCreateView, self).get_context_data(**kwargs)
if self.request.POST:
data['ticketfiles'] = TicketAttachmentFormset(self.request.POST)
print("data 1 ", data['ticketfiles'] )
else:
data['ticketfiles'] = TicketAttachmentFormset()
print("data 2 ", data)
return data
def form_valid(self, form):
context = self.get_context_data()
ticketfiles = context['ticketfiles']
with transaction.atomic():
self.object = form.save()
print("CONTEXT:::: ", ticketfiles)
if ticketfiles.is_valid():
ticketfiles.instance = self.object
ticketfiles.save()
return super(TicketCreateView, self).form_valid(form)
# ticket_form.html
<form action="" method="post" enctype="multipart/form-data">{% csrf_token %}
{{ form.as_p }}
<table class="table">
{{ ticketfiles.management_form }}
{% for form in ticketfiles.forms %}
{% if forloop.first %}
<thead>
<tr>
{% for field in form.visible_fields %}
<th>{{ field.label|capfirst }}</th>
{% endfor %}
</tr>
</thead>
{% endif %}
<tr class="{% cycle row1 row2 %} formset_row">
{% for field in form.visible_fields %}
<td>
{# Include the hidden fields in the form #}
{% if forloop.first %}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{% endif %}
{{ field.errors.as_ul }}
{{ field }}
</td>
{% endfor %}
</tr>
{% endfor %}
</table>
<input type="submit" value="Save" />
</form>
All the data related to the Ticket model uploads correctly, but the files related to the TicketFile model do not upload. If I try to upload the data in the admin interface then everything works perfectly. I assume this means that all the media settings are correct.
If I add the description field to the formset then that gets saved correctly. The file is not saved though.
I resolved the file upload problem by changing the code in TicketCreateView from:
data['ticketfiles'] = TicketAttachmentFormset(self.request.POST)
to:
data['ticketfiles'] = TicketAttachmentFormset(self.request.POST, self.request.FILES, instance=form.instance))
I have a modelform that only works(saves input data to database) if none of the fields has choices. When i introduce choices, i don't get any errors and the form seems to be valid but nothing gets saved.
I have combed through the documentation and i am not returning anything useful.
I am convinced that i need to do more in my views to get the selected input choices or i need to add a few methods to the model class. Please point me in the right direction.
Here is my model:
class OpeningHours(models.Model):
'''
'''
class Meta:
verbose_name = 'Opening Hour'
verbose_name_plural = 'Opening Hours'
#######################################################
mytime = Bizhours()
################################################
id = models.AutoField(primary_key=True)
company =models.CharField(max_length=100, null=True, blank=True)
weekday = models.CharField(max_length=100, choices=mytime.getweekdays(), default='Monday', null=True)
fromHour = models.CharField(max_length=100, null=True, blank=True)
fromMinute = models.CharField(max_length=100, null=True, blank=True)
toHour = models.CharField(max_length=100, null=True, blank=True)
toMinute = models.CharField(max_length=100, null=True, blank=True)
'''
id = models.AutoField(primary_key=True)
company = models.ForeignKey(Company)
weekday = models.IntegerField(choices=mytime.getweekdays())
fromHour = models.TimeField(choices=mytime.gettime12())
fromMinute = models.TimeField(choices=mytime.getminutes())
toHour = models.TimeField(choices=mytime.gettime12())
toMinute = models.TimeField(choices=mytime.getminutes())
'''
def __str__(self):
return "%s %s (%s - %s)" % (self.company, self.weekday, self.fromHour, self.toHour)
here is my views
#login_required
def addprofile(request):
current_user = request.user
#OpeningHoursFormSet = modelformset_factory(OpeningHours, form=OpeningHoursForm,extra=1)
if request.session['entry_count'] > 1:
messages.success( request, 'You can only create two business profiles now' )
return HttpResponseRedirect( reverse('home') )
else:
if request.method == 'POST':
form = OpeningHoursForm(request.POST)
if form.is_valid():
model_instance = form.save(commit=False)
model_instance.company ="thiscompany"
model_instance.weekday = request.POST.get('weekday')
model_instance.save()
else:
print("problems saving edited form")
return HttpResponseRedirect('/bizprofile/success')
else:
form = OpeningHoursForm()
context = {'form': form}
return render_to_response('bizprofile/addprofile.html', context, context_instance=RequestContext(request))
here is the form
{% extends "bizprofile/bizprofilebase.html" %}
{% block content %}
{% if form.subject.errors %}
<ol>
{% for error in form.subject.errors %}
<li><strong>{{ error|escape }}</strong></li>
{% endfor %}
</ol>
{% endif %}
{% if user.is_authenticated %}
<p>Welcome, {{ user.get_username }}. Thanks for logging in.</p>
<form method="post" action="">
{% csrf_token %}
<table>
{{form}}
</table>
<input type="submit" value="Submit Form"/>
</form>
{% else %}
<p>Welcome, new user. Please log in.</p>
{% endif %}
{% endblock %}
The problem lies in the fact that OP is using CharField for weekday data type, but the choices returned from a function are defined as integers. Since they are not compatible, the data could not be saved.
I'm trying to make a simple search function. I'm using the User model and a UserProfile model with a OneToOne relationship. I've successfully managed to search by the username, first name and last name, all three field being in the User model. However, I want to filter by the fields present in UserProfile model, e.g. sex, age, location, etc.
views.py
def Home(request):
query = request.GET.get('query', '')
queryset = User.objects.none()
if request.method == "GET":
form = SearchForm(request.GET)
if form.is_valid():
if query != '':
queryset = User.objects.filter(Q(username__icontains=query)
| Q(first_name__icontains=query)
| Q(last_name__icontains=query)).exclude(is_staff=True)
else:
form = SearchForm()
return render(request, "main/home.html", {'form': form, 'queryset': queryset})
home.html
{% extends "base.html" %}
{% block body %}
<h3>Home</h3>
<p>
<form method="get" action="">
{{ form.as_p }}
<input type="submit" value="Search"/>
</form>
</p>
<p>
{% for user in queryset %}
{{ user.get_full_name }} <br/>
{% endfor %}
</p>
{% endblock %}
forms.py
class SearchForm(forms.Form):
query = forms.CharField(max_length=100, label="", widget=forms.TextInput(attrs={'placeholder': 'Search...'}), required=False)
age_from = forms.IntegerField(label="", widget=forms.NumberInput(attrs={'placeholder': 'Age from'}), required=False)
age_to = forms.IntegerField(label="", widget=forms.NumberInput(attrs={'placeholder': 'Age to'}), required=False)
models.py
UserProfile(models.Model):
sex_choices = (
('Male', 'Male'),
('Female', 'Female'),
('Other', 'Other'),
)
sex = models.CharField(max_length=6,
choices=sex_choices,
default='Male')
birth_date = models.DateField()
How can I include options to search based on other fields on UserProfile model which has a OneToOne relation with User model?
You need to use the related name.
As you haven't shared your models definition, let's suppose:
class UserProfile(models.Model):
user = models.OneToOneField(User)
sex = models.CharField(...)
...
So, when you query:
User.objects.filter(user_profile_set__sex='female')
EDIT
To query based in age, given that you ask for an 'age' in your form and you store a birthdate in your model, you could try something like this:
from datetime import datetime
age = 18 # this shoudl come from form
birth_year = datetime.now().year - age
User.objects.filter(user_profile_set__birth_date__year=birth_year)