split defined configuration constants into config, and implement search result pagination
authorTero Marttila <terom@fixme.fi>
Mon, 09 Feb 2009 23:49:57 +0200
changeset 73 5a7188bf2894
parent 72 5ade0288f2ec
child 74 1ab95857d584
split defined configuration constants into config, and implement search result pagination
channels.py
config.py
handlers.py
helpers.py
log_formatter.py
log_source.py
preferences.py
static/irclogs.css
templates/channel_search.tmpl
templates/preferences.tmpl
urls.py
utils.py
--- a/channels.py	Mon Feb 09 22:17:10 2009 +0200
+++ b/channels.py	Mon Feb 09 23:49:57 2009 +0200
@@ -2,65 +2,37 @@
     Our list of LogChannels
 """
 
-import pytz
-
-# for relpath
-import os.path
-
-from log_channel import LogChannel
-from log_source import LogDirectory
-from log_parser import IrssiParser
-
-relpath = lambda path : os.path.join(os.path.dirname(__file__), path)
-
 class ChannelList (object) :
     """
         The list of channels, and related methods
     """
 
-    # timezone to use
-    TIMEZONE = pytz.timezone('Europe/Helsinki')
 
-    # the parser that we use
-    PARSER = IrssiParser(TIMEZONE, "%H:%M:%S")
-    
-    # the statically defined channel list
-    CHANNELS = {
-        'tycoon':   LogChannel('tycoon', "OFTC", "#tycoon", 
-            LogDirectory(relpath('logs/tycoon'), TIMEZONE, PARSER)
-        ),
-        'openttd':   LogChannel('openttd', "OFTC", "#openttd", 
-            LogDirectory(relpath('logs/openttd'), TIMEZONE, PARSER)
-        ),
-    }
-
-    def __init__ (self, channels) :
+    def __init__ (self, channel_list) :
         """
             Initialize with the given channel dict
         """
-
-        self.channels = channels
+        
+        self.channel_list = channel_list
+        self.channel_dict = dict((channel.id, channel) for channel in channel_list)
 
     def lookup (self, channel_name) :
         """
             Looks up the LogChannel for the given name
         """
 
-        return self.channels[channel_name]
+        return self.channel_dict[channel_name]
     
     def dict (self) :
         """
             Returns a { name: LogChannel } dict
         """
-        return self.channels
+        return self.channel_dict
 
     def __iter__ (self) :
         """
             Iterate over our defined LogChannel objects
         """
 
-        return self.channels.itervalues()
+        return iter(self.channel_list)
 
-# the global singletone ChannelList...
-channel_list = ChannelList(ChannelList.CHANNELS)
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/config.py	Mon Feb 09 23:49:57 2009 +0200
@@ -0,0 +1,69 @@
+"""
+    Configureable defaults
+"""
+
+import os.path, pytz
+from log_parser import IrssiParser
+from log_channel import LogChannel
+from log_source import LogDirectory
+from log_formatter import IrssiFormatter
+from channels import ChannelList
+import log_formatter
+
+# build relative paths
+relpath = lambda path : os.path.join(os.path.dirname(__file__), path)
+
+# timezone to use for logs
+LOG_TIMEZONE = pytz.timezone('Europe/Helsinki')
+
+# timestamp format for logfiles
+LOG_TIMESTAMP_FMT = '%H:%M:%S'
+
+# character set used for logfiles
+LOG_CHARSET = 'utf-8'
+
+# log filename format
+LOG_FILENAME_FMT = '%Y-%m-%d'
+
+# the log parser that we use
+LOG_PARSER = IrssiParser(LOG_TIMEZONE, LOG_TIMESTAMP_FMT)
+
+# the statically defined channel list
+LOG_CHANNELS = ChannelList([
+    LogChannel('tycoon',    "OFTC",     "#tycoon", 
+        LogDirectory(relpath('logs/tycoon'),    LOG_TIMEZONE, LOG_PARSER, LOG_CHARSET, LOG_FILENAME_FMT)
+    ),
+
+    LogChannel('openttd',   "OFTC",     "#openttd", 
+        LogDirectory(relpath('logs/openttd'),   LOG_TIMEZONE, LOG_PARSER, LOG_CHARSET, LOG_FILENAME_FMT)
+    ),
+])
+
+# date format for URLs
+URL_DATE_FMT = '%Y-%m-%d'
+
+# month name format
+MONTH_FMT = '%B %Y'
+
+# timezone name format
+TIMEZONE_FMT = '%Z %z'
+
+# available formatters
+LOG_FORMATTERS = log_formatter.FORMATTERS
+
+# default preferences
+PREF_TIME_FMT_DEFAULT = '%H:%M:%S'
+PREF_DATE_FMT_DEFAULT = '%Y-%m-%d'
+PREF_TIMEZONE_DEFAULT = pytz.utc
+PREF_FORMATTER_DEFAULT = IrssiFormatter
+PREF_COUNT_DEFAULT = 200
+PREF_COUNT_MAX = None
+
+# search line count options
+SEARCH_LINE_COUNT_OPTIONS = (
+    (50,    50), 
+    (100,   100), 
+    (200,   200), 
+    (None,  "&#8734;"),
+)
+
--- a/handlers.py	Mon Feb 09 22:17:10 2009 +0200
+++ b/handlers.py	Mon Feb 09 23:49:57 2009 +0200
@@ -9,12 +9,14 @@
 import urls, channels, helpers
 import preferences as prefs
 from preferences import preferences
+import config
 
 # load templates from here
 templates = template.TemplateLoader("templates",
     _helper_class   = helpers.Helpers,
     urls            = urls,
-    channel_list    = channels.channel_list,
+    channel_list    = config.LOG_CHANNELS,
+    config          = config,
 )
 
 # our LogSearch thing
@@ -162,8 +164,8 @@
         lines           = lines,
     )
 
-@preferences.handler(prefs.formatter)
-def channel_search (request, channel, formatter, q=None, count=None, skip=None) :
+@preferences.handler(prefs.formatter, prefs.count)
+def channel_search (request, channel, formatter, count, q=None, skip=0, max=None) :
     """
         Display the search form for the channel for GET, or do the search for POST
     """
@@ -185,6 +187,9 @@
         prefs           = request.prefs,
         channel         = channel,
         search_query    = q,
+        count           = count,
+        skip            = skip,
+        max             = max,
         lines           = lines,
     )
 
--- a/helpers.py	Mon Feb 09 22:17:10 2009 +0200
+++ b/helpers.py	Mon Feb 09 23:49:57 2009 +0200
@@ -6,7 +6,7 @@
 
 import qmsk.web.helpers
 
-import preferences, urls
+import preferences, urls, config
 
 class Helpers (qmsk.web.helpers.Helpers) :
     """
