root/SimpleBlog/trunk/SimpleBlogTool.py

Revision 1039 (checked in by mylan, 10 months ago)

Merged revisions 815-1037 via svnmerge from
http://svn/products/SimpleBlog/branches/optimizations

........

r815 | chervol | 2007-03-02 10:39:16 +0200 (Fri, 02 Mar 2007) | 1 line


SimpleBlog? optimizations
........
r816 | chervol | 2007-03-02 11:03:07 +0200 (Fri, 02 Mar 2007) | 1 line


getEntries optimized, added new navigation macros, shortened recent portlet code
........
r817 | chervol | 2007-03-02 11:04:58 +0200 (Fri, 02 Mar 2007) | 1 line


fixed navigation macro name in blog view
........
r823 | chervol | 2007-03-09 19:22:50 +0200 (Fri, 09 Mar 2007) | 1 line


getObject calls reducing
........
r838 | mylan | 2007-04-06 16:42:57 +0300 (Fri, 06 Apr 2007) | 1 line


Move Blog and BlogFolder? to ATCTBtreeFolder. Correct minor issues.
........
r839 | mylan | 2007-04-10 15:45:16 +0300 (Tue, 10 Apr 2007) | 1 line


Add migration to BTree folder module.
........
r840 | mylan | 2007-04-11 12:27:22 +0300 (Wed, 11 Apr 2007) | 1 line


Finish with migration.
........
r841 | mylan | 2007-04-11 13:20:09 +0300 (Wed, 11 Apr 2007) | 1 line


Clean-up code code.
........
r842 | mylan | 2007-04-11 15:53:11 +0300 (Wed, 11 Apr 2007) | 1 line


Remove needless collectEntries method from SimpleblogTool? and simpleblog_standalone template.
........
r843 | mylan | 2007-04-13 19:46:36 +0300 (Fri, 13 Apr 2007) | 1 line


Speed up getAvailableCategory function in SimpleBlog? tool.
........
r844 | mylan | 2007-04-17 18:27:50 +0300 (Tue, 17 Apr 2007) | 1 line


add batch navigation for simple blog view template
........
r845 | mylan | 2007-04-17 18:35:18 +0300 (Tue, 17 Apr 2007) | 1 line


Add squared brackets around current page in navigation.
........
r867 | piv | 2007-05-11 11:47:26 +0300 (Fri, 11 May 2007) | 1 line


Fix blog's type syndication content, update migration external method.
........
r869 | mylan | 2007-05-22 16:42:30 +0300 (Tue, 22 May 2007) | 1 line


Fix batching bugs
........
r870 | mylan | 2007-05-22 16:46:05 +0300 (Tue, 22 May 2007) | 1 line


Fix batching bugs..forget template update
........
r888 | fenix | 2007-06-19 12:25:46 +0300 (Tue, 19 Jun 2007) | 1 line


Remove navigation for only one page of blog content.
........
r896 | chervol | 2007-06-25 15:36:07 +0300 (Mon, 25 Jun 2007) | 1 line


fixed the recent portlet markup, added extra css class for the 'more...' link
........

  • Property svn:eol-style set to native
