web2py: How to execute instructions before delete

2019-06-03 10:48发布

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}}

标签: python web2py
2条回答
Ridiculous、
2楼-- · 2019-06-03 11:10

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:

Screenshot of 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).

Screenshot

Which one should I fix and how?

Note If any of these issue is resolved I can accept Anthony's answer.

查看更多
我命由我不由天
3楼-- · 2019-06-03 11:15

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.

查看更多
登录 后发表回答