@@ -18,14 +18,14 @@
             Returns a string describing the given timezone
         """
 
-        return self.now().strftime("%Z%z")
+        return self.now().strftime(config.TIMEZONE_FMT)
 
     def fmt_month (self, date) :
         """
             Formats a month
         """
 
-        return date.strftime('%B %Y')
+        return date.strftime(config.MONTH_FMT)
         
     def fmt_weekday (self, wday) :
         """
@@ -151,4 +151,40 @@
         """
 
         return urls.types['ts'].build(dtz)
+    
+    def skip_next (self, count, skip) :
+        """
+            Return skip offset for next page
+        """
 
+        return count + skip
+    
+    def skip_page (self, count, page) :
+        """
+            Skip to page
+        """
+
+        if page :
+            return count * page
+
+        else :
+            return None
+
+    def skip_prev (self, count, skip) :
+        """
+            Return skip offset for previous page, None for first page
+        """
+
+        if skip > count :
+            return skip - count
+
+        else :
+            return None
+
+    def max (self, *values) :
+        """
+            Returns the largest of the given values
+        """
+
+        return max(values)
+
--- a/log_formatter.py	Mon Feb 09 22:17:10 2009 +0200
+++ b/log_formatter.py	Mon Feb 09 23:49:57 2009 +0200
@@ -42,7 +42,7 @@
         
         # full timestamps?
         if full_timestamp :
-            # XXX: ugly
+            # XXX: ugly hack
             timestamp_fmt = '%Y-%m-%d ' + self.timestamp_fmt
 
         else :
--- a/log_source.py	Mon Feb 09 22:17:10 2009 +0200
+++ b/log_source.py	Mon Feb 09 23:49:57 2009 +0200
@@ -39,7 +39,7 @@
         XXX: modify to implement LogSource?
     """
 
