Why does the render_template keep on showing the old value of the flask form? - flask

I've been searching for an answer for hours. I apologise if I missed something.
I'm using the same form multiple times in order to add rows to my database.
Every time I check an excel file to pre-fill some of the wtforms StringFields with known information that the user may want to change.
The thing is: I change the form.whatever.data and when printing it, it shows the new value. But when I render the template it keeps showing the old value.
I tried to do form.hours_estimate.data = "" before assigning it a new value just in case but it didn't work.
I will attach here the route I'm talking about. The important bit is after # Get form ready for next service. If there's more info needed please let me know.
Thank you very much.
#coordinator_bp.route("/coordinator/generate-order/<string:pev>", methods=['GET', 'POST'])
#login_required
def generate_order_services(pev):
if not (current_user.is_coordinator or current_user.is_manager):
return redirect(url_for('public.home'))
# Get the excel URL
f = open("./app/database/datafile", 'r')
filepath = f.read()
f.close()
error = None
if GenerateServicesForm().submit1.data and GenerateServicesForm().validate():
# First screen submit (validate the data -> first Service introduction)
form = FillServiceForm()
next_service_row = get_next_service_row(filepath)
if next_service_row is None:
excel_info = excel_get_pev(filepath)
error = "Excel error. Service code not found. If you get this error please report the exact way you did it."
return render_template('coordinator/get_pev_form.html', form=GetPevForm(), error=error, info=excel_info)
service_info = get_service_info(filepath, next_service_row)
service_code = service_info[0]
start_date = service_info[1]
time_estimate = service_info[2]
objects = AssemblyType.get_all()
assembly_types = []
for assembly_type in objects:
assembly_types.append(assembly_type.type)
form.service_code.data = service_code
form.start_date.data = start_date
form.hours_estimate.data = time_estimate
return render_template('coordinator/fill_service_form.html', form=form, error=error, assembly_types=assembly_types)
if FillServiceForm().submit2.data:
if not FillServiceForm().validate():
objects = AssemblyType.get_all()
assembly_types = []
for assembly_type in objects:
assembly_types.append(assembly_type.type)
return render_template('coordinator/fill_service_form.html', form=FillServiceForm(), error=error,
assembly_types=assembly_types)
# Service screen submits
# Here we save the data of the last submit and ready the next one or end the generation process
# Ready the form
form = FillServiceForm()
next_service_row = get_next_service_row(filepath)
if next_service_row is None:
excel_info = excel_get_pev(filepath)
error = "Excel error. Service code not found. If you get this error please report the exact way you did it."
return render_template('coordinator/get_pev_form.html', form=GetPevForm(), error=error, info=excel_info)
service_info = get_service_info(filepath, next_service_row)
service_code = service_info[0]
form.service_code.data = service_code
# create the service (this deletes the service code from the excel)
service = create_service(form, filepath)
if isinstance(service,str):
return render_template('coordinator/fill_service_form.html', form=form, error=service)
# Get next service
next_service_row = get_next_service_row(filepath)
if next_service_row is None:
# This means there is no more services pending
return "ALL DONE"
else:
# Get form ready for next service
service_info = get_service_info(filepath, next_service_row)
service_code = service_info[0]
start_date = service_info[1]
time_estimate = service_info[2]
print("time_estimate")
print(time_estimate) # I get the new value.
objects = AssemblyType.get_all()
assembly_types = []
for assembly_type in objects:
assembly_types.append(assembly_type.type)
form.service_code.data = service_code
form.start_date.data = start_date
form.hours_estimate.data = time_estimate
print(form.hours_estimate.data) # Here I get the new value. Everything should be fine.
# In the html, the old value keeps on popping.
return render_template('coordinator/fill_service_form.html', form=form, error=error,
assembly_types=assembly_types)
number_of_services = excel_get_services(filepath=filepath, selected_pev=pev)
# Get the number of the first excel row of the selected pev
first_row = excel_get_row(filepath, pev)
if first_row is None:
excel_info = excel_get_pev(filepath)
error = "Excel error. PEV not found. If you get this error please report the exact way you did it."
return render_template('coordinator/get_pev_form.html', form=GetPevForm(), error=error, info=excel_info)
service_code = []
start_date = []
time_estimate_code = []
quantity = []
# Open the excel
wb = load_workbook(filepath)
# grab the active worksheet
ws = wb.active
for idx in range(number_of_services):
# Append the data to the lists
service_code.append(ws.cell(row=first_row+idx, column=12).value)
start_date.append(str(ws.cell(row=first_row + idx, column=5).value)[:10])
time_estimate_code.append(ws.cell(row=first_row+idx, column=7).value)
quantity.append(ws.cell(row=first_row + idx, column=9).value)
wb.close()
return render_template('coordinator/generate_services_form.html',
form=GenerateServicesForm(),
pev=pev,
service_code=service_code,
start_date=start_date,
time_estimate_code=time_estimate_code,
quantity=quantity)

