Mercurial > p > roundup > code
comparison roundup/cgi/templating.py @ 1016:d6c13142e7b9
Keep a cache of compiled PageTemplates.
Reinstated query saving.
| author | Richard Jones <richard@users.sourceforge.net> |
|---|---|
| date | Tue, 03 Sep 2002 02:58:11 +0000 |
| parents | 10ed4791f969 |
| children | 16498e77e3ff |
comparison
equal
deleted
inserted
replaced
| 1014:8816534e6a1a | 1016:d6c13142e7b9 |
|---|---|
| 1 import sys, cgi, urllib, os, re, os.path | 1 import sys, cgi, urllib, os, re, os.path, time |
| 2 | 2 |
| 3 from roundup import hyperdb, date | 3 from roundup import hyperdb, date |
| 4 from roundup.i18n import _ | 4 from roundup.i18n import _ |
| 5 | 5 |
| 6 | 6 try: |
| 7 import cPickle as pickle | |
| 8 except ImportError: | |
| 9 import pickle | |
| 10 try: | |
| 11 import cStringIO as StringIO | |
| 12 except ImportError: | |
| 13 import StringIO | |
| 7 try: | 14 try: |
| 8 import StructuredText | 15 import StructuredText |
| 9 except ImportError: | 16 except ImportError: |
| 10 StructuredText = None | 17 StructuredText = None |
| 11 | 18 |
| 27 sys.modules['ExtensionClass'] = ExtensionClass | 34 sys.modules['ExtensionClass'] = ExtensionClass |
| 28 if not sys.modules.has_key('Acquisition'): | 35 if not sys.modules.has_key('Acquisition'): |
| 29 import Acquisition | 36 import Acquisition |
| 30 sys.modules['Acquisition'] = Acquisition | 37 sys.modules['Acquisition'] = Acquisition |
| 31 | 38 |
| 32 # now it's safe to import PageTemplates and ZTUtils | 39 # now it's safe to import PageTemplates, TAL and ZTUtils |
| 33 from PageTemplates import PageTemplate | 40 from PageTemplates import PageTemplate |
| 41 from PageTemplates.Expressions import getEngine | |
| 42 from TAL.TALInterpreter import TALInterpreter | |
| 34 import ZTUtils | 43 import ZTUtils |
| 44 | |
| 45 # XXX WAH pagetemplates aren't pickleable :( | |
| 46 #def getTemplate(dir, name, classname=None, request=None): | |
| 47 # ''' Interface to get a template, possibly loading a compiled template. | |
| 48 # ''' | |
| 49 # # source | |
| 50 # src = os.path.join(dir, name) | |
| 51 # | |
| 52 # # see if we can get a compile from the template"c" directory (most | |
| 53 # # likely is "htmlc" | |
| 54 # split = list(os.path.split(dir)) | |
| 55 # split[-1] = split[-1] + 'c' | |
| 56 # cdir = os.path.join(*split) | |
| 57 # split.append(name) | |
| 58 # cpl = os.path.join(*split) | |
| 59 # | |
| 60 # # ok, now see if the source is newer than the compiled (or if the | |
| 61 # # compiled even exists) | |
| 62 # MTIME = os.path.stat.ST_MTIME | |
| 63 # if (not os.path.exists(cpl) or os.stat(cpl)[MTIME] < os.stat(src)[MTIME]): | |
| 64 # # nope, we need to compile | |
| 65 # pt = RoundupPageTemplate() | |
| 66 # pt.write(open(src).read()) | |
| 67 # pt.id = name | |
| 68 # | |
| 69 # # save off the compiled template | |
| 70 # if not os.path.exists(cdir): | |
| 71 # os.makedirs(cdir) | |
| 72 # f = open(cpl, 'wb') | |
| 73 # pickle.dump(pt, f) | |
| 74 # f.close() | |
| 75 # else: | |
| 76 # # yay, use the compiled template | |
| 77 # f = open(cpl, 'rb') | |
| 78 # pt = pickle.load(f) | |
| 79 # return pt | |
| 80 | |
| 81 templates = {} | |
| 82 | |
| 83 def getTemplate(dir, name, classname=None, request=None): | |
| 84 ''' Interface to get a template, possibly loading a compiled template. | |
| 85 ''' | |
| 86 # find the source, figure the time it was last modified | |
| 87 src = os.path.join(dir, name) | |
| 88 stime = os.stat(src)[os.path.stat.ST_MTIME] | |
| 89 | |
| 90 key = (dir, name) | |
| 91 if templates.has_key(key) and stime < templates[key].mtime: | |
| 92 # compiled template is up to date | |
| 93 return templates[key] | |
| 94 | |
| 95 # compile the template | |
| 96 templates[key] = pt = RoundupPageTemplate() | |
| 97 pt.write(open(src).read()) | |
| 98 pt.id = name | |
| 99 pt.mtime = time.time() | |
| 100 return pt | |
| 35 | 101 |
| 36 class RoundupPageTemplate(PageTemplate.PageTemplate): | 102 class RoundupPageTemplate(PageTemplate.PageTemplate): |
| 37 ''' A Roundup-specific PageTemplate. | 103 ''' A Roundup-specific PageTemplate. |
| 38 | 104 |
| 39 Interrogate the client to set up the various template variables to | 105 Interrogate the client to set up the various template variables to |
| 75 | 141 |
| 76 *modules* | 142 *modules* |
| 77 python modules made available (XXX: not sure what's actually in | 143 python modules made available (XXX: not sure what's actually in |
| 78 there tho) | 144 there tho) |
| 79 ''' | 145 ''' |
| 80 def __init__(self, client, classname=None, request=None): | 146 def getContext(self, client, classname, request): |
| 81 ''' Extract the vars from the client and install in the context. | |
| 82 ''' | |
| 83 self.client = client | |
| 84 self.classname = classname or self.client.classname | |
| 85 self.request = request or HTMLRequest(self.client) | |
| 86 | |
| 87 def pt_getContext(self): | |
| 88 c = { | 147 c = { |
| 89 'klass': HTMLClass(self.client, self.classname), | 148 'klass': HTMLClass(client, classname), |
| 90 'options': {}, | 149 'options': {}, |
| 91 'nothing': None, | 150 'nothing': None, |
| 92 'request': self.request, | 151 'request': request, |
| 93 'content': self.client.content, | 152 'content': client.content, |
| 94 'db': HTMLDatabase(self.client), | 153 'db': HTMLDatabase(client), |
| 95 'instance': self.client.instance | 154 'instance': client.instance |
| 96 } | 155 } |
| 97 # add in the item if there is one | 156 # add in the item if there is one |
| 98 if self.client.nodeid: | 157 if client.nodeid: |
| 99 c['item'] = HTMLItem(self.client.db, self.classname, | 158 c['item'] = HTMLItem(client.db, classname, client.nodeid) |
| 100 self.client.nodeid) | 159 c[classname] = c['item'] |
| 101 c[self.classname] = c['item'] | |
| 102 else: | 160 else: |
| 103 c[self.classname] = c['klass'] | 161 c[classname] = c['klass'] |
| 104 return c | 162 return c |
| 105 | 163 |
| 106 def render(self, *args, **kwargs): | 164 def render(self, client, classname, request, **options): |
| 107 if not kwargs.has_key('args'): | 165 """Render this Page Template""" |
| 108 kwargs['args'] = args | 166 |
| 109 return self.pt_render(extra_context={'options': kwargs}) | 167 if not self._v_cooked: |
| 168 self._cook() | |
| 169 | |
| 170 __traceback_supplement__ = (PageTemplate.PageTemplateTracebackSupplement, self) | |
| 171 | |
| 172 if self._v_errors: | |
| 173 raise PTRuntimeError, 'Page Template %s has errors.' % self.id | |
| 174 | |
| 175 # figure the context | |
| 176 classname = classname or client.classname | |
| 177 request = request or HTMLRequest(client) | |
| 178 c = self.getContext(client, classname, request) | |
| 179 c.update({'options': options}) | |
| 180 | |
| 181 # and go | |
| 182 output = StringIO.StringIO() | |
| 183 TALInterpreter(self._v_program, self._v_macros, | |
| 184 getEngine().getContext(c), output, tal=1, strictinsert=0)() | |
| 185 return output.getvalue() | |
| 110 | 186 |
| 111 class HTMLDatabase: | 187 class HTMLDatabase: |
| 112 ''' Return HTMLClasses for valid class fetches | 188 ''' Return HTMLClasses for valid class fetches |
| 113 ''' | 189 ''' |
| 114 def __init__(self, client): | 190 def __init__(self, client): |
| 223 ''' Render this class with the given template. | 299 ''' Render this class with the given template. |
| 224 ''' | 300 ''' |
| 225 # create a new request and override the specified args | 301 # create a new request and override the specified args |
| 226 req = HTMLRequest(self.client) | 302 req = HTMLRequest(self.client) |
| 227 req.classname = self.classname | 303 req.classname = self.classname |
| 228 req.__dict__.update(kwargs) | 304 req.update(kwargs) |
| 229 | 305 |
| 230 # new template, using the specified classname and request | 306 # new template, using the specified classname and request |
| 231 pt = RoundupPageTemplate(self.client, self.classname, req) | |
| 232 | |
| 233 # use the specified template | |
| 234 name = self.classname + '.' + name | 307 name = self.classname + '.' + name |
| 235 pt.write(open(os.path.join(self.db.config.TEMPLATES, name)).read()) | 308 pt = getTemplate(self.db.config.TEMPLATES, name) |
| 236 pt.id = name | |
| 237 | 309 |
| 238 # XXX handle PT rendering errors here nicely | 310 # XXX handle PT rendering errors here nicely |
| 239 try: | 311 try: |
| 240 return pt.render() | 312 # use our fabricated request |
| 313 return pt.render(self.client, self.classname, req) | |
| 241 except PageTemplate.PTRuntimeError, message: | 314 except PageTemplate.PTRuntimeError, message: |
| 242 return '<strong>%s</strong><ol>%s</ol>'%(message, | 315 return '<strong>%s</strong><ol>%s</ol>'%(message, |
| 243 cgi.escape('<li>'.join(pt._v_errors))) | 316 cgi.escape('<li>'.join(pt._v_errors))) |
| 244 | 317 |
| 245 class HTMLItem: | 318 class HTMLItem: |
| 849 if isinstance(value, type([])): | 922 if isinstance(value, type([])): |
| 850 return [value.value for value in value] | 923 return [value.value for value in value] |
| 851 else: | 924 else: |
| 852 return value.value.split(',') | 925 return value.value.split(',') |
| 853 | 926 |
| 927 class ShowDict: | |
| 928 ''' A convenience access to the :columns index parameters | |
| 929 ''' | |
| 930 def __init__(self, columns): | |
| 931 self.columns = {} | |
| 932 for col in columns: | |
| 933 self.columns[col] = 1 | |
| 934 def __getitem__(self, name): | |
| 935 return self.columns.has_key(name) | |
| 936 | |
| 854 class HTMLRequest: | 937 class HTMLRequest: |
| 855 ''' The *request*, holding the CGI form and environment. | 938 ''' The *request*, holding the CGI form and environment. |
| 856 | 939 |
| 857 "form" the CGI form as a cgi.FieldStorage | 940 "form" the CGI form as a cgi.FieldStorage |
| 858 "env" the CGI environment variables | 941 "env" the CGI environment variables |
| 862 "classname" the current classname (possibly None) | 945 "classname" the current classname (possibly None) |
| 863 "template_type" the current template type (suffix, also possibly None) | 946 "template_type" the current template type (suffix, also possibly None) |
| 864 | 947 |
| 865 Index args: | 948 Index args: |
| 866 "columns" dictionary of the columns to display in an index page | 949 "columns" dictionary of the columns to display in an index page |
| 950 "show" a convenience access to columns - request/show/colname will | |
| 951 be true if the columns should be displayed, false otherwise | |
| 867 "sort" index sort column (direction, column name) | 952 "sort" index sort column (direction, column name) |
| 868 "group" index grouping property (direction, column name) | 953 "group" index grouping property (direction, column name) |
| 869 "filter" properties to filter the index on | 954 "filter" properties to filter the index on |
| 870 "filterspec" values to filter the index on | 955 "filterspec" values to filter the index on |
| 871 "search_text" text to perform a full-text search on for an index | 956 "search_text" text to perform a full-text search on for an index |
| 884 # store the current class name and action | 969 # store the current class name and action |
| 885 self.classname = client.classname | 970 self.classname = client.classname |
| 886 self.template_type = client.template_type | 971 self.template_type = client.template_type |
| 887 | 972 |
| 888 # extract the index display information from the form | 973 # extract the index display information from the form |
| 889 self.columns = {} | 974 self.columns = [] |
| 890 if self.form.has_key(':columns'): | 975 if self.form.has_key(':columns'): |
| 891 for entry in handleListCGIValue(self.form[':columns']): | 976 self.columns = handleListCGIValue(self.form[':columns']) |
| 892 self.columns[entry] = 1 | 977 self.show = ShowDict(self.columns) |
| 893 | 978 |
| 894 # sorting | 979 # sorting |
| 895 self.sort = (None, None) | 980 self.sort = (None, None) |
| 896 if self.form.has_key(':sort'): | 981 if self.form.has_key(':sort'): |
| 897 sort = self.form[':sort'].value | 982 sort = self.form[':sort'].value |
| 932 | 1017 |
| 933 # full-text search argument | 1018 # full-text search argument |
| 934 self.search_text = None | 1019 self.search_text = None |
| 935 if self.form.has_key(':search_text'): | 1020 if self.form.has_key(':search_text'): |
| 936 self.search_text = self.form[':search_text'].value | 1021 self.search_text = self.form[':search_text'].value |
| 1022 | |
| 1023 # pagination - size and start index | |
| 1024 # figure batch args | |
| 1025 if self.form.has_key(':pagesize'): | |
| 1026 self.pagesize = int(self.form[':pagesize'].value) | |
| 1027 else: | |
| 1028 self.pagesize = 50 | |
| 1029 if self.form.has_key(':startwith'): | |
| 1030 self.startwith = int(self.form[':startwith'].value) | |
| 1031 else: | |
| 1032 self.startwith = 0 | |
| 1033 | |
| 1034 def update(self, kwargs): | |
| 1035 self.__dict__.update(kwargs) | |
| 1036 if kwargs.has_key('columns'): | |
| 1037 self.show = ShowDict(self.columns) | |
| 937 | 1038 |
| 938 def __str__(self): | 1039 def __str__(self): |
| 939 d = {} | 1040 d = {} |
| 940 d.update(self.__dict__) | 1041 d.update(self.__dict__) |
| 941 f = '' | 1042 f = '' |
| 954 template_type: %(template_type)r | 1055 template_type: %(template_type)r |
| 955 columns: %(columns)r | 1056 columns: %(columns)r |
| 956 sort: %(sort)r | 1057 sort: %(sort)r |
| 957 group: %(group)r | 1058 group: %(group)r |
| 958 filter: %(filter)r | 1059 filter: %(filter)r |
| 959 filterspec: %(filterspec)r | 1060 search_text: %(search_text)r |
| 1061 pagesize: %(pagesize)r | |
| 1062 startwith: %(startwith)r | |
| 960 env: %(env)s | 1063 env: %(env)s |
| 961 '''%d | 1064 '''%d |
| 962 | 1065 |
| 963 def indexargs_form(self, columns=1, sort=1, group=1, filter=1, | 1066 def indexargs_form(self, columns=1, sort=1, group=1, filter=1, |
| 964 filterspec=1): | 1067 filterspec=1): |
| 965 ''' return the current index args as form elements ''' | 1068 ''' return the current index args as form elements ''' |
| 966 l = [] | 1069 l = [] |
| 967 s = '<input type="hidden" name="%s" value="%s">' | 1070 s = '<input type="hidden" name="%s" value="%s">' |
| 968 if columns and self.columns: | 1071 if columns and self.columns: |
| 969 l.append(s%(':columns', ','.join(self.columns.keys()))) | 1072 l.append(s%(':columns', ','.join(self.columns))) |
| 970 if sort and self.sort is not None: | 1073 if sort and self.sort[1] is not None: |
| 971 if self.sort[0] == '-': | 1074 if self.sort[0] == '-': |
| 972 val = '-'+self.sort[1] | 1075 val = '-'+self.sort[1] |
| 973 else: | 1076 else: |
| 974 val = self.sort[1] | 1077 val = self.sort[1] |
| 975 l.append(s%(':sort', val)) | 1078 l.append(s%(':sort', val)) |
| 976 if group and self.group is not None: | 1079 if group and self.group[1] is not None: |
| 977 if self.group[0] == '-': | 1080 if self.group[0] == '-': |
| 978 val = '-'+self.group[1] | 1081 val = '-'+self.group[1] |
| 979 else: | 1082 else: |
| 980 val = self.group[1] | 1083 val = self.group[1] |
| 981 l.append(s%(':group', val)) | 1084 l.append(s%(':group', val)) |
| 982 if filter and self.filter: | 1085 if filter and self.filter: |
| 983 l.append(s%(':filter', ','.join(self.filter))) | 1086 l.append(s%(':filter', ','.join(self.filter))) |
| 984 if filterspec: | 1087 if filterspec: |
| 985 for k,v in self.filterspec.items(): | 1088 for k,v in self.filterspec.items(): |
| 986 l.append(s%(k, ','.join(v))) | 1089 l.append(s%(k, ','.join(v))) |
| 1090 if self.search_text: | |
| 1091 l.append(s%(':search_text', self.search_text)) | |
| 1092 l.append(s%(':pagesize', self.pagesize)) | |
| 1093 l.append(s%(':startwith', self.startwith)) | |
| 987 return '\n'.join(l) | 1094 return '\n'.join(l) |
| 988 | 1095 |
| 989 def indexargs_href(self, url, args): | 1096 def indexargs_href(self, url, args): |
| 990 ''' embed the current index args in a URL ''' | 1097 ''' embed the current index args in a URL ''' |
| 991 l = ['%s=%s'%(k,v) for k,v in args.items()] | 1098 l = ['%s=%s'%(k,v) for k,v in args.items()] |
| 992 if self.columns: | 1099 if self.columns: |
| 993 l.append(':columns=%s'%(','.join(self.columns.keys()))) | 1100 l.append(':columns=%s'%(','.join(self.columns))) |
| 994 if self.sort is not None: | 1101 if self.sort[1] is not None: |
| 995 if self.sort[0] == '-': | 1102 if self.sort[0] == '-': |
| 996 val = '-'+self.sort[1] | 1103 val = '-'+self.sort[1] |
| 997 else: | 1104 else: |
| 998 val = self.sort[1] | 1105 val = self.sort[1] |
| 999 l.append(':sort=%s'%val) | 1106 l.append(':sort=%s'%val) |
| 1000 if self.group is not None: | 1107 if self.group[1] is not None: |
| 1001 if self.group[0] == '-': | 1108 if self.group[0] == '-': |
| 1002 val = '-'+self.group[1] | 1109 val = '-'+self.group[1] |
| 1003 else: | 1110 else: |
| 1004 val = self.group[1] | 1111 val = self.group[1] |
| 1005 l.append(':group=%s'%val) | 1112 l.append(':group=%s'%val) |
| 1006 if self.filter: | 1113 if self.filter: |
| 1007 l.append(':filter=%s'%(','.join(self.filter))) | 1114 l.append(':filter=%s'%(','.join(self.filter))) |
| 1008 for k,v in self.filterspec.items(): | 1115 for k,v in self.filterspec.items(): |
| 1009 l.append('%s=%s'%(k, ','.join(v))) | 1116 l.append('%s=%s'%(k, ','.join(v))) |
| 1117 if self.search_text: | |
| 1118 l.append(':search_text=%s'%self.search_text) | |
| 1119 l.append(':pagesize=%s'%self.pagesize) | |
| 1120 l.append(':startwith=%s'%self.startwith) | |
| 1010 return '%s?%s'%(url, '&'.join(l)) | 1121 return '%s?%s'%(url, '&'.join(l)) |
| 1011 | 1122 |
| 1012 def base_javascript(self): | 1123 def base_javascript(self): |
| 1013 return ''' | 1124 return ''' |
| 1014 <script language="javascript"> | 1125 <script language="javascript"> |
| 1042 re.findall(r'\b\w{2,25}\b', self.search_text), klass) | 1153 re.findall(r'\b\w{2,25}\b', self.search_text), klass) |
| 1043 else: | 1154 else: |
| 1044 matches = None | 1155 matches = None |
| 1045 l = klass.filter(matches, filterspec, sort, group) | 1156 l = klass.filter(matches, filterspec, sort, group) |
| 1046 | 1157 |
| 1047 # figure batch args | |
| 1048 if self.form.has_key(':pagesize'): | |
| 1049 size = int(self.form[':pagesize'].value) | |
| 1050 else: | |
| 1051 size = 50 | |
| 1052 if self.form.has_key(':startwith'): | |
| 1053 start = int(self.form[':startwith'].value) | |
| 1054 else: | |
| 1055 start = 0 | |
| 1056 | |
| 1057 # return the batch object | 1158 # return the batch object |
| 1058 return Batch(self.client, self.classname, l, size, start) | 1159 return Batch(self.client, self.classname, l, self.pagesize, |
| 1160 self.startwith) | |
| 1059 | 1161 |
| 1060 | 1162 |
| 1061 # extend the standard ZTUtils Batch object to remove dependency on | 1163 # extend the standard ZTUtils Batch object to remove dependency on |
| 1062 # Acquisition and add a couple of useful methods | 1164 # Acquisition and add a couple of useful methods |
| 1063 class Batch(ZTUtils.Batch): | 1165 class Batch(ZTUtils.Batch): |
