Query and display one row based on user input in jinja - python

I want to print one table row based on the user input. I am getting the user input (id) via HTML and Python, here:
html file:
<tbody class="table-group-divider">
{% for course in user.courses %}
<tr>
<!-- here an ID gets rendered from the table, and user clicks on it -->
<td>{{ course.course_name }}</td>
<td class="align-middle">
{% for student in course.students %}
<span>{{ student.student_name }}</span><br>
{% endfor %}
</td>
<td>{{ course.course_day }} {{ course.course_time }}</td>
<td>{{ course.course_language }}</td>
</tr>
{% endfor %}
</tbody>
app.py:
#app.route('/course_detail/<id>', methods=["GET", "POST"])
#login_required
def course_detail(id):
return render_template("course_detail.html", user=current_user, id=id)
My logic is to loop through the table, and if id provided by user matches id (primary key) in table, I found what I want to display, and then display it. However, my attempt does not print anything:
<!-- setting the ID from url/html, basically what user clicked -->
{% set id = id %}
{% for course in user.courses %}
{% if course.id == id %}
<p>im working</p>
{% endif %}
{% endfor %}
I get the id from user, but I can't seem to compare it with course.id so I could display the whole row. Is there a problem with my if statement?
Edit: If I hardcode the if statement to be for example {% if course.id == 2 %} (or any other valid course.id from the table), the information gets printed with no issues.

While I'm not sure why the original proposition doesn't work, I managed to reach my goal via querying my database in app.py, not my html file.
#app.route('/course_detail/<id>', methods=["GET", "POST"])
#login_required
def course_detail(id):
# query the id in db
db_id = Course.query.filter_by(id=id).first()
# save all info into variables
db_course_name = db_id.course_name
db_course_language = db_id.course_language
db_course_day = db_id.course_day
db_course_time = db_id.course_time
db_hourly_rate = db_id.hourly_rate
# send the variables and print them in html
return render_template("course_detail.html", user=current_user, pageid=db_id, db_course_name=db_course_name, db_course_language=db_course_language, db_course_day=db_course_day, db_course_time=db_course_time, db_hourly_rate=db_hourly_rate)
Then I simply printed the variables using double curly braces. Not sure if this is the best design, but it certainly feels better than my previous attempt.

Related

How to render multiple update forms in Django

here is my problem.
I have a list of objects that I display in a table and all works just fine except that I would like to edit them inside a modal and submit them using AJAX.
I though that it was a simple idea to render, for each row, a form with the inputs pre-filled and then submit the selected form with AJAX.
I wonder if there is a relatively simplified way to render the UpdateForm without writing manually all the input fields.
Something like this:
<table>
{% for transaction in transactions %}
<tr>
<td>{{ transaction.date|date:"d.m.Y" }}</td>
<td>{{ transaction.amount }}</td>
<td>
Edit
<div class="modal" id="edit{{ transaction.id }}">
{{ transaction_form }}
</div>
</td>
<tr>
{% endfor %}
</table>
But how can I pass the form from the view?
The way I'm currently doing it is that when the user click on edit the page refresh and the modal is displayed with the form prefilled but it is (a) slow to open and (b) I don't think it is a nice way to do it.
This is my current code
views.py
class ProjectDetailView(DetailView):
model = Project
template_name = "template.html"
context_object_name = "project"
def get_transactions(self):
transactions = Transaction.objects.filter(project=self.get_object())
return transactions
def get_transaction_form(self):
form = TransactionForm()
if self.request.POST:
form = TransactionForm(self.request.POST)
elif 'edit_entry' in self.request.GET:
form = TransactionForm(instance=self.get_entry())
return form
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['transactions'] = self.get_transactions()
context['transaction_form'] = self.get_transaction_form()
return context
template.html
<table>
{% for transaction in transactions %}
<tr>
<td>{{ transaction.date|date:"d.m.Y" }}</td>
<td>{{ transaction.amount }}</td>
<td>
Edit
</td>
<tr>
{% endfor %}
</table>
<div class="modal" id="edit-modal">
{{ transaction_form }}
</div>
<script>
{% if 'edit_entry' in request.GET %}
$('#edit-modal').modal('show')
{% endif %}
</script>
Thank you for any feedback
This solution needs you work with Javascript to do that,
when the user clicks 'Edit' for an object on your page,
you send AJAX request (using Fetch API or Jquery) to your view,
The view will return HTML of the form and you put this HTML in the modal's body
Show the modal with an action button to submit the form.
As the user clicks submit, your code submits the form through Ajax, you can use Formdata or AjaxForm for that, the view which return an JSON or HTML which indicates if the data is saved successfully or not.
The problem I'm not a Class-Based View guy so I can't give you specifics from Django side.

