[Checkins] SVN: Sandbox/luciano/kirbi/src/kirbi/ interfaces and
templates refactored
Luciano Ramalho
luciano at ramalho.org
Tue Aug 14 23:58:21 EDT 2007
Log message for revision 78834:
interfaces and templates refactored
Changed:
U Sandbox/luciano/kirbi/src/kirbi/app.py
U Sandbox/luciano/kirbi/src/kirbi/app_templates/master.pt
U Sandbox/luciano/kirbi/src/kirbi/book.py
U Sandbox/luciano/kirbi/src/kirbi/copy.py
U Sandbox/luciano/kirbi/src/kirbi/interfaces.py
U Sandbox/luciano/kirbi/src/kirbi/pac.py
U Sandbox/luciano/kirbi/src/kirbi/pac_templates/addbooks.pt
D Sandbox/luciano/kirbi/src/kirbi/pac_templates/incomplete.pt
U Sandbox/luciano/kirbi/src/kirbi/static/master.css
U Sandbox/luciano/kirbi/src/kirbi/user.py
-=-
Modified: Sandbox/luciano/kirbi/src/kirbi/app.py
===================================================================
--- Sandbox/luciano/kirbi/src/kirbi/app.py 2007-08-15 03:24:20 UTC (rev 78833)
+++ Sandbox/luciano/kirbi/src/kirbi/app.py 2007-08-15 03:58:21 UTC (rev 78834)
@@ -20,13 +20,6 @@
class Index(grok.View):
- def menu_items(self):
- return [
- {'url':self.url(self.context[USER_FOLDER_NAME],'join'), 'text':u'join'},
- {'url':'''http://circulante.incubadora.fapesp.br/''',
- 'text':u'about'},
- ]
-
def pac_url(self):
return self.url(self.context[PAC_NAME])
@@ -48,3 +41,4 @@
class Master(grok.View):
"""The master page template macro."""
grok.context(Interface)
+
Modified: Sandbox/luciano/kirbi/src/kirbi/app_templates/master.pt
===================================================================
--- Sandbox/luciano/kirbi/src/kirbi/app_templates/master.pt 2007-08-15 03:24:20 UTC (rev 78833)
+++ Sandbox/luciano/kirbi/src/kirbi/app_templates/master.pt 2007-08-15 03:58:21 UTC (rev 78834)
@@ -25,10 +25,16 @@
<img tal:attributes="src static/circulante-logo-transp-212x51.png"
title="Circulante.org" width="212" height="51" />
</a>
- <ul>
- <li tal:repeat="item view/menu_items"><a tal:attributes="href item/url"
- tal:content="item/text">catalog</a></li>
+ <ul tal:condition="not:exists:view/request/principal/getLogin">
+ <li><a tal:attributes="href python:view.application_url('pac')">catalog</a></li>
+ <li><a tal:attributes="href python:view.application_url('u/join')">join</a></li>
</ul>
+ <ul tal:condition="exists:view/request/principal/getLogin">
+ <li><a tal:attributes="href python:view.application_url('pac')">catalog</a></li>
+ <li><a tal:attributes="href python:view.application_url('u')">users</a></li>
+ <li><a tal:attributes="href python:view.application_url('pac/addbooks')">add books</a></li>
+ <li><a tal:attributes="href python:view.application_url('u/logout')">logout</a></li>
+ </ul>
</div>
<div metal:define-slot="body">
<div class="content_menu">
@@ -38,6 +44,14 @@
Content title
</span>
</span>
+ <span class="content_login">
+ <span tal:condition="exists:view/request/principal/getLogin">
+ login: <span tal:replace="view/request/principal/getLogin" />
+ </span>
+ <span tal:condition="not:exists:view/request/principal/getLogin">
+ (not logged in)
+ </span>
+ </span>
<span metal:define-slot="content_actions" />
</span>
</div>
Modified: Sandbox/luciano/kirbi/src/kirbi/book.py
===================================================================
--- Sandbox/luciano/kirbi/src/kirbi/book.py 2007-08-15 03:24:20 UTC (rev 78833)
+++ Sandbox/luciano/kirbi/src/kirbi/book.py 2007-08-15 03:58:21 UTC (rev 78834)
@@ -268,11 +268,3 @@
return self.static.get(cover_name,
self.static['covers/small-placeholder.jpg'])()
- def menu_items(self):
- return [
- {'url':self.url(self.context.__parent__.__parent__[USER_FOLDER_NAME],'join'),
- 'text':u'join'},
- {'url':'''http://circulante.incubadora.fapesp.br/''',
- 'text':u'about'},
- ]
-
Modified: Sandbox/luciano/kirbi/src/kirbi/copy.py
===================================================================
--- Sandbox/luciano/kirbi/src/kirbi/copy.py 2007-08-15 03:24:20 UTC (rev 78833)
+++ Sandbox/luciano/kirbi/src/kirbi/copy.py 2007-08-15 03:58:21 UTC (rev 78834)
@@ -1,28 +1,16 @@
import grok
+from interfaces import ICopy, ILease
from zope.interface import Interface, implements, invariant
from zope import schema
-class ICopy(Interface):
- """An exemplar of a book."""
-
- book_id = schema.TextLine(title=u"Book id",
- description=u"The id of the book of which this is a copy.",
- required=True)
- description = schema.Text(title=u"Description",
- description=(u"Details of this copy, such as autographs,"
- u"marks, damage etc."),
- required=False)
- #XXX: This should be filled automatically.
- catalog_date = schema.Date(title=u"Catalog date",
- description=u"Date when added to your collection.",
- required=False)
-
class Copy(grok.Container):
"""An exemplar of a book.
- A Copy is associated to a Book instance.
+ A copy is associated to a Book instance.
- A Copy can contain Lease instances, recording each time it was lent.
+ A copy can contain Lease instances, recording each time it was lent.
+ When a copy is transferred or deleted, the lease history automatically
+ goes with it.
"""
implements(ICopy)
@@ -30,42 +18,3 @@
def __init__(self, book_id):
super(User, self).__init__()
-
-class ILease(Interface):
- """A book lease."""
-
- copy_id = schema.TextLine(title=u"Copy id",
- description=u"The id of the copy being lent.",
- required=True)
-
- # Note: the lender_id can usually be obtained from the copy, however if a
- # copy is given to a new owner, the lease history would become incomplete.
- lender_id = schema.Text(title=u"Lender",
- description=(u"Lender login."),
- required=True)
-
- borrower_id = schema.Text(title=u"Borrower",
- description=(u"Borrower login."),
- required=True)
-
- #XXX: This should be filled automatically.
- request_date = schema.Date(title=u"Request date",
- description=u"When the lease was requested.",
- required=False)
-
- delivery_date = schema.Date(title=u"Delivery date",
- description=u"When the copy was delivered to the borrower.",
- required=False)
-
- due_date = schema.Date(title=u"Due date",
- description=u"When the copy should be returned to the lender.",
- required=False)
-
- return_date = schema.Date(title=u"Returnd date",
- description=u"When the copy was returned to the lender.",
- required=False)
-
- @invariant
- def dueAfterDelivery(lease):
- if not (lease.due_date > lease.delivery_date):
- raise Invalid(u'The due date must be after the delivery date.')
Modified: Sandbox/luciano/kirbi/src/kirbi/interfaces.py
===================================================================
--- Sandbox/luciano/kirbi/src/kirbi/interfaces.py 2007-08-15 03:24:20 UTC (rev 78833)
+++ Sandbox/luciano/kirbi/src/kirbi/interfaces.py 2007-08-15 03:58:21 UTC (rev 78834)
@@ -64,5 +64,58 @@
if (not book.title or not book.title.strip()) and (not book.isbn):
raise Invalid('Either the title or the ISBN must be given.')
+class ICopy(Interface):
+ """An exemplar of a book."""
+
+ book_id = schema.TextLine(title=u"Book id",
+ description=u"The id of the book of which this is a copy.",
+ required=True)
+ description = schema.Text(title=u"Description",
+ description=(u"Details of this copy, such as autographs,"
+ u"marks, damage etc."),
+ required=False)
+ #XXX: This should be filled automatically.
+ catalog_date = schema.Date(title=u"Catalog date",
+ description=u"Date when added to your collection.",
+ required=False)
+
+class ILease(Interface):
+ """A book lease."""
+
+ copy_id = schema.TextLine(title=u"Copy id",
+ description=u"The id of the copy being lent.",
+ required=True)
+
+ # Note: the lender_id can usually be obtained from the copy, however if a
+ # copy is given to a new owner, the lease history would become incomplete.
+ lender_id = schema.Text(title=u"Lender",
+ description=(u"Lender login."),
+ required=True)
+
+ borrower_id = schema.Text(title=u"Borrower",
+ description=(u"Borrower login."),
+ required=True)
+
+ #XXX: This should be filled automatically.
+ request_date = schema.Date(title=u"Request date",
+ description=u"When the lease was requested.",
+ required=False)
+
+ delivery_date = schema.Date(title=u"Delivery date",
+ description=u"When the copy was delivered to the borrower.",
+ required=False)
+
+ due_date = schema.Date(title=u"Due date",
+ description=u"When the copy should be returned to the lender.",
+ required=False)
+
+ return_date = schema.Date(title=u"Returnd date",
+ description=u"When the copy was returned to the lender.",
+ required=False)
+
+ @invariant
+ def dueAfterDelivery(lease):
+ if not (lease.due_date > lease.delivery_date):
+ raise Invalid(u'The due date must be after the delivery date.')
Modified: Sandbox/luciano/kirbi/src/kirbi/pac.py
===================================================================
--- Sandbox/luciano/kirbi/src/kirbi/pac.py 2007-08-15 03:24:20 UTC (rev 78833)
+++ Sandbox/luciano/kirbi/src/kirbi/pac.py 2007-08-15 03:58:21 UTC (rev 78834)
@@ -84,46 +84,10 @@
if not book.title and book.isbn13:
pac = book.__parent__
pac.addIncomplete(book.isbn13)
-
-class Incomplete(grok.View):
-
- def menu_items(self):
- return [
- {'url':self.url(self.context.__parent__[USER_FOLDER_NAME],'join'),
- 'text':u'join'},
- {'url':'''http://circulante.incubadora.fapesp.br/''',
- 'text':u'about'},
- ]
-
- def sortedByTime(self, isbn_dict):
- pairs = ((timestamp, isbn) for isbn, timestamp in
- isbn_dict.items())
- return (dict(timestamp=timestamp,isbn=isbn)
- for timestamp, isbn in sorted(pairs))
-
- def incompleteIsbns(self):
- return list(self.sortedByTime(self.context.getIncomplete()))
-
- def pendingIsbns(self):
- return list(self.sortedByTime(self.context.getPending()))
-
- def update(self, isbns=None):
- if isbns:
- self.context.retryPending(isbns)
- if self.context.getIncomplete() or self.context.getPending():
- self.request.response.setHeader("Refresh", "5; url=%s" % self.url())
class Index(grok.View):
grok.context(Pac)
- def menu_items(self):
- return [
- {'url':self.url(self.context.__parent__[USER_FOLDER_NAME],'join'),
- 'text':u'join'},
- {'url':'''http://circulante.incubadora.fapesp.br/''',
- 'text':u'about'},
- ]
-
def coverUrl(self, book):
cover_name = 'covers/large/'+book.isbn13+'.jpg'
return self.static.get(cover_name,
@@ -193,18 +157,10 @@
invalid_isbns = []
- def menu_items(self):
- return [
- {'url':self.url(self.context.__parent__[USER_FOLDER_NAME],'join'),
- 'text':u'join'},
- {'url':'''http://circulante.incubadora.fapesp.br/''',
- 'text':u'about'},
- ]
-
- def update(self, isbns=None):
+ def update(self, isbns=None, retry_isbns=None):
+ self.invalid_isbns = []
if isbns is not None:
isbns = list(set(isbns.split()))
- self.invalid_isbns = []
for isbn in isbns:
if isValidISBN(isbn):
book = Book(isbn=isbn)
@@ -212,6 +168,13 @@
self.context.addBook(book)
else:
self.invalid_isbns.append(isbn)
+ if retry_isbns:
+ self.context.retryPending(retry_isbns)
+ # XXX this would be great with AJAX, avoiding the ugly refresh
+ if (not self.invalid_isbns) and (self.context.getIncomplete()
+ or self.context.getPending()):
+ self.request.response.setHeader("Refresh", "5; url=%s" % self.url())
+
def invalidISBNs(self):
if self.invalid_isbns:
@@ -219,6 +182,18 @@
else:
return ''
+ def sortedByTime(self, isbn_dict):
+ pairs = ((timestamp, isbn) for isbn, timestamp in
+ isbn_dict.items())
+ return (dict(timestamp=timestamp,isbn=isbn)
+ for timestamp, isbn in sorted(pairs))
+
+ def incompleteIsbns(self):
+ return list(self.sortedByTime(self.context.getIncomplete()))
+
+ def pendingIsbns(self):
+ return list(self.sortedByTime(self.context.getPending()))
+
class NameChooser(grok.Adapter, BaseNameChooser):
implements(INameChooser)
Modified: Sandbox/luciano/kirbi/src/kirbi/pac_templates/addbooks.pt
===================================================================
--- Sandbox/luciano/kirbi/src/kirbi/pac_templates/addbooks.pt 2007-08-15 03:24:20 UTC (rev 78833)
+++ Sandbox/luciano/kirbi/src/kirbi/pac_templates/addbooks.pt 2007-08-15 03:58:21 UTC (rev 78834)
@@ -14,11 +14,12 @@
</form>
</span>
<div metal:fill-slot="content">
-
+ <table><tr>
+ <td valign="top">
<form tal:attributes="action view/url" method="post">
<p>Type or scan several ISBNs separated by spaces or newlines.</p>
<p>Either 10 or 13-digit ISBNs can be used.</p>
- <textarea name="isbns" rows="30" cols="40"
+ <textarea name="isbns" rows="20" cols="40"
tal:content="view/invalidISBNs">
</textarea>
<br />
@@ -37,6 +38,100 @@
</form>
</p>
- </div>
+ </td><td valign="top">
+
+ <div tal:condition="view/incompleteIsbns">
+ <h3>New Book Records Without Title</h3>
+ <table class="isbn_list">
+ <tr tal:repeat="item view/incompleteIsbns">
+ <th align="right" tal:content="repeat/item/number" />
+ <td tal:content="item/timestamp" />
+ <td>
+ <a tal:attributes="href python:view.url(item['isbn'])+'/details'"
+ tal:content="item/isbn">9780123456789</a>
+
+ </td>
+ </tr>
+ </table>
+ </div>
+
+ <form tal:condition="view/pendingIsbns" method="post"
+ tal:attributes="action view/url">
+ <h3>Pending Book Searches</h3>
+ <table class="isbn_list">
+ <tr>
+ <th></th>
+ <th>#</th>
+ <th>Created</th>
+ <th>ISBN-13</th>
+ <th>
+ <a tal:attributes="href string:http://www.worldcatlibraries.org/"
+ title="WorldCat Libraries Search">
+ <img tal:attributes="src static/worldcat24.gif" />
+ </a>
+ </th>
+ <th>
+ <a tal:attributes="href string:http://www.alibris.com/search/search.cfm"
+ title="Alibris Book Search">
+ <img tal:attributes="src static/alibris24.gif" />
+ </a>
+ </th>
+ <th>
+ <a tal:attributes="href string:http://books.google.com/"
+ title="Google Book Search">
+ <img tal:attributes="src static/googlebook24.gif" />
+ </a>
+ </th>
+ <th>
+ <a tal:attributes="href string:http://google.com/"
+ title="Google Search">
+ <img tal:attributes="src static/google24.gif" />
+ </a>
+ </th>
+ </tr>
+ <tr tal:repeat="item view/pendingIsbns">
+ <td>
+ <input type="checkbox" name="retry_isbns:list"
+ tal:attributes="value item/isbn" />
+
+ </td>
+ <th align="right" tal:content="repeat/item/number" />
+ <td tal:content="item/timestamp" />
+ <td>
+ <a tal:attributes="href python:view.url(item['isbn'])+'/details'"
+ tal:content="item/isbn">9780123456789</a>
+
+ </td>
+ <td>
+ <a tal:attributes="href string:http://www.worldcatlibraries.org/search?q=${item/isbn}"
+ title="WorldCat Libraries Search">
+ <img tal:attributes="src static/worldcat16.gif" />
+ </a>
+ </td>
+ <td>
+ <a tal:attributes="href string:http://www.alibris.com/search/search.cfm?wtit=${item/isbn}"
+ title="Alibris Book Search">
+ <img tal:attributes="src static/alibris16.gif" />
+ </a>
+ </td>
+ <td>
+ <a tal:attributes="href string:http://books.google.com/books?q=${item/isbn}"
+ title="Google Book Search">
+ <img tal:attributes="src static/google16.gif" />
+ </a>
+ </td>
+ <td>
+ <a tal:attributes="href string:http://google.com/search?q=${item/isbn}"
+ title="Google Search">
+ <img tal:attributes="src static/google16.gif" />
+ </a>
+
+ </td>
+ </tr>
+ </table>
+ <input type="submit" name="retry" value="Retry">
+ </form>
+
+ </div><!--/content-->
</body>
</html>
Deleted: Sandbox/luciano/kirbi/src/kirbi/pac_templates/incomplete.pt
===================================================================
--- Sandbox/luciano/kirbi/src/kirbi/pac_templates/incomplete.pt 2007-08-15 03:24:20 UTC (rev 78833)
+++ Sandbox/luciano/kirbi/src/kirbi/pac_templates/incomplete.pt 2007-08-15 03:58:21 UTC (rev 78834)
@@ -1,118 +0,0 @@
-<html metal:use-macro="context/@@master/page">
-<head>
- <title metal:fill-slot="title">
- Public Catalog
- </title>
-
-</head>
-<body>
- <span metal:fill-slot="content_title">Collective catalog</span>
- <span metal:fill-slot="content_actions">
- <form class="search" action=".">
- <input type="text" name="query">
- <input type="submit" name="submit" value="search">
- </form>
- </span>
- <div metal:fill-slot="content">
-
- <div tal:condition="view/incompleteIsbns">
- <h3>New Book Records Without Title</h3>
- <table class="isbn_list">
- <tr tal:repeat="item view/incompleteIsbns">
- <th align="right" tal:content="repeat/item/number" />
- <td tal:content="item/timestamp" />
- <td>
- <a tal:attributes="href python:view.url(item['isbn'])+'/details'"
- tal:content="item/isbn">9780123456789</a>
-
- </td>
- </tr>
- </table>
- </div>
- <p tal:condition="not:view/incompleteIsbns">No new book records without title.</p>
-
- <form tal:condition="view/pendingIsbns" method="post"
- tal:attributes="action view/url">
- <h3>Pending Book Searches</h3>
- <table class="isbn_list">
- <tr>
- <th><input type="checkbox" name="select_all"></th>
- <th>#</th>
- <th>Created</th>
- <th>ISBN-13</th>
- <th>
- <a tal:attributes="href string:http://www.worldcatlibraries.org/"
- title="WorldCat Libraries Search">
- <img tal:attributes="src static/worldcat24.gif" />
- </a>
- </th>
- <th>
- <a tal:attributes="href string:http://www.alibris.com/search/search.cfm"
- title="Alibris Book Search">
- <img tal:attributes="src static/alibris24.gif" />
- </a>
- </th>
- <th>
- <a tal:attributes="href string:http://books.google.com/"
- title="Google Book Search">
- <img tal:attributes="src static/googlebook24.gif" />
- </a>
- </th>
- <th>
- <a tal:attributes="href string:http://google.com/"
- title="Google Search">
- <img tal:attributes="src static/google24.gif" />
- </a>
- </th>
- </tr>
- <tr tal:repeat="item view/pendingIsbns">
- <td>
- <input type="checkbox" name="isbns:list"
- tal:attributes="value item/isbn" />
-
- </td>
- <th align="right" tal:content="repeat/item/number" />
- <td tal:content="item/timestamp" />
- <td>
- <a tal:attributes="href python:view.url(item['isbn'])+'/details'"
- tal:content="item/isbn">9780123456789</a>
-
- </td>
- <td>
- <a tal:attributes="href string:http://www.worldcatlibraries.org/search?q=${item/isbn}"
- title="WorldCat Libraries Search">
- <img tal:attributes="src static/worldcat16.gif" />
- </a>
- </td>
- <td>
- <a tal:attributes="href string:http://www.alibris.com/search/search.cfm?wtit=${item/isbn}"
- title="Alibris Book Search">
- <img tal:attributes="src static/alibris16.gif" />
- </a>
- </td>
- <td>
- <a tal:attributes="href string:http://books.google.com/books?q=${item/isbn}"
- title="Google Book Search">
- <img tal:attributes="src static/google16.gif" />
- </a>
- </td>
- <td>
- <a tal:attributes="href string:http://google.com/search?q=${item/isbn}"
- title="Google Search">
- <img tal:attributes="src static/google16.gif" />
- </a>
-
- </td>
- </tr>
- </table>
- <input type="submit" name="retry" value="Retry">
- </form>
- <p tal:condition="not:view/pendingIsbns">No pending book searches.</p>
-
- <p><a tal:attributes="href python:view.url('addbook')">
- Add Book
- </a></p>
-
- </div>
-</body>
-</html>
Modified: Sandbox/luciano/kirbi/src/kirbi/static/master.css
===================================================================
--- Sandbox/luciano/kirbi/src/kirbi/static/master.css 2007-08-15 03:24:20 UTC (rev 78833)
+++ Sandbox/luciano/kirbi/src/kirbi/static/master.css 2007-08-15 03:58:21 UTC (rev 78834)
@@ -68,6 +68,12 @@
margin-top: 1px;
}
+span.content_login {
+ position: relative;
+ left: 33%;
+ font-family: sans-serif;
+}
+
div.content {
margin: 20px;
}
Modified: Sandbox/luciano/kirbi/src/kirbi/user.py
===================================================================
--- Sandbox/luciano/kirbi/src/kirbi/user.py 2007-08-15 03:24:20 UTC (rev 78833)
+++ Sandbox/luciano/kirbi/src/kirbi/user.py 2007-08-15 03:58:21 UTC (rev 78834)
@@ -1,6 +1,7 @@
import grok
from interfaces import IUser
from zope.app.authentication.interfaces import IPrincipalInfo
+from zope.app.authentication.interfaces import IAuthenticatorPlugin
from zope.interface import Interface, implements, invariant, Invalid
from zope import schema
import sha
@@ -46,16 +47,7 @@
class Index(grok.View):
grok.context(User)
-
- def menu_items(self):
- return [
- {'url':self.url(self.context.__parent__,'join'),
- 'text':u'join'},
- {'url':'''http://circulante.incubadora.fapesp.br/''',
- 'text':u'about'},
- ]
-
class PrincipalInfoAdapter(grok.Adapter):
grok.context(User)
grok.implements(IPrincipalInfo)
@@ -87,14 +79,6 @@
grok.context(UserFolder)
grok.name('index')
- def menu_items(self):
- return [
- {'url':self.url('join'),
- 'text':u'join'},
- {'url':'''http://circulante.incubadora.fapesp.br/''',
- 'text':u'about'},
- ]
-
def update(self, query=None):
self.results_title = '%d users' % len(self.context)
@@ -103,6 +87,11 @@
def render(self):
return 'This should log you in...'
+class Logout(grok.View):
+ grok.context(UserFolder)
+ def render(self):
+ return "This should log you out (but doesn't yet)."
+
class Join(grok.AddForm):
grok.context(UserFolder)
"""User registration form"""
@@ -114,4 +103,31 @@
login = data['login']
self.context[login] = User(**data)
self.redirect(self.url(login))
+
+
+class UserAuthenticationPlugin(object):
+ """Simple authentication and search plugin"""
+ implements(IAuthenticatorPlugin)
+ principals = (
+ {'id':'alice', 'login':'alice', 'password':'123'},
+ {'id':'bob', 'login':'bob', 'password':'123'}
+ )
+
+ prefix = "users" # principal id prefix
+
+ def principalInfo(self, id):
+ """Find a principal given an id"""
+ for principal in self.principals:
+ if self.prefix + "." + principal['id'] == id:
+ return {'login' : principal['login']}
+
+ def authenticateCredentials(self, credentials):
+ """Authenticate a principal"""
+ for principal in self.principals:
+ if credentials['login']==principal['login'] and \
+ credentials['password']==principal['password']:
+ return (self.prefix + "." + principal['id'],
+ {'login' : principal['login']})
+
+
More information about the Checkins
mailing list