[Checkins] SVN: Sandbox/luciano/kirbi/ first signs of life from
book leases
Luciano Ramalho
luciano at ramalho.org
Tue Aug 21 03:57:43 EDT 2007
Log message for revision 79062:
first signs of life from book leases
Changed:
U Sandbox/luciano/kirbi/README.txt
U Sandbox/luciano/kirbi/src/kirbi/README.txt
U Sandbox/luciano/kirbi/src/kirbi/app.py
U Sandbox/luciano/kirbi/src/kirbi/book_templates/index.pt
U Sandbox/luciano/kirbi/src/kirbi/collection.py
U Sandbox/luciano/kirbi/src/kirbi/collection_templates/index.pt
U Sandbox/luciano/kirbi/src/kirbi/interfaces.py
U Sandbox/luciano/kirbi/src/kirbi/item.py
U Sandbox/luciano/kirbi/src/kirbi/pac.py
U Sandbox/luciano/kirbi/src/kirbi/static/master.css
-=-
Modified: Sandbox/luciano/kirbi/README.txt
===================================================================
--- Sandbox/luciano/kirbi/README.txt 2007-08-21 03:29:18 UTC (rev 79061)
+++ Sandbox/luciano/kirbi/README.txt 2007-08-21 07:57:42 UTC (rev 79062)
@@ -1,165 +1 @@
-=============================
-Kirbi: a P2P library manager
-=============================
-
-Kirbi is a sample application to test Grok and help programmers learn
-how to use it to build a complete app.
-
-Kirbi also aims to be useful and not just a sample. It is a system to allow
-friends and colleagues to share their books and DVDs without losing track
-of them.
-
-Use cases
-===========
-
-Done
------
-
-* Add books to the public catalog via a Web form or XML-RPC
-
-* Allow searches to the public catalog
-
-* Add books by entering just the ISBN, and letting Kirbi fetch the book data
- from Amazon.com
-
-* User self-registration
-
-* User catalogs own collection
-
-To Do
-------
-
-* User invites friends to share specific collections
-
-* User requests to borrow an item
-
-* User approves the loan of an item
-
-* User tracks lent items
-
-* User tracks borrowed items
-
-* Add books by entering title words or author names, and letting Kirbi fetch
- some likely candidates from Amazon.com
-
-* User adds book which belongs to a friend, who maybe a current user or someone
- to be invited (recovering a lost book is a very strong motivation to join!)
-
-Features
-==============
-
-This is a list of other use cases, organized by view.
-
-app/index
--------------
-
-* implement recent additions
-
-pac/index
--------------
-
-For each item listed:
-
-* button: "i own it/add to my collection"
-
-* button: "borrow"
-
-* list: owners
-
-* display: rating
-
-book/index
-------------
-
-* button: "i own it/add to my collection"
-
-* button: "borrow"
-
-* list: owners
-
-* button: "recommend" (to a friend/to yourself also?)
-
-* display: rating
-
-* control: "rate it"
-
-* list: reviews
-
-* button: "review"
-
-* button: rate review
-
-* display: tags
-
-* control: "tag"
-
-* button: "liberate"
-
-user/menu
--------------
-
-(currently this is part of app/master and not a separate view)
-
-* link: invite
-
-* link: preferences
-
-user/index
-------------
-
-(currently this is part of collection/index and not a separate view)
-
-* list: lease requests/due
-
-* list: borrow requests/due
-
-* list: recent invitations (pending/accepted)
-
-* list: recomendations for you
-
-lease/borrow
---------------
-
-* list: owners of copies of that book (items of that manifestation)
-
-* list: alternative manifestations based on OCLC xISBN service
-
-* control: duration needed
-
-* text area: suggested time/place for pickup
-
-lease/lend
----------------
-
-* control: duration approved
-
-* text area: edit suggested time/place for pickup
-
-* control: date when available
-
-* button: approve
-
-* text line: reason for denial
-
-* button: deny
-
-new views
--------------
-
-* manage invitations
-
-* preferences (at least passwd change; default lease time; create
- alternative collecions; privacy)
-
-
-Other tasks
-===========
-
-* Refactor kirbifetch to allow pluggable metadata sources, instead of
- relying on Amazon.com exclusively
-
-* Increase test coverage
-
-* Packaging (buildout, eggification)
-
-* AJAXification using same framework used for Grok Admin UI
+See ./src/kirbi/README.txt
\ No newline at end of file
Modified: Sandbox/luciano/kirbi/src/kirbi/README.txt
===================================================================
--- Sandbox/luciano/kirbi/src/kirbi/README.txt 2007-08-21 03:29:18 UTC (rev 79061)
+++ Sandbox/luciano/kirbi/src/kirbi/README.txt 2007-08-21 07:57:42 UTC (rev 79062)
@@ -19,19 +19,16 @@
* Allow searches to the public catalog
-To Do
-------
-
* Add books by entering just the ISBN, and letting Kirbi fetch the book data
- from Amazon.com (under development)
+ from Amazon.com
-* Add books by entering title words or author names, and letting Kirbi fetch
- some likely candidates from Amazon.com
-
* User self-registration
-* User catalogs own collections
+* User catalogs own collection
+To Do
+------
+
* User invites friends to share specific collections
* User requests to borrow an item
@@ -42,14 +39,125 @@
* User tracks borrowed items
+* Add books by entering title words or author names, and letting Kirbi fetch
+ some likely candidates from Amazon.com
+* User adds book which belongs to a friend, who maybe a current user or someone
+ to be invited (recovering a lost book is a very strong motivation to join!)
+
+Features
+==============
+
+This is a list of other use cases, organized by view.
+
+app/index
+-------------
+
+* implement recent additions
+
+pac/index
+-------------
+
+For each item listed:
+
+* button: "i own it/add to my collection"
+
+* button: "borrow"
+
+* list: owners
+
+* display: rating
+
+book/index
+------------
+
+* button: "i own it/add to my collection"
+
+* button: "borrow"
+
+* list: owners
+
+* button: "recommend" (to a friend/to yourself also?)
+
+* display: rating
+
+* control: "rate it"
+
+* list: reviews
+
+* button: "review"
+
+* button: rate review
+
+* display: tags
+
+* control: "tag"
+
+* button: "liberate"
+
+user/menu
+-------------
+
+(currently this is part of app/master and not a separate view)
+
+* link: invite
+
+* link: preferences
+
+user/index
+------------
+
+(currently this is part of collection/index and not a separate view)
+
+* list: lease requests/due
+
+* list: borrow requests/due
+
+* list: recent invitations (pending/accepted)
+
+* list: recomendations for you
+
+item/borrow
+--------------
+
+* list: alternative manifestations based on OCLC xISBN service
+
+* control: duration needed
+
+* text area: suggested time/place for pickup
+
+item/lend
+---------------
+
+* control: duration approved
+
+* text area: edit suggested time/place for pickup
+
+* control: date when available
+
+* button: approve
+
+* text line: reason for denial
+
+* button: deny
+
+new views
+-------------
+
+* manage invitations
+
+* preferences (at least passwd change; default lease time; create
+ alternative collecions; privacy)
+
+
Other tasks
===========
-* Add tests
+* Refactor kirbifetch to allow pluggable metadata sources, instead of
+ relying on Amazon.com exclusively
+* Increase test coverage
+
* Packaging (buildout, eggification)
-* AJAXification using some framework (whatever Uli chooses for the Grok Admin
- UI, currently MojiKit or KSS)
-
+* AJAXification using same framework used for Grok Admin UI
Modified: Sandbox/luciano/kirbi/src/kirbi/app.py
===================================================================
--- Sandbox/luciano/kirbi/src/kirbi/app.py 2007-08-21 03:29:18 UTC (rev 79061)
+++ Sandbox/luciano/kirbi/src/kirbi/app.py 2007-08-21 07:57:42 UTC (rev 79062)
@@ -19,7 +19,7 @@
from kirbi.pac import Pac
from kirbi.book import Book
from kirbi.collection import Collection
-from kirbi.interfaces import IItem, IUser
+from kirbi.interfaces import IItem, IUser, ILease
from zope.interface import Interface, implements
from zope.component import getSiteManager
from zope.traversing import browser
@@ -102,7 +102,19 @@
manifestation_id = index.Field()
owner_login = index.Field()
+
+class LeaseIndexes(grok.Indexes):
+ grok.site(Kirbi)
+ grok.context(ILease)
+ item_id = index.Field()
+ lender_login = index.Field()
+ borrower_login = index.Field()
+ getDue = index.Field()
+ request_date = index.Field()
+ status = index.Field()
+
+
class Master(grok.View):
"""The master page template macro."""
# register this view for all objects
Modified: Sandbox/luciano/kirbi/src/kirbi/book_templates/index.pt
===================================================================
--- Sandbox/luciano/kirbi/src/kirbi/book_templates/index.pt 2007-08-21 03:29:18 UTC (rev 79061)
+++ Sandbox/luciano/kirbi/src/kirbi/book_templates/index.pt 2007-08-21 07:57:42 UTC (rev 79062)
@@ -64,13 +64,23 @@
</table>
</div><!-- /details -->
+
+ <div class="book_owners">
+ <h3>People who own this book</h3>
+ <tal:owner repeat="item context/getItems">
+ <a tal:attributes="href
+ string:${view/application_url}/u/${item/owner}/${item/item_id}/borrow"
+ tal:content="item/owner" />
+ </tal:owner>
+ <p>(click on a name to borrow it from that person)</p>
+ </div>
- <!-- XXX: only the site manager should be able to edit a book -->
+ <!-- XXX: only the site manager should be able to edit a book
<form class="search" tal:attributes="action python:view.url('edit')">
<input type="submit" name="submit" value="edit">
</form>
+ -->
-
</div><!-- /content -->
</body>
</html>
Modified: Sandbox/luciano/kirbi/src/kirbi/collection.py
===================================================================
--- Sandbox/luciano/kirbi/src/kirbi/collection.py 2007-08-21 03:29:18 UTC (rev 79061)
+++ Sandbox/luciano/kirbi/src/kirbi/collection.py 2007-08-21 07:57:42 UTC (rev 79062)
@@ -22,7 +22,10 @@
from kirbi.item import Item
from kirbi.book import Book
from zope.app.container.interfaces import INameChooser
+from zope.component import getUtility
+from zope.app.catalog.interfaces import ICatalog
+
class Collection(grok.Container):
"""A collection of items (books, disks etc.) belonging to one user.
@@ -60,7 +63,23 @@
cover_name = 'covers/large/'+book.__name__+'.jpg'
return self.static.get(cover_name,
self.static['covers/small-placeholder.jpg'])()
+
+ def yourRequests(self):
+ catalog = getUtility(ICatalog)
+ res = catalog.searchResults(
+ borrower_login=(self.context.__name__,
+ self.context.__name__)
+ )
+ return ['%s->%s' % (r.lender_login, r.item_id) for r in res]
+ def othersRequests(self):
+ catalog = getUtility(ICatalog)
+ res = catalog.searchResults(
+ lender_login=(self.context.__name__,
+ self.context.__name__)
+ )
+ return ['%s->%s' % (r.lender_login, r.item_id) for r in res]
+
class AddFromPac(grok.View):
def render(self,manifestation_id,camefrom):
pac = grok.getSite()['pac']
Modified: Sandbox/luciano/kirbi/src/kirbi/collection_templates/index.pt
===================================================================
--- Sandbox/luciano/kirbi/src/kirbi/collection_templates/index.pt 2007-08-21 03:29:18 UTC (rev 79061)
+++ Sandbox/luciano/kirbi/src/kirbi/collection_templates/index.pt 2007-08-21 07:57:42 UTC (rev 79062)
@@ -14,26 +14,37 @@
</span>
<div metal:fill-slot="content">
- <h3 tal:content="view/results_title">999 items matched the query</h3>
- <table tal:condition="view/results">
- <tr tal:repeat="item view/results">
- <th align="right" tal:content="repeat/item/number" />
- <td align="center">
- <img class="cover"
- tal:attributes="src python:view.coverUrl(item)"
- height="53" />
- </td>
- <td>
- <dl>
- <dt><a tal:attributes="href python:view.url(item)"
- tal:content="item/filing_title">title goes here</a>
- </dt>
- <dd tal:content="item/creatorsLine">
- </dd>
- </dl>
- </td>
- </tr>
- </table>
+ <table><tr>
+ <td>
+ <h3 tal:content="view/results_title">999 items matched the query</h3>
+ <table tal:condition="view/results">
+ <tr tal:repeat="item view/results">
+ <th align="right" tal:content="repeat/item/number" />
+ <td align="center">
+ <img class="cover"
+ tal:attributes="src python:view.coverUrl(item)"
+ height="53" />
+ </td>
+ <td>
+ <dl>
+ <dt><a tal:attributes="href python:view.url(item)"
+ tal:content="item/filing_title">title goes here</a>
+ </dt>
+ <dd tal:content="item/creatorsLine">
+ </dd>
+ </dl>
+ </td>
+ </tr>
+ </table>
+ <td><div class="leases">
+ <h2>Leases</h2>
+ <h3>Your requests</h3>
+ <span tal:replace="view/yourRequests|string:(none)" />
+ <h3>Other's requests</h3>
+ <span tal:replace="view/othersRequests|string:(none)" />
+ </div>
+ </td>
+ </tr></table>
</div>
</body>
Modified: Sandbox/luciano/kirbi/src/kirbi/interfaces.py
===================================================================
--- Sandbox/luciano/kirbi/src/kirbi/interfaces.py 2007-08-21 03:29:18 UTC (rev 79061)
+++ Sandbox/luciano/kirbi/src/kirbi/interfaces.py 2007-08-21 07:57:42 UTC (rev 79062)
@@ -145,38 +145,22 @@
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)
+ item_id = Attribute(u"The id of the copy being lent.")
- # 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)
+ # Note: the lender_login can usually be obtained from the item.__parent__,
+ # however if an item is given to a new owner, the lease history would
+ # become incomplete.
+ lender_login = Attribute(u"Lender login.")
- borrower_id = schema.Text(title=u"Borrower",
- description=(u"Borrower login."),
- required=True)
+ borrower_login = Attribute(u"Borrower login.")
- #XXX: This should be filled automatically.
- request_date = schema.Date(title=u"Request date",
- description=u"When the lease was requested.",
- required=False)
+ request_date = Attribute(u"When the lease was requested.")
- delivery_date = schema.Date(title=u"Delivery date",
- description=u"When the copy was delivered to the borrower.",
- required=False)
+ getDue = Attribute(u"Calculated due date.")
- due_date = schema.Date(title=u"Due date",
- description=u"When the copy should be returned to the lender.",
- required=False)
+ # XXX: This should have a vocabulary
+ duration = Attribute(u"One of: minute week month quarter semester year")
+
+ # XXX: This should have a vocabulary
+ status = Attribute(u"One of: pending approved denied")
- 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/item.py
===================================================================
--- Sandbox/luciano/kirbi/src/kirbi/item.py 2007-08-21 03:29:18 UTC (rev 79061)
+++ Sandbox/luciano/kirbi/src/kirbi/item.py 2007-08-21 07:57:42 UTC (rev 79062)
@@ -15,11 +15,13 @@
"""
import grok
-from interfaces import IItem, IBook
+from interfaces import IItem, IBook, ILease
from zope.interface import Interface, implements, invariant
from zope import schema
-from datetime import datetime
+from datetime import datetime, timedelta
+from zope.app.container.interfaces import INameChooser
+
class Item(grok.Container):
"""An exemplar of a book.
@@ -59,5 +61,113 @@
def __getattr__(self,name):
# XXX: Martijn Faassen sugests a refactoring here, implementing
# the Item->Manifestation relationship not using this sort of
- # dynamically hacked inheritance but in as an association
+ # dynamically hacked inheritance but as an association
return getattr(self.manifestation, name)
+
+ def addLease(self, lease):
+ name = INameChooser(self).chooseName(lease.borrower_login, lease)
+ self[name] = lease
+ return lease.__name__
+
+
+class Borrow(grok.View):
+ # control: duration needed
+ # text area: suggested time/place for pickup
+ grok.context(Item)
+
+
+ def __init__(self, context, request):
+ super(Borrow, self).__init__(context, request)
+
+ self.form_title = u'Borrow item'
+ self.borrow_from = u'%s (%s)' % (context.__parent__.title,
+ context.__parent__.__name__)
+
+ def getDurations(self):
+ return u'minute week month quarter semester year'.split()
+
+ def coverUrl(self):
+ cover_name = 'covers/large/'+self.context.manifestation_id+'.jpg'
+ return self.static.get(cover_name,
+ self.static['covers/small-placeholder.jpg'])()
+
+ def update(self, duration=None, pickup=''):
+ if duration is not None:
+ lease = Lease(self.context.__name__, self.context.__parent__.__name__,
+ self.request.principal.id, duration, pickup)
+
+ self.context.addLease(lease)
+
+ self.redirect(self.url(self.context))
+
+class Lease(grok.Model):
+ """A book lease.
+
+ >>> start = datetime(2007,1,1,0,0,0)
+ >>> start.isoformat()
+ '2007-01-01T00:00:00'
+
+ >>> Lease.calculateDue(start,u'minute').isoformat()
+ '2007-01-01T00:01:00'
+ >>> Lease.calculateDue(start,u'hour').isoformat()
+ '2007-01-01T01:00:00'
+ >>> Lease.calculateDue(start,u'day').isoformat()
+ '2007-01-02T00:00:00'
+ >>> Lease.calculateDue(start,u'week').isoformat()
+ '2007-01-08T00:00:00'
+ >>> Lease.calculateDue(start,u'month').isoformat()
+ '2007-02-01T00:00:00'
+ >>> Lease.calculateDue(start,u'quarter').isoformat()
+ '2007-04-01T00:00:00'
+ >>> Lease.calculateDue(start,u'semester').isoformat()
+ '2007-07-01T00:00:00'
+ >>> Lease.calculateDue(start,u'year').isoformat()
+ '2008-01-01T00:00:00'
+
+ Test year wrap-around:
+
+ >>> start = datetime(2007,12,1,0,0,0)
+ >>> start.isoformat()
+ '2007-12-01T00:00:00'
+ >>> Lease.calculateDue(start,u'month').isoformat()
+ '2008-01-01T00:00:00'
+ >>> Lease.calculateDue(start,u'quarter').isoformat()
+ '2008-03-01T00:00:00'
+ >>> Lease.calculateDue(start,u'semester').isoformat()
+ '2008-06-01T00:00:00'
+
+ """
+ implements(ILease)
+
+ def __init__(self, item_id, lender_login, borrower_login, duration, pickup):
+ super(Lease, self).__init__()
+
+ self.item_id = item_id
+ self.lender_login = lender_login
+ self.borrower_login = borrower_login
+ self.duration = duration
+ self.pickup = pickup
+ self.request_date = datetime.now()
+ self.status = u'pending' # One of: pending approved denied
+
+ def getDue(self):
+ return Lease.calculateDue(self.request_date, self.duration)
+
+ @staticmethod
+ def calculateDue(fromDateTime, interval):
+ MONTHLY_INTERVALS = {u'month':1, u'quarter':3, u'semester':6}
+ if interval in [u'minute',u'hour',u'day',u'week']:
+ interval = timedelta(**{interval+u's':1})
+ return fromDateTime + interval
+ elif interval in MONTHLY_INTERVALS:
+ months = MONTHLY_INTERVALS[interval]
+ if fromDateTime.month < (13-months):
+ return fromDateTime.replace(month=fromDateTime.month+months)
+ else:
+ return fromDateTime.replace(year=fromDateTime.year+1,
+ month=fromDateTime.month-(12-months))
+ elif interval == u'year':
+ return fromDateTime.replace(year=fromDateTime.year+1)
+ else:
+ raise ValueError, u'Unknown interval: "%s"' % interval
+
Modified: Sandbox/luciano/kirbi/src/kirbi/pac.py
===================================================================
--- Sandbox/luciano/kirbi/src/kirbi/pac.py 2007-08-21 03:29:18 UTC (rev 79061)
+++ Sandbox/luciano/kirbi/src/kirbi/pac.py 2007-08-21 07:57:42 UTC (rev 79062)
@@ -136,6 +136,9 @@
self.results_title = u'"%s" is not a valid query' % query
self.results = []
return
+ # XXX: remove Items from the result; like Martijn said
+ # Items and Books must be refactored to become less symbiotic
+ results = (r for r in results if not hasattr(r,'manifestation_id'))
# Note: to sort the results, we must cast the result iterable
# to a list, which can be very expensive
results = list(results)
Modified: Sandbox/luciano/kirbi/src/kirbi/static/master.css
===================================================================
--- Sandbox/luciano/kirbi/src/kirbi/static/master.css 2007-08-21 03:29:18 UTC (rev 79061)
+++ Sandbox/luciano/kirbi/src/kirbi/static/master.css 2007-08-21 07:57:42 UTC (rev 79062)
@@ -1,5 +1,7 @@
body {
margin: 0px;
+ font-family: sans-serif;
+ font-size: 11pt;
}
a:link {
@@ -173,4 +175,9 @@
.listing th {
text-align: center;
-}
\ No newline at end of file
+}
+
+div.leases {
+ background-color: #eef;
+ padding: 10px;
+}
More information about the Checkins
mailing list