Drop down lists not populating in DB in Django Forms on click of submit button

I have a table inside form. It looks like below:
{% extends "base.html" %}
{% block title %}Title{% endblock title %}
{% block content %}
<form actions="" method="post">
{% csrf_token %}
<table>
<table border = "1" cellpadding = "10" cellspacing = "10" bordercolor = "green">
<tr>
<th>numbers</th>
<th>Extension</th>
<th>Vendor</th>
</tr>
{% for number in numbers %}
<tr>
<td>{{ number }}</td>
<td class = "select">Select Extension
<select name="extensions">
{% for obj in sipextensionsets %}
<option value={{obj.sip_extension}}>{{ obj.sip_extension }}</option>
{% endfor %}
</select>
</td>
<td>vendor</td>
</tr>
{% endfor %}
</table>
<input type="submit" value="save"/>
</form>
{% endblock content %}
My forms.py is below:
from django import forms
from .models import column
class didsForm(forms.ModelForm):
class Meta:
model = column
fields = ('extension')
My views.py is below
def saveintodb(request):
try:
instance = coloumn.objects.get(pk=1)
except:
instance = coloumn(pk=1)
instance.save()
if request.method == 'POST':
dids_form = didsForm(data=request.POST['extensions'], instance=instance)
if dids_form.is_valid():
dids_form.save()
messages.success(request, "Settings updated. Please apply settings.")
else:
messages.error(request, "Error: Invalid settings.")
else:
dids_form = didsForm(instance=instance)
return render(request, 'dids/index.html', {'dids_form': dids_form})
In the table, there is a drop down (select tag). I want to save the data into database when user selects something from dropdown and clicks on save button. I know I have mistaken somewhere in views.
You're doing a few things wrong here, unfortunately.
The main problem is that you're passing request.POST['extensions'] as the data argument to your form on POST; but that argument is expecting the whole POST, not a single field.
Linked to that is that you have not used the same name for the field in the model and the field in the form. Although you say in your comment that this is intentional, there doesn't seem to be a reason for it, and it's breaking things. Give them the same name.
Thirdly, you aren't letting Django populate the form, or show any errors when it's not valid. You shouldn't be explicitly passing sipextenionset (although you actually don't seem to be passing that at all, so I'm not sure where it's coming from), and you certainly shouldn't be explicitly iterating. You should let Django display the field:
<td>{{ number }}</td>
<td class="select"><label for="id_extension">Select Extension</label>
{{ form.extension }}
</td>
Finally, I can't at all understand what you are doing with that outer for loop through numbers; you will end up with several values for extension, which is not expected by your form, your model, or your view.

Flask: delete file from server and database

Hat I'm trying to accomplish is to delete file from server ('static' folder, to be specific).
My jinja template:
<table>
<tr>
{% for file in files_.items %}
<td data-title="title" style="text-align: center">{{ file.title }}</td>
<td data-title="download">Download</td>
{% if current_user.username == "admin" %}
<td data-title="delete" style="text-align: center">Delete</td>
{% endif %}
</tr>
{% endfor %}
</table>
and my function:
#app.route('/upload/<path:filename>/', methods=['GET', 'POST'])
#login_required
def delete(filename):
item = db.session.query(File).get(filename)
os.remove(os.path.join(app.static_folder, item.filename))
db.session.query(File).filter_by(file=filename).delete()
db.session.commit()
return render_template('dashboard.html',delete=delete)
What I'm trying to do is to after clicking on delete in html I want to delete record from database and file from the server. Right now I'm not sure if my approach to call this function is correct, since I've tried to use prints as a primitive log system and there was nothing in the terminal, co I would say function was not called. Also my guess is that I would need to pass filename to it, so Ive tried
{{ delete(filename=file.file) }}
but it returned
UndefinedError: 'delete' is undefined
{{ delete(filename=file.file) }} in template tells python "when rendering template, call function delete()". What you want to do is generate link which, when clicked, will call delete endpoint.
So, use {{ url_for('delete', filename=...) }}

