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 WatchList(QonPersistent): """Keep track of a collection of Watchable items by their oids. Stores last-seen time for each object watched. """ persistenceVersion = 3 _max_footprints = 10 def __init__(self): self.__items = ConflictAvoidingOOBTree() self.__footprints = ConflictAvoidingPersistentList() def upgradeToVersion3(self): """Make footprints persistent. No need to del _v_footprints since it's volatile anyway.""" self.__footprints = ConflictAvoidingPersistentList() self.version_upgrade_done() def upgradeToVersion2(self): newbt = ConflictAvoidingOOBTree(self.__items) del self.__items self.__items = newbt self.version_upgrade_done() def upgradeToVersion1(self): if hasattr(self, '_WatchList__footprints'): del self.__footprints def watch_item(self, item): """Add item to watch list. Records current time as last-seen.""" typecheck(item, Watchable) typecheck(item, Persistent) if not item.watchable(): return if not self.__items.has_key(item._p_oid): self.__items[item._p_oid] = datetime.utcnow() def stop_watching_item(self, item): """Remove item from watch list.""" typecheck(item, Watchable) typecheck(item, Persistent) if not item.watchable(): return if self.__items.has_key(item._p_oid): del self.__items[item._p_oid] def watchable_seen(self, obj): """Called when a watchable item is seen, even if it's not in my watchable list.""" typecheck(obj, Watchable) typecheck(obj, Persistent) if not obj.watchable(): return if self.__items.has_key(obj._p_oid): self.__items[obj._p_oid] = datetime.utcnow() self.add_footprint(obj) def changed_items(self, since): """Return list of items changed since datetime since.""" watched = self.watched_items() changed = [item for item in watched if item.changed_since(since)] return changed def changed_unseen_items(self): """Return list of items changed and unseen since datetime since.""" changed = self.watched_items() return [item for item in changed \ if self.last_seen(item) < item.watchable_last_change()] def watched_items(self): """Return list of all items being watched.""" return self._get_items(self.__items.keys()) def watched_items_oids(self): """Return list of all item OIDs being watched.""" return self.__items.keys() def _get_items(self, oid_list): """Return a list of valid watchable items from oid_list""" items = [] for k in oid_list: try: obj = get_oid(k) obj_name = obj.watchable_name() except: self._lost_oid(k) else: items.append(obj) return items def _lost_oid(self, oid): """We can't find this oid, remove from list.""" if self.__items.has_key(oid): del self.__items[oid] while oid in self.__footprints: self.__footprints.remove(oid) def footprints(self): """Return list of footprinted items, most recent first.""" items = self._get_items(self.__footprints) items.reverse() return items def add_footprint(self, obj): """Add obj to fooprint list.""" self.__footprints.append(obj._p_oid) if len(self.__footprints) > self._max_footprints: self.__footprints = ConflictAvoidingPersistentList(self.__footprints[-self._max_footprints:]) remove_left_duplicates(self.__footprints) def last_seen(self, item): """Return datetime this item was last seen.""" typecheck(item, Watchable) typecheck(item, Persistent) return self.__items.get(item._p_oid, never) def is_watching(self, item): """Returns true if item is in watch list.""" typecheck(item, Watchable) typecheck(item, Persistent) return item._p_oid in self.__items
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