Dynamic Length Form handling and Data Collection in Django - python

I have a form in which the number of input fields keeps changing and depends on a parameter that I pass. This means that I can't go to forms.py and create a Form class because that requires me to define the input parameters beforehand.
Here is how I am defining the form.
<!-- Table to Display the original sentence and take the input of the translation -->
<table class="table">
<thead class="thead-dark">
<tr>
<th scope="col">#</th>
<th scope="col">Original Sentence</th>
<th scope="col">Translation</th>
</tr>
</thead>
<form name="translated-text-form" class="form-control" action="" method="post">
{% csrf_token %}
<tbody>
{% for sentence in sentences %}
<tr>
<th scope="row">{{ forloop.counter }}</th>
<td>{{ sentence.original_sentence }}</td>
<td><input type="text" name="translated-{{sentence.id}}" value="{{ sentence.translated_sentence }}" /></td>
</tr>
{% endfor %}
<tr>
<td colspan="2">
<br>
<p style="text-align: center;">
<input class="btn btn-secondary" type="submit" value="Save Translations"/>
</p>
</td>
</tr>
</tbody>
</form>
</table>
This is the form output
The user will add some text in the rightmost column and I want to save that text with a particular sentence.id in my Sentence model. This is what my model looks like
class Sentence(models.Model):
""" Model which holds the details of a sentence. A sentence is a part of a Wikipedia article which is tokenized.
Fields:
project_id: The ID of the project to which the sentence belongs
original_sentence: The original sentence tokenized from from the Wikipedia article.
translated_sentence: The translated sentence in the target language.
"""
# Define the sentence model fields
project_id = models.ForeignKey(Project, on_delete=models.CASCADE)
original_sentence = models.CharField(max_length=5000)
translated_sentence = models.CharField(max_length=5000, default="No Translation Found")
I know how I will approach this. In my views.py, I want to run a for-loop and collect the data from the form where the name is given as translated-{{sentence.id}}. However, I am not able to make the POST request handler that can directly collect the data from the form and save it in the Sentence model based on the id of the sentence. I need help in writing that request handler in my views.

I found a fix for this which doesn't use the forms.py at all. This is what the post request handler would look like.
# Handle the post request
if request.method == 'POST':
# Get the project details from the database
all_sentence_data = Sentence.objects.filter(project_id=pk)
sentence_ids = [sentence.id for sentence in all_sentence_data]
# Iterate through all the input fields in the form
for i in range(len(sentence_ids)):
# Get the translated text from the form
try:
translated_text = request.POST[f"translated-{str(sentence_ids[i])}"]
if translated_text:
# Update the Sentence object with the translated text
Sentence.objects.filter(id=sentence_ids[i]).update(translated_sentence=translated_text)
except:
continue
messages.success(request, "Your translations have been saved successfully!")
return redirect('/translation/' + str(pk)+'/')
else:
return redirect('/translation/' + str(pk)+'/')

Related

Can’t loop value in html table

I’m building a function which can let admin to delete account in that page.
I have several accounts in admin page but all the delete buttons are holding a value from first account in that page instead of other account.
<form action="/admin" method="post">
<table class="table">
<thead class="table-dark">
<tr>
<th>Users</th>
</tr>
</thead>
{% for user in users %}
<tr style="background-color:burlywood">
<td><input type="hidden" name="user" value="{{user.username}}">{{user.username}}</td>
<td><input type="submit" for="user" value="Delete"></td>
{% endfor %}
</tr>
</table>
#app.route("/admin", methods=["GET", "POST"])
#login_required
def admin():
"""Manage account"""
if request.method == "GET":
user_id = session["user_id"]
admin = db.execute("SELECT id FROM users WHERE id = (?)",
user_id)
for row in admin:
if int(row["id"]) == 17:
users = db.execute("SELECT username FROM users")
return render_template("admin.html", users = users)
else:
return apology("Only Admin can access this page!")
return apology("valid return")
else:
username = request.form.get("user")
flash(f"{username} has been deleted!")
return redirect("/")
I expected each delete button is holding each value in column.
After the issue is fixed, when delete button is clicked flash function will show the different username based on the value not admin at all.
here is html page
From the tags I believe you're using Jinja to load and render the html page, however, you should be more clear about what you want to ask.
This may sound like a bad idea and definitely something you shouldn't do in production but how about creating different forms for each user?
Something like this :
<table class="table">
<thead class="table-dark">
<tr>
<th>Users</th>
</tr>
</thead>
{% for user in users %}
<form action="/admin" method="post">
<tr style="background-color:burlywood">
<td><input type="hidden" name="user" value="{{user.username}}">{{user.username}}</td>
<td><input type="submit" for="user" value="Delete"></td>
</tr>
</form>
{% endfor %}
</table>
I think your line only returns usernames in your users variable:
users = db.execute("SELECT username FROM users")
So users in your template is an array that only contains a list of names, or only contains one name.
1 / Whats contains users?
2 / what if you put {{ user }} instead of {{ user.username }} in your template?

