Skip to content
Snippets Groups Projects
Commit 33a5e29f authored by Renaud Le Gac's avatar Renaud Le Gac
Browse files

Improve the HttpDb class.

Define private and public methods.
Redesign some _is_.. method.
Add the insert method.
parent 54649a7a
No related branches found
No related tags found
No related merge requests found
""" module services.py
Author: R. Le Gac
$Id$
"""
__version__ = "$Revision"
#from gluon.html import *
#from gluon.http import *
#from gluon.validators import *
......@@ -13,15 +16,20 @@
import re
from gluon.contrib import simplejson as json
#from tools import debug
FIELD_BAD_SYNTAX = "The syntax of the request field %s is wrong: db.table.field"
FIELD_BAD_TABLE = "Table defined in the request field %s doesn't exist."
FIELD_BAD_FIELD = "Field defined in the request field %s doesn't exist."
INSERT_SUCCESSFUL = "Data insert successfully."
SVC_INVALID = "Requested service doesn't exist."
SVC_NOTDEFINED = "The keyword service is not defined."
SVC_NOT_DEFINED = "The keyword service is not defined."
SVC_NOT_IMPLEMENTED = "Service not yet implemented."
TABLE_NOT_IN_DB = "The request table doesn't exist in the database."
TABLE_NOTDEFINED = "The keyword table is not defined."
TABLE_MISSING = "The keyword table is not defined."
TABLE_FIELD_SAME_TABLE = "Keywords table and field refer to the same table."
TABLE_OR_FIELD_MISSING = "Keyword table or fields is missing"
class HttpDb:
""" HttpDb
......@@ -31,15 +39,18 @@ class HttpDb:
It is the glue between wep2py and javascript, ajax, extjs.
Handle HTTP POST and HTTP GET request.
The selection of the service and its configuration are based on steering keywords:
service, table, fields, debug,...
the former is mandatory while the laters are optional.
The selection of the service and its configuration are based on
steering keywords: service, table, fields, debug,...
The former is mandatory while the laters are optional.
Each service return a dictionary withe the following key: success, msg, data.
The former is always present.
Each service return a dictionary with the the following key:
success, msg, data. The former is always present.
The most important method is HttpToDb.proceed()
The most useful sesrvives are: delete, insert, select, update,
as well as get_defaults, get_fields, get_services and get_tables.
Example:
dbsvc = HttpToDb (db)
....
......@@ -54,22 +65,34 @@ class HttpDb:
self._db = db
self.__vars = None
def _debug(self):
"""Return true if the keyword debug is in the request and
if the associate value is true. Otherwise return false.
def delete(self):
"""Delete data in the database.
"""
return ("debug" in self._vars) and self._vars.debug
Return a dictionary with status and error message:
{success: False, msg: 'invalid service'}
def _is_field_valid(self, table, field):
"""Check if the field is defined in the table.
Return a dictionary with status and message:
{success: False, msg: 'blalbla'}
"""
return {"success": False, "msg": SVC_NOT_IMPLEMENTED}
if field not in self._db[table].fields:
return {"success": False, "msg": FIELD_BAD_FIELD % field}
return {"success": True}
def _is_field_string_valid(self, field):
"""Check is the request field string is valid.
def is_field_valid(self, field):
"""Check is the request field is valid.
A well form field string is 'db.table.field'
Return a dictionary with status and error message:
{success: False, msg: 'invalid service'}
{success: False, msg: 'blalbla'}
"""
# Check the syntax
......@@ -78,61 +101,151 @@ class HttpDb:
return {"success": False, "msg": FIELD_BAD_SYNTAX % field}
# Check the table
if m.group("table") not in self._db.tables:
return {"success": False, "msg": FIELD_BAD_TABLE % field}
resp = self._is_table_valid(m.group("table"))
if not resp["success"]:
return resp
# Check the field
if m.group("field") not in self._db[m.group("table")].fields:
return {"success": False, "msg": FIELD_BAD_FIELD % field}
return self._is_field_valid(m.group("table"), m.group("field"))
return {"success": True}
def _is_field_value_valid(self, table, field, val):
"""Check is the sent field value statisfy validators
defined in the database model.
def is_field_value_valid(self): pass
"""
errmsg = self._db[table][field].validate(val)[1]
if val and self._db[table][field].notnull and errmsg:
return {"success": False, "msg": errmsg}
return {"success": True}
def is_service_valid(self):
def _is_service_valid(self):
"""Check if the request service is valid.
Return a dictionary with status and error message:
{success: False, msg: 'invalid service'}
{success: False, msg: 'blalbla'}
"""
if "service" not in self._vars:
return {"success": False, "msg": SVC_NOTDEFINED}
return {"success": False, "msg": SVC_NOT_DEFINED}
if self._vars.service not in dir(self):
services = [el for el in list(dir(self)) if not el.startswith("_")]
if self._vars.service not in services:
return {"success": False, "msg": SVC_INVALID}
return {"success": True}
def is_table_valid(self):
def _is_table_valid(self, table):
"""Check if the request table is valid.
Return a dictionary with status and error message:
{success: False, msg: 'invalid service'}
{success: False, msg: 'blalbla'}
"""
if "table" not in self._vars:
return {"success": False, "msg": TABLE_NOTDEFINED}
if self._vars.table not in self._db.tables:
if table not in self._db.tables:
return {"success": False, "msg": TABLE_NOT_IN_DB}
return {"success": True}
def get_columns(self): pass
def _mapdb(self, s):
""" Evaluate the string s and return an SQL object.
def insert(self):
"""Insert data in the database.
The string define a field or a query directive.
It is looks like: 'db.table.field'
The model/database is named db in string defining fields.
This variable should be map to the python object self._db.
this mapping is therefore usedf in the eval function.
"""
return eval(s, {'db': self._db})
def delete(self):
"""Delete data in the database.
Return a dictionary with status and error message:
{success: False, msg: 'invalid service'}
{success: False, msg: 'blalbla'}
"""
return {"success": False, "msg": SVC_NOT_IMPLEMENTED}
def get_defaults(self): pass
def get_fields(self): pass
def get_services(self): pass
def get_tables(self): pass
def insert(self):
"""Insert data in the database.
Thsi service is strongly linked to a Ext.FormPanel widget.
Form sent field values via a JSON string containing the steering
keywords table and field names. The field name is the simplest
version which doesn't contains the dabatase model and the table:
version instead of db.distributions.version.
Return a dictionary with status, error message and id of the insert row:
{success: False, msg: 'blalbla', errors: {field: 'blabla',..}, data:{id:xx}}
The keyword success is mandatory.
The keyword error is required and processed by the Ext.FormPanel widget.
"""
if self._debug():
print "Start HttpDb.insert service."
# Check table
if "table" not in self._vars:
resp = {"success": False, "msg": TABLE_MISSING}
return resp
table = self._vars.table
resp = self._is_table_valid(table)
if not resp["success"]:
return resp
# Build a dictionary containing only field names and values
kwfields = dict(self._vars)
for key in ["debug", "service", "table"]:
if key in kwfields:
del kwfields[key]
# Check that fields exits in the selected table
for field in kwfields:
resp = self._is_field_valid(table, field)
if not resp["success"]:
return resp
# Validate field values against model validators
# Check all fields in one go
resp = {"success": True, "errors": {}}
for (field, val) in kwfields.items():
di = self._is_field_value_valid(table, field, val)
if not di["success"]:
resp["success"] = False
resp["errors"][field] = di["msg"]
if not resp["success"]:
return resp
# Insert the data in the table
try:
id = self._db[table].insert (**kwfields)
except sqlite3.Error, e:
return {"success": False, "msg": e.message}
if self._debug():
print "End HttpDb.insert service."
return {"success": True, "msg": INSERT_SUCCESSFUL, "data": {"id": id}}
def proceed(self, vars):
"""Proceed the data sends via the POST/GET method.
......@@ -140,13 +253,16 @@ class HttpDb:
the input vars is a Storage object.
Return a dictionary with status, data and error message:
{success: False, msg: 'invalid service'}
{success: False, msg: 'blalbla'}
"""
self._vars = vars
resp = self.is_service_valid()
if self._debug():
print "Start HttpDb.proceed method on:", vars
resp = self._is_service_valid()
if not resp["success"]:
return resp
......@@ -158,68 +274,87 @@ class HttpDb:
"""Select data in the database.
The steering keywords are:
table
A string
When defined, all columns of the table are selected.
fields
A list of string ['db.table.col1', db.table.col2,...]
A list of string ['db.table.field1', db.table.field2,...]
When defined, specific columns are added.
Both table and fields keywords can be used at the same time,
but can't refer to the same table.
where
A string: "(db.table.id==1) and db.table2.col4=='foo')"
orderby
A string: "db.table.col1"
groupby
A string: "db.table.col1"
A string: "(db.table.id==1) and db.table2.field4=='foo')"
The equivalent or the SQL where directive.
orderby, orderby
A string: "db.table.field1"
The equivalent or the SQL where directive.
having
limitby
A string: "(0,10)"
The equivalent or the SQL where directive.
debug
A boolean
This method relies on the web2py model for the naming of column, directive,...
When a table is defined select all columns.
When columns is defined add specific columns.
This method relies on the web2py model for the naming of field
and the construction of the query, orderby, ... directives.
Return a dictionary with selected data, status and error message:
{success: false, msg: 'blabla', data: []}
"""
if not ("table" in self._vars or "fields" in self._vars):
resp = {"success": False, "msg": "Keyword table or fields is missing"}
if self._debug():
print "Start HttpDb.select service."
if ("table" not in self._vars) and ("fields" not in self._vars):
resp = {"success": False, "msg": TABLE_OR_FIELD_MISSING}
return resp
args, kwargs, args2 = list(), dict(), list()
if ("table" in self._vars) and ("fields" in self._vars):
table = self._vars.table
for field in self._vars.fields:
if table in field:
resp = {"success": False, "msg": TABLE_FIELD_SAME_TABLE}
return resp
# The model/database is named db in string defining fields.
# This variable should be map to the python object.
# the mapping is need in the eval function.
mapdb = {'db': self._db}
args, kwargs, query = list(), dict(), list()
# When a table is defined select all fields
# Select all fields of a table
if "table" in self._vars:
resp = self.is_table_valid()
table = self._vars.table
resp = self._is_table_valid(table)
if not resp["success"]:
return resp
table = self._vars.table
args.append(self._db[table].ALL)
# Add dedicated field
# Add dedicated fields
if "fields" in self._vars:
for field in self._vars.fields:
resp = self.is_field_valid(field)
resp = self._is_field_string_valid(field)
if not resp["success"]:
return resp
args.append(eval(field, mapdb))
args.append(self._mapdb(field))
# Add directive orderby, groupby,...
for key in ["groupby", "having", "left", "limitby", "orderby"]:
if key in self._vars:
kwargs[key] = eval(self._vars[key], mapdb)
kwargs[key] = self._mapdb(self._vars[key])
# Decode the where directive
if "where" in self._vars:
args2.append(eval(self._vars.where, mapdb))
query.append(self._mapdb(self._vars.where))
# Proceed
if self._debug():
print "HttpDb.select proceed request:", query, args, kwargs
try:
rows = self._db(*args2).select(*args, **kwargs)
rows = self._db(*query).select(*args, **kwargs)
except sqlite3.Error, e:
return {"success": False, "msg": e.message}
......@@ -228,19 +363,11 @@ class HttpDb:
return {"success": True, "data": json.loads(rows.json())}
def published_services(self):
"""Return dictionary with the list of published services.
{success: True, msg: 'blabla', data: []}
"""
return {"success": False, "msg": SVC_NOT_IMPLEMENTED}
def update(self):
"""Update data in the database.
Return a dictionary with selected status and error message:
{success: false, msg: 'invalid service'}
{success: false, msg: 'blalbla'}
"""
return {"success": False, "errmsg": SVC_NOT_IMPLEMENTED}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment