I have a problem using Django forms while learning Django and adapting code from a variety of online courses and examples. As a result, the code may be “messy” – but if I can get it to work the way I need, I can improve my coding style later.
I wish to display a template that contains a form. Some of the data displayed in the page rendered in the template is read from one table/model, polls_CC_Questions, and I wish to write data input in the page into a related table, polls_CC_Resp_NoFK.
The models used are:
class CC_Questions(models.Model):
q_text = models.CharField('Question text', max_length=200)
C1_Type = models.CharField('Choice 1 Type', max_length=2)
Choice1_text = models.CharField('Choice 1 text', max_length=100)
C2_Type = models.CharField('Choice 2 Type', max_length=2)
Choice2_text = models.CharField('Choice 2 text', max_length=100)
#
def __str__(self):
return self.q_text[:20]
class CC_Resp_NoFK(models.Model):
Person_ID = models.IntegerField()
Test_date = models.DateTimeField('date test taken')
Q_ID = models.IntegerField()
Response_value = models.IntegerField(default=0,
validators=[MaxValueValidator(100), MinValueValidator(-100)])
#
def __str__(self):
return self.Person_ID
Now I can display the template containing valid data when I enter the url:
http://localhost:8000/polls/p2vote/4/
This is processed in urls.py
app_name = 'polls'
urlpatterns = [
…..
……
# ex: /polls/p2vote/<q_id>
path('p2vote/<int:q_id>/', p2_views.p2vote, name='p2vote'),
…..
The views.py entry that is used:
def p2vote(request,q_id):
#next line has been copied from CC_quest view to GET Question data
CC_question = get_object_or_404(CC_Questions, pk=q_id)
#
if request.method == 'POST':
form = VoteForm(request.POST)
if form.is_valid():
form.save()
return redirect('/polls/p2')
else:
formV = VoteForm()
#context = {'form' : formV}
return render(request, 'pollapp2/vote.html', {'var_name':CC_question,'form' : VoteForm()})
in forms.py
class VoteForm(forms.ModelForm):
class Meta:
model = CC_Resp_NoFK
fields = ['Person_ID', 'Test_date', 'Q_ID','Response_value']
The template launched, uses data from the polls_CC_Questions model/table to create the labels of the input field. This works fine so my displayed page
http://localhost:8000/polls/p2vote/5/
Displays data from the CC_Questions table, “carried in the variable varname” what the questions and their choices are. For example, the template displays the contents of {{ var_name.q_text }} and {{ var_name.Choice1_text }} , see below
Also, the page displayed containing the ModelForm is correctly displayed with labels. The template used :
<!-- vote.html based on create.html -->
<!-- 2022-02-17
Change text on page
Extracted data from CC_Question record passed as varname
-->
{% extends "pollapp2/base.html" %}
<!-- load widget tools to give me more control over layout of form in template -->
{% load widget_tweaks %}
<!-- block Title is the name in the tab -->
{% block title %}Vote on Question{% endblock %}
{% block main %}
<div class="row">
<div class="col-lg-10 col-lg-offset-2">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Select one from two choices</h3>
</div>
<form method="POST">
{% csrf_token %}
<div class="panel-body">
<div class="row">
<div class="col-lg-12">
<div class="form-group">
<label for="question">Question to answer</label>
{{ var_name.q_text }}
</div>
</div>
</div>
<div class="row">
<div class="col-lg-5">
<div class="form-group">
<label for="Choice1_text ">Choice 1</label>
{{ var_name.Choice1_text }}
</div>
</div>
<div class="col-lg-5">
<div class="form-group">
<label for="option2">Choice 2</label>
{{ var_name.Choice2_text }}
</div>
</div>
</div>
<!-- Attempt at Input fields follow -->
<div class="row">
<div class="col-lg-12">
<div class="form-group">
<label for="Person_id">Person ID</label>
{% render_field form.Person_ID rows="1" class="form-control" %}<br>
<label for="Test_date">Test Date</label>
{% render_field form.Test_date rows="1" class="form-control" %}<br>
<label for="Q_ID">Question ID</label>
{% render_field form.Q_ID rows="1" class="form-control" %} <br>
<label for="Response_value">Response value</label>
{% render_field form.Response_value rows="1" class="form-control" %}
</div>
</div>
</div>
<div class="row">
<hr />
<div class="col-lg-4">
<button type="submit" class="btn btn-info">Submit</button>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
{% endblock %}
To summarise. All the above “works” in the sense a page is displayed when url : http://localhost:8000/polls/p2vote/X/ is entered in the browser and “X” is the id of the question , extracting data from the model: CC_questions. Also, on the page are input boxes created by the form, VoteForm, that allow data to be entered into table/model CC_Resp_noFK.
However, what I want to do is NOT offer Q_ID as an input field in the page, but instead populate it with the value from variable {{ var_name.id }}. I can’t work out whether I need to modify the vote.html template in some way, particularly the line:
<label for="Q_ID">Question ID</label>
{% render_field form.Q_ID rows="1" class="form-control" %} << change this ??
or the view, somewhere around form.save() ??
def p2vote(request,q_id):
#next line has been copied from CC_quest view to get Question data
CC_question = get_object_or_404(CC_Questions, pk=q_id)
#
if request.method == 'POST':
form = VoteForm(request.POST)
if form.is_valid():
form.save() << Somewhere around here ??
return redirect('/polls/p2')
else:
formV = VoteForm()
#context = {'form' : formV}
# return render(request, 'pollapp2/vote.html', context)
# following return tries to send question record into vote.html template
return render(request, 'pollapp2/vote.html', {'var_name':CC_question,'form' : VoteForm()})
Step 1: Delete Q_ID from VoteForm.
class VoteForm(forms.ModelForm):
class Meta:
model = CC_Resp_NoFK
fields = ['Person_ID', 'Test_date', 'Response_value']
Step 2: Add Q_ID after check if the form is valid and before save the object.
def p2vote(request,q_id):
#next line has been copied from CC_quest view to get Question data
CC_question = get_object_or_404(CC_Questions, pk=q_id)
if request.method == 'POST':
form = VoteForm(request.POST)
if form.is_valid():
item = form.save(commit=False)
item.Q_ID = q_id
item.save()
return redirect('/polls/p2')
Related
I would like to add information in a form, coming from the model linked with a M2M relationship to the model I'm updating.The form works properly, but I'm not able to add any information.
Here is what I get:
Here is the expected result:
My solution: finally, I updated __str__() mthod in UserGroup model to display what I expect (but, at this stage, I lost the dynamic part and my view does not work anymore :-/)
The main model is Event and it's linked to Groups thanks to this relationship; in the form, all groups are listed and displayed with checkboxes, but I'm only able to display the groups' name, no other information.
It looks like I miss some data / information: the group name is displayed only because I use {{grp}}} (see below) but it has no attribute / filed available, even if it is initialized with a query from the group model.
I envisaged a workaround (see below) because my first tries made me consider this kind of solution, but I'm not able to reproduce what I did :-/
Any idea of what I did wrong, or any advice to manage this? Thanks in advance.
Here are related code parts.
Models:
class UserGroup(models.Model):
company = models.ForeignKey(
Company, on_delete=models.CASCADE, verbose_name="société"
)
users = models.ManyToManyField(UserComp, verbose_name="utilisateurs", blank=True)
group_name = models.CharField("nom", max_length=100)
weight = models.IntegerField("poids", default=0)
hidden = models.BooleanField(default=False)
def __str__(self):
return self.group_name
class Event(models.Model):
company = models.ForeignKey(
Company, on_delete=models.CASCADE, verbose_name="société"
)
groups = models.ManyToManyField(UserGroup, verbose_name="groupes", blank=True)
rules = [("MAJ", "Majorité"), ("PROP", "Proportionnelle")]
event_name = models.CharField("nom", max_length=200)
event_date = models.DateField("date de l'événement")
slug = models.SlugField()
current = models.BooleanField("en cours", default=False)
quorum = models.IntegerField(default=33)
rule = models.CharField(
"mode de scrutin", max_length=5, choices=rules, default="MAJ"
)
class Meta:
verbose_name = "Evénement"
constraints = [
models.UniqueConstraint(fields=["company_id", "slug"], name="unique_event_slug")
]
def __str__(self):
return self.event_name
Form:
class EventDetail(forms.ModelForm):
groups = forms.ModelMultipleChoiceField(
label = "Liste des groupes",
queryset = None,
widget = forms.CheckboxSelectMultiple,
required = False
)
class Meta:
model = Event
fields = ['event_name', 'event_date', 'quorum', 'rule', 'groups']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
instance = kwargs.get('instance', None)
self.fields['groups'].queryset= UserGroup.objects.\
filter(company=instance.company).\
order_by('group_name')
View:
#user_passes_test(lambda u: u.is_superuser or (u.id is not None and u.usercomp.is_admin))
def adm_event_detail(request, comp_slug, evt_id=0):
'''
Manage events creation and options
'''
company = Company.get_company(request.session['comp_slug'])
# all_groups = list(UserGroup.objects.filter(company=company, hidden=False).order_by('group_name').values())
if evt_id > 0:
current_event = Event.objects.get(id=evt_id)
event_form = EventDetail(request.POST or None, instance=current_event)
else:
event_form = EventDetail(request.POST or None)
event_form.fields['groups'].queryset = UserGroup.objects.\
filter(company=company, hidden=False).\
order_by('group_name')
if request.method == 'POST':
if event_form.is_valid():
if evt_id == 0:
# Create new event
event_data = {
"company": company,
"groups": event_form.cleaned_data["groups"],
"event_name": event_form.cleaned_data["event_name"],
"event_date": event_form.cleaned_data["event_date"],
"quorum": event_form.cleaned_data["quorum"],
"rule":event_form.cleaned_data["rule"]
}
new_event = Event.create_event(event_data)
else:
new_event = event_form.save()
else:
print("****** FORMULAIRE NON VALIDE *******")
print(event_form.errors)
return render(request, "polls/adm_event_detail.html", locals())
HTML (I did not put each parts of the 'accordion' widget, I do not think they have anything to do with the problem):
{% if evt_id %}
<form action="{% url 'polls:adm_event_detail' company.comp_slug evt_id %}" method="post">
{% else %}
<form action="{% url 'polls:adm_create_event' company.comp_slug %}" method="post">
{% endif %}
{% csrf_token %}
<!-- Hidden field where the referer is identified to go back to the related page after validation -->
<input type="hidden" name="url_dest" value="{{ url_dest }}" />
<br>
<!-- Accordion -->
<div id="eventDetails" class="accordion shadow">
<div class="card">
<div class="card-header bg-white shadow-sm border-0">
<h6 class="mb-0 font-weight-bold">
Evénement
</h6>
</div>
<div class="card-body p-5">
<p>Nom : {{event_form.event_name}} </p>
<p>Date : {{event_form.event_date}} </p>
</div>
</div>
<!-- Accordion item 2 - Event's groups -->
<div class="card">
<div id="headingTwo" class="card-header bg-white shadow-sm border-0">
<h6 class="mb-0 font-weight-bold">
<a href="#" data-toggle="collapse" data-target="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo" class="d-block position-relative collapsed text-dark collapsible-link py-2">
Groupes d'utilisateurs
</a>
</h6>
</div>
<div id="collapseTwo" aria-labelledby="headingTwo" data-parent="#eventDetails" class="collapse show">
<div class="card-body p-5">
<p>Sélectionnez le(s) groupe(s) d'utilisateurs participants à l'événement :</p>
<ul>
{% for grp in event_form.groups %}
<li>{{ grp }}
{{ grp.weight }}
{{ grp.hidden }}
{{ grp.nb_users }}
</li>
{% endfor %}
</ul>
<p></p>
</div>
</div>
</div>
</div> <!-- Accordion end -->
<button class="btn btn-success mt-5" type="submit">{% if evt_id %}Mettre à jour{% else %}Créer{% endif %}</button>
     
