Example #1
0
 def __init__(self):
     GenericDB.__init__(self)
     HasOwnership.__init__(self)
     self.mod_queue = SponsoredQueue()
     self.invitations = Invitations()
Example #2
0
class GroupDB(GenericDB, HasOwnership):
    """Database for Groups (qon.group.Group).
    
    When a new group is added to the database, it is automatically
    added to the moderation queue.
    
    Users must have write permission on db in order to create groups.
    """
    
    _karma_new_group        = 10    # cost to create new group
    _karma_sponsor_group    = 5     # cost to sponsor group
    _karma_pm_group         = 1     # cost to PM entire group

    def __init__(self):
        GenericDB.__init__(self)
        HasOwnership.__init__(self)
        self.mod_queue = SponsoredQueue()
        self.invitations = Invitations()
        
    def get_group(self, group_id):
        """Return the group with given group_id, or None if not found."""
        return self.root.get(group_id)

    def add_group(self, group):
        self[group.user_id] = group
        self.mod_queue.add_to_queue(group)
        
    def remove_group(self, group):  
        del self[group.user_id]
        self.mod_queue.remove_from_queue(group)
        
        # quit groups that group is a member of
        for g in self.member_groups(group):
            self.leave_group(g, group)
            
        # remove members from group
        for member in group.get_member_list():
            self.leave_group(member, group)

        # remove owners from group
        for owner in group.get_owners():
            self.leave_group(owner, group)
        
        # delete usergroup - must be last, since leave_group needs it
        get_usergroup_database().remove_usergroup(group.user_id)
            
    def active_groups(self):
        return [self[group] for group in self.root.keys() if self[group].get_state() == 'accepted']

    def recently_active_groups(self, user=None, days_cutoff=3):
        cutoff_date = datetime.utcnow() - timedelta(days=days_cutoff)
        return [self[group] for group in self.root.keys() if self[group].can_read(user) and self[group].watchable_last_change() > cutoff_date]        

    def recently_active_tuples(self, user=None, days_cutoff=3):
        """Returns a sorted list of (group, active_discussions, active_pages) tuples
        for groups that have been active in the last X days and are readdable by the given user"""
        cutoff_date = datetime.utcnow() - timedelta(days=days_cutoff)
        readable_active_tuples = [(g, g.blog.num_active_items(days=days_cutoff, consider_comments=True)[0], g.wiki.num_active_pages(days=days_cutoff)[0]) for g in self.active_groups() if g.can_read(user) and g.watchable_last_change() > cutoff_date]        
        sorted_group_tuples = qon.util.sort_list(readable_active_tuples, lambda x: x[0].watchable_last_change())
        return sorted_group_tuples     
    
    def recent_groups(self, age):
        """Recent recently-created groups not older than age."""
        cutoff = datetime.utcnow() - age
        groups = [g for g in self.active_groups() if g.date > cutoff]
        return qon.util.sort_list(groups, lambda x: x.date)
    
    def active_groups_anon(self):
        """Active groups available for anonymous browsing"""
        groups = self.active_groups()
        return [group for group in groups if group.anon_read]
        
    def rejected_groups(self):
        return [self[group] for group in self.root.keys() if self[group].get_state() == 'rejected']

    def limbo_groups(self):
        return [self[group] for group in self.root.keys() if self[group].get_state() == 'limbo']

    def top_level_groups(self):
        """ Return top-level groups.  Currently hard-coded. """        
        top_level_group_ids = ('issues-business', 'issues-cyf', 'issues-education', 'issues-env', \
                               'issues-health', 'issues-pol', 'regional', 'issues-religion', \
                               'issues-soc', 'issues-tech', 'issues-general', \
                               'private', 'public', 'social', 'orgs-general', \
                               'help', 'community-general', 'suggestions', \
                               'general-other')
        top_level = []
        for gid in top_level_group_ids:
            g = self.get_group(gid)
            if g:
                top_level.append(g)

        return top_level
    
    #
    # permission rules
    #
    can_create_group = HasOwnership.can_write
    
    #
    # group management
    #
    def can_pay_for_new_group(self, user):
        return user.can_give_karma(self._karma_new_group)
        
    def can_pay_to_sponsor_group(self, user):
        return user.can_give_karma(self._karma_sponsor_group)
        
    def can_pay_for_pm_group(self, user):
        return user.can_give_karma(self._karma_pm_group)

    def create_group(self, user_id, name, owner, description='',
        member_perms=[],
        other_perms=[],
        member_join_perms=[],
        other_join_perms=[],
        anon_read=0,
        no_pay=False):
        """Create a new group.
        
        member_perms, other_perms: see qon.group.HasAccessPolicy
        member_join_perms, other_join_Perms: see qon.group.HasMembership
        """
        
        user_id = user_id.lower()
        
        if self.root.has_key(user_id) or get_usergroup_database().has_key(user_id):
            raise KeyError, "Key %s already exists." % user_id

        if not no_pay:
            # charge owner for new group - don't create group if can't pay
            from qon.karma import NoKarmaToGive
            try:
                owner.pay_karma(self._karma_new_group)
            except NoKarmaToGive:
                return None

        group = Group(user_id=user_id, name=name, owner=owner)
        group.add_owner(owner)
        group.add_sponsor(owner)
        group.anon_read = anon_read
        group.description = description
        
        usergroup = UserGroup(group.user_id)
        get_usergroup_database().add_usergroup(usergroup)
        group.set_owning_group(usergroup)
        
        # members must have at least read access -- otherwise, what's the point of membership?
        mem_perms = member_perms
        if 'read' not in mem_perms:
            mem_perms.append('read')
        
        group.set_group_perms(mem_perms)
        group.set_other_perms(other_perms)
        group.get_members().set_group_perms(member_join_perms)
        group.get_members().set_other_perms(other_join_perms)
        
        # flush owner's group list cache
        self._flush_user_data_caches(owner)

        self.add_group(group)
        return group
        
    def add_sponsor(self, group, sponsor):
        """Add sponsor to group. Returns True on success"""
        
        # check if already a sponsor so we don't pay twice
        if group.is_accepted() or (sponsor in group.get_sponsors()):
            return True
        
        # pay for sponsorship
        from qon.karma import NoKarmaToGive
        try:
            sponsor.pay_karma(self._karma_sponsor_group)
        except NoKarmaToGive:
            return False
        
        self.mod_queue.add_sponsor(group, sponsor)
        
        # if group got enough sponsors:
        if group.is_accepted():
            self._sponsors_to_members(group)
        
        return True

    def force_accept(self, group):
        """Force a Sponsored group into accepted state."""
        self.mod_queue.force_accept(group)
        
        # sponsors become members
        if group.is_accepted():
            self._sponsors_to_members(group)
    
    def _sponsors_to_members(self, group):
        if group.is_accepted():
            for user in group.get_sponsors():
                if not group.is_owner(user):
                    self.join_group(user, group, force=1)
    
    #
    # user/owner/sponsor management
    #
    def owned_groups(self, user):
        """Return list of groups owned by owner. owner can be a User or Group."""
        
        if not hasattr(user, '_group_owned_groups'):
            user._group_owned_groups = [self[group] for group in self.root.keys() \
                if self[group].is_owner(user)]
            
        return user._group_owned_groups

    def member_groups(self, user):
        """Return list of groups user is a member of. user can be a User or Group."""
        
        if not hasattr(user, '_group_member_groups'):
            # be sure to use "slow" version of is_member to prevent it recursing back
            user._group_member_groups = [self[group] for group in self.root.keys() \
                if self[group].is_member(user, slow=True)]

        return user._group_member_groups

    def users_groups(self, user):
        """Return groups relevant to user: owned + member, with duplicates removed."""
        return qon.util.unique_items(self.owned_groups(user) + self.member_groups(user))
    
    def users_invitations(self, user):
        """Return groups user has been invited to join."""
        return self.invitations.get_invitations(user, self)
    
    def join_group(self, user, group, force=0):
        """User joins group. Disable can_join check by passing True to force."""
        if not force and not group.can_join(user):
            raise NotEnoughPrivileges
            
        group.add_member(user)
        user.add_to_group(get_usergroup_database().get_usergroup(group.get_user_id()))
        if hasattr(user, 'karma_activity_credit'):
            # groups can join groups, and groups don't have karma_activity_credit
            user.karma_activity_credit()
        
        self._flush_user_data_caches(user)

    def _flush_user_data_caches(self, user):
        if hasattr(user, '_group_owned_groups'):
            del user._group_owned_groups
        if hasattr(user, '_group_member_groups'):
            del user._group_member_groups
            
    def decline_invitation(self, user, group):  
        """Decline an invitation to join group. Removes invitations."""
        if group.is_invited(user):
            group.remove_invitation(user)

    def leave_group(self, user, group):
        group.remove_member(user)
        user.remove_from_group(get_usergroup_database().get_usergroup(group.get_user_id()))
        self._flush_user_data_caches(user)
        
    def set_group_owners(self, group, users):
        """Set users as owners of group."""
        
        # Ensure that if we are removing an existing owner, he remains a member
        for u in group.owners:
            if u not in users:
                group.add_member(u)
                self._flush_user_data_caches(u)

        group.set_owner(users)
        for u in users:
            self._flush_user_data_caches(u)
                    
    def notify_new_user(self, user):
        """Called by user_db when a new user is created."""
        # join to default group
        g = self.root.get('community-general')
        if g:
            self.join_group(user, g)
            
    def notify_email_confirmed(self, user, email):
        """Notice user just confirmed email."""
        
        # make sure user isn't still invited to groups he owns or is a member of
        for g in self.users_groups(user):
            g.remove_invitation(user)