flask-wtf same form on same page using for loop

I'm brand new to programming. I came up with a project to help me learn and I'm stuck already. I'm using Flask, Flask-SQLAlchemy and Flask-wtf.
I'm trying to create a club attendance system that lists members and checks them off if they are present and logs the amount they paid (either $15 for 1 lesson, or $25 for the week). I have a table that I populate from my database that looks like this:
I want to click on submit to mark the person as present but this ticks the checkbox for everyone in the list and sets the amount paid to the same for everyone.
I have tried lots of things. I have seen similar issues here and people suggesting using FieldList and FormField - I tried this with no luck. Here is my Form code:
class MemberForm(Form):
form_id = HiddenField()
member_id = DecimalField('id')
member_name = StringField('name')
attend_date = StringField('date', default=todays_date())
is_here = BooleanField('here')
has_paid = SelectField('Amount', choices=[(15, '15'), (25, '25')])
submit = SubmitField("Submit")
def __init__(self, *args, **kwargs):
super(MemberForm, self).__init__(*args, **kwargs)
read_only(self.member_name)
My controller code:
#app.route('/', methods=['GET', 'POST'])
def home():
members = Member.query.order_by(Member.name).all()
form = MemberForm()
if request.method == 'POST': # TODO form validation and database stuff
print('got this far')
print(form.data)
return render_template('index.html', title='Tong Long',
today=todays_date(), members=members,
form=form)
and the jinja2 template part:
<table width="483" border="1">
<tbody>
<tr>
<th width="271"><strong>Member</strong></th>
<th width="152"><strong>Grade</strong></th>
<th><strong>Last Seen</strong></th>
<th width="38"><strong>Paid?</strong></th>
<th><strong>Is Here?</strong></th>
<th>Submit</th>
</tr>
{% for member in members %}
<form action="" method="post" name="{{ member.id }}">
<tr>
<td>{{form.member_name(value=member.name)}}</td>
{% for g in member.grade %}
<td>{{ g.grade }}</td>
{% endfor %}
<td>{{ form.attend_date }}</td>
<td>{{ form.has_paid }}</td>
<td>{{form.is_here}}</td>
<td>
{{ form.submit }}
</td>
</tr>
</form>
{% endfor %}
</tbody>
</table>
Viewing the rendered HTML I can see that all the fields have the same id.
I'm starting to think this can't be done with WTForms. Will I need to use javascript perhaps (something I know nothing about). Or manually create the forms rather than using WTF? Any help appreciated!
This is very late, but perhaps it is helpful to somebody.
What calabash is doing, is create one single form and then display it multiple times in the template.
However, to achieve the desired outcome (independend forms with independend submit buttons), multiple forms need to be created within the route function. They can be passed as a list to the template and then looped over. (A simpler solution would be one form with one submit button and dynamically created "lines" for each member. See FieldList...)
Logic:
def home():
members = Member.query.order_by(Member.name).all()
forms = []
for member in members:
form = MemberForm(prefix=member.name)
form.member_name.data = member.name
forms.append(form)
# validation:
for form in forms:
if form.submit.data and form.validate_on_submit():
# do_something here for each form, e.g. write to database
return render_template('index.html', title='Tong Long',
today=todays_date(),
forms=forms,
members=members)
The different forms need to have individual prefixes. They need to be validated individually and it needs to be checked which submit-button was used.
Note: It is perhaps not a good idea to use a form field for the name, as that information is already known from the members database entry and it might not be intended to change it here. A simple text label would make more sense in that case.
The table rows in the template could look like this:
{% for form in forms %}
<form action="" method="post">
{{ form.hidden_tag() }}
<tr>
<td>{{ form.member_name }}</td>
<td>{{ members[loop.index0].grade }}</td>
<td>{{ form.attend_date }}</td>
<td>{{ form.has_paid }}</td>
<td>{{ form.is_here }}</td>
<td>{{ form.submit }}</td>
</tr>
</form>
{% endfor %}

Model method or custom template filter

