class DeferralList(QonPersistent): """Provides simple timed deferral mechanism. Maintains a list of keyed times. Client calls defer() to add a key and a delay. Subsequent calls to defer() with the same key will return True if delay has elapsed, or false if not. """ persistenceVersion = 1 def __init__(self): self.defers = ConflictAvoidingOOBTree() def upgradeToVersion1(self): # upgrade from PersistentMapping bt = ConflictAvoidingOOBTree() for k,v in self.defers.iteritems(): bt[k] = v del self.defers self.defers = bt self.version_upgrade_done() def defer(self, key, delay): """Schedule a deferred event labeled by key. If key already exists, returns True if enough time has elapsed since key was originally added. Otherwise, add key and return False. """ now = datetime.utcnow() if self.defers.has_key(key): if self.defers[key] <= now: del self.defers[key] return True else: return False else: self.defers[key] = now + delay return False def cancel(self, key): """Unschedule a deferred task.""" if self.defers.has_key(key): del self.defers[key]
class IPAddresses(QonPersistent): """Container to track IP addresses.""" _resolution = timedelta(minutes=15) def __init__(self): self.__items = ConflictAvoidingOOBTree() def ip_hit(self, ip, dt=None): """Record a hit from ip at dt (or now). Only records if more than _resolution time has elapsed. """ from qon.util import pack_ip try: packed_ip = pack_ip(ip) except: # a problem in format of ip return dt = dt or datetime.utcnow() last_hit = self.__items.get(packed_ip, None) if last_hit: if dt - last_hit < self._resolution: return self.__items[packed_ip] = dt def get_ips(self): """Return dict of [IP.Address] -> datetime, ...""" from qon.util import unpack_ip ips = {} for k, v in self.__items.iteritems(): ips[unpack_ip(k)] = v return ips
class BlogItem(QonPersistent, qon.karma.HasKarma, Watchable, HasComments): persistenceVersion = 6 def __init__(self, blog, author, title, summary, main='', dont_watch=0): qon.karma.HasKarma.__init__(self) Watchable.__init__(self) HasComments.__init__(self) self.blog = blog self.__deleted = 0 self.author = author self.title = title self.__summary = CompressedText(summary) if main: self.__main = CompressedText(main) else: self.__main = None self.__cached_html_summary = PersistentCache(self._update_html_cache) # history is a string, showing diffs as items are edited self.history = None self.date = datetime.utcnow() self.modified = None self.parent_blogitem = None # for comment, will point to parent blogitem upon add_comment(); otherwise None if dont_watch: """Comments aren't watchable.""" self.not_watchable = 1 else: # for watchable items only (not comments) self.__user_access = ConflictAvoidingOOBTree() def upgradeToVersion6(self): self.history = None def upgradeToVersion5(self): self.title = iso_8859_to_utf_8(self.title) def upgradeToVersion4(self): self.__deleted = self.deleted del self.deleted # upgrade HasComments HasComments._upgradeToVersion1(self) # elim __main's CompressedText if not self.get_main(): self.__main = None self.version_upgrade_done() def upgradeToVersion3(self): if not self.not_watchable: self.__user_access = ConflictAvoidingOOBTree() # get rid of old Readers attribute if hasattr(self, '_BlogItem__readers'): del self.__readers self.version_upgrade_done() def upgradeToVersion2(self): # do self.parent_blogitem 2005-03-17 self.parent_blogitem = None comments = HasComments.get_all_comments(self) for item in comments: item.parent_blogitem = self # point comments to point to itself self.version_upgrade_done() def upgradeToVersion1(self): # compress text self.__summary = CompressedText(self.summary) self.__main = CompressedText(self.main) del self.summary del self.main # create cache self.__cached_html_summary = PersistentCache(self._update_html_cache) self.version_upgrade_done() def is_deleted(self, use_attr=False): if use_attr or not self.parent_blogitem: return self.__deleted return self.parent_blogitem.get_comment_flags(self) def set_deleted(self, val): self.__deleted = bool(val) # cache/copy deleted attribute into paren't comment_flags # for fast lookup to avoid db reading each comment if self.parent_blogitem: self.parent_blogitem.set_comment_flags(self, bool(val)) # alex added so that recent items cache for parent blog gets marked as dirty self.watchable_changed() def set_deleted_note(self, note): self._deleted_note = note def get_deleted_note(self): return getattr(self, '_deleted_note', None) def can_read(self, user): return self.blog.ihb.can_read(user) def can_edit(self, user): return (self.author is user) or self.can_manage(user) def can_delete(self, user): """Return True if user can delete this item.""" # managers can always delete if self.can_manage(user): return True # authors can only delete if there are no undeleted comments if self.num_comments() == 0: return self.author is user return False def can_manage(self, user): """Return True if user can manage this item (usually a group owner).""" return self.blog.ihb and self.blog.ihb.can_manage(user) def can_show(self): """Return False if this item should be suppressed due to feedback score.""" if self.get_karma_score() < qon.karma.min_karma_to_show: return False if self.author.get_karma_score() < qon.karma.min_author_karma: return False return True def why_cant_show(self): """Only really useful when can_show()==False. Returns the reason for an item not being shown. Return value is ('item' | 'user', fbscore)""" if self.get_karma_score() < qon.karma.min_karma_to_show: return ('item', self.get_karma_score()) if self.author.get_karma_score() < qon.karma.min_author_karma: return ('user', self.author.get_karma_score()) return () def last_modified(self, consider_comments=True): if consider_comments: # this Watchable changes whenever a comment is added dt = self.watchable_last_change() if dt is never: dt = self.modified or self.date return dt else: return self.modified or self.date def new_comment(self, author, title, summary, main=''): """Create a new comment item and return it.""" comment = BlogItem(blog=self.blog, author=author, title=title, summary=summary, main=main, dont_watch=1) # Check to see if this new comment is a duplicate # of the previous comment. If so, ignore it, since # it's probably unintended, and just return None. if HasComments.is_duplicate(self, comment): comment = None else: self.add_comment(comment) # avoid 'bogus new to me' comment.read_item(author, datetime.utcnow()) author.karma_activity_credit() return comment def notify_karma_changed(self): """Called by HasKarma.add_karma.""" # also delegate to watchable_changed # self.watchable_changed() # removed by alex to keep blogitems from boldfacing when left feedback if self.blog and hasattr(self.blog, 'notify_karma_changed'): self.blog.notify_karma_changed() def add_comment(self, comment): comment.parent_blogitem = self HasComments.add_comment(self, comment) self.watchable_changed(comment.date) def watchable_name(self): return self.title def watchable_changed(self, now=None): # tells blog he has changed, too Watchable.watchable_changed(self, now) if self.blog: self.blog.watchable_changed(now) def watchable_modified_date(self): return self.last_modified() def can_get_karma_from(self, other): return other is not self.author def get_summary(self): return self.__summary.get_raw() def set_summary(self, raw): self._log_summary_change() self.__summary.set_raw(raw) self.invalidate_html_cache() def get_main(self): if not self.__main: return '' return self.__main.get_raw() def set_main(self, raw): if not self.__main: self.__main = CompressedText() self.__main.set_raw(raw) def _log_summary_change(self): import qon.log if hasattr(self.blog.ihb, 'get_user_id'): qon.log.edit_info('SetSummary\t%s\n%s' % (self.blog.ihb.get_user_id(), self.get_summary())) else: qon.log.edit_info('SetSummary2\t%s\n%s' % (self.blog.ihb.get_name(), self.get_summary())) # HTML cache methods def add_html_dependency(self, target): """Adds target as something self depends on for its HTML cache.""" self.__cached_html_summary.add_dependency(target) def invalidate_html_cache(self): self.__cached_html_summary.flush() def get_cached_html(self): return self.__cached_html_summary.get().get_raw() def _update_html_cache(self): from qon.ui.blocks.wiki import rst_to_html return CompressedText(str(rst_to_html(self.get_summary(), wiki=self.blog.ihb.get_wiki(), container=self))) def disable_cache(self): self.__cached_html_summary.disable_cache() def cache_disabled(self): return self.__cached_html_summary.cache_disabled() def read_item(self, user, now=None): """Notice that user has accessed this item. If we are a comment, we pass this on to our parent item, to catch up based on comment's submission date. """ if not hasattr(self, "_BlogItem__user_access"): # we don't keep track of user access -- we're a comment, # so just pass this on to our parent. if self.parent_blogitem: return self.parent_blogitem.read_item(user, self.date) return if not user: return now = now or datetime.utcnow() user_oid = unpack_oid(user._p_oid) dt, count = self.__user_access.get(user_oid, (never, 0)) now = max(now, dt) # increment hit count self.__user_access[user_oid] = (now, count + 1) def has_read_item(self, user, updated=None): if not hasattr(self, "_BlogItem__user_access"): return False if not user: return True dt, count = self._get_user_access(user) if count == 0: return False if updated: return dt >= updated else: return True def last_read(self, user): """Return datetime when user last read this item.""" dt, count = self._get_user_access(user) return dt def _get_user_access(self, user): if not hasattr(self, "_BlogItem__user_access"): return (never, 0) user_oid = unpack_oid(user._p_oid) return self.__user_access.get(user_oid, (never, 0)) def item_views(self): """Returns (number of views, number of readers).""" views = 0 readers = 0 for user_oid, (dt, count) in self.__user_access.iteritems(): if count > 0: # alex added if on 2006-10-09 readers += 1 views += count return (views, readers) def get_pageview_counts_per_user(self): """return a list of (user, counts)""" return_list = [] for user_oid, (dt, count) in self.__user_access.iteritems(): if count > 0: return_list.append((get_oid(pack_oid(user_oid)), count)) return return_list def get_title(self): # this is here and in WikiPage return self.title