How do I make a post request work after using get_queryset?

I would like to have a list of a user's devices with a checkbox next to each one. The user can select the devices they want to view on a map by clicking on the corresponding checkboxes then clicking a submit button. I am not including the mapping portion in this question, because I plan to work that out later. The step that is causing problems right now is trying to use a post request.
To have the user only be able to see the devices that are assigned to them I am using the get_queryset method. I have seen a couple questions regarding using a post request along with the get_queryset method, but they do not seem to work for me. Using the view below, when I select a checkbox and then click submit, it looks like a post request happens followed immediately by a get request and the result is my table is empty when the page loads.
Portion of my views.py file:
class DeviceListView(LoginRequiredMixin, ListView):
model = Device
template_name = 'tracking/home.html'
context_object_name = 'devices'
def get_queryset(self, *args, **kwargs):
return super().get_queryset(*args, **kwargs).filter(who_added=self.request.user)
def post(self, request):
form = SelectForm(request.POST)
return render(request, self.template_name, {'form': form})
Portions of my template:
<div class="table-responsive">
<form action="" method="post" name="devices_to_check">
{% csrf_token %}
<table id="registered_devices"
class="table table-striped table-bordered table-hover table-sm"
style="width:100%; border: 1px solid black; font-size: 10px">
<thead class="table-primary"
style="text-align:center; border: 1px solid black">
<tr>
<th>IMEI</th>
<th>Label</th>
<th>Device Type</th>
<th>Group</th>
<th>Subgroup</th>
<th>Description</th>
<th>Display</th>
</tr>
</thead>
<tbody>
{% for device in devices %}
<tr>
<td>{{device.imei}}</td>
<td>{{device.label}}</td>
<td style="text-transform:uppercase">{{device.device_type}}</td>
<td>{{device.main_group}}</td>
<td>{{device.subgroup}}</td>
<td>{{device.description}}</td>
<td style="text-align:center">
i
<input type="checkbox" id="{{device.imei}}" name="chk"
value="{{device.imei}}" onclick="show_info_icon()"
class="chckvalues"/>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<input type="button" class="btn btn-outline-info" onclick='selects()'
value="Select All"/>
<input type="button" class="btn btn-outline-info" onclick='deSelect()'
value="Deselect All"/>
<button type="submit" class="btn btn-outline-info">Show on Map</button>
</form>
</div>
I think you're better off using a function based view with the typical "if request.method = POST" logic, this isn't really what the generic list view is for. (edits to snippet added)
#login_required
def device_list_view(request):
context = {}
if request.method == 'POST':
form = SelectForm(request.POST)
if form.is_valid():
# if a request was posted and is valid, do your thing:
# maybe your thing is to give your map view the device id
# and render it
# assuming your form has a field called device_id:
context['device_id'] = form.device_id
return render(request, 'tracking/device_map.html', context)
# either request method is not post or the form wasn't valid
user_devices = Device.objects.filter(who_added=request.user)
device_forms = []
for i, device in enumerate(user_devices):
form = SelectForm(instance=device, prefix=i)
device_forms.append(form)
context['device_forms'] = device_forms
return render(request, 'tracking/home.html', context)
The key part here is the if-else logic of checking whether the request was POST or not, you'll need to tweak it based on exactly what you want to happen. It sounds like what you're really doing is creating one form from which you're pulling multiple device_ids. This is a good reference for what's going on with the prefix bit and should help you decide whether to do things that way or with one form like you're trying.

Flask - cant post to route [duplicate]