My problem is that I need to know if a user has rated a certain model instance, BlogSite. On the page, there are multiple BlogSite instances, which have a 5-star rating system. When the current user has rated a certain instance, it should be set to read-only.
I'm running into a roadblock because if I use a model function, I need to pass 2 variables - current_user and BlogSite. I haven't been able to find how to access request.user in models.py and it's looking like I shouldn't be doing that?
The other path I went down was to create a custom filter - but I found out that I can only pass in one parameter. I would rather not do this method because I feel it would be better to keep the logic in views.py
Does anyone have ideas of how I can solve this problem?
#models.py
class BlogSite(models.Model):
#fields
#get the average rating for a blogsite
def rating_avg(self):
rating_dict = BlogSiteReview.objects.filter(blog_site=self).aggregate(Avg('review_rating'))
rating_avg = rating_dict.get('review_rating__avg')
if rating_avg:
return rating_avg
else:
#no ratings
return 0
def rated(self, current_user):
#If there is a row for the blogsitereview with this blogsite for the logged in user, return True, else False
#can I access the current user? This does not work, seems like I can't get request here.
current_user = request.user
review = BlogSiteReview.objects.filter(blog_site=self, user=current_user)
if review:
return True
else:
return False
class BlogSiteReview(models.Model):
blog_site = models.ForeignKey(BlogSite)
user = models.ForeignKey(User)
#other fields
Here is the relevant part of the view:
#views.py
def search(request, type, regionValue):
#....
#ideally, the solution would be to have a field or function in the BlogSite model
blog_sites = BlogSite.objects.filter(country=region.id, active=True)
#....
In the template I would have an if statement to add a class if rated returns True
<tr>
<td>{{ blogsite.site_name }}</td>
<td><div id="rating{{ blogsite.id }}" class="rating {% if blogsite.user_rated %}jDisabled{% endif %}" data-average="{{ blogsite.rating_avg }}" data-id="{{ blogsite.id }}"></div></td>
<td>{{ blogsite.create_date }}</td>
</tr>
I'm looking for 2 things here - does using a model method to get if a user has rated seem like the correct approach? The problem I have with this so far is that I can't find how to access the current user to use in models.py. Another idea I thought of is to pass in the request.current_user from the view somehow, but the user is not associated with BlogSite, so I can't filter on that.
I ended up taking a slightly different approach which I thought of when trying to fall asleep but couldn't get Django out of my head :) In the view I created a list of blog_sites that a certain user left a review
#views.py
rated = BlogSiteReview.objects.filter(user=request.user).values_list('blog_site', flat=True)
return render(request, page, {..., 'blogsites':blog_sites, 'rated':rated})
And in the template I added a class if that blog_site FK id was in the blog_sites for loop:
#template
{% for blogsite in blogsites %}
<tr>
<td>{{ blogsite.site_name }}</td>
<td><div id="rating{{ blogsite.id }}" class="rating {% if blogsite.id in rated %}jDisabled{% endif %}" data-average="{{ blogsite.rating_avg }}" data-id="{{ blogsite.id }}"></div></td>
<td>{{ blogsite.create_date }}</td>
<td>{{ rated }}</td>
</tr>
{% empty %}
<p>There are no registered blogsites for this country.</p>
{% endfor %}
Finally I can go to sleep now!
You could pass the result of blog_site.rated(request.user) via template parameters. If you feed your template with multiple BlogSite instances (blog_sites in your view sample) and iterate over them in the template, you could group the instance and the rated result into tuples or dicts in your view like so:
({"site": block_site, "rated": block_site.rated(request.user)} for block_site in block_sites)
And access these in the template with result.site and result.rated, respectively, assuming that result is the iteration variable (because you named your view search) in the template (replacing blogsite in your template snippet).
Hence, I personally would keep the rated method as you posted it, with the current_user parameter.
EDIT: here is an example which mixes your code (question and answer) and my proposal:
#views.py
def search(request, type, regionValue):
#....
blog_sites = BlogSite.objects.filter(country=region.id, active=True)
search_results = ({"site": block_site, "rated": block_site.rated(request.user)} for block_site in block_sites)
return render(request, page, {..., 'search_results': search_results})
And in the template:
{% for result in search_results %}
{% with blogsite=result.site rated=result.rated %}
<tr>
<td>{{ result.site.site_name }}</td>
<td><div id="rating{{ blogsite.id }}" class="rating {% if rated %}jDisabled{% endif %}" data-average="{{ blogsite.rating_avg }}" data-id="{{ blogsite.id }}"></div></td>
<td>{{ blogsite.create_date }}</td>
<td>{{ rated }}</td>
</tr>
{% endwith %}
{% empty %}
<p>There are no registered blogsites for this country.</p>
{% endfor %}

Categories