commit 2e50716262572c7e2a83eccfedd2a1d4c2b203d4
Author: dsorber <david.sorber@gmail.com>
Date:   Mon Jul 6 14:06:01 2015 -0400

    Started working on tag list and a "tagged" controller. Need to work on pagination for the tagged controller.

diff --git a/software/bookmark_library/bmklib/lib/tag.py b/software/bookmark_library/bmklib/lib/tag.py
index f7ec82a..4f5fe23 100644
--- a/software/bookmark_library/bmklib/lib/tag.py
+++ b/software/bookmark_library/bmklib/lib/tag.py
@@ -6,6 +6,8 @@ from bmklib.lib import DBConnectionError
 SQL_SELECT_TAG_BY_ID = "SELECT * FROM tags WHERE id=?"
 SQL_SELECT_TAG_BY_VALUE = "SELECT * FROM tags WHERE value=?"
 SQL_INSERT_TAG = "INSERT INTO tags VALUES (?,?)"
+SQL_GET_CATEGORY_BMKS = "SELECT bookmark_id FROM category_tags WHERE tag_id=?"
+SQL_GET_DESCRIPTION_BMKS = "SELECT bookmark_id FROM description_tags WHERE tag_id=?"
 
 class TagNotFoundError(Exception):
     
@@ -29,6 +31,8 @@ class Tag(object):
         # Data attributes
         self.id = id
         self.value = value
+        self.category_bmks = []
+        self.description_bmks = []
 
         if self.id is not None:
             # If an ID was passed in then the tag's value must match the ID
@@ -37,7 +41,15 @@ class Tag(object):
             row = db.fetchone()
             if not row:
                 raise TagNotFoundError(self.id)
-            self.value = row[1]   
+            self.value = row[1]
+            
+            # Get category bookmark IDs
+            db.execute(SQL_GET_CATEGORY_BMKS, (self.id,))
+            self.category_bmks = [int(row[0]) for row in db.fetchall()]
+            
+            # Get description bookmark IDs
+            db.execute(SQL_GET_DESCRIPTION_BMKS, (self.id,))
+            self.description_bmks = [int(row[0]) for row in db.fetchall()]
                  
         elif self.value:
             # If a value was passed in, check to see if it already exists in 
diff --git a/software/bookmark_library/bmklib/web/bmklib.wsgi b/software/bookmark_library/bmklib/web/bmklib.wsgi
index 6da212b..265c6bc 100644
--- a/software/bookmark_library/bmklib/web/bmklib.wsgi
+++ b/software/bookmark_library/bmklib/web/bmklib.wsgi
@@ -1,8 +1,10 @@
 import re
 
 from bmklib.web.controllers.all_bookmarks import AllBookmarksPageController
+from bmklib.web.controllers.all_tags import AllTagsPageController
 from bmklib.web.controllers.bookmark import BookmarkPageController
 from bmklib.web.controllers.main import MainPageController
+from bmklib.web.controllers.tagged import TaggedPageController
 from bmklib.web.controllers.update_bookmark import UpdateBookmarkController
 
 # It accepts two arguments:
@@ -32,7 +34,17 @@ def application(environ, start_response):
     elif re.search('^/update_bookmark$', path) or re.search('^/update_bookmark/$', path):
         # Update bookmark
         controller = UpdateBookmarkController(environ)
-        response_headers, response_body = controller.generate_response()        
+        response_headers, response_body = controller.generate_response()
+        
+    elif re.search('^/tags$', path) or re.search('^/tags/', path):
+        # All tags page
+        controller = AllTagsPageController(environ)
+        response_headers, response_body = controller.generate_response()
+   
+    elif re.search('^/tagged/', path):
+        # All bookmarks tagged with tag page
+        controller = TaggedPageController(environ)
+        response_headers, response_body = controller.generate_response()
         
     else:
         # User supplied an invalid URL
diff --git a/software/bookmark_library/bmklib/web/controllers/all_bookmarks.py b/software/bookmark_library/bmklib/web/controllers/all_bookmarks.py
index 719f8d3..b251f6c 100644
--- a/software/bookmark_library/bmklib/web/controllers/all_bookmarks.py
+++ b/software/bookmark_library/bmklib/web/controllers/all_bookmarks.py
@@ -8,11 +8,13 @@ from bmklib.web.base_controller import BaseHTMLController, DB_PATH, MAX_PER_PAGE
 
 class AllBookmarksPageController(BaseHTMLController):
     
