Flask: Site reload prior check with database - python

I try to build a flask site with a very simple dictionary. A user can save words which are stored in a simple sqlite database. All that works fine, but if I try to implement a simple test on the vocabulary I canĀ“t get consistent data from the database. The site is called, and after validate_on_submit it is relaoded so my choice from the database is a new one. Sorry for the bad description, may be it is gettinger clearer if I show you the code. I followed the CoreySchaefer Tutorials and the structur, so
# forms.py
# ...
class VocTestForm(FlaskForm):
german = StringField('Deutsch', validators=[DataRequired()])
submit = SubmitField('Check')
# models.py
# ...
class Dictionary(db.Model):
id = db.Column(db.Integer, primary_key=True)
engl = db.Column(db.String(120), unique=True, nullable=False)
german = db.Column(db.String(120), unique=True, nullable=False)
def __repr__(self):
return f"{self.engl} {self.german}"
# voc_test.html
{% extends "base_layout.html" %}
{% import "bootstrap/wtf.html" as wtf%}
{% block content %}
<h1>{{check_word.engl}}</h1>
{{ wtf.quick_form(form) }}
{% endblock %}
# routes.py
# ...
#app.route('/voc_test', methods=['GET', 'POST'])
def voc_test():
form = VocTestForm()
content = Dictionary.query.all()
check_word = random.choice(content)
if form.validate_on_submit():
if str(form.german.data) == str(check_word.german):
flash(f'Correct', 'success')
return redirect(url_for('voc_test'))
else:
flash(f'wrong', 'danger')
form.german = " "
return render_template('voc_test.html', form=form, check_word=check_word
The last function is not working. If I compare the String from the form with the String from the database the random.choice has already chosen a new word. How can I prevent this?
Thanks for the help
Steffen

If I've read what you're trying to do right, you could use the random library to generate a number. Then pull the item at that index from the dictionary.

Related

Relationship between models and filter_by errors in Python

I am currently learning Python. I have an app with two classes - customer and order. I would like to display a page that shows all orders associated with one customer (many to one relationship). I believe that I have defined the relationship correctly, but I'm getting some errors.
models.py
from app import db, ma
class customer(db.model):
__tablename__="customer"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(120))
orders = relationship("order", back_populates="customer")
def __repr__(self):
return '<customer %r' % self.id
class order(db.Model):
__tablename_="order"
id = db.Column(db.Integer, primary_key=True)
customer_id = db.Column(db.Integer, db.ForeignKey("customer.id"))
content = db.Column(db.String(120))
customer = relationship("customer")
def __repr__(self):
return '<order %r' % self.id`
routes.py
from app import app
from flask import Flask, render_template, request, redirect
from app.models import *
#app.route('/orders/<customer>')
def show_orders(name):
orders = order.query.join(customer, customer.id==order.customer_id).filter_by(customer.name==name).all()
return render_template('orders.html', name=name)
So for example, if I typed 'orders/bob_smith', this page should display all the orders made by bob_smith in the database.
But when I try to run this, I get the following error:
TypeError: filter_by() takes 1 positional argument but 2 were given.
Am I doing something wrong with the models? Or am I not assigning the right variables in filter_by? Would appreciate any help!
Edit: After some advice, I made the following change to routes.py
#app.route('/orders/<customer>')
def show_orders(name):
orders = order.query.join(customer, customer.id==order.customer_id).filter_by(customer.name==customer.name).all()
return render_template('orders.html', name=name)
I was able to see orders/bob_smith, but instead of showing me only bob_smith's orders, I saw all orders in the database.
orders.html
{{ name }} Orders
{% for order in orders %}
{{ order.customer }} # should all be bob_smith
{{ order.content }}
But instead, I'm seeing a list of all the orders. Am I not limiting the query sufficiently?
Just some small updates:
#app.route('/orders/<customer_name>')
def show_orders(customer_name):
orders = order.query.join(customer, customer.id==order.customer_id).filter(customer.name==customer_name).all()
return render_template('orders.html', orders=orders)
order.html will then need to have:
{% for order in orders %}
{{ order.customer }}
{{ order.content }}
{% endfor %}

What should I use to get the title and post to show?

I'm working on an assignment using python, flask-env, and my sql and I am stuck on trying to get the blog title and blog post to show. I can only get the name of the person to show(which I need but the other stuff is important as well).
If I leave the template with just "indiv_post" for both fields, it shows "None", but if I put anything other that or anything with it, nothing but the author name shows.
in my main.py:
#app.route('/blog', methods=['POST', 'GET'])
def blog():
owner = User.query.filter_by(username=session['username']).first()
if request.method == 'POST':
blog_name = request.form('blog')
new_blog = Blog(blog_name, owner)
#post_title = request.form('blog_title')
#post_entry = request.form('your_thoughts')
db.session.add(new_blog)
db.session.commit()
post_id = request.args.get('id')
indiv_post = Blog.query.get('post_id')
user_id = request.args.get('owner_id')
posts = Blog.query.filter_by(owner_id=user_id)
blog = Blog.query.filter_by(owner_id=user_id)
return render_template('indiv_post.html', owner_id=owner, blog=blog, post=post_id, indiv_post=indiv_post)
in my template(indiv_post.html):
<h1>My Posts</h1>
<h2>{{posts}}</h2>
<p>{{indiv_post}}</p>
<p class="byline">Written by {{session['username']}}</p>
<hr>
I expect the output to be show:
Blog Title
Blog post
written by: user
But I'm getting:
None
None
written by: user

How can I get database's content data?

How can I get database's content data? I wrote in index.py,
def index():
user_data = UserData.query.filter_by(id=1)
return render_template('index.html', user_data=user_data)
in index.html
<div>
<p>{{ user_data.name }}</p>
</div>
in models.py
class UserData(db.Model):
id = db.Column(db.Integer)
name = db.Column(db.Text)
When I show index.html in browser,jinja2.exceptions.UndefinedError: 'name' is undefined error happens.I rewrote in index.py,
def index():
user_data = UserData.query.filter_by(id=1).all()
return render_template('index.html', user_data=user_data)
but same error happens.How can i get& show name value whose id=1 in index.html?How should I fix this code?
Basically, your query returns a list of objects, not a single object.
Eric Wilson's comment is correct. .all() will return a list while .one() (I prefer .first()) will return a single object with .name as a property.

Django Sessions not Working

I have built an application that shows users their storage usage and quotas on a system. Since there are many users, sifting through their storage allocations can be tedious so I want to give admins the option of acting as that user. The application decides which user is accessing the application based on an employee ID received via a secure badg, the variable (EMPLOYEE_ID) is stored in the request.META dictionary.
Ideally, I want the admins to be able to override this employee ID with another user's ID by posting it in a form. The form works and then serves the storage_home.html page as the employee the admin wishes to act as via a POST request, but when I or another admin clicks and does a GET for the quotas, the request.session dictionary is empty!
EMPLOYEE_ID is the original employee id of the admin
SIM_EMPLOYEE_ID is the employee the admin wishes to act as
I wonder if it's the way I'm linking to the quotas view in the storage_home.html template? Not sure.
Here is my code, I believe you should only need views, and the template that calls the quotas view function to see what the issue is since the request.sessions dictionary does have the SIM_EMPLOYEE_ID variable after the post that serves storage_home.html. I've omitted some variables from the views that are used in the template, but they work just fine, didn't want to clutter the code too much.
The sim_user function is called when the form is submitted. This then just recalls the storage function and right now successfully displays what I want it to, it's the GET request subsequently that fail to keep the session. I also have the following set in my settings:
SESSION_COOKIE_SECURE = True
SESSION_COOKIE_DOMAIN = '.mydomain.com'
SESSION_SAVE_EVERY_REQUEST = True
SESSION_EXPIRE_AT_BROWSER_CLOSE = False
views.py
def home(request):
"""Redirect requests at root url to /storage"""
return HttpResponseRedirect('/storage/')
def storage(request):
"""Return the home template."""
context = {}
context.update(csrf(request))
empid = request.session.get('SIM_EMPLOYEE_ID')
if not empid:
empid = request.META.get('EMPLOYEE_ID')
if functions.is_admin(empid):
form = UserForm()
context['form'] = form
template = loader.get_template('storage_admin.html')
else:
template = loader.get_template('storage_home.html')
data = RequestContext(request, context)
return HttpResponse(template.render(data))
def sim_user(request):
context = {}
context.update(csrf(request))
if request.method == 'POST':
form = UserForm(request.POST)
if form.is_valid():
empid = form.cleaned_data['empid']
request.session['SIM_EMPLOYEE_ID'] = empid
request.session.modified = True
return storage(request)
template = loader.get_template('deny.html')
data = RequestContext(request, context)
return HttpResponse(template.render(data))
def quotas(request, sitename):
"""Return quota page depending on the
id of the employee. If employee is an
administrator, show all the quota information
for all users/projects. If employee is a user
of the sitename, show them user specific quota information.
Otherwise, deny access and display a custom template."""
context = {}
site = sitename.capitalize()
# EMPLOYEE_ID is in the Http Request's META information
empid = request.session.get('SIM_EMPLOYEE_ID')
if not empid:
empid = request.META.get('EMPLOYEE_ID')
if not empid:
template = loader.get_template('deny.html')
return HttpResponse(template.render(RequestContext(request, context)))
if functions.is_admin(empid):
template = loader.get_template('all_quotas.html')
else:
template = loader.get_template('personal_quotas.html')
data = RequestContext(request, context)
return HttpResponse(template.render(data))
storage_home.html
{% extends 'base.html' %}
{% block title %}Storage Utilization{% endblock %}
{% block content %}
<h1 id="header"><b>Storage Utilization</b></h1>
<p></p>
<table id="storage_table" cellspacing="15">
<tbody>
{% for site in sites %}
{% url "su.views.quotas" as quota %}
<tr>
<td><img src="/static/images/{{ site }}.png"></td>
</tr>
{% endfor %}
</tbody>
</table>
<br></br>
{% endblock %}
Thanks for any help, please let me know if you need more explanation, code, or simplification.
Turns out removing SESSION_COOKIE_SECURE = True fixed the issue. This is my fault for not forgetting that my dev environment uses http and prod https. I actually have separate settings files, but failed to use them properly when I went back to test this new feature. I believe setting the SESSION_COOKIE_SECURE to True when using https should work once I test the production server.
Django provided session stopped working for me for some reason. I made my own it's really easy:
models.py
class CustomSession(models.Model):
uid = models.CharField(max_length=256)
def __str__(self):
return self.uid
How to work with CustomSession
from oauth.models import CustomSession
session = CustomSession.objects # get a list of session objects
new_user = CustomSession(uid=<UID>) # save a user to the session (by uid)
session.get(id=<ID>).uid # get user id
session.get(id=<ID>).delete() # delete user from session (logout)
session.all().delete() # delete all user data in session

WTForms: setting default value from an SQLAlchemy field with relationship

There are many questions here on SO with titles that sound similar to what I'm about to describe but as far as I can tell from literally hours of research, this question is unique. So here goes!
I'm writing my first Flask app. I'm using SQLAlchemy for the model layer and WTForms to handle forms. The app is going to be a lightweight personal finance manager that I probably will not actually use for for serious biz. I have one table for a list of all transactions and another for all expense categories (groceries, clothing, etc). The transaction table has a column ("category") which references the Category table. In the view, I represent the list of categories with a element.
My issue is that when editing a transaction, I can't figure out how to tell WTForms to set the element to a specific pre-defined value. (Yes, I know that you can set a default value at the time that the form is defined, this is not what I am asking.)
The model looks like this (with irrelevant fields removed):
class Category(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(80), nullable=False, unique=True)
# ...
class Trans(db.Model):
# ...
category_id = db.Column(db.Integer, db.ForeignKey('category.id'))
category = db.relationship('Category',
backref=db.backref('trans', lazy='dynamic'))
# ...
forms.py:
def category_choices():
return [('0', '')] + [(c.id, c.name) for c in Category.query.all()]
class TransactionForm(Form):
# ...
category = SelectField('Category', coerce=int, validators=[InputRequired()])
# ...
The route (POST not yet implemented):
#app.route('/transactions/edit/<trans_id>', methods=['GET', 'POST'])
def trans_edit(trans_id):
transaction = Trans.query.get(trans_id)
form = forms.TransactionForm(obj=transaction)
form.category.choices = forms.category_choices()
form.category.default = str(transaction.category.id)
#raise Exception # for debugging
return render_template('trans.html',
title='Edit Transaction',
form=form)
And finally, the template (Jinja2):
{{ form.category(class='trans-category input-medium') }}
As you can see in the route, I set form.category.default from the transaction.category.id, but this doesn't work. I think my issue is that I'm setting "default" after the form has been created. Which I'm rather forced to because the model comes from the database via SQLAlchemy. The root cause seems to be that form.category is an object (due to the relationship), which WTForms can't seem to handle easily. I can't have been the first one to come across this... Do I need to rework the model to be more WTForms compatible? What are my options?
Thanks!
I alluded to this in my comment. It sounds like you might benefit from using WTForm's SQLAlchemy extension. This will create a dropdown list for categories in your trans form.
My example's use case is slightly different. I am relating blog post's to topic. That is, many posts share one topic. I image in your case, many transactions share one category.
Form
from wtforms.ext.sqlalchemy.fields import QuerySelectField #import the ext.
def enabled_topics(): # query the topics (a.k.a categories)
return Topic.query.all()
class PostForm(Form): # create your form
title = StringField(u'title', validators=[DataRequired()])
body = StringField(u'Text', widget=TextArea())
topic = QuerySelectField(query_factory=enabled_topics, allow_blank=True)
models
The important part here is a) making sure you have the relationship correctly defined, and b.) adding topic to your init since you use it to create a new entry.
class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(80))
body = db.Column(db.Text)
# one-to-many with Topic
topic = db.relationship('Topic', backref=db.backref('post', lazy='dynamic'))
def __init__(self, title, body, topic):
self.title = title
self.body = body
self.topic = topic
class Topic(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50))
def __init__(self, name):
self.name = name
view
Nothing special here. Just a regular view that generates a form and processes submitted results.
#app.route('/create', methods=['GET', 'POST'])
#login_required
def create_post():
form = PostForm()
if form.validate_on_submit():
post = Post(title=form.title.data, body=form.body.data,
topic=form.topic.data)
db.session.add(post)
db.session.commit()
Topic.update_counts()
flash('Your post has been published.')
return redirect(url_for('display_post', url=url))
posts = Post.query.all()
return render_template('create_post.html', form=form, posts=posts)
template
Nothing fancy here either. Just be sure to render the field in the template like you would a basic field. No fancy loop required since WTForms Sqlalchemy extensions does all that for you.
{% extends "base.html" %}
{% block title %}Create/Edit New Post{% endblock %}
{% block content %}
<H3>Create/Edit Post</H3>
<form action="" method=post>
{{form.hidden_tag()}}
<dl>
<dt>Title:
<dd>{{ form.title }}
<dt>Post:
<dd>{{ form.body(cols="35", rows="20") }}
<dt>Topic:
<dd>{{ form.topic }}
</dl>
<p>
<input type=submit value="Publish">
</form>
{% endblock %}
That's It! Now my post form has a topic dropdown list. To use your terminology, when you load a transaction the default category for that transaction will be highlighted in the dropdown list. The correct way to state this is to say that the category associated with the transaction is loaded via the relationship defined in the trans model.
Also note, there is also a multisellect SQLAlchemy extension in case one transaction has many 'default' categories.
Now, my issue is how to deal with many-to-many relationships.... I'm trying to pass a string of tags that are stored in a many-to-many table to a TextArea field. No SQLAlchemy extension for that!
I posted that question here

Categories