This question already has answers here:
Sending data from HTML form to a Python script in Flask
(2 answers)
Closed last year.
I'm creating a configurator app for heating systems. Essentially the idea is by putting in a few inputs - the app will spit out the part number for a system pack where a user can see a detailed bill of material (BOM).
One key element is the output where we need to show a few options. I.e. if someone needs a 200kW system, there could be 3-4 packs that are suitable (190kW -210kW might be more cost effective).
I want in the first instance to show on a route the pack options that are suitable- then the user selects the pack they want- which takes you to a route (/cart) which shows the BOM.
I have put in input variables min and max which searches a database cascades.db. This successfully shows me the table of options.
from cs50 import SQL
from flask import Flask, render_template, request, url_for, redirect
app = Flask(__name__)
db = SQL("sqlite:///cascades.db")
#app.route("/")
def index():
return render_template("index.html")
#app.route("/search", methods=["GET", "POST"])
def search():
output = request.form.get("output")
hydraulic = request.form.get("hydraulic")
layout = request.form.get("layout")
controls = request.form.get("controls")
min_output = int(output) - 15
max_output = int(output) + 15
cascades = db.execute("SELECT * FROM cascades WHERE hydraulic = ? AND layout = ? AND output BETWEEN ? and ?", hydraulic, layout, min_output, max_output)
return render_template("search.html", cascades=cascades)
#app.route("/cart", methods=["GET", "POST"])
def cart():
bom_id = request.form.get("bom_id")
bom = db.execute("SELECT * FROM bom WHERE bom_id = ?", bom_id)
return render_template("bom.html", bom = bom)
When running the app- the first bit works - i.e. it shows me a list of all the packs that meet the criteria- but when clicking the 'Choose' button Im getting stuck.
{% extends "layout.html" %}
{% block body %}
<h3> Boiler Cascade Pack Options:</h3>
<table class="table table-striped table-boardered">
<tr>
<th>Part Number</th>
<th>Description</th>
<th>Number of boilers</th>
<th>BOM</th>
</tr>
{% for cascades in cascades %}
<tr>
<td scope="cascades">{{ cascades["id"] }}</td>
<td>{{ cascades["description"] }}</td>
<td>{{ cascades["number_of_boilers"] }}</td>
<td>
<form action "/cart" method="post">
<input name="bom_id" type="hidden" value="{{ cascades["id"] }}">
<input class="btn btn-primary" type="submit" value="Choose">
</form>
</td>
</tr>
{% endfor %}
</table>
{% endblock %}
But when submitting the form- where they select the pack (which has the pack id number- as a hidden form) I get the following error:
File "/home/ubuntu/cascade2/application.py", line 26, in search
min_output = int(output) - 15 TypeError: int() argument must be a
string, a bytes-like object or a number, not 'NoneType'
It seems like the route is trying to use the same logic in the second search but at this point its redundant. The second search all I want is to show me information where the Pack id = BOM.
I've noticed the URL stays on the (/search) route with this and not going to the (/cart).
I've tried several things such as putting in a IF NOT output- redirect to Cart to try and bi pass this- which successfully loads the bill of material page but nothing comes up as I don't think its posted the id to the route.
I've also changed it to GET instead of POST, which results in the query string /search?bom_id=71723827132. Which shows its picking up the part number- but staying on the /search route where I see the error.
<h3> Boiler Cascade Pack Options:</33>
<table class="table table-striped table-boardered">
<tr>
<th>Part Number</th>
<th>Description</th>
<th>Number of boilers</th>
</tr>
{% for cascade in cascade %}
<tr>
<td scope="cascade">{{ cascades["id"] }}</td>
<td>{{ cascades["description"] }}</td>
<td>{{ cascades["number_of_boilers"] }}</td>
</tr>
{% endfor %}
</table>
<br>
<h4>Bill of material:</h4>
<br>
<table class="table table-striped table-boardered">
<tr>
<th>Product ID</th>
<th>Product Description</th>
<th>Quantity</th>
</tr>
{% for bom in bom %}
<tr>
<td scope="bom">{{ bom["part_number"] }}</td>
<td>{{ bom["product_description"] }}</td>
<td>{{ bom["quantity"] }}</td>
</tr>
{% endfor %}
</table>
Been stuck on this for a month. This to me is the last piece of the puzzle. Any help/suggestions would be great. I'm new to programming so I bet I've missed something obvious :)
Your <form action "/cart" method="post"> is missing an = after action. That means the action attribute is not properly defined, and the default for action always is the URL you're currently on. That's why it stays at /search.

