From 48701178bd9283ee1aed137da988cd34dd8edc34 Mon Sep 17 00:00:00 2001 From: helmutm Date: Mon, 20 Jul 2009 14:56:04 +0000 Subject: [PATCH] add a portlet showing the presence of other users; may be activated by the global option 'organize.showPresence' git-svn-id: svn://svn.cy55.de/Zope3/src/loops/trunk@3466 fd906abe-77d9-0310-91a1-e0d9ade77398 --- browser/node.py | 13 ++++++ browser/node_macros.pt | 7 ++++ locales/de/LC_MESSAGES/loops.mo | Bin 9899 -> 9936 bytes locales/de/LC_MESSAGES/loops.po | 5 ++- organize/README.txt | 7 ++++ organize/auth.py | 11 ++++++ organize/configure.zcml | 7 ++++ organize/interfaces.py | 33 +++++++++++++++- organize/presence.py | 68 ++++++++++++++++++++++++++++++++ 9 files changed, 149 insertions(+), 2 deletions(-) create mode 100644 organize/presence.py diff --git a/browser/node.py b/browser/node.py index f04e12a..b32c1ee 100644 --- a/browser/node.py +++ b/browser/node.py @@ -59,6 +59,7 @@ from loops import util from loops.util import _ from loops.browser.common import BaseView from loops.browser.concept import ConceptView +from loops.organize.interfaces import IPresence from loops.organize.tracking import access from loops.versioning.util import getVersion @@ -135,9 +136,21 @@ class NodeView(BaseView): icon='cybertools.icons/user.png', url=url, priority=10) + if self.globalOptions('organize.showPresence'): + cm.register('portlet_right', 'presence', title=_(u'Presence'), + subMacro=node_macros.macros['presence'], + icon='cybertools.icons/group.png', + priority=11) # force early portlet registrations by target by setting up target view self.virtualTarget + @Lazy + def usersPresent(self): + presence = component.getUtility(IPresence) + presence.update(self.request.principal.id) + data = presence.getPresentUsers() + return data + @Lazy def view(self): name = self.request.get('loops.viewName', '') or self.context.viewName diff --git a/browser/node_macros.pt b/browser/node_macros.pt index 8944c32..0fc3916 100644 --- a/browser/node_macros.pt +++ b/browser/node_macros.pt @@ -236,6 +236,13 @@ + + +
+ + + +
qv)6=@{!6;|k+ysIi(shz1!`G-xKo_q+S#oqqdy-sgSZ=lDI(`)}pwPe-S|F34IC zxYiQAh{f30z3%UL|=)|-^L!) ze~1N)AHJsG0KXys!e9Jxrn&i@9TcMj_eI-PpzUhWj_T0~jF0t~V^8j15`%Mj@U@At&ic#oH$Dsp_M^|tW+R>DFKOIZBzYFbX5!!ww+HM{8 z$G6Zk@eA7Tzv#rWIN>ZH6}nNdq7W;v1nuZ#^o29fEjSAYU;{dE3p(&B^!+wu7s6|3 z>Nmyu_UM<$F$&wz)c=78`u%6HaSqfCO-*;SK@u~tCO$tFO>rHX`V-L!jE>LGMKgIm z9*vDyiu2J4twY~$L*IWLv;F=*uz;VSD{n^|euYkCJ37#B*w`h6y;#rvq{BGjxCCvt z4Q;m*-LgICf-*^0B^D!9;be3|lQ898O{U}A{!?_N+p*)Y z#ru8HgZPic!(4z)q#Q58f#@Nehi0I)7x{Ok&r#vttwCRSH#Xc9>%T!W^Lz9!^t}vz z4(+HLvYnw2UEu)q%$ycI4_(Mr=r3e*w6!<+ci@+(aOEFIzd={98#_{XcnC*uUyKeg zB02$?OSlp3xCNcaN;HET&@-?FUBDhR1N+b|$W8Ioy*n6PQEzlbNwi@VdNxMH`g(K% zW6_CSiB9}>bcN|yzXDy@o9M*a(TVOtj!(!f>&#FpNx?HP1byKYbOq<512my4nS*Ae zHQujAC;T@0^?Vrp1YPN8Xn$X#Tly=ykiXIA8GZQm^ZV~g!H!Nv58+sJLgUdCU4|aE zDe?JK^w7;jS27oEzYNVp8=AQfurF>w2i%MH*M&+mo{jl_{{F6=#%p4xEQhtPpK?6mqOWmGz7t#!zsNE=E^+D|!ZI zqc1!d>mNl|vIreuIXdAN(FwkTZrMik{qNCsJJAX3#p5x-Np`$Zm~w#06zuq_Xj61L zdT3^$?VgM-MLS%LgYYe^z(3J}%aWZLt;A~XtI>%z#QGc1cDE$Szu#$dY_I@b$zsgJ zRmg8jSc|51BbuSDXuBU{{jcbrhkl*U^Uw(&inc4m!B~N&eiE9Qnf=JW4ezDG0q0{j zJ{)}leZDBV0-f+Ww1aoB3wqK=6OF_~;#p$FHU9Jm{263*RgZhB+Rk*Q^r785J2ir4daAyFRh zFN@xU9uBW32(~iJCj9m%5!VrZZl@Dj#8l!z!h__sh8RLTL(~#n>89NLiPI@eBPJ8$ z39qw>8H8u6fmljZ5w{Rtrx9-3-QL7&QFJJJ6iy-@PjAaD$-k4*F~qfm*UWSxQQqaQ zbakSr@xIQga1S0w+)vCS?j>#}1`-p91%&&YCcOB0_ALE>EunM)v52$0)U~Z_nF%Kz3tO<24QIKPnuSSv2U_!DTg7Rl^2U w4H-~fTUK3DH)vR0&Cqm7@tBO7s`XQfXLijw;hLL$3(R7=Yn{76iK}YppD$)}jRpgiL^H`Kadz*IprK!_TJAHMIsgC~4_=FFKh%Qtf_pN)BCOmct6 z>~(>wp6E_&Y!kv!%Q^hv`Zhm=aoCJ~vCml{w8b0o44i^lSdG4NCl=v6JR8?xd)$V( zSdX@U4{4tayW@jTunP~q!>)J?^Y9FMv4bw?z(vvC(Q>3|NT3rMi4$-%w!>$m+vD@M zuoLy~U`NIepHgsu1IWK{gg?&o6xu=i4w->FqU{RNb|q*>33LKi$NHPFGxv950WQXK z@JZ~5Z=efn!hFULpHZ-bpU@7EqAzCA+84U<#}4~r54;j-5~ic==f?YGSjhbb%*Fk7l5*6ZyBn4l3;UgZSVpJd^u_@%|5V#wXF0v?d)+xGi#C!&&Icx}XCU zqYLPZzE^<>yg1(9jkcTHnfyDGMO4_~%J^U{dYCq&E8iAvL|6JTI$$&Ueha$CC(r?N zS-unPgtjX{CsY!>5M6M2l0qqkDl`+*(1GVfYtV@;!3tb~4)89Tp(eEbC+Jq}Lo?EX zPV@*m!GDo|p&g%={q;mMkh~yP3`A#Gg$^(ro%vO0N8{uDbnM3cEVQHLX!|v2yA9X} z>(I0CCED*V=){ho6F;80Pli(z%BX0~k+-9M=nIu->W5%CUV{$203CQO+TkW7>*0Ac z<&Cj^XS4}9HsM1wb_l4+0x}z@?qcblZ$jUH5uJFw`u*>q;L3NR4c|v+x)&YjTdZ!y4-}5%e$=@k48>Jw zyARQJ`_L`hk1psp?2D$2V(5oXXcW3t*J9EaCsA;(W}vB>kL*fVfp)Yt*1wIebT6h4 zTfA?H9z_rFKj=ixKuq%29=c5^@MHl*5ck=Jvt*62lUX2YK(fU1TX10+_i#9Ak4@qgP?~AT* zFgmg8(FxyyuJFEC{{*_Sm(YprL?`++a(u!GG()+)GG`#!lY%dlqbs-q9bgi=lG$iR zYUBMnbi&)wuj9?=4s@mOqWv|YTlx*Uke|`#hw)N8hV+vR{fjfFcQAS?hodPPiypS| z@%d!*&{d-=nTNK26wSmYG;{TM9_~U1Jb?Ch2u=C#Xa@elHh%v(w6>!T=;zW4P3dU# z>zIMQcz3+7!Li&gMKkd^Ih_-8r^?T91{ysiGj86Cj+U_I{#_Zmi)DJ~7GZAf9jgB{~_Z^uT&Y_|W z75Byli=q#qGhU5$umxM8CwvT1O-v!4BTiqhQdoHUCb~2=J*S{7#ogRkvjk@m=H>Lo zk4>0EtWRytDJXq_5=SUpPHZ9k{N06dgmd*;o%%87g5)+z&l4|O6|YxtD#6~TFBY1< z=JD4<;q?r`)`eQaZ+#juoAC3xmdGZ&9wR(JUT+W=6VDQr1Xt?8+zwM}C@dguBW@zR z#uN7wo~aqci$n!+FX44H;dVXjO}w^7hoDE`N@7Fmcy6~24^rw++(URRO7+MqY4uQQ zL|#GlicD2lhC_*!#G}M=Vm>i|m`JQ8JOFD5FMfRkTl)X?0;S2s)5Md+t%TQN;!RwnJ6MQ5w{cmN_y3$cIOwhd5Y4;hL-$)+cq5Pa#2=6Wumgzpo)r1$}1A(gDQGe d46aHfswxMj5`|-153FpcEnM8bA-|+4>pw-kQ1}1< diff --git a/locales/de/LC_MESSAGES/loops.po b/locales/de/LC_MESSAGES/loops.po index 21d8e83..cf259d4 100644 --- a/locales/de/LC_MESSAGES/loops.po +++ b/locales/de/LC_MESSAGES/loops.po @@ -3,7 +3,7 @@ msgstr "" "Project-Id-Version: $Id$\n" "POT-Creation-Date: 2007-05-22 12:00 CET\n" -"PO-Revision-Date: 2009-07-13 12:00 CET\n" +"PO-Revision-Date: 2009-07-20 12:00 CET\n" "Last-Translator: Helmut Merz \n" "Language-Team: loops developers \n" "MIME-Version: 1.0\n" @@ -158,6 +158,9 @@ msgstr "Aktuelles Objekt zu Lesezeichen hinzufügen" msgid "Remove from favorites" msgstr "Aus Lesezeichen entfernen" +msgid "Presence" +msgstr "Anwesenheit" + msgid "Actions" msgstr "Aktionen" diff --git a/organize/README.txt b/organize/README.txt index 99b15fc..af84133 100644 --- a/organize/README.txt +++ b/organize/README.txt @@ -415,6 +415,13 @@ Send Email to Members u'\n\nEvent #1\nhttp://127.0.0.1/loops/views/menu/.97\n\n' +Show Presence of Other Users +============================ + + >>> from loops.organize.presence import Presence + >>> component.provideUtility(Presence()) + + Fin de partie ============= diff --git a/organize/auth.py b/organize/auth.py index b1819df..24690a1 100644 --- a/organize/auth.py +++ b/organize/auth.py @@ -34,6 +34,9 @@ from zope.app.security.interfaces import IAuthentication from zope import schema from zope.traversing.api import getParent +from cybertools.browser.loops.auth import LoopsSessionCredentialsPlugin \ + as BaseSessionCredentialsPlugin +from loops.organize.interfaces import IPresence from loops.util import _ @@ -98,6 +101,14 @@ class PersonBasedAuthenticator(Persistent, Contained): return InternalPrincipal(self, login) +class LoopsSessionCredentialsPlugin(BaseSessionCredentialsPlugin): + + def logout(self, request): + presence = component.getUtility(IPresence) + presence.removePresentUser(request.principal.id) + super(LoopsSessionCredentialsPlugin, self).logout(request) + + class InternalPrincipal(object): def __init__(self, auth, login): diff --git a/organize/configure.zcml b/organize/configure.zcml index 2a6c454..7e075ab 100644 --- a/organize/configure.zcml +++ b/organize/configure.zcml @@ -50,6 +50,11 @@ + + + + diff --git a/organize/interfaces.py b/organize/interfaces.py index 7b59a0b..6e35ea0 100644 --- a/organize/interfaces.py +++ b/organize/interfaces.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2007 Helmut Merz helmutm@cy55.de +# Copyright (c) 2009 Helmut Merz helmutm@cy55.de # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -177,3 +177,34 @@ class IAllocated(IConceptSchema): )), default='standard', required=True) + + +# presence + +class IPresence(Interface): + """ Utility for getting information about active principals, + mapping principal.id to timestamp of last activity. + """ + + def update(self, principalId): + """ Update Dictionary of active users, by calling addPresentUser(); + automaticly check for inactive users by calling + removeInactiveUsers(). + """ + + def addPresentUser(self, principalId): + """Add a user to dictionary of active users. + """ + + def removeInactiveUsers(self): + """ Remove a user from dictionary of active users if user + didn't interact for last min_until_logout minutes. + """ + + def getPresentUsers(self): + """ Return list of titles of active users. + """ + + def removePresentUser(self, principalId): + """ Remove user from dictionary of active users, e.g. when user logs out. + """ diff --git a/organize/presence.py b/organize/presence.py new file mode 100644 index 0000000..b1b951f --- /dev/null +++ b/organize/presence.py @@ -0,0 +1,68 @@ +# +# Copyright (c) 2009 Helmut Merz helmutm@cy55.de +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +""" +Utility for collecting information about logged-in/active users. + +Author: Hannes Plattner. + +$Id$ +""" + +from zope.interface import implements +from zope.cachedescriptors.property import Lazy + +from cybertools.meta.interfaces import IOptions +from cybertools.util.date import getTimeStamp +from loops.organize.interfaces import IPresence +from loops.organize import util + + +class Presence(object): + + implements(IPresence) + + def __init__(self, min_until_logout=10, presentUsers=None): + self.min_until_logout = min_until_logout + self.presentUsers = presentUsers or {} + + def update(self, principalId): + self.addPresentUser(principalId) + self.removeInactiveUsers() + + def addPresentUser(self, principalId): + self.presentUsers[principalId] = getTimeStamp() + + def removeInactiveUsers(self): + toDelete = [] + for id, timeStamp in self.presentUsers.iteritems(): + if (getTimeStamp() - timeStamp) > (self.min_until_logout*60): + toDelete.append(id) + for id in toDelete: + if id in self.presentUsers: + del self.presentUsers[id] + + def getPresentUsers(self): + ret = [] + for id, timeStamp in self.presentUsers.iteritems(): + ret.append(util.getPrincipalForUserId(id).title) + return ret + + def removePresentUser(self, principalId): + if principalId in self.presentUsers: + del self.presentUsers[principalId]