-    def __init__ (self, path, parser, start_date=None, charset='utf-8', sep='\n') :
+    def __init__ (self, path, parser, charset, start_date=None, sep='\n') :
         """
             Open the file at the given path, which contains data with the given charset, as lines separated by the
             given separator. Lines are parsed using the given parser, using the given date as an initial date, see
@@ -204,7 +204,7 @@
         A directory containing a series of timestamped LogFiles
     """
 
-    def __init__ (self, path, tz, parser, charset='utf-8', filename_fmt='%Y-%m-%d') :
+    def __init__ (self, path, tz, parser, charset, filename_fmt) :
         """
             Load the logfiles at the given path.
             
@@ -247,7 +247,7 @@
         try :
             if load :
                 # open+return the LogFile
-                return LogFile(path, self.parser, d, self.charset)
+                return LogFile(path, self.parser, self.charset, d)
             
             else :
                 # test
--- a/preferences.py	Mon Feb 09 22:17:10 2009 +0200
+++ b/preferences.py	Mon Feb 09 23:49:57 2009 +0200
@@ -195,6 +195,7 @@
 
 # now for our defined preferences....
 import pytz
+import config
 
 class TimeFormat (urltree.URLStringType, Preference) :
     """
@@ -205,7 +206,7 @@
     name = 'time_format'
 
     # default value
-    default = "%H:%M:%S"
+    default = config.PREF_TIME_FMT_DEFAULT
 
 class DateFormat (urltree.URLStringType, Preference) :
     """
@@ -216,7 +217,7 @@
     name = 'date_format'
 
     # default value
-    default = "%Y-%m-%d"
+    default = config.PREF_DATE_FMT_DEFAULT
 
 class Timezone (Preference) :
     """
@@ -227,7 +228,7 @@
     name = 'timezone'
 
     # default value is UTC...
