Flask-SQLAlchemy and GET requests with pagination - python

I'm trying to build a paginated search form for a simple site. My issue is this - when I navigate away from page 1 (via pagination), and execute or update a search, rather than returning to page 1, the form reloads on whatever page I happen to be on at the time. This breaks the display of the new set of results.
For example, if the URL is this, and I update the search and hit submit, I stay on page two rather than starting over at one.
mysite/coorddash/2?coord_status=Pending&start_date=01/23/2017&end_date=01/23/2018
# VIEWS
#app.route('/coorddash/<int:page>', methods=['GET', 'POST'])
def coordDash(page=1):
per_page = 3
existingISG = ISG.query \
.filter(or_(ISG.coordinator_status == 'IAT Verified', ISG.coordinator_status == 'New')) \
.order_by("isg.id desc") \
.paginate(page,per_page,error_out=False)
form=coordDashForm(request.args)
coordstatus=start=end=""
if form.validate():
coordstatus = request.values.get("coord_status")
form.coord_status.data = coordstatus
start = request.values.get("start_date")
form.start_date.data = datetime.strptime(start, '%m/%d/%Y')
end = request.values.get("end_date")
form.end_date.data = datetime.strptime(end, '%m/%d/%Y')
existingISG = ISG.query.filter(ISG.coordinator_status == coordstatus).filter(ISG.isg_created_on.between(start, end)).paginate(page,per_page,error_out=False)
return render_template('coorddash.html',
title = 'Coordinator Dashboard',
existingisg = existingISG,
form=form,
url_vals=request.query_string.decode())
#MODELS
class ISG(db.Model):
__tablename__ = 'isg'
id = db.Column(db.Integer, primary_key=True)
coordinator_status = db.Column(db.String(30), unique=False)
startdate = db.Column(db.DateTime)
enddate = db.Column(db.DateTime)
#HTML
<form action="" method="get" name="searchForm">
{{ form.coord_status.label }}:{{ form.coord_status }} | {{ form.sort_by.label }}:{{ form.sort_by }}<br>
{{ render_field(form.start_date) }}-{{ render_field(form.end_date) }}<br>
{{ form.submit }}
{{ form.csrf_token }}
</form>
{% for item in existingisg.items %}
<div>
<span>{{ item.location }}</span>
<span>{{ item.implementing_team }}</span>
</div>
{% endfor %}
{% if existingisg.has_prev %} Previous Page{% else %}No previous{% endif %} |
{% if existingisg.has_next %}Next Page {% else %}No more{% endif %}

It looks like you're passing back the pagination as a URL variable on reload of the page. Even with a new query, mysite.coorddash/2 is being sent to your coordDash method and you get the second page of results instead of the first.
If you change your form action to reference the current URL with a trailing /1 for the pagination, it should be sent to the correct view and pagination should restart. For example,
<form action={{ url_for('coordDash', page=1) }} method="get" name="searchForm">
The syntax may be slightly off, but something like this will send your results to the correct function for that URL and specify pagination.

Related

WTForms SelectField returning None

I'm using WTForms and Flask, I am trying to create a form where I can enter information about a recipe, but the product_name SelectField is returning None every time.
The form:
class CreateRecipeForm(Form):
product_name = SelectField(choices=get_craftables_options())
product_quantity = IntegerField(default=1)
job_field = SelectField(choices=['ALC', 'GSM', 'WVR'])
line_item_list = FieldList(FormField(RecipeLineForm), min_entries=6)
save_button = SubmitField()
The view:
#bp.route('/edit/new', methods=('GET', 'POST'))
def create_recipe():
form = CreateRecipeForm()
if request.method == 'POST':
selected_product = Item.query.get(form.product_name.data)
(do stuff here)
The template
{% block content %}
<form method="post">
{{ render_field(form.product_name) }}
{{ render_field(form.product_quantity) }}
{{ render_field_no_label(form.line_item_list) }}
{{ render_field_no_label(form.save_button) }}
</form>
{% endblock %}
I believe your issue lies in declaring the product_name. Make sure the get_craftables_options() is supposed to be a function and is returning a list of items compatible with the choices argument.
product_name = SelectField(choices=get_craftables_options())

AttributeError: 'list' object has no attribute 'product_img' Python, Flask, SQLite