Well I found a workarround: I send the data outside the form like this:
return render_template('coordinator/fill_service_form.html', form=form, error=error,
assembly_types=assembly_types,
service_code=service_code,
start_date=start_date,
time_estimate=time_estimate)
And replace the jinja form for this:
<input class="form-control" placeholder="2021-04-23" name="start_date" type="text" value="{{start_date}}">
I'm still using the form (name= the form field name) and at the same time I input the value externally.
I hope this helps somebody.

Related

How do I tackle this issue in my Guess The Flag Django web app

I'm completely new to Django and I'm trying to build this guess the flag web game with it. In main page when someone presses the 'play' button, he's sent to a page where a list of 4 randomly selected countries from the DB is generated, and only of one these 4 countries is the actual answer.
Here's the code from views.py in my App directory :
context = {}
context['submit'] = None
context['countries_list'] = None
score = []
score.clear()
context['score'] = 0
def play(request):
len_score = len(score)
countries = Country.objects.all()
real_choice = None
if request.POST:
get_guess = request.POST.get('guess')
print(request.POST)
if str(get_guess).casefold() == str(context['submit']).casefold():
score.append(1)
else:
score.clear()
len_score = len(score)
choices = random.sample(tuple(countries),4)
real_choice = random.choice(choices)
context['countries_list'] = choices
context['submit'] = real_choice
context['score'] = len_score
return render (request, 'base/play.html', context)
Everything works as expected when there's only one person playing, or the site is opened in only one tab.
The issue here is one someone else opens the site or it's opened in more than one tab, the score gets reset and a new list of random countries is generated for all users, so your guess will never be right!
How do I go about to solve this? Again, I'm completely new to this so I'm left clueless here.
The best way to store short term, per user information is by session variables. This way you can maintain the user's individual settings. So, something like:
def play(request):
len_score = len(score)
countries = Country.objects.all()
real_choice = None
if request.POST:
get_guess = request.POST.get('guess')
print(request.POST)
#Compare guess to session variable
#(using .get() which returns none rather than error if variable not found)
if str(get_guess).casefold() == str(request.session.get('real_choice').casefold():
score.append(1)
else:
score.clear()
len_score = len(score)
choices = random.sample(tuple(countries),4)
real_choice = random.choice(choices)
#Store answer in session variable
request.session['real_choice'] = real_choice
context['countries_list'] = choices
context['submit'] = real_choice
context['score'] = len_score
return render (request, 'base/play.html', context)

Form fields are empty when created using instance = instance

I have two models prodcut_prices and WrongPrice.
In WrongPrice the user can correct report wrong prices - when the price is reported it should also be updated in product_price.
My problem is, even though I instantiate product_price at the very beginning as instance_productprice, all of its required fields returns the "this field has to be filled out" error.
How come those field are not set when im using the instance instance_productprice = product_prices.objects.filter(id=pk)[0] ? Note, that all fields in product_prices are always non-empty since they are being pulled from the product_price model, which is handled in another view, thus that is not the issue.
def wrong_price(request,pk):
#Get the current price object
instance_productprice = product_prices.objects.filter(id=pk)[0]
#Get different values
wrong_link = instance_productprice.link
img_url = instance_productprice.image_url
wrong_price = instance_productprice.last_price
domain = instance_productprice.domain
# Create instances
instance_wrongprice = WrongPrice(
link=wrong_link,
correct_price=wrong_price,
domain = domain)
if request.method == "POST":
form_wrong_price = wrong_price_form(request.POST,instance=instance_wrongprice)
# Update values in product_prices
form_product_price = product_prices_form(request.POST,instance=instance_productprice)
form_product_price.instance.start_price = form_wrong_price.instance.correct_price
form_product_price.instance.last_price = form_wrong_price.instance.correct_price
if form_wrong_price.is_valid() & form_product_price.is_valid():
form_wrong_price.save()
form_product_price.save()
messages.success(request, "Thanks")
return redirect("my_page")
else:
messages.error(request, form_product_price.errors) # Throws empty-field errors,
messages.error(request, form_wrong_price.errors)
return redirect("wrong-price", pk=pk)
else:
form_wrong_price = wrong_price_form(instance=instance_wrongprice)
return render(request, "my_app/wrong_price.html",context={"form":form_wrong_price,"info":{"image_url":img_url}})
I am bit confused about how you implemented it. You have passed a instance of WrongPrice price through the form, which is unnecessary, you could have used initial:
wrong_values = dict(
link=wrong_link,
correct_price=wrong_price,
domain = domain
)
form_wrong_price = wrong_price_form(initial= wrong_values)
Then you are adding values to product_prices_form from instance of form_wrong_price. I don't see why you need a form again here. You can simple use:
form_wrong_price = wrong_price_form(request.POST, initial= wrong_values)
if form_wrong_price.is_valid():
instance = form_wrong_price.save()
instance_productprice.start_price = instance.correct_price
instance_productprice.last_price = instance.correct_price
instance_productprice.save()
Finally, please use PascalCase when defining class names. And you can get the product prices by product_prices.objects.get(id=pk)(instead of filter()[0]).

Flask wtforms AttributeError: 'HTMLString' object has no attribute 'paginate'

Somebody has helped me with some great code here to show the same form multiple times each with a submit button, it works a treat, But as I will have hundreds of forms I need to paginate the page, I have been able to paginate pages in the past but I dont no how to use that code with a form in a for loop.
here is my code:(with lots of help from Greg)
#bp.route('/stock', methods=['GET', 'POST'])
#bp.route('/stock/stock/', methods=['GET', 'POST'])
#login_required
def stock():
stocks = Stock.query.all()
forms = []
for stock in stocks:
form = AddStockForm()
form.id.default = stock.id
form.image.default = stock.image_url
form.date.default = stock.date
form.description.default = stock.description
form.event.default = stock.event
form.achat.default = stock.achat
form.vente.default = stock.vente
form.sold.default = stock.sold
forms.append(form)
for form in forms:
if form.validate_on_submit():
if form.modify.data:
stock = Stock.query.filter_by(id=form.id.data).one()
stock.date = form.date.data
stock.description = form.description.data
stock.event = form.event.data
stock.achat = form.achat.data
stock.vente = form.vente.data
stock.sold = form.sold.data
db.session.add(stock)
db.session.commit()
elif form.delete.data:
stock = Stock.query.filter_by(id=form.id.data).one()
db.session.delete(stock)
db.session.commit()
return redirect(url_for('stock.stock'))
form.process() # Do this after validate_on_submit or breaks CSRF token
page = request.args.get('page', 1, type=int)
forms = forms[1].id().paginate(
page, current_app.config['ITEMS_PER_PAGE'], False)
next_url = url_for('stock.stock', page=forms.next_num) \
if forms.has_next else None
prev_url = url_for('stock.stock', page=forms.prev_num) \
if forms.has_prev else None
return render_template('stock/stock.html',forms=forms.items, title=Stock, stocks=stocks)
I am trying to use the fact "forms" is a list to paginate the results, I obviously dont understand how to do this, I have looked at flask-paginate but I didnt understand that either!
all help is greatly needed
Warm regards, Paul.
EDIT
I have tried to use flask_pagination, here is my code:
#bp.route('/stock/stock/', methods=['GET', 'POST'])
#login_required
def stock():
search = False
q = request.args.get('q')
if q:
search = True
page = request.args.get(get_page_parameter(), type=int, default=1)
stocks = Stock.query.all()
forms = []
#rest of code here#
pagination = Pagination(page=page, total=stocks.count(), search=search, record_name='forms')
form.process() # Do this after validate_on_submit or breaks CSRF token
return render_template('stock/stock.html',forms=forms, title=Stock, pagination=pagination)
This gives a different error "TypeError: count() takes exactly one argument (0 given)" I also tried with "total=forms.count()" and got the same error!
I hate doing this as it shows a lack of patience at the begining but this answer may help others, I solved my problem in two ways the first was the query which decides the order of display (descending or ascending) this then allowed me to use flask-paginate to display the results on several pages, I realised that I was dealing with a list, and the example by one of the developers link showed me the way, here is my code,
from flask_paginate import Pagination, get_page_args
#bp.route('/stock', methods=['GET', 'POST'])
#bp.route('/stock/stock/', methods=['GET', 'POST'])
#login_required
def stock():
stocks = Stock.query.order_by(Stock.id.desc())# this gives order of results
forms = []
def get_forms(offset=0, per_page=25): #this function sets up the number of
return forms[offset: offset + per_page] #results per page
for stock in stocks:
form = AddStockForm()
form.id.default = stock.id
form.image.default = stock.image_url
form.date.default = stock.date
form.description.default = stock.description
form.event.default = stock.event
form.achat.default = stock.achat
form.vente.default = stock.vente
form.sold.default = stock.sold
forms.append(form)
for form in forms:
if form.validate_on_submit():
if form.modify.data:
stock = Stock.query.filter_by(id=form.id.data).one()
stock.date = form.date.data
stock.description = form.description.data
stock.event = form.event.data
stock.achat = form.achat.data
stock.vente = form.vente.data
stock.sold = form.sold.data
db.session.add(stock)
db.session.commit()
elif form.delete.data:
stock = Stock.query.filter_by(id=form.id.data).one()
db.session.delete(stock)
db.session.commit()
return redirect(url_for('stock.stock'))
#this is the code from the link that I used to paginate
page, per_page, offset = get_page_args(page_parameter='page',
per_page_parameter='per_page')
total = len(forms) # this code counts the resulting list to be displayed
pagination_forms = get_forms(offset=offset, per_page=per_page)
pagination = Pagination(page=page, per_page=per_page, total=total)
form.process() # Do this after validate_on_submit or breaks CSRF token
return render_template('stock/stock.html', title=Stock, stocks=stocks
page=page, forms=pagination_forms, per_page=per_page, pagination=pagination)
#And finally this is the pagination passed to the html
so this for all those numptys like me who struggle with everything but still love it.

Django session loses complex object in production, but not in dev

So I am successfully storing a complex object (non-model) in my session in development. I've tried every session engine and cache type and they are all working in development (Pycharm). However, when I move the code to production, while no error are thrown, the session losses the object.
Here is the method I use to set the session object:
def instantiate_command_object(request):
try:
ssc = request.session['specimen_search_criteria']
logger.debug('found ssc session variable')
except KeyError:
logger.debug('failed to find ssc session variable')
ssc = SpecimenSearchCommand()
return ssc
Then in a method that runs asynchronously via an ajax call I start making changes to the object in the session:
def ajax_add_collection_to_search(request):
ssc = instantiate_command_object(request)
collection_id = request.GET.get('collection')
collection = Collection.objects.get(pk=collection_id)
if collection and collection not in ssc.collections:
ssc.collections.append(collection)
# save change to session
request.session['specimen_search_criteria'] = ssc
# refresh search results
ssc.search()
return render(request, '_search.html')
All this works as far as it goes. However, if I then refresh the browser, the session is lost. Here is a snippet from the template:
{% with criteria=request.session.specimen_search_criteria %}
<div class="search-criteria" id="search-criteria">
<div class="row">
Sesssion:
{{ request.session }}<br/>
Search:
{{ request.session.specimen_search_criteria }}<br/>
Created:
{{ request.session.specimen_search_criteria.key }}<br/>
Collections:
{{ request.session.specimen_search_criteria.collections }}<br/>
Again, in development I can refresh all day and the same object will be returned. In production, it will either create a new object or occasionally will return a previously created copy.
A few relevant items:
The production server is running Apache httpd with mod_wsgi.
I've tried memcached, databasecache, etc. the behavior remains the same. Always works in development, never in production.
I've tried it with
SESSION_SERIALIZER = 'django.contrib.sessions.serializers.PickleSerializer'
and without. I can see the session info in the database and when I unpickle it it just seems to be pointing to a location in memory for the complex object.
I'm guessing this might have something to do with running in a multi-user environment, but again, I'm not using locmem and I've tried all of the caching approaches to no effect.
To be clear, the session itself seems to be fine, I can store a string or other simple item in it and it will stick. It's the complex object within the session that seems to be getting lost.
Edit: I might also point out that if I refresh the browser immediately following the return of the search criteria it will actually return successfully. Anything more than about a second and it will disappear.
Edit (adding code of SpecimenSearchCommand):
class SpecimenSearchCommand:
def __init__(self):
pass
created = datetime.datetime.now()
key = ''.join(random.SystemRandom().choice(string.ascii_uppercase + string.digits) for _ in range(6))
jurisdictions = []
taxa = []
strata = []
collections = []
chrons = []
has_images = False
query = None # The active SQL query, not the actual result records
page_size = 50
current_page = 1
sort_order = 'number'
results = [] # Page of results from paginator
def is_empty(self):
if len(self.jurisdictions) == 0 and len(self.taxa) == 0 and len(self.strata) == 0 and \
len(self.collections) == 0 and len(self.chrons) == 0 and self.has_images is False:
return True
else:
return False
def get_results(self):
paginator = Paginator(self.query, self.page_size)
try:
self.results = paginator.page(self.current_page)
except PageNotAnInteger:
self.results = paginator.page(1)
except TypeError:
return []
except EmptyPage:
self.results = paginator.page(paginator.num_pages)
return self.results
def get_results_json(self):
points = []
for s in self.results:
if s.locality.latitude and s.locality.longitude:
points.append({"type": "Feature",
"geometry": {"type": "Point",
"coordinates": [s.locality.longitude, s.locality.latitude]},
"properties": {"specimen_id": s.id,
"sci_name": s.taxon.scientific_name(),
"cat_num": s.specimen_number(),
"jurisdiction": s.locality.jurisdiction.full_name()}
})
return json.dumps({"type": "FeatureCollection", "features": points})
def search(self):
if self.is_empty():
self.query = None
return
query = Specimen.objects.filter().distinct().order_by(self.sort_order)
if len(self.taxa) > 0:
query = query.filter(taxon__in=get_hierarchical_search_elements(self.taxa))
if len(self.jurisdictions) > 0:
query = query.filter(locality__jurisdiction__in=get_hierarchical_search_elements(self.jurisdictions))
if len(self.strata) > 0:
query = query.filter(stratum__in=get_hierarchical_search_elements(self.strata))
if len(self.chrons) > 0:
query = query.filter(chron__in=get_hierarchical_search_elements(self.chrons))
if len(self.collections) > 0:
query = query.filter(collection__in=get_hierarchical_search_elements(self.collections))
if self.has_images:
query = query.filter(images__isnull=False)
self.query = query
return
def get_hierarchical_search_elements(elements):
search_elements = []
for element in elements:
search_elements = set().union(search_elements, element.get_descendants(True))
return search_elements
OK, so as Daniel pointed out, the attributes of the SSC class were class-level instead of instance level. The correct version looks like this now:
self.created = datetime.datetime.now()
self.key = ''.join(random.SystemRandom().choice(string.ascii_uppercase + string.digits) for _ in range(6))
self.jurisdictions = []
self.taxa = []
self.strata = []
self.collections = []
self.chrons = []
self.has_images = False
self.query = None # The active SQL query, not the actual result records
self.page_size = 50
self.current_page = 1
self.sort_order = 'number'
self.results = [] # Page of results from paginator

How to access the values inside the 'files' field in scrapy

I have downloaded some files using the file pipeline and i want to get the values of the files field. I tried to print item['files'] and it gives me a key error. Why is this so and how can i do it?
class testspider2(CrawlSpider):
name = 'genspider'
URL = 'flu-card.com'
URLhttp = 'http://www.flu-card.com'
allowed_domains = [URL]
start_urls = [URLhttp]
rules = (
[Rule(LxmlLinkExtractor(allow = (),restrict_xpaths = ('//a'),unique = True,),callback='parse_page',follow=True),]
)
def parse_page(self, response):
List = response.xpath('//a/#href').extract()
item = GenericspiderItem()
date = strftime("%Y-%m-%d %H:%M:%S")#get date&time dd-mm-yyyy hh:mm:ss
MD5hash = '' #store as part of the item, some links crawled are not file links so they do not have values on these fields
fileSize = ''
newFilePath = ''
File = open('c:/users/kevin123/desktop//ext.txt','a')
for links in List:
if re.search('http://www.flu-card.com', links) is None:
responseurl = re.sub('\/$','',response.url)
url = urljoin(responseurl,links)
else:
url = links
#File.write(url+'\n')
filename = url.split('/')[-1]
fileExt = ''.join(re.findall('.{3}$',filename))
if (fileExt != ''):
blackList = ['tml','pdf','com','php','aspx','xml','doc']
for word in blackList:
if any(x in fileExt for x in blackList):
pass #url is blacklisted
else:
item['filename'] = filename
item['URL'] = url
item['date'] = date
print item['files']
File.write(fileExt+'\n')
yield GenericspiderItem(
file_urls=[url]
)
yield item
It is not possible to access item['files'] in your spider. That is because the files are download by the FilesPipeline, and items just reach pipelines after they get out of your spider.
You first yield the item, then it gets to FilesPipeline, then the files are dowloaded, an just then the field images is populated with the info you want. To access it, you have to write a pipeline and schedule it after the FilesPipeline. Inside your pipeline, you can access the files field.
Also note that, in your spider, you are yielding to different kinds of items!