-    default = pytz.utc
+    default = config.PREF_TIMEZONE_DEFAULT
 
     def parse (self, name) :
         """
@@ -280,18 +281,32 @@
 
         return fmt_cls(prefs[timezone], prefs[time_format])
 
-# and then build the Preferences object
-import log_formatter
+class Count (urltree.URLIntegerType, Preference) :
+    """
+        Number of lines of log data to display per page
+    """
 
-time_format = TimeFormat()
-date_format = DateFormat()
-timezone    = Timezone()
-formatter   = Formatter(log_formatter.FORMATTERS, log_formatter.IrssiFormatter)
+    # set name
+    name = "count"
+    
+    # default
+    default = config.PREF_COUNT_DEFAULT
+    
+    def __init__ (self) :
+        super(Count, self).__init__(allow_negative=False, allow_zero=False, max=config.PREF_COUNT_MAX)
+
+# and then build the Preferences object
+time_format     = TimeFormat()
+date_format     = DateFormat()
+timezone        = Timezone()
+formatter       = Formatter(config.LOG_FORMATTERS, config.PREF_FORMATTER_DEFAULT)
+count           = Count()
 
 preferences = Preferences([
     time_format,
     date_format,
     timezone,
     formatter,
+    count,
 ])
 
--- a/static/irclogs.css	Mon Feb 09 22:17:10 2009 +0200
+++ b/static/irclogs.css	Mon Feb 09 23:49:57 2009 +0200
@@ -307,3 +307,45 @@
     color: #000000;
 }
 
+/* Pagination */
+div.paginate {
+    height: 50px;
+
+    padding-top: 20px;
+
+    text-align: center;
+}
+
+div.paginate ul {
+    margin: 0px;
+    padding: 0px;
+
+    line-height: 30px;
+
+    border: 1px solid #aaa;
+
+    white-space: nowrap;
+}
+
+div.paginate li {
+    list-style-type: none;
+    display: inline;
+}
+
+div.paginate li * {
+    padding: 7px 10px;
+}
+
+div.paginate li a {
+    color: black;
+    text-decoration: none;
+}
+
+div.paginate li span {
+    color: #d5d5d5;
+}
+
+div.paginate li a:hover {
+    background-color: #d0d0d0;
+}
+
--- a/templates/channel_search.tmpl	Mon Feb 09 22:17:10 2009 +0200
+++ b/templates/channel_search.tmpl	Mon Feb 09 23:49:57 2009 +0200
@@ -1,5 +1,35 @@
 <%inherit file="channel.tmpl" />
 
+<%def name="paginate(url, count, skip, max, **args)">
+    ## update max?
+    <% max = h.max(max, skip) %>
+    ## number of pages
+    <% page_count = max / count + 1 %>
+    <div class="paginate">
+        <ul>
+            <li>
+            % if skip :
+                <a href="${h.build_url(url, count=count, skip=h.skip_prev(count, skip), max=max, **args)}">&laquo; Prev</a>
+            % else :
+                <span>&laquo; Prev</span>
+            %endif
+            </li>
+        % for page in xrange(page_count) :
+            <li>
+            % if page == skip / count :
+                <strong>${page + 1}</strong>
+            % else :
+                <a href="${h.build_url(url, count=count, skip=h.skip_page(count, page), max=max, **args)}">${page + 1}</a>
+            % endif
+            </li>
+        % endfor
+            <li>
+                <a href="${h.build_url(url, count=count, skip=h.skip_next(count, max), **args)}">More &raquo;</a>
+            </li>
+        </ul>
+    </div>
+</%def>
+
 % if not search_query :
 <div id="title">${channel.title} :: Search</div>
 
@@ -9,10 +39,9 @@
         <input type="submit" value="Search" />
         
         Results/page: <select name="count">
-            <option value="50">50</option>
-            <option value="100">100</option>
-            <option value="200">200</option>
-            <option value="">&#8734;</option>
+        % for cc, cc_label in config.SEARCH_LINE_COUNT_OPTIONS :
+            <option value="${cc if cc else ''}"${' selected="selected"' if cc == count else ''}>${cc_label}</option>
+        % endfor
         </select>
     </form>
     
@@ -32,5 +61,7 @@
 % else :
 <div id="title">${channel.title} :: Search '${search_query}'</div>
 
+${paginate(urls.channel_search, count, skip, max, channel=channel, q=search_query)}
 <%include file="lines.tmpl" />
+${paginate(urls.channel_search, count, skip, max, channel=channel, q=search_query)}
 % endif
--- a/templates/preferences.tmpl	Mon Feb 09 22:17:10 2009 +0200
+++ b/templates/preferences.tmpl	Mon Feb 09 23:49:57 2009 +0200
@@ -41,6 +41,12 @@
             % endfor
             </select>
         </p>
+
+        <p>
+            <label for="count">Lines / Page:</label>
+            <input type="text" name="count" value="${prefs['count']}" />
+            <span class="example">(Blank for infinite)</span>
+        </p>
     </fieldset>
 
     <input type="submit" value="Save" />
--- a/urls.py	Mon Feb 09 22:17:10 2009 +0200
+++ b/urls.py	Mon Feb 09 23:49:57 2009 +0200
@@ -13,15 +13,15 @@
 import utils
 
 # for configuration
-import channels
+import config
 
 # our URLTypes
 types   = dict(
     # LogChannel
-    cid     = utils.URLChannelName(channels.channel_list.dict()),
+    cid     = utils.URLChannelName(config.LOG_CHANNELS.dict()),
 
     # datetime
-    date    = utils.URLDateType('%Y-%m-%d'),
+    date    = utils.URLDateType(config.URL_DATE_FMT),
 
     # UTC timestamp
     ts      = utils.URLTimestampType(),
@@ -39,7 +39,7 @@
 channel_link        = url('/channels/{channel:cid}/link/{timestamp:ts}',                    handlers.channel_link                       )
 channel_calendar    = url('/channels/{channel:cid}/calendar/{year:int=0}/{month:int=0}',    handlers.channel_calendar                   )
 channel_date        = url('/channels/{channel:cid}/date/{date:date}',                       handlers.channel_date                       )
-channel_search      = url('/channels/{channel:cid}/search/?q=&count:int=&skip:int=',        handlers.channel_search                     )
+channel_search      = url('/channels/{channel:cid}/search/?q=&skip:int=0&max:int=',         handlers.channel_search                     )
 
 # mapper
 mapper = urltree.URLTree(urls)
--- a/utils.py	Mon Feb 09 22:17:10 2009 +0200
+++ b/utils.py	Mon Feb 09 23:49:57 2009 +0200
@@ -37,7 +37,7 @@
         Handle dates in URLs as naive datetime objects (with indeterminate time info)
     """
 
-    def __init__ (self, date_fmt="%Y-%m-%d") :
+    def __init__ (self, date_fmt) :
         """
             Format/parse dates using the given format
         """