I'm trying to display text and image on template from SQLite database.
Database model:
class NewProduct(db.Model):
id = db.Column(db.Integer, primary_key=True)
product_name = db.Column(db.String(100), nullable=False)
product_description = db.Column(db.String(200), nullable=False)
product_date = db.Column(db.DateTime, default=datetime.utcnow)
product_img = db.Column(db.BLOB())
def __repr__(self):
return '<NewProduct %r>' % self.id
Function to display information from db in .html:
#app.route('/products')
def products():
products = NewProduct.query.order_by(NewProduct.product_date.desc()).all()
product_img = b64encode(products.product_img).decode('utf-8')
return render_template('products.html', products=products, image=product_img)
HTML:
<body>
{% if products|length > 0 %}
{% for el in products %}
<p>Name: {{ el.product_name }}</p>
<p>Description: {{ el.product_description }}</p>
<p>Image: </p> <img src="data:;base64,{{ image }}">
<p>Date: {{ el.product_date }}</p>
{% endfor %}
{% else %}
<p>There are have no products</p>
{% endif %}
So, I'm get an error: AttributeError: 'list' object has no attribute 'product_img' in line product_img = b64encode(products.product_img).decode('utf-8')
I did it for this answer. But I anyway get this error. What I'm doing whong and how can I fix that?
products is a list so the line products.product_img raise the error you are facing. If you need to convert the image into something else you need to loop over the products and modify the image of each product.
You are trying to access product_img for a list of products, not on a single object. You need to iterate through the objects and update the value for each of them:
#app.route('/products')
def products():
products = NewProduct.query.order_by(NewProduct.product_date.desc()).all()
for product in products:
product.product_img = b64encode(product.product_img).decode('utf-8')
return render_template('products.html', products=products)
Then update your template to get the value from the product object:
<body>
{% if products|length > 0 %}
{% for el in products %}
<p>Name: {{ el.product_name }}</p>
<p>Description: {{ el.product_description }}</p>
<p>Image: </p> <img src="data:;base64,{{ el.product_img }}">
<p>Date: {{ el.product_date }}</p>
{% endfor %}
{% else %}
<p>There are have no products</p>
{% endif %}
I would also suggest to remove the prefix product_ from your variables, as it seems redundant and makes your code less readable.

How do I link specific Flask form data to a Bootstrap button

I'm trying to make three button options that fill a Flask form with specific data that is already hard coded (example: yellow button = 1, red button = 2, blue button = 3, and so on), but I'm not really sure how to.
This is some of my code:
forms.py
class NewClientForm(FlaskForm):
name= StringField('Name', validators=[DataRequired()])
category = StringField('Category') # <-- THE ONE I WANT TO CHANGE TO BUTTONS
notes = TextAreaField('Notes')
submit = SubmitField('Create')
form.html
<div class = "form-group">
{{ form.category.label(class = "form-control-label") }}
{% if form.category.errors %}
{{ form.category(class = "form-control form-control-lg is-invalid") }}
<div class = "invalid-feedback">
{% for error in form.category.errors %}
<span>{{ error }}</span>
{% endfor %}
</div>
{% else %}
{{ form.category(class = "form-control form-control-lg") }}
{% endif %}
</div>
routes.py
#app.route("/new_client", methods = ['GET', 'POST'])
def new_client():
form = NewClientForm()
if form.validate_on_submit():
client = Client(name=form.name.data, category=form.category.data, notes=form.notes.data)
db.session.add(client)
db.session.commit()
return redirect(url_for('home'))
Now I can only submit written data instead of hardcoded data.
This is how it looks:

WTforms IntegerField in fieldlist never validates using manual iteration

I have an InterField, that validates if a number is between the values 0 and 99. For some reason it never validates.
I have a feeling it is related to the FieldList and ther way I iterate over it in the template, but can't seem to get it working.
The form:
class dpiaImpactAnalysisForm(Form):
severity_score = IntegerField("Severity Score"),
validators=[NumberRange(min=0, max=99, message="Please provide a valid number")]))
identifiability_score = IntegerField("Identifiability Score"),
validators=[NumberRange(min=0, max=99, message="Please provide a valid number")]))
class dpiaThreatAnalysisForm(Form):
impact = FieldList(FormField(dpiaImpactAnalysisForm), min_entries=1)
In views I append the entries dynamically as required:
#app.route('/dpia/analysis/<project_id>', methods=["GET", "POST"])
def analysis(project_id):
form = dpiaThreatAnalysisForm()
prim = Assets.query.filter_by(selected=True, primary=True).all()
primary_assets = list(map(vars, prim))
ev = Events.query.all()
events = list(map(vars, ev))
# add fields to the form...
for z in range(len(prim) * len(ev)):
form.impact.append_entry()
supp = Assets.query.filter_by(selected=True, primary=False).all()
supporting_assets = list(map(vars, supp))
ths = Threats.query.all()
threats = list(map(vars, ths))
# add fields to the form
for z in range(len(ths) * len(supp)):
form.likelihood.append_entry()
if form.is_submitted():
print "submitted"
if form.validate():
print "valid"
print form.errors
if form.validate_on_submit():
# This is never printed:
app.logger.info("success!!")
pprint(form.likelihood)
return redirect(url_for(next_step, project_id=project_id))
return render_template('analysis.html', form=form, threats=threats, supporting_assets=supporting_assets, primary_assets=primary_assets, events=events)
In the template I iterate over a list primary_assets in a list events, and add the fields per iteration:
{% for val in events %}
{% if not counter or loop.index0 == 0 %}
{% set counter = [] %} <!-- loop hack !-->
{% endif %}
<strong>Event: {{ val.name }}</strong><br />
Jeopardizes: {{ val.jeopardizes }}<br />
{% for pa in primary_assets %}
<strong>{{ pa['name'] }}</strong><br />
{{ form.impact[counter|length].identifiability_score(placeholder='') }} <br />
{{ form.impact[counter|length].severity_score(placeholder='') }}
{{ form.impact[counter|length].hidden_tag() }}
{% if counter.append('1') %}{% endif %}
{% endfor %}
{% endfor %}
The hidden_tag() doesn't work either. Normally I iterate of the forms with with something like
{% for impact in form.impact %}
{{ impact.form.hidden_tag() }}
# do cbg
{% endfor %}
and that works, that's why I believe it's my manual looping that spoils it...
EDIT 2 march, 17:26
After some testing, I found that using
severity_score = IntegerField("Severity Score", validators=[Optional(), NumberRange(
min=0, max=9999999999, message="Please provide a valid number")])
works (if I set min=50 I get an error when inserting a number below 50), however, the CSRF is still not getting passed.
{{ form.impact[counter|length].hidden_tag() }} or
{{ form.impact[counter|length].form.hidden_tag() }} both don't work :(
I'm getting: {'impact': [{'csrf_token': ['CSRF token missing']}, {'csrf_token': ['CSRF token missing']}]}
EDIT 18:22
It seems that this: Form validation fails due missing CSRF is the solution. Investigating...
This: Form validation fails due missing CSRF is the solution.
In previous versions this wasn't needed, and after an installation of a new extension pip updated flask-wtf as well...

