1 # ZPsycopgDA/DA.py - ZPsycopgDA Zope product: Database Connection
3 # Copyright (C) 2004 Federico Di Gregorio <fog@initd.org>
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by the
7 # Free Software Foundation; either version 2, or (at your option) any later
10 # Or, at your option this program (ZPsycopgDA) can be distributed under the
11 # Zope Public License (ZPL) Version 1.0, as published on the Zope web site,
12 # http://www.zope.org/Resources/ZPL.
14 # This program is distributed in the hope that it will be useful, but
15 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
16 # or FITNESS FOR A PARTICULAR PURPOSE.
18 # See the LICENSE file for details.
21 ALLOWED_PSYCOPG_VERSIONS = ('2.0.1', '2.0.2', '2.0.3', '2.0.4', '2.0.5')
29 import Shared.DC.ZRDB.Connection
32 from Globals import HTMLFile
33 from ExtensionClass import Base
34 from App.Dialogs import MessageDialog
35 from DateTime import DateTime
37 # Build Zope version in a float for later cheks
39 zope_version = App.version_txt.getZopeVersion()
40 zope_version = float("%s.%s" %(zope_version[:2]))
42 # ImageFile is deprecated in Zope >= 2.9
43 if zope_version < 2.9:
44 from ImageFile import ImageFile
46 from App.ImageFile import ImageFile
48 # import psycopg and functions/singletons needed for date/time conversions
51 from psycopg2 import NUMBER, STRING, ROWID, DATETIME
52 from psycopg2.extensions import INTEGER, LONGINTEGER, FLOAT, BOOLEAN, DATE
53 from psycopg2.extensions import TIME, INTERVAL
54 from psycopg2.extensions import new_type, register_type
57 # add a new connection to a folder
59 manage_addZPsycopgConnectionForm = HTMLFile('dtml/add',globals())
61 def manage_addZPsycopgConnection(self, id, title, connection_string,
62 zdatetime=None, tilevel=2,
63 check=None, REQUEST=None):
64 """Add a DB connection to a folder."""
65 self._setObject(id, Connection(id, title, connection_string,
66 zdatetime, check, tilevel))
67 if REQUEST is not None: return self.manage_main(self, REQUEST)
70 # the connection object
72 class Connection(Shared.DC.ZRDB.Connection.Connection):
73 """ZPsycopg Connection."""
74 _isAnSQLConnection = 1
76 id = 'Psycopg2_database_connection'
77 database_type = 'Psycopg2'
78 meta_type = title = 'Z Psycopg 2 Database Connection'
81 def __init__(self, id, title, connection_string,
82 zdatetime, check=None, tilevel=2, encoding=''):
83 self.zdatetime = zdatetime
85 self.edit(title, connection_string, zdatetime,
86 check=check, tilevel=tilevel, encoding=encoding)
91 ## connection parameters editing ##
93 def edit(self, title, connection_string,
94 zdatetime, check=None, tilevel=2, encoding=''):
96 self.connection_string = connection_string
97 self.zdatetime = zdatetime
98 self.tilevel = tilevel
99 self.encoding = encoding
101 self.set_type_casts()
103 if check: self.connect(self.connection_string)
105 manage_properties = HTMLFile('dtml/edit', globals())
107 def manage_edit(self, title, connection_string,
108 zdatetime=None, check=None, tilevel=2, encoding='UTF-8',
110 """Edit the DB connection."""
111 self.edit(title, connection_string, zdatetime,
112 check=check, tilevel=tilevel, encoding=encoding)
113 if REQUEST is not None:
114 msg = "Connection edited."
115 return self.manage_main(self,REQUEST,manage_tabs_message=msg)
117 def connect(self, s):
119 self._v_database_connection.close()
123 # check psycopg version and raise exception if does not match
124 if psycopg2.__version__[:5] not in ALLOWED_PSYCOPG_VERSIONS:
125 raise ImportError("psycopg version mismatch (imported %s)" %
126 psycopg2.__version__)
128 self.set_type_casts()
129 self._v_connected = ''
132 # TODO: let the psycopg exception propagate, or not?
133 self._v_database_connection = dbf(
134 self.connection_string, self.tilevel, self.encoding)
135 self._v_database_connection.open()
136 self._v_connected = DateTime()
140 def set_type_casts(self):
141 # note that in both cases order *is* important
143 # use zope internal datetime routines
144 register_type(ZDATETIME)
149 register_type(DATETIME)
153 ## browsing and table/column management ##
155 manage_options = Shared.DC.ZRDB.Connection.Connection.manage_options
157 # {'label': 'Browse', 'action':'manage_browse'},)
159 #manage_tables = HTMLFile('dtml/tables', globals())
160 #manage_browse = HTMLFile('dtml/browse', globals())
164 def table_info(self):
165 return self._v_database_connection.table_info()
168 def __getitem__(self, name):
169 if name == 'tableNamed':
170 if not hasattr(self, '_v_tables'): self.tpValues()
171 return self._v_tables.__of__(self)
176 conn = self._v_database_connection
177 for d in conn.tables(rdb=0):
179 name = d['TABLE_NAME']
185 b.icon = table_icons[d['TABLE_TYPE']]
194 ## database connection registration data ##
196 classes = (Connection,)
198 meta_types = ({'name':'Z Psycopg 2 Database Connection',
199 'action':'manage_addZPsycopgConnectionForm'},)
202 'manage_addZPsycopgConnection': manage_addZPsycopgConnection,
203 'manage_addZPsycopgConnectionForm': manage_addZPsycopgConnectionForm}
205 __ac_permissions__ = (
206 ('Add Z Psycopg Database Connections',
207 ('manage_addZPsycopgConnectionForm', 'manage_addZPsycopgConnection')),)
211 misc_={'conn': ImageFile('Shared/DC/ZRDB/www/DBAdapterFolder_icon.gif')}
213 for icon in ('table', 'view', 'stable', 'what', 'field', 'text', 'bin',
214 'int', 'float', 'date', 'time', 'datetime'):
215 misc_[icon] = ImageFile('icons/%s.gif' % icon, globals())
218 ## zope-specific psycopg typecasters ##
220 # convert an ISO timestamp string from postgres to a Zope DateTime object
221 def _cast_DateTime(iso, curs):
223 return DateTime(re.split("GMT\+?|GMT-?", iso)[0])
225 # this will split us into [date, time, GMT/AM/PM(if there)]
226 # dt = str.split(' ')
228 # # we now should split out any timezone info
229 # dt[1] = dt[1].split('-')[0]
230 # dt[1] = dt[1].split('+')[0]
231 # return DateTime(' '.join(dt[:2]))
233 # return DateTime(dt[0])
235 # convert an ISO date string from postgres to a Zope DateTime object
236 def _cast_Date(iso, curs):
240 # Convert a time string from postgres to a Zope DateTime object.
241 # NOTE: we set the day as today before feeding to DateTime so
242 # that it has the same DST settings.
243 def _cast_Time(iso, curs):
245 return DateTime(time.strftime('%Y-%m-%d %H:%M:%S',
246 time.localtime(time.time())[:3]+
247 time.strptime(iso[:8], "%H:%M:%S")[3:]))
249 # NOTE: we don't cast intervals anymore because they are passed
251 def _cast_Interval(iso, curs):
254 ZDATETIME = new_type((1184, 1114), "ZDATETIME", _cast_DateTime)
255 ZINTERVAL = new_type((1186,), "ZINTERVAL", _cast_Interval)
256 ZDATE = new_type((1082,), "ZDATE", _cast_Date)
257 ZTIME = new_type((1083,), "ZTIME", _cast_Time)
260 ## table browsing helpers ##
262 class TableBrowserCollection(Acquisition.Implicit):
266 def __getattr__(self, name):
270 raise AttributeError, name
276 def __getitem__(self, i):
279 except AttributeError:
284 class TableBrowser(Browser, Acquisition.Implicit):
286 Description = check = ''
287 info = HTMLFile('table_info', globals())
288 menu = HTMLFile('table_menu', globals())
292 v._f = self.tpValues_
298 for d in self._c.columns(tname):
301 try: b.icon=field_icons[d['Type']]
307 def tpId(self): return self._d['TABLE_NAME']
308 def tpURL(self): return "Table/%s" % self._d['TABLE_NAME']
309 def Name(self): return self._d['TABLE_NAME']
310 def Type(self): return self._d['TABLE_TYPE']
312 manage_designInput=HTMLFile('designInput',globals())
313 def manage_buildInput(self, id, source, default, REQUEST=None):
314 "Create a database method for an input form"
318 columns=self._columns
319 for i in range(len(source)):
321 if s=='Null': continue
328 values.append("<dtml-sqlvar %s type=%s>'" %
330 a='%s%s' % (n, boboType(t))
331 if d: a="%s=%s" % (a,d)
334 values.append("<dtml-sqlvar %s type=%s>'" %
338 if find(d,"\'") >= 0: d=join(split(d,"\'"),"''")
339 values.append("'%s'" % d)
341 values.append(str(d))
344 'no default was given for <em>%s</em>' % n)
346 class ColumnBrowser(Browser):
350 return ('\t<input type=checkbox name="%s.%s">' %
351 (self.TABLE_NAME, self._d['Name']))
352 def tpId(self): return self._d['Name']
353 def tpURL(self): return "Column/%s" % self._d['Name']
354 def Description(self):
357 return " %(Type)s(%(Precision)s,%(Scale)s) %(Nullable)s" % d
359 return " %(Type)s(%(Precision)s) %(Nullable)s" % d
364 'SYSTEM_TABLE': 'stable',
370 DATETIME.name: 'date',