-    def __init__(self, environ, template_name='all_bookmarks.html'):
+    def __init__(self, environ, template_name='bookmark_list.html'):
         super(AllBookmarksPageController, self).__init__(environ, template_name)
     
     def build_content(self):
         
+        self.content['page_title'] = 'All Bookmarks'
+        
         # Split the path so we can parse it below
         path = self.environ['PATH_INFO'][1:]
         path_parts = path.split('/')
diff --git a/software/bookmark_library/bmklib/web/controllers/all_tags.py b/software/bookmark_library/bmklib/web/controllers/all_tags.py
new file mode 100644
index 0000000..2398bed
--- /dev/null
+++ b/software/bookmark_library/bmklib/web/controllers/all_tags.py
@@ -0,0 +1,84 @@
+import math
+import os.path
+
+import sqlite3
+
+from bmklib.lib.tag import Tag
+from bmklib.web.base_controller import BaseHTMLController, DB_PATH, MAX_PER_PAGE
+
+class AllTagsPageController(BaseHTMLController):
+    
+    def __init__(self, environ, template_name='tag_list.html'):
+        super(AllTagsPageController, self).__init__(environ, template_name)
+    
+    def build_content(self):
+        
+        self.content['page_title'] = 'All Tags'
+        
+        # Split the path so we can parse it below
+        path = self.environ['PATH_INFO'][1:]
+        path_parts = path.split('/')
+        
+        conn = sqlite3.connect(DB_PATH)        
+        db = conn.cursor()
+        
+        # Grab the total number of tags
+        db = conn.cursor()
+        db.execute("SELECT COUNT(*) FROM tags")
+        total_tags = int(db.fetchone()[0])
+        self.content['total_num_tags'] = total_tags
+        self.content['max_page_idx'] = math.ceil(float(total_tags) / MAX_PER_PAGE)
+        
+        # Start pagination at 1 (instead of 0)
+        page_idx = 1
+        
+        # Parse the path and modify the query as needed
+        if len(path_parts) == 2:
+            if path_parts[1] == 'all':
+                db.execute("SELECT id FROM tags")
+            else:
+                # Attempt to grab a new query index from the URL
+                try:
+                    page_idx = int(path_parts[1])
+                except ValueError:
+                    pass
+                    
+                if page_idx < 1:
+                    page_idx = 1
+                    
+                query = "SELECT id FROM tags LIMIT {:d} OFFSET {:d}".format(
+                        MAX_PER_PAGE, (page_idx - 1) * MAX_PER_PAGE)
+                db.execute(query)
+        else:
+            db.execute("SELECT id FROM tags LIMIT {:d}".format(MAX_PER_PAGE))
+        
+        self.content['page_idx'] = page_idx
+        
+        # Build Tag objects for all of the returned results
+        tags = [Tag(conn, id=row[0]) for row in db.fetchall()]
+        
+        self.content['tags'] = tags
+        self.content['path_parts'] = len(path_parts)
+        
+        # Calculate the URL "offset" for the update bookmark link
+        update_link = ['..']  * (len(path_parts) - 1)
+        update_link.extend(['tag'])
+        self.content['update_link'] = os.path.join(*update_link)
+        
+        # Set previous link to control pagination in the template
+        if page_idx == 1:
+            self.content['previous'] = None
+        else:
+            prev_idx = page_idx - 1
+            prev_path = ['..']  * (len(path_parts) - 1)
+            prev_path.extend(['tags', str(prev_idx)])
+            self.content['previous'] = os.path.join(*prev_path)
+        
+        # Set the next link to control pagination in the template
+        next_idx = page_idx + 1
+        if next_idx > self.content['max_page_idx']:
+            self.content['next'] = None
+        else:
+            next_path = ['..']  * (len(path_parts) - 1)
+            next_path.extend(['tags', str(next_idx)])
+            self.content['next'] = os.path.join(*next_path)
diff --git a/software/bookmark_library/bmklib/web/controllers/tagged.py b/software/bookmark_library/bmklib/web/controllers/tagged.py
new file mode 100644
index 0000000..4b19158
--- /dev/null
+++ b/software/bookmark_library/bmklib/web/controllers/tagged.py
@@ -0,0 +1,67 @@
+import math
+import os.path
+
+import sqlite3
+
+from bmklib.lib.bookmark import Bookmark
+from bmklib.lib.tag import Tag
+from bmklib.web.base_controller import BaseHTMLController, DB_PATH, MAX_PER_PAGE
+
+class TaggedPageController(BaseHTMLController):
+    
+    def __init__(self, environ, template_name='bookmark_list.html'):
+        super(TaggedPageController, self).__init__(environ, template_name)
+    
+    def build_content(self):
+        
+        # Split the path so we can parse it below
+        path = self.environ['PATH_INFO'][1:]
+        path_parts = path.split('/')
+        
+        if len(path_parts) != 2:
+            # TODO: this is an errar
+            pass
+
+        conn = sqlite3.connect(DB_PATH)
+        db = conn.cursor()
+                
+        tag_id = int(path_parts[1])
+        tag = Tag(conn, tag_id)
+        
+        self.content['page_title'] = 'Tagged: {:s}'.format(tag.value)
+        
+        self.content['total_num_bmks'] = len(tag.category_bmks)
+        self.content['max_page_idx'] = math.ceil(float(len(tag.category_bmks)) / MAX_PER_PAGE)
+        
+        # Start pagination at 1 (instead of 0)
+        page_idx = 1
+        self.content['page_idx'] = page_idx
+        
+        # Build Bookmark objects for all of the returned results
+        bookmarks = [Bookmark(conn, id=bmk_id) for bmk_id in tag.category_bmks]
+        
+        self.content['bookmarks'] = bookmarks
+        self.content['path_parts'] = len(path_parts)
+        
+        # Calculate the URL "offset" for the update bookmark link
+        update_link = ['..']  * (len(path_parts) - 1)
+        update_link.extend(['bookmark'])
+        self.content['update_link'] = os.path.join(*update_link)
+        
+        # Set previous link to control pagination in the template
+        if page_idx == 1:
+            self.content['previous'] = None
+        else:
+            prev_idx = page_idx - 1
+            prev_path = ['..']  * (len(path_parts) - 1)
+            prev_path.extend(['tagged', str(prev_idx)])
+            self.content['previous'] = os.path.join(*prev_path)
+        
+        # Set the next link to control pagination in the template
+        next_idx = page_idx + 1
+        if next_idx > self.content['max_page_idx']:
+            self.content['next'] = None
+        else:
+            next_path = ['..']  * (len(path_parts) - 1)
+            next_path.extend(['tagged', str(next_idx)])
+            self.content['next'] = os.path.join(*next_path)
diff --git a/software/bookmark_library/bmklib/web/htdocs/css/bmk_list.css b/software/bookmark_library/bmklib/web/htdocs/css/bmk_list.css
index 113a7e7..8db6c21 100644
--- a/software/bookmark_library/bmklib/web/htdocs/css/bmk_list.css
+++ b/software/bookmark_library/bmklib/web/htdocs/css/bmk_list.css
@@ -44,6 +44,7 @@
     }
 
     .tag {
+        font-family: arial;
         font-size: 7pt;
         font-weight: bold;
         background-color: #003399;
diff --git a/software/bookmark_library/bmklib/web/templates/all_bookmarks.html b/software/bookmark_library/bmklib/web/templates/all_bookmarks.html
deleted file mode 100644
index 5603dec..0000000
--- a/software/bookmark_library/bmklib/web/templates/all_bookmarks.html
+++ /dev/null
@@ -1,35 +0,0 @@
-<html xmlns="http://www.w3.org/1999/xhtml"
-      xmlns:xi="http://www.w3.org/2001/XInclude"
-      xmlns:py="http://genshi.edgewall.org/">
-<xi:include href="layout.html" />
-<head>
-<title>Bookmark Library</title>
-<link rel="stylesheet" type="text/css" href="/htdocs/css/bmk_list.css"></link>
-</head>
-
-<body>
-<h3>All Bookmarks</h3>
-<p>${page_idx} of ${max_page_idx}</p>
-
-<div id="pagination_nav_top">
-    <py:if test="previous is not None"><div id="previous"><a href="${previous}">Previous</a></div></py:if>
-    <py:if test="next is not None"><div id="next"><a href="${next}">Next</a></div></py:if>
-</div>
-
-<div id="bmk_list">
-    <div py:for="idx, bookmark in enumerate(bookmarks)" id="${idx == 0 and 'first' or ''}" class="${idx % 2 == 1 and 'odd' or 'even'} row">
-        <span class="bmk_index"><a href="${update_link}/${bookmark.id}">${idx + ((page_idx - 1) * len(bookmarks)) + 1}</a></span>
-        <span><a href="${bookmark.url}">${bookmark.title and bookmark.title or 'no title'}</a><py:for each="tag in bookmark.category_tags"><span class="tag">${tag.value}</span></py:for></span>
-
-        <!--
-        <td><py:for each="tag in bookmark.description_tags"><span class="tag">${tag.value}</span></py:for></td>
-        -->
-    </div>
-</div>
-
-<div id="pagination_nav_bottom">
-    <py:if test="previous is not None"><div id="previous"><a href="${previous}">Previous</a></div></py:if>
-    <py:if test="next is not None"><div id="next"><a href="${next}">Next</a></div></py:if>
-</div>
-</body>
-</html>
diff --git a/software/bookmark_library/bmklib/web/templates/bookmark_list.html b/software/bookmark_library/bmklib/web/templates/bookmark_list.html
new file mode 100644
index 0000000..8b5c5fe
--- /dev/null
+++ b/software/bookmark_library/bmklib/web/templates/bookmark_list.html
@@ -0,0 +1,35 @@
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:xi="http://www.w3.org/2001/XInclude"
+      xmlns:py="http://genshi.edgewall.org/">
+<xi:include href="layout.html" />
+<head>
+<title>Bookmark Library</title>
+<link rel="stylesheet" type="text/css" href="/htdocs/css/bmk_list.css"></link>
+</head>
+
+<body>
+<h3>${page_title}</h3>
+<p>Page ${page_idx} of ${max_page_idx}</p>
+
+<div id="pagination_nav_top">
+    <py:if test="previous is not None"><div id="previous"><a href="${previous}">Previous</a></div></py:if>
+    <py:if test="next is not None"><div id="next"><a href="${next}">Next</a></div></py:if>
+</div>
+
+<div id="bmk_list">
+    <div py:for="idx, bookmark in enumerate(bookmarks)" id="${idx == 0 and 'first' or ''}" class="${idx % 2 == 1 and 'odd' or 'even'} row">
+        <span class="bmk_index"><a href="${update_link}/${bookmark.id}">${idx + ((page_idx - 1) * len(bookmarks)) + 1}</a></span>
+        <span><a href="${bookmark.url}">${bookmark.title and bookmark.title or 'no title'}</a><py:for each="tag in bookmark.category_tags"><span class="tag">${tag.value}</span></py:for></span>
+
+        <!--
+        <td><py:for each="tag in bookmark.description_tags"><span class="tag">${tag.value}</span></py:for></td>
+        -->
+    </div>
+</div>
+
+<div id="pagination_nav_bottom">
+    <py:if test="previous is not None"><div id="previous"><a href="${previous}">Previous</a></div></py:if>
+    <py:if test="next is not None"><div id="next"><a href="${next}">Next</a></div></py:if>
+</div>
+</body>
+</html>
diff --git a/software/bookmark_library/bmklib/web/templates/tag_list.html b/software/bookmark_library/bmklib/web/templates/tag_list.html
new file mode 100644
index 0000000..dd70ad0
--- /dev/null
+++ b/software/bookmark_library/bmklib/web/templates/tag_list.html
@@ -0,0 +1,31 @@
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:xi="http://www.w3.org/2001/XInclude"
+      xmlns:py="http://genshi.edgewall.org/">
+<xi:include href="layout.html" />
+<head>
+<title>Bookmark Library</title>
+<link rel="stylesheet" type="text/css" href="/htdocs/css/bmk_list.css"></link>
+</head>
+
+<body>
+<h3>${page_title}</h3>
+<p>Page ${page_idx} of ${max_page_idx}</p>
+
+<div id="pagination_nav_top">
+    <py:if test="previous is not None"><div id="previous"><a href="${previous}">Previous</a></div></py:if>
+    <py:if test="next is not None"><div id="next"><a href="${next}">Next</a></div></py:if>
+</div>
+
+<div id="bmk_list">
+    <div py:for="idx, tag in enumerate(tags)" id="${idx == 0 and 'first' or ''}" class="${idx % 2 == 1 and 'odd' or 'even'} row">
+        <span class="bmk_index"><a href="${update_link}/${tag.id}">${idx + ((page_idx - 1) * len(tags)) + 1}</a></span>
+        <span><a href="tagged/${tag.id}">${tag.value}</a></span>
+    </div>
+</div>
+
+<div id="pagination_nav_bottom">
+    <py:if test="previous is not None"><div id="previous"><a href="${previous}">Previous</a></div></py:if>
+    <py:if test="next is not None"><div id="next"><a href="${next}">Next</a></div></py:if>
+</div>
+</body>
+</html>