Django Voting : Sort by Votes

Lots of documentation on this already, but haven't been able to get any of them to work for me. Like the title implies, trying to get a set of objects that use Django Voting to sort based on the number of their votes (high to low). Have tried this and a few others, but nothing fruitful.
Using Django Voting, here is URL Conf & HTML
#urls.py
url(r'^$', 'questions'),
url(r'^(?P<object_id>\d+)/(?P<direction>up|down|clear)/vote/$',
vote_on_object, dict(model = Question, template_object_name = 'question',
template_name = 'qanda/confirm_vote.html', post_vote_redirect = '/home/', allow_xmlhttprequest=True)),
Questions in the URLconf leads to the questions view which renders questions.html
#questions.html
{% load voting_tags %}
{% votes_by_user user on the_question as vote_dict %}
{% scores_for_objects the_question as score_dict %}
<div class="question">
{% if the_question %}
<ul>
{% for question in the_question %}
{% dict_entry_for_item question from vote_dict as vote %}
{% dict_entry_for_item question from score_dict as score %}
<div class="votearrowdiv">
<div class="upvotearrow"></div></a>
<form class="linkvote" id="linkup{{ question.id }}" action="/home/{{ question.id }}/{% if vote and vote.is_upvote %}clear{% else %}up{% endif %}/vote/" method="POST">
{% csrf_token %}
<input type="image" id="linkuparrow{{ question.id }}" src="{{ media_url }}img/aup{% if vote and vote.is_upvote %}mod{% else %}grey{% endif %}.png">
</form>
<div class="downvotearrow"></div></a>
<form class="linkvote" id="linkdown{{ question.id }}" action="/home/{{ question.id }}/{% if vote and vote.is_downvote %}clear{% else %}down{% endif %}/vote/" method="POST">
{% csrf_token %}
<input type="image" id="linkdownarrow{{ question.id }}" src="{{ media_url }}img/adown{% if vote and vote.is_downvote %}mod{% else %}grey{% endif %}.png">
</form>
</div>
<li>
<div class="votecounter"><div class="numbercount">
<span class="score" id="linkscore{{ question_id }}"
title="after {{ score.num_votes|default:0 }} vote{{ score.num_votes|default:0|pluralize }}">
{{ score.score|default:0 }}
</span>
</div></div>
{{ question.question_text }}
{% endfor %}
{% endif %}
Here's my current view:
#views.py
def questions(request, movie_id):
p = Movie.objects.get(pk=movie_id)
k = Question.objects.filter(movie=p).order_by('q_pub_date')
l = k.reverse()
return render_to_response('qanda/questions.html', {'movie':p, 'the_question':l}, context_instance = RequestContext(request))
I know I can't sort using "score" because it's not in the model. What do I need to change in my view to sort this correctly?
EDIT:
Robert, here's models.py. Tried your solution, and a number of variations, but I don't have a voting attribute to the model. Take a look:
#models.py
class Question(models.Model):
movie = models.ForeignKey(Movie, blank=True, null=True)
question_text = models.CharField(verbose_name = "Question", max_length = 250)
question_detail = models.CharField(verbose_name = "Details (Optional)", max_length = 5000, blank = True, null = True)
q_pub_date = models.DateTimeField(auto_now_add = True)
q_author = models.ForeignKey(User)
Any insight?
It'd be handy if you posted your model.py, but I'm going to make some guesses.
Firstly, this might help:
#views.py
def questions(request, movie_id):
p = Movie.objects.get(pk=movie_id)
k = Question.objects.filter(movie=p).order_by('-q_pub_date')
...
(don't need to use reverse, can just begin it with -)
I'm going to guess that your score could be sorted as follows:
k = Question.objects.filter(movie=p).order_by('movie__score', '-q_pub_date')
The __ (double underscore) will refer to an attribute of related model.
I've been known to live and die by this: https://docs.djangoproject.com/en/dev/topics/db/queries/#related-objects

Categories