I use SQLFORM.smartgrid to show a list of records from a table (service_types). In each row of the smartgrid there is a delete link/button to delete the record. I want to executive some code before smartgrid/web2py actually deletes the record, for example I want to know if there are child records (services table) referencing this record, and if any, flash a message telling user that record cannot be deleted. How is this done?
db.py
db.define_table('service_types',
Field('type_name', requires=[IS_NOT_EMPTY(), IS_ALPHANUMERIC()]),
format='%(type_name)s',
)
db.define_table('services',
Field('service_name',requires=[IS_NOT_EMPTY(),IS_NOT_IN_DB(db,'services.service_name')]),
Field('service_type','reference service_types',requires=IS_IN_DB(db,db.service_types.id,
'%(type_name)s',
error_message='not in table',
zero=None),
ondelete='RESTRICT',
),
Field('interest_rate','decimal(15,2)',requires=IS_DECIMAL_IN_RANGE(0,100)),
Field('max_term','integer'),
auth.signature,
format='%(service_name)s',
)
db.services._plural='Services'
db.services._singular='Service'
if db(db.service_types).count() < 1:
db.service_types.insert(type_name='Loan')
db.service_types.insert(type_name='Contribution')
db.service_types.insert(type_name='Other')
controller
def list_services():
grid = SQLFORM.smartgrid(db.services
, fields = [db.services.service_name,db.services.service_type]
)
return locals()
view
{{extend 'layout.html'}}
{{=grid}}
There are two options. First, the deletable argument can be a function that takes the Row object of a given record and returns True or False to indicate whether the record is deletable. If it returns False, the "Delete" button will not be shown for that record, nor the delete operation be allowed on the server.
def can_delete(row):
return True if [some condition involving row] else False
grid = SQLFORM.smartgrid(..., deletable=can_delete)
Second, there is an ondelete argument that takes the db Table object and the record ID. It is called right before the delete operation, so to prevent the delete, you can do a redirect within that function:
def ondelete(table, record_id):
record = table(record_id)
if [some condition]:
session.flash = 'Cannot delete this record'
redirect(URL())
grid = SQLFORM.smartgrid(..., ondelete=ondelete)
Note, if the grid is loaded via an Ajax component and its actions are therefore performed via Ajax, using redirect within the ondelete method as shown above will not work well, as the redirect will have no effect and the table row will still be deleted from the grid in the browser (even though the database record was not deleted). In that case, an alternative approach is to return a non-200 HTTP response to the browser, which will prevent the client-side Javascript from deleting the row from the table (the delete happens only on success of the Ajax request). We should also set response.flash instead of session.flash (because we are not redirecting/reloading the whole page):
def ondelete(table, record_id):
record = table(record_id)
if [some condition]:
response.flash = 'Cannot delete this record'
raise HTTP(403)
Note, both the deletable and ondelete arguments can be dictionaries with table names as keys, so you can specify different values for different tables that might be linked from the smartgrid.
Finally, notice the delete URLs look like /appname/list_services/services/delete/services/[record ID]. So, in the controller, you can determine if a delete is being requested by checking if 'delete' in request.args. In that case, request.args[-2:] represents the table name and record ID, which you can use to do any checks.
From Anthony's answer I chose the second option and came up with the following:
def ondelete_service_type(service_type_table, service_type_id):
count = db(db.services.service_type == service_type_id).count()
if count > 0:
session.flash = T("Cant delete")
#redirect(URL('default','list_service_types#'))
else:
pass
return locals()
def list_service_types():
grid = SQLFORM.smartgrid(db.service_types
, fields = [db.service_types.type_name, db.services.service_name]
, ondelete = ondelete_service_type
)
return locals()
But, if I do this...
if count > 0:
session.flash = T("Cant delete")
else:
pass
return locals()
I get this error:
And if I do this:
if count > 0:
session.flash = T("Cant delete")
redirect(URL('default','list_service_types#')) <== please take note
else:
pass
return locals()
I get the flash error message Cant delete but the record appears deleted from the list, and reappears after a page refresh with F5 (apparently because the delete was not allowed in the database, which is intended).
Which one should I fix and how?
Note
If any of these issue is resolved I can accept Anthony's answer.
I'm developing a webapp using Google App Engine and Python.
I'm facing a strange problem and i don't know how to solve it and what causes it.
When I fill a form I send the data for checking them. If they aren't complete and some fields are missed the server send the form back with an advice "FILL ALL THE FIELDS!".
That's work pretty well.
What I'm trying to do is sending the form back with the "description" and "title" fields filled with what the user has written before submitting the form, so he must to fill only the unfilled fields (and he doesn't need to rewrite everything from the beginning).
That's the code:
class SaleAnnCheck(BaseHandler):
def post(self):
title = self.request.POST.get('title')
cat = self.request.POST.get('cat')
description = self.request.POST.get('description')
AcqOpt = self.request.POST.get('AcqOpt')
lat = self.request.POST.get('lat')
lng = self.request.POST.get('lng')
image1 = self.request.POST.get("file1", None)
image2 = self.request.POST.get("file2", None)
image3 = self.request.POST.get("file3", None)
logging.info("info sale announcment")
logging.info(title)
logging.info(cat)
logging.info(description)
logging.info(AcqOpt)
logging.info(lat)
logging.info(lng)
logging.info(image1)
logging.info(image2)
logging.info(image3)
self.session["ECommerceUser"]["description"]=description
self.session["ECommerceUser"]["title"]=title
logging.info('session')
logging.info(self.session["ECommerceUser"])
if title == '' or cat == None or description == '' or AcqOpt == None or lat == '' or lng == '':
error = 'empty'
urlR='/CreateSaleAnn?'+urllib.urlencode({'error':'empty'})
self.redirect(urlR)
class Create(BaseHandler):
def get(self):
error = self.request.get('error')
if error == 'empty':
logging.info('sbagliato')
logging.info(self.session["ECommerceUser"])
template = JINJA_ENVIRONMENT.get_template('templates/CreateAnnouncement.html')
w = self.response.write
w(template.render({'error':'FILL ALL THE MANDATORY FIELDS!', 'description': self.session["ECommerceUser"]["description"], 'title': self.session["ECommerceUser"]["title"]}))
else:
logging.info('giusto')
logging.info(self.session["ECommerceUser"])
template = JINJA_ENVIRONMENT.get_template('templates/CreateAnnouncement.html')
w = self.response.write
w(template.render({'description': self.session["ECommerceUser"]["description"], 'title': self.session["ECommerceUser"]["title"]}))
When I submit the form the content is checked by making an HTTP post request to a certain URL, handled by SaleAnnCheck.
Description and Title are saved in the session correctly (i checked it by printing the content of self.session["ECommerceUser"] in the logs). Then, if a field isn't filled, the server redirect again to the form page, by a GET request to a related URL.
The requests to that URL is handled by Create. But when i try to render the HTML template of the form (using jinja2) with the previous typed values of Description and Title the related text areas are not filled with that values.
It happens because self.session["ECommerceUser"]["description"] and self.session["ECommerceUser"]["title"] are empty, but they weren't when i checked them before (in SaleAnnCheck).
Why it happens? Any explanation? It's a weird problem and there aren't any tips or suggestion about on internet
This is because 'self.session' is not a Session, it's just a class variable and will not be readable outside the class. If you really want to use persistent sessions for storing variables, try something like this:
From the docs:
http://docs.python-requests.org/en/master/user/advanced/
s = requests.Session()
s.get('http://httpbin.org/cookies/set/sessioncookie/123456789')
r = s.get('http://httpbin.org/cookies')
I'm working with Python to create a Google App Engine application.To test my app, i am using html forms to enter data.
In my form i have a line:
<tr><td>Age</td><td><input type="number" size="10" name="age"/></td></tr>
and in my model class, a property defined like this:
class Person(ndb.Model):
...
age = ndb.IntegerProperty()
when i test my app locally it displays the form but on entering a value for age, i get a BadValueError: Expected integer, got u '23' message.Posting image because i do not know how to copy command prompt text.I hope it's clear enough.
Edit: This is how the data is been passed from html form.
# Data taken from the registration form (above) is used to
# create a new member.
class PersonHandler(webapp2.RequestHandler):
def post(self):
# need to check this member-name does not exist
name = self.request.get('name')
callback = self.request.get('callback')
member = Member.get_by_id(name)
if member: # This member name already exists.
self.error(409) # This is the HTTP status code for 'unable to process due to conflict'
else:
...
a = self.request.get("age")
member = Member(age=a,...)
member.put()
if callback:
self.response.write(callback + '(' + member.toJSON + ')')
else:
self.response.write(member.toJSON())
Can someone tell me what am doing wrong?
You simply need to convert the retrieved value to an integer:
...
else:
...
a = int(self.request.get("age"))
Ok this one should be an easy question id imagine, but cannot figure it out.
Im passing form data to the controller and attempting to do a data search there, which in turn, runs this..
def initLogin():
userName = request.vars.user_name;
counter = db(db.Users.UserName == userName).count()
if counter > 0:
return DIV("User exists")
return DIV("user does not exist")
I have checked the value is being passed correctly( which is is) by returning userName instead of the string, which shown me it was the correct value, and when i had a direct string of a correct username, it seemed to work. So my question is.. how do you run a count() function with web2py databases correctly using variables?
Your code is correct and shouldn't be giving you any problems, the only possible problem should be in your userName var not being what you expected or an incorrect sql query. I recommend that you try changing your controller to:
def initLogin():
userName = request.vars.user_name;
counter = db(db.Users.UserName == userName).count()
lastExecutedQuery = db._lastsql
return DIV( lastExecutedQuery )
And check if the query being performed is the one that you expected.
i have the following model in google app engine:
class CustomUsers(db.Model):
cuid = db.StringProperty()
name = db.StringProperty()
email = db.StringProperty()
bday = db.StringProperty()
now given a cuid i just want to check if he is present in the high replication data store.
in my views i write the following, but eventually get an 500 error:
usr_prev = CustomUsers.all().filter('cuid =', request.POST['id'])
if(not usr_prev):
logging.info("this user does not exist")
else:
logging.info("user exists")
but this gives me an error. How shall i do it?
(Do not consider any importing issues.)
"500 errors" don't exist in isolation. They actually report useful information, either on the debug page or in the logs. I'd guess that your logs show that request.POST has no element id, but that is just a guess.
I noticed that you have assigned a query object to usr_prev. You need to execute the query. I think Query.count(1) function should do it.
However, this may not be related to the 500 error.
you can go like this to see:
query = Users.all()
cnt = query.filter('id =', user_id).count()
logging.info(cnt)
return int(cnt)
once this is done, seems you are using django with app engine, as per the error you are not returning string response. you might want to return
return HttpResponse("1") instead of return HttpResponse(1)