@@ -22,7 +22,7 @@ Customisation of Roundup can take one of six forms:
22221. `tracker configuration`_ changes
23232. database, or `tracker schema`_ changes
24243. "definition" class `database content`_ changes
25- 4. behavioural changes, through detectors_ and extensions_
25+ 4. behavioural changes through detectors_, extensions_ and interfaces.py_
26265. `security / access controls`_
27276. change the `web interface`_
2828
@@ -1107,6 +1107,140 @@ flexible extension point mechanism.
11071107 Generic action can be added by inheriting from ``action.Action``
11081108 instead of ``cgi.action.Action``.
11091109
1110+ interfaces.py - hooking into the core of roundup
1111+ ================================================
1112+ .. _interfaces.py:
1113+
1114+ There is a magic trick for hooking into the core of roundup. Using
1115+ this you can:
1116+
1117+ * modify class data structures
1118+ * monkey patch core code to add new functionality
1119+ * modify the email gateway
1120+ * add new rest endpoints
1121+
1122+ but with great power comes great responsibility.
1123+
1124+ Interfaces.py has been around since the earliest releases of roundup
1125+ and used to be the main way to get a lot of customization done. In
1126+ modern roundup, the extensions_ mechanism is used, but there are places
1127+ where interfaces.py is still useful.
1128+
1129+ Example: Changing Cache-Control headers
1130+ ---------------------------------------
1131+
1132+ The Client class in cgi/client.py has a lookup table that is used to
1133+ set the Cache-Control headers for static files. The entries in this
1134+ table are set from interfaces.py using::
1135+
1136+ from roundup.cgi.client import Client
1137+
1138+ Client.Cache_Control['text/css'] = "public, max-age=3600"
1139+ Client.Cache_Control['application/javascript'] = "public, max-age=30"
1140+ Client.Cache_Control['rss.xml'] = "public, max-age=900"
1141+ Client.Cache_Control['local.js'] = "public, max-age=7200"
1142+
1143+ In this case static files delivered using @@file will have cache
1144+ headers set. These files are searched for along the `static_files`
1145+ path in the tracker's `config.ini`. In the example above:
1146+
1147+ * a css file (e.g. @@file/style.css) will be cached for an hour
1148+ * javascript files (e.g. @@file/libraries/jquery.js) will be cached
1149+ for 30 seconds
1150+ * a file named rss.xml will be cached for 5 minutes
1151+ * a file named local.js will be cached for 2 hours
1152+
1153+ Note that a file name match overrides the mime type settings.
1154+
1155+ Example: Implement password complexity checking
1156+ -----------------------------------------------
1157+
1158+ This example uses the zxcvbn_ module that you can place in the zxcvbn
1159+ subdirectory of your tracker's lib directory.
1160+
1161+ If you add this to the interfaces.py file in the root directory of
1162+ your tracker (same place as schema.py)::
1163+
1164+ import roundup.password as password
1165+ from roundup.exceptions import Reject
1166+ from zxcvbn import zxcvbn
1167+
1168+ # monkey patch the setPassword method with this method
1169+ # that checks password strength.
1170+ origPasswordFunc = password.Password.setPassword
1171+ def mpPasswordFunc(self, plaintext, scheme, config=None):
1172+ """ Replace the password set function with one that
1173+ verifies that the password is complex enough. It
1174+ has to be done at this point and not in an auditor
1175+ as the auditor only sees the encrypted password.
1176+ """
1177+ results = zxcvbn(plaintext)
1178+ if results['score'] < 3:
1179+ l = []
1180+ map(l.extend, [[results['feedback']['warning']], results['feedback']['suggestions']])
1181+ errormsg = " ".join(l)
1182+ raise Reject ("Password is too easy to guess. " + errormsg)
1183+ return origPasswordFunc(self, plaintext, scheme, config=config)
1184+
1185+ password.Password.setPassword = mpPasswordFunc
1186+
1187+ it replaces the setPassword method in the Password class. The new
1188+ version validates that the password is sufficiently complex. Then it
1189+ passes off the setting of password to the original method.
1190+
1191+ Example: Enhance time intervals
1192+ -------------------------------
1193+
1194+ To make the user interface easier to use, you may want to support
1195+ other forms for intervals. For example you can support an interval
1196+ like 1.5 by interpreting it the same as 1:30 (1 hour 30 minutes).
1197+ Also you can allow a bare integer (e.g. 45) as a number of minutes.
1198+
1199+ To do this we intercept the from_raw method of the Interval class in
1200+ hyperdb.py with::
1201+
1202+ import roundup.hyperdb as hyperdb
1203+ origFrom_Raw = hyperdb.Interval.from_raw
1204+
1205+ def normalizeperiod(self, value, **kw):
1206+ ''' Convert alternate time forms into standard interval format
1207+
1208+ [+-] [#y] [#m] [#w] [#d] [[[H]H:MM]:SS]
1209+
1210+ if value is float, it's hour and fractional hours
1211+ if value is integer, it's number of minutes
1212+ '''
1213+ if ":" not in value:
1214+ # Not a specified interval
1215+ # if int consider number of minutes
1216+ try:
1217+ isMinutes = int(value)
1218+ minutes = isMinutes%60
1219+ hours = (isMinutes - minutes) / 60
1220+ value = "%d:%d"%(hours,minutes)
1221+ except ValueError:
1222+ pass
1223+ # if float, consider it number of hours and fractional hours.
1224+ import math
1225+ try:
1226+ afterdecimal, beforedecimal = math.modf(float(value))
1227+ value = "%d:%d"%(beforedecimal,60*afterdecimal)
1228+ except ValueError:
1229+ pass
1230+
1231+ return origFrom_Raw(self, value, **kw)
1232+
1233+ hyperdb.Interval.from_raw = normalizeperiod
1234+
1235+ any call to convert an interval from raw form now has two simpler
1236+ (and more friendly) ways to specify common time intervals.
1237+
1238+ Other Examples
1239+ --------------
1240+
1241+ See the `rest interface documentation`_ for instructions on how to add
1242+ new rest endpoints using interfaces.py.
1243+
11101244Database Content
11111245================
11121246
@@ -5474,3 +5608,5 @@ rather than requiring a web server restart.
54745608
54755609.. _`design documentation`: design.html
54765610.. _`developer's guide`: developers.html
5611+ .. _`rest interface documentation`: rest.html#programming-the-rest-api
5612+ .. _`zxcvbn`: https://github.com/dwolfhub/zxcvbn-python
0 commit comments