Line 
1 from Products.CMFCore.utils import UniqueObject
2 from OFS.SimpleItem import SimpleItem
3 from OFS.PropertyManager import PropertyManager
4 from Globals import InitializeClass
5 from AccessControl import ClassSecurityInfo, Unauthorized
6 from Products.CMFCore import CMFCorePermissions
7 import zLOG,os
8 from Products.CMFCore.utils import getToolByName
9 import re
10 import calendar
11 calendar.setfirstweekday(6) #start day  Mon(0)-Sun(6)
12 from DateTime import DateTime
13
14
15 class SimpleBlogManager(UniqueObject, SimpleItem,PropertyManager):
16     """ This tool provides some functions for SimpleBlog objects """
17     id = 'simpleblog_tool'
18     meta_type= 'SimpleBlog manager'
19     plone_tool = 1
20
21     manage_options=PropertyManager.manage_options
22
23     security = ClassSecurityInfo()
24     calendar_types=['BlogEntry']
25     use_session=""
26
27     def __init__(self):
28         self.manage_addProperty('publishedState', 'published', 'string')
29         self.manage_addProperty('maxItemsInPortlet', 5, 'int')
30         self.manage_addProperty('globalCategories', '', 'lines')
31         self.manage_addProperty('createPortletOnBlogCreation', 1,'boolean')
32
33     security.declarePublic('getByUID')
34     def getByUID(self, uid):
35         "Shortcut method for the [Blogger,MetaWeblog]API code"
36
37         uid_catalog = getToolByName(self, 'uid_catalog')
38         lazy_cat = uid_catalog(UID=uid)
39         o = lazy_cat[0].getObject()
40         return o
41
42     security.declarePublic('findRPCAuth')
43     def findRPCAuth(self, parent):
44         while hasattr(parent,'aq_parent'):
45             RPCAuths = parent.objectValues('RPC Auth')
46             for RPCAuth in RPCAuths:
47                 return RPCAuth
48             parent = parent.aq_parent
49         return None
50
51
52     security.declarePublic('idFromTitle')
53     def idFromTitle(self, title):
54         id = re.sub('[^A-Za-z0-9_]', '', re.sub(' ', '_', title)).lower()
55         return id
56
57     def _getState(self):
58         try:
59             return self.publishedState
60         except:
61             return 'published'
62
63     def _getMaxItemsInPortlet(self):
64         try:
65             return self.maxItemsInPortlet
66         except:
67             return 5
68     def _getGlobalCategories(self):
69         try:
70             cats = self.globalCategories
71             ret=[]
72             for c in cats:
73                 if c!='':
74                     ret.append(c)
75             return ret
76         except:
77             return []
78
79     def _getCreatePortletOnBlogCreation(self):
80         try:
81             return self.createPortletOnBlogCreation
82         except:
83             return 1
84
85     security.declareProtected(CMFCorePermissions.ManagePortal,'setProperties')       
86     def setProperties(self, publishedState='published', createPortletOnBlogCreation=None, maxItemsInPortlet=5, globalCategories=''):
87         self.publishedState = publishedState
88         if createPortletOnBlogCreation==1 or createPortletOnBlogCreation=='on':
89             self.createPortletOnBlogCreation=1
90         else:
91             self.createPortletOnBlogCreation=0
92
93         self.maxItemsInPortlet=int(maxItemsInPortlet)
94
95         value=''
96         if globalCategories<>'':
97             value =  globalCategories.split('\n')
98             value = [v.strip() for v in value if v.strip()]
99             value = filter(None, value)
100
101         self.globalCategories=value
102
103     security.declarePublic('getPublishedState')
104     def getPublishedState(self):
105         return self._getState()
106
107     security.declarePublic('getMaxItemsInPortlet')
108     def getMaxItemsInPortlet(self):
109         return self._getMaxItemsInPortlet()
110
111     security.declarePublic('getFrontPage')
112     def getFrontPage(self, context):
113         """
114         returns the frontpage (Blog object) when viewing an Entry
115         """
116         if context.portal_type!='Blog':
117             portal = context.portal_url.getPortalObject()
118             if context!=portal:
119                 parent=context.aq_parent
120             else:
121                 parent=context
122             found=0
123             while parent!=portal and context.portal_type!='Blog':
124                 if parent.portal_type=='Blog':
125                     found=1
126                     break
127                 parent=parent.aq_parent
128
129             if found==1:
130                 return parent
131             else:
132                 return None
133         else:
134             return context
135     security.declarePublic('getStartpointForSearch')
136     def getStartpointForSearch(self, context):
137         """
138         When in the context of a blog, return the blog
139         Outside the context of a blog, return context or if context isn't
140         folderish, it's parent container
141         """
142         plone_utils = getToolByName(context, 'plone_utils')
143
144         startpoint = self.getFrontPage(context)
145         if not startpoint:
146             # we weren't in the context of a blog
147             if plone_utils.isStructuralFolder(context):
148                 return context
149             else:
150                 return context.aq_parent
151         else:
152             return startpoint
153
154     security.declarePublic('getAvailableCategories')
155     def getAvailableCategories(self, context, startpoint=None):
156         """
157         returns a dict of all the available categories with the number of posts inside
158         """
159         # get all EntryFolders
160         # first get the starting point in case we are inside a Blog section
161         # if we are higher in the tree than any Blog then we will end up in the portalobject itself
162         # in that case we just search for categories starting in context.
163         if not startpoint:
164             startpoint = self.getStartpoint(context, fromHere=0)
165         categories = context.portal_catalog.uniqueValuesFor('EntryCategory')
166         path = self.getObjectPath(startpoint)
167
168         # now we have a list of unique categories available from startpoint and deeper in tree
169         # next step is to count the number of entries for each category
170         rescats={}
171         [rescats.update({c:0}) for c in categories]
172         result = startpoint.portal_catalog.searchResults(review_state=self._getState(), meta_type='BlogEntry', path={'query':path,'level':0})
173
174         for r in result:
175             for c in r.EntryCategory: rescats[c] = rescats[c]+1
176         for c,n in rescats.items():
177             if n==0: del rescats[c]
178         return rescats
179
180     security.declarePublic('getSortedKeys')
181     def getSortedKeys(self, dict):
182         keys = dict.keys()
183         keys.sort()
184         return keys
185
186     security.declarePublic('getGlobalCategories')
187     def getGlobalCategories(self):
188         return self._getGlobalCategories()
189
190     security.declarePublic('getStartpoint')
191     def getStartpoint(self, context, fromHere=0):
192         if context.portal_type!='Blog' and fromHere==0:
193             portal = context.portal_url.getPortalObject()
194             if context!=portal:
195                 parent=context.aq_parent
196             else:
197                 parent=context
198             found=0
199             while parent!=portal and context.portal_type!='Blog':
200                 if parent.portal_type=='Blog':
201                     found=1
202                     break
203                 parent=parent.aq_parent
204
205             if found==1:
206                 startpoint=parent
207             else:
208                 if context.isPrincipiaFolderish:
209                     startpoint=context
210                 else:
211                     startpoint=context.aq_parent
212         else:
213             startpoint=context
214
215         return startpoint
216
217     security.declarePublic('searchForEntries')
218     def searchForEntries(self, context, category=None, maxResults=None, fromHere=0, filterState=1, **kwargs):
219         # set maxResults=0 for all the results,
220         # leave it to None to get the max from the properties
221         # set fromHere=1 to search from the current location. Is used for BlogFolders
222
223         # first, get the context right
224         # when inside a Blog: search for the frontpage
225         # when outside a Blog: use context (or its container)
226
227         #filterState controls whether you want to return only published entries
228
229         startpoint = self.getStartpoint(context, fromHere)
230         query=kwargs
231         publishedState = self._getState()
232         if category!=None:
233             query['EntryCategory']=category
234
235         if filterState:
236             query['review_state']=publishedState           
237
238         results = startpoint.portal_catalog.searchResults(query, meta_type='BlogEntry',
239                   path={'query':self.getObjectPath(startpoint),'level':0}, sort_order='reverse',
240                   sort_on='effective', sort_limit=maxResults and maxResults or None)
241
242         if  maxResults==0:
243             return results
244         elif maxResults==None:
245             return results[:self._getMaxItemsInPortlet()]
246         else:
247             return results[:maxResults]   
248
249
250     security.declarePublic('searchForDay')
251     def searchForDay(self, context, date):
252         startpoint = self.getStartpoint(context, fromHere=0)
253         # now we have the starting point for our search
254
255         query={'start': DateTime(date).earliestTime(), 'start_usage': 'range:min',
256                     'end': DateTime(date).latestTime(), 'end_usage':'range:max'}
257         query['getAlwaysOnTop']=1
258         resultsTop = startpoint.portal_catalog.searchResults(query,
259                                                              review_state=self._getState(),
260                                                              meta_type='BlogEntry',
261                                                              path={'query':self.getObjectPath(startpoint),'level':0},
262                                                              sort_order='reverse', sort_on='effective')
263         query['getAlwaysOnTop']=0
264         resultsNoTop = startpoint.portal_catalog.searchResults(query,
265                                                              review_state=self._getState(),
266                                                              meta_type='BlogEntry',
267                                                              path={'query':self.getObjectPath(startpoint),'level':0},
268                                                              sort_order='reverse', sort_on='effective')
269         results = resultsTop + resultsNoTop
270         return results
271
272     security.declarePublic('getUnpublishedEntries')
273     def getUnpublishedEntries(self, blog):
274         states = self. getEntryWorkflowStates(blog)
275         pubstate = self.getPublishedState()
276         states = [s for s in states if s!=pubstate]
277         query={'review_state':states}
278         entries = self.searchForEntries(blog, filterState=0, maxResults=0, fromHere=1, **query)
279         return entries
280
281     security.declarePublic('blogHasEntries')
282     def blogHasEntries(self, context, fromHere=0):
283         """
284         returns if a blog has entries, either published or not published.
285         this function is used to display a message in the simpleblog(folder)_view when
286         there are entries but none of them published
287         """
288         startpoint = self.getStartpoint(context, fromHere=0)
289
290         # get all entries, doesn't matter what state they're in
291         results = startpoint.portal_catalog.searchResults(meta_type='BlogEntry', path={'query':self.getObjectPath(startpoint),'level':0})       
292
293         if results:
294             return True
295         else:
296             return False
297    
298     security.declarePublic('getEntryDate')
299     def getEntryDate(self, context):
300         if context.EffectiveDate()=='None':
301             return context.modification_date.aCommon()
302         else:
303             return context.EffectiveDate()
304
305     security.declarePublic('getCreatePortletOnBlogCreation')
306     def getCreatePortletOnBlogCreation(self):
307         return self._getCreatePortletOnBlogCreation()
308        
309     security.declareProtected(CMFCorePermissions.ManagePortal,'getAllWorkflowStates')
310     def getAllWorkflowStates(self, context):
311         lst=[]
312         for wf in context.portal_workflow.listWorkflows():
313             states = context.portal_workflow.getWorkflowById(wf).states
314             for s in states.keys():
315                 if not states[s].id in lst:
316                     lst.append(states[s].id)
317         return lst
318
319     security.declareProtected(CMFCorePermissions.ManagePortal,'getEntryWorkflowStates')
320     def getEntryWorkflowStates(self, context):
321         chain = context.portal_workflow.getChainForPortalType('BlogEntry', 0)
322         lst=[]
323         for wf in chain:
324             states = context.portal_workflow.getWorkflowById(wf).states
325             for s in states.keys():
326                 if not states[s].id in lst:
327                     lst.append(states[s].id)
328
329         return lst
330
331     # return object's url relative to the portal
332     def getObjectPath(self, object):
333         return os.path.join(*object.getPhysicalPath()).replace('\\', '/')
334
335     # ======================================================
336     # calendar stuff, copied from CMFCalender
337     # ======================================================
338
339     security.declarePublic('getCalendarTypes')
340     def getCalendarTypes(self):
341         """ Returns a list of type that will show in the calendar """
342         return self.calendar_types
343
344     security.declarePublic('getUseSession')
345     def getUseSession(self):
346         """ Returns the Use_Session option """
347         return self.use_session
348
349     security.declarePublic('getDays')
350     def getDays(self):
351         """ Returns a list of days with the correct start day first """       
352         return calendar.weekheader(2).split()
353
354     security.declarePublic('getWeeksList')
355     def getWeeksList(self, month='1', year='2002'):
356         """Creates a series of weeks, each of which contains an integer day number.
357            A day number of 0 means that day is in the previous or next month.
358         """
359         # daysByWeek is a list of days inside a list of weeks, like so:
360         # [[0, 1, 2, 3, 4, 5, 6],
361         #  [7, 8, 9, 10, 11, 12, 13],
362         #  [14, 15, 16, 17, 18, 19, 20],
363         #  [21, 22, 23, 24, 25, 26, 27],
364         #  [28, 29, 30, 31, 0, 0, 0]]
365         daysByWeek=calendar.monthcalendar(year, month)
366
367         return daysByWeek
368
369     security.declarePublic('getEventsForCalendar')
370     def getEventsForCalendar(self, context, month='1', year='2002'):
371         """ recreates a sequence of weeks, by days each day is a mapping.
372             {'day': #, 'url': None}
373         """
374         year=int(year)
375         month=int(month)
376         # daysByWeek is a list of days inside a list of weeks, like so:
377         # [[0, 1, 2, 3, 4, 5, 6],
378         #  [7, 8, 9, 10, 11, 12, 13],
379         #  [14, 15, 16, 17, 18, 19, 20],
380         #  [21, 22, 23, 24, 25, 26, 27],
381         #  [28, 29, 30, 31, 0, 0, 0]]
382         daysByWeek=calendar.monthcalendar(year, month)
383         weeks=[]
384
385         events=self.catalog_getevents(context, year, month)
386
387         for week in daysByWeek:
388             days=[]
389             for day in week:
390                 if events.has_key(day):
391                     days.append(events[day])
392                 else:
393                     days.append({'day': day, 'event': 0, 'eventslist':[]})
394
395             weeks.append(days)
396
397         return weeks
398
399     security.declarePublic('catalog_getevents')
400     def catalog_getevents(self, context, year, month):
401         """ given a year and month return a list of days that have events """
402         first_date=DateTime(str(month)+'/1/'+str(year))
403         last_day=calendar.monthrange(year, month)[1]
404         ## This line was cropping the last day of the month out of the
405         ## calendar when doing the query
406         ## last_date=DateTime(str(month)+'/'+str(last_day)+'/'+str(year))
407         last_date=first_date + last_day   
408
409         # get the starting point for our search. This is where we depart from the standard catalog_tool:
410         startpoint = self.getStartpoint(context, fromHere=0)
411
412         query=self.portal_catalog(portal_type=self.calendar_types,
413                               review_state=self._getState(),
414                               start=last_date,
415                               start_usage='range:max',
416                               end=first_date,
417                               end_usage='range:min',
418                               path={'query':self.getObjectPath(startpoint),'level':0},
419                               sort_on='start')
420
421         # compile a list of the days that have events
422         eventDays={}
423         for daynumber in range(1, 32): # 1 to 31
424             eventDays[daynumber] = {'eventslist':[], 'event':0, 'day':daynumber}
425         includedevents = []
426         for result in query:
427             if result.getRID() in includedevents:
428                 break
429             else:
430                 includedevents.append(result.getRID())
431             event={}
432             # we need to deal with events that end next month
433             if  result.end.month() != month:  # doesn't work for events that last ~12 months - fix it if it's a problem, otherwise ignore
434                 eventEndDay = last_day
435                 event['end'] = None
436             else:
437                 eventEndDay = result.end.day()
438                 event['end'] = result.end.Time()
439             # and events that started last month
440             if result.start.month() != month:  # same as above re: 12 month thing
441