<a class="btn btn-secondary back_btn mt-5" href="*">Annuler</a>
<div class="row">
<div hidden>
<!-- List of groups in event -->
{{ event_form.group_list }}
</div>
</div>
</form>
Workaround
If it's not possible to achieve this directly, I thought to a workaround that would be implemented in several parts:
Create a list almost like the queryset: group_list = UserGroup.objects.filter(company=instance.company).order_by('group_name').values()
I already know I can display each group with its details and a checkbox
on client side (javascript), I manage an hidden list that would be part of the form, with the ID of each selected group. That means that the list will be dynamically updated when a box is checked on unchecked
on the POST request, read the list to update the group attribute of updated event.
I would have prefered the users actions having effect directly to the form, but I know this could work
You're accessing the form's groups field, not the model instance. The form field doesn't have any relationship to other models, it's just a field. You can access the underlying instance of the model form using form.instance.
Also note that you get a relationship manager object when querying related models. Hence, use .all to query all groups.
Try
<ul>
{% for grp in event_form.instance.groups.all %}
<li>{{ grp }}
{{ grp.weight }}
{{ grp.hidden }}
{{ grp.nb_users }}
</li>
{% endfor %}
</ul>
I’m having an issue on understanding how to render a page with Django the correct way.
I would like the initial page to load with the below form only. Then after posting and getting back data, display the results.
The issue I’m having is when i add an initial end date, the page loads with anything in the if statement.
forms.py
class TrainingStatsForm(forms.Form):
start_date = forms.CharField(label='Start Date', max_length=12, widget=DateInput())
end_date = forms.CharField(label='End Date', max_length=12, widget=DateInput(), initial=datetime.date.today)
dog = forms.ModelChoiceField(queryset=k9Table.objects.all())
If I remove the initial=datetime.date.today. Then the page load correctly
Views.py
def trainingstats(request):
if request.POST:
date_data = TrainingStatsForm(request.POST)
if date_data.is_valid():
start_date = date_data.cleaned_data['start_date']
end_date = date_data.cleaned_data['end_date']
dog = date_data.cleaned_data['dog']
requested_search_training = TrainingTable.objects.filter(date__gte=start_date, date__lte=end_date,
dog=dog).order_by('-date')
requested_search_detection = DetectionTable.objects.filter(date__gte=start_date, date__lte=end_date,
dog=dog).order_by('-date')
requested_search = sorted(
chain(requested_search_training, requested_search_detection),
key=lambda data: data.created, reverse=True
else:
date_data = TrainingStatsForm()
requested_search_training = None
requested_search_detection = None
requested_search = None
total_time = 0
content = {
'returned_data': returned_data,
'date_data': date_data,
'requested_search': requested_search,
}
return render(request, 'trainingstats.html', content)
Template
<form method="post"> {% csrf_token %}
<div class="form-row">
<div class="form-group col-md-2 mb-0">
{{ date_data.dog|as_crispy_field }}
</div>
<div class="form-group col-md-2 mb-0">
{{ date_data.start_date|as_crispy_field }}
</div>
<div class="form-group col-md-2 mb-0">
{{ date_data.end_date|as_crispy_field }}
</div>
</div>
<div class="form-row">
<input type="submit" value="Submit" class="btn btn-primary"/>
</div>
</form>
{% if date_data.has_changed %}
// show table with results of search
The goal is to show the form with the current date as the end date of first load. Then when the user submits the search is displays the results. Any help?
I am trying to display a pdf file in an html template and also be able to update that pdf file, I read the tutorials on how to display a pdf file but I'm not sure why mine isn't really working, I have a model called Reference
class References(models.Model):
REFERENCE_MATERIAL = (
('seating', 'Seating Plan'),
('organization', 'Organization Chart')
)
# the variable associated with the reference
reference_name = models.CharField(max_length=100, choices=REFERENCE_MATERIAL,)
reference_file = models.FileField(null=True, blank=True, upload_to='references/')
date_updated = models.DateField(auto_now=True)
# on submit click of update entry page, it redirects to the url below.
def get_absolute_url(self):
return reverse('references-home')
Here are the views to update a reference and the one that displays it
class SeatingReferenceView(generic.ListView):
context_object_name = 'seating_plan'
template_name = 'catalog/references-seating.html'
def get_queryset(self):
return References.objects.filter(reference_name='seating').order_by('date_updated').first()
def reference_update(request):
if request.method == 'POST':
form = UploadReference(request.POST, request.FILES)
if form.is_valid():
form.save()
return HttpResponseRedirect('references-home')
else:
form = UploadReference()
return render(request, 'catalog/references_update.html', {'form': form,})
Here is my template
<div class="container maintenance-container">
<div class="row" >
<div class="maintenance-title">
<h1 class="mainrow">Seating Plan</h1>
<h4 class="maintenance-under">Seating Plan for Kitchener Floor 1</h4>
</div>
</div>
<div class="row">
{% if seating_plan %}
<div class="col-lg-12">
{{ seating_plan.read }}
</div>
{% else %}
<div class="col-lg-12">
<div class="mainpanelcontent">
<h4>There are no seating plans to display</h4>
</div>
</div>
{% endif %}
</div>
</div>
{% endblock %}
However I'm receiving nothing in my html template, I am storing my files in a folder under /media/references, I can see the files are in there currently and I can see in my database that under the reference_file column that there are files called seatingplan.pdf but still nothing is showing. Thank you !
I have related model EntertainmentCollage with image fields and did't now how to transfer instance models to the editing form. I need to display the existing images in the editing form and have the ability to add new ones.
models.py
class Entertainment(models.Model):
main_photo = models.ImageField(upload_to = 'where/')
place = models.CharField(max_length=200)
description = models.CharField(max_length=200)
event_date = models.DateTimeField(auto_now_add=False, blank=True, null = True)
class EntertainmentCollage(models.Model):
img = models.ImageField(upload_to = 'entertainment/portfolio', blank = True)
album = models.ForeignKey(Entertainment, blank = True, null = True)
views.py
def edit_where(request, pk):
place = Entertainment.objects.get(id=pk)
FormSet2 = inlineformset_factory(Entertainment, EntertainmentCollage, fields =['img',], extra=6)
form = WhereCreateForm(instance=place)
form2 = FormSet2()
if request.user.is_authenticated():
if request.method == "POST":
form = WhereCreateForm(request.POST, request.FILES, instance=place)
if form.is_valid():
form2 = FormSet2(request.POST or None, request.FILES)
if form2.is_valid():
form.save()
form2.save()
return redirect('entertainment:where_list')
else:
form = WhereCreateForm()
form2 = FormSet2()
return render(request, "entertainment/where_edit.html", {'form': form, 'form2': form2})
html
<section>
<div class="container">
<div class="row">
<div class="col-md-3">
<h2>Editing Where</h2>
<br>
<form method = "post" enctype="multipart/form-data">
{% csrf_token %}
<p>{{ form.eventname }}</p>
<p>{{ form.place }}</p>
<p>{{ form.event_date }}</p>
</div>
<div class="col-md-9">
<section class="admin-section">
{{ form2.management_form }}
<div class="row">
{% for frm in form2 %}
<div class="col-md-4 admin__block" is-cover="false">
<div class="cover__wrapper edit__wrapper">
<a class="delete-button">Delete</a>
<a class="make-cover-button">Cover</a>
<img src="{{ frm.url }}" alt="">
</div>
</div>
{% endfor %}
</div>
Add photo
</section>
<section>
<h4>Description</h4>
{{ form.description }}
<input type="submit" value="Save">
Cancel
</section>
</form>
</div>
</div>
</div>
</section>
If I try to add
collage = EntertainmentCollage.objects.filter(album = place)
to my views.py and make form2(instance = collage) error occured QuerySet' object has no attribute 'pk'
___ UPDATE ___
I need to get page like http://joxi.ru/Dr8X8w4tkGw1zr
where images are taken from the model EntertainmentCollage,
and if they are added then I could see them in the form.
I realized that my question was incorrectly asked. I just had to display all the images from the queryset and use the inline form to add a button to add a new image.
:::views.py:::
class userdetailsView(FormView):
form_class = userdetailsViewForm
template_name = "customer/user.html"
def form_valid(self, form):
import os
fir = self.request.POST['employee_id']
sec = self.request.POST['name']
thi = self.request.POST['email']
fou = self.request.POST['address']
yo ="&&".join([fir,sec,thi,fou])
print 'Encrypted string:', yo
return render(self.request, userdetailsView.template_name, {'result': yo , 'form': form})
:::forms.py:::
from django import forms
class userdetailsForm(forms.Form):
employee_id = forms.IntegerField(min_value = 1,max_value = 999, widget=forms.TextInput(attrs={'class':'form-control', 'placeholder': 'employee id'}))
name = forms.CharField(max_length = 10, widget=forms.TextInput(attrs={'class':'form-control', 'placeholder': 'name'}))
email = forms.EmailField(max_length = 25, widget=forms.TextInput(attrs={'class':'form-control', 'placeholder': 'email'}))
address = forms.CharField(max_length = 50, widget=forms.TextInput(attrs={'class':'form-control', 'placeholder': 'address'}))
:::user.html:::
{% block body %}
<div class='col-md-6'>
<form role="form" method='POST'>
{% csrf_token %}
{{ form.non_field_errors }}
<div class="form-group">
<label for="id_employee_id">{{form.employee_id.label}}</label>
{{ form.employee_id.errors }}{{form.employee_id}}
<div class="form-group">
<label for="id_name">{{form.name.label}}</label>
{{ form.name.errors }}{{form.name}}
</div>
<div class="form-group">
<label for="id_email">{{form.email.label}}</label>
{{ form.email.errors }}{{form.email}}
</div>
<div class="form-group">
<label for="id_address">{{form.address.label}}</label>
{{ form.address.errors}}{{form.address}}
</div>
<div class="form-group">
<button type="submit" class="btn btn-default">Submit</button>
</div>
</form>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"><strong>user encrypted data</strong></h3>
</div>
<div class="well well-lg" >
{{ result }}
</div>
{% endblock %}
dont go for the encryption algo as i have not written it and right now im just joining the values and printing it in a box.
The problem i m facing is if im right now inserting 999,asa,a#gmail.com,82a in respective employee_id,name,email,address...it is taking and printing value in box..
BUT if i insert wrong input like 9999 in employee_id(or any wrong input in either 4 fields) forms:
it is showing this error in page::
ValueError at /device/userdetail/
The view devices.views.userdetailFormView didn't return an HttpResponse object
and in terminal it is showing below error::
<ul class="errorlist"><li>employee_id<ul class="errorlist"><li>Ensure this value is less than or equal to 999.</li></ul></li></ul>
**I want to print the errors in template page (like besides input box) itself so that user knows what to insert and insert correctly,if he enters incorrect data.
*the Answer to my question.Add the following codes after form_valid function in views.py*
def form_invalid(self, form):
print form.errors
return render(self.request, userdetailsView.template_name, {'result': '' , 'form': form})