How to download an updated file by admin in Django

what i am trying to do is:
Admin uploads a PDF file from admin panel. (1)
It needs to go to the specified template. (2)
And it should be downloaded by pressing download button in the template.
So here are codes:
(1)
class Reports(models.Model):
name = models.CharField(max_length=100, null=False, blank=False, verbose_name="File Name")
report = models.FileField()
(2)
<tr>
<td>"File Name must be showed in here"</td>
<td class="text-center">PDF</td>
<td class="text-center lang-tr-src"><i class="fas fa-file-download"></i></td>
<td class="text-center lang-en-src"><i class="fas fa-file-download"></i></td>
</tr>
In the website there will be one report for every month. I want to list them in the template and make them downloadable.
Should i write a view for that(if yes how it should be?) or what should i do?
Every single data you want to show to your template you need write it in your views.py, so this case is so specefic.
views.py:
def your_view_name(request):
reports = Reports.objects.all()
context = {
'reports': reports
}
return render(request, 'your_template.html', context)
Then make a url for your view in urls.py
urlpatterns = [
path("", views.your_view_name, name='your_url_name')
]
Your template:
<tr>
{% for obj in reports %}
<td>{{ obj.name }}</td>
<td class="text-center">PDF</td>
<td class="text-center lang-tr-src"><a href="{{ obj.report.url }}" Download
target="_blank"><i class="fas fa-file-download"></i></a></td>
<td class="text-center lang-en-src"><a href="" target="_blank"><i
class="fas fa-file-download"></i></a></td>
{% endfor %}
</tr>
create a new view firstly.
def report_view(request):
context = {}
reports= Reports.objects.all()
context['reports'] = reports
return render(request, "pages/report.html", context)
create an url for this view in urls.py
path('reports', report_view, name='report_view'),
in your template create forloop for this context like below:
{% for report in reports %}
<tr>
<td>"File Name must be showed in here"</td>
<td class="text-center">PDF</td>
<td class="text-center lang-tr-src"><i class="fas fa-file-download"></i></td>
<td class="text-center lang-en-src"><i class="fas fa-file-download"></i></td>
</tr>
{% endfor %}

Dynamically render matrix like form in Django

I am using a Django model to represent day-hour combinations. The goal is to render a matrix-like form with the days on the x-axis and the hours on the y-axis. The user can check each combination to define when a vehicle is available during a week.
The model looks as follows:
class TimeMatrix(models.Model):
MO00 = models.BooleanField(default=False)
MO01 = models.BooleanField(default=False)
MO02 = models.BooleanField(default=False)
MO03 = models.BooleanField(default=False)
....
SO22 = models.BooleanField(default=False)
SO23 = models.BooleanField(default=False)
I like to render the corresponding form coming from the CreateView as the mentioned matrix. The html to do this requires a list of days = ['MON', 'TUE', ..., 'SUN'] and hours = ['00', '01', ..., '23'] in order to render the form dynamically. The following html shows this without using the form the Django CreateView provides:
<form action="#">
<div class="table-responsive">
<table class="table">
<thead class="thead-light">
<tr>
<th scope="col">Day</th>
{% for h in hours %}
<th scope="col">{{h}}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for d in days %}
<tr>
<th scope="row" class="wt-col">{{d}}</th>
{% for h in hours %}
<td><input type="checkbox" name="{{d}}{{h}}" value="{{d}}{{h}}" id="id_{{d}}{{h}}"></td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
</form>
Since I like to use the security measures build into Django forms I like this to make use of the form the CreateView provides. Following this explanation, the individual form can be accessed using form.<field_name>. Combining this with the html would require a dynamic way to access the form fields, which for me raises two questions:
Following this advice I was able to implement getattr functionality for the templates which works with my models, but fails on the form the CreateView provides (e.g. {{form|getattribute:'MON00'}}). What am I missing?
Since I cannot concat the day and hour string in the template ({{form|getattribute:d+h}} will not work), what is a suitable way to access the required form field?

Categories

Resources