forked from gergely/calendar-social
		
	WIP: Response visibility calculation
This commit is contained in:
		@@ -123,6 +123,39 @@ EVENT_VISIBILITY_TRANSLATIONS = {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ResponseVisibility(Enum):
 | 
			
		||||
    """Enumeration for response visibility
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    #: The response is only visible to the invitee
 | 
			
		||||
    private = 0
 | 
			
		||||
 | 
			
		||||
    #: The response is only visible to the event organisers
 | 
			
		||||
    organisers = 1
 | 
			
		||||
 | 
			
		||||
    #: The response is only visible to the event attendees
 | 
			
		||||
    attendees = 2
 | 
			
		||||
 | 
			
		||||
    #: The response is visible to the invitee’s friends (ie. mutual follows)
 | 
			
		||||
    friends = 3
 | 
			
		||||
 | 
			
		||||
    #: The response is visible to the invitee’s followers
 | 
			
		||||
    followers = 4
 | 
			
		||||
 | 
			
		||||
    #: The response is visible to anyone
 | 
			
		||||
    public = 5
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
RESPONSE_VISIBILITY_TRANSLATIONS = {
 | 
			
		||||
    ResponseVisibility.private: _('Visible only to myself'),
 | 
			
		||||
    ResponseVisibility.organisers: _('Visible only to event organisers'),
 | 
			
		||||
    ResponseVisibility.attendees: _('Visible only to event attendees'),
 | 
			
		||||
    ResponseVisibility.friends: _('Visible only to my friends'),
 | 
			
		||||
    ResponseVisibility.followers: _('Visible only to my followers'),
 | 
			
		||||
    ResponseVisibility.public: _('Visible to anyone'),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SettingsProxy:
 | 
			
		||||
    """Proxy object to get settings for a user
 | 
			
		||||
    """
 | 
			
		||||
@@ -404,6 +437,45 @@ class Profile(db.Model):  # pylint: disable=too-few-public-methods
 | 
			
		||||
 | 
			
		||||
        return notification
 | 
			
		||||
 | 
			
		||||
    def is_following(self, profile):
 | 
			
		||||
        """Check if this profile is following ``profile``
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            UserFollow.query.filter(UserFollow.follower == self).filter(UserFollow.followed == profile).one()
 | 
			
		||||
        except NoResultFound:
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    def is_friend_of(self, profile):
 | 
			
		||||
        """Check if this profile is friends with ``profile``
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        reverse = db.aliased(UserFollow)
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            UserFollow.query \
 | 
			
		||||
                      .filter(UserFollow.follower == self) \
 | 
			
		||||
                      .join(reverse, UserFollow.followed_id == reverse.follower_id) \
 | 
			
		||||
                      .filter(UserFollow.follower_id == reverse.followed_id) \
 | 
			
		||||
                      .one()
 | 
			
		||||
        except NoResultFound:
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    def is_attending(self, event):
 | 
			
		||||
        """Check if this profile is attending ``event``
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            Response.query.filter(Response.profile == self).filter(Response.event == event).one()
 | 
			
		||||
        except NoResultFound:
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Event(db.Model):
 | 
			
		||||
    """Database model for events
 | 
			
		||||
@@ -791,6 +863,54 @@ class Response(db.Model):  # pylint: disable=too-few-public-methods
 | 
			
		||||
    #: The response itself
 | 
			
		||||
    response = db.Column(db.Enum(ResponseType), nullable=False)
 | 
			
		||||
 | 
			
		||||
    #: The visibility of the response
 | 
			
		||||
    visibility = db.Column(db.Enum(ResponseVisibility), nullable=False)
 | 
			
		||||
 | 
			
		||||
    def visible_to(self, profile):
 | 
			
		||||
        """Checks if the response can be visible to ``profile``.
 | 
			
		||||
 | 
			
		||||
        :param profile: the profile looking at the response.  If None, it is viewed as anonymous
 | 
			
		||||
        :type profile: Profile, None
 | 
			
		||||
        :returns: ``True`` if the response should be visible, ``False`` otherwise
 | 
			
		||||
        :rtype: bool
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        if self.profile == profile:
 | 
			
		||||
            return True
 | 
			
		||||
 | 
			
		||||
        if self.visibility == ResponseVisibility.private:
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        if self.visibility == ResponseVisibility.organisers:
 | 
			
		||||
            return profile == self.event.profile
 | 
			
		||||
 | 
			
		||||
        if self.visibility == ResponseVisibility.attendees:
 | 
			
		||||
            return profile is not None and \
 | 
			
		||||
                (profile.is_attending(self.event) or \
 | 
			
		||||
                 profile == self.event.profile)
 | 
			
		||||
 | 
			
		||||
        # From this point on, if the event is not public, only attendees can see responses
 | 
			
		||||
        if self.event.visibility != EventVisibility.public:
 | 
			
		||||
            return profile is not None and \
 | 
			
		||||
                (profile.is_attending(self.event) or
 | 
			
		||||
                 profile == self.event.profile)
 | 
			
		||||
 | 
			
		||||
        if self.visibility == ResponseVisibility.friends:
 | 
			
		||||
            return profile is not None and \
 | 
			
		||||
                (profile.is_friend_of(self.profile) or \
 | 
			
		||||
                 profile.is_attending(self.event) or \
 | 
			
		||||
                 profile == self.event.profile or \
 | 
			
		||||
                 profile == self.profile)
 | 
			
		||||
 | 
			
		||||
        if self.visibility == ResponseVisibility.followers:
 | 
			
		||||
            return profile is not None and \
 | 
			
		||||
                (profile.is_following(self.profile) or \
 | 
			
		||||
                 profile.is_attending(self.event) or \
 | 
			
		||||
                 profile == self.event.profile or \
 | 
			
		||||
                 profile == self.profile)
 | 
			
		||||
 | 
			
		||||
        return self.visibility == ResponseVisibility.public
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AppState(app_state_base(db.Model)):  # pylint: disable=too-few-public-methods
 | 
			
		||||
    """Database model for application state values
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										175
									
								
								tests/test_response_visibility.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										175
									
								
								tests/test_response_visibility.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,175 @@
 | 
			
		||||
# Calendar.social
 | 
			
		||||
# Copyright (C) 2018  Gergely Polonkai
 | 
			
		||||
#
 | 
			
		||||
# This program is free software: you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU Affero General Public License as published by
 | 
			
		||||
# the Free Software Foundation, either version 3 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 Affero General Public License for more details.
 | 
			
		||||
#
 | 
			
		||||
# You should have received a copy of the GNU Affero General Public License
 | 
			
		||||
# along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | 
			
		||||
 | 
			
		||||
"""Profile related tests for Calendar.social
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from datetime import datetime
 | 
			
		||||
 | 
			
		||||
from calsocial.models import db, Event, EventVisibility, Profile, Response, ResponseType, \
 | 
			
		||||
    ResponseVisibility, UserFollow
 | 
			
		||||
 | 
			
		||||
from helpers import database
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_response_visibility(database):
 | 
			
		||||
    """Test response visibility in different scenarios
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    test_data = (
 | 
			
		||||
        # Third element value descriptions:
 | 
			
		||||
        #     none=not logged in
 | 
			
		||||
        #     unknown=completely unrelated profile
 | 
			
		||||
        #     follower=spectator is following respondent
 | 
			
		||||
        #     friend=spectator and respondent are friends (mutual follow)
 | 
			
		||||
        #     attendee=spectator is an attendee of the event
 | 
			
		||||
        #     respondent=spectator is the respondent
 | 
			
		||||
        (EventVisibility.public, ResponseVisibility.public, 'anon', True),
 | 
			
		||||
        (EventVisibility.public, ResponseVisibility.public, 'unknown', True),
 | 
			
		||||
        (EventVisibility.public, ResponseVisibility.public, 'follower', True),
 | 
			
		||||
        (EventVisibility.public, ResponseVisibility.public, 'friend', True),
 | 
			
		||||
        (EventVisibility.public, ResponseVisibility.public, 'attendee', True),
 | 
			
		||||
        (EventVisibility.public, ResponseVisibility.public, 'organiser', True),
 | 
			
		||||
        (EventVisibility.public, ResponseVisibility.public, 'respondent', True),
 | 
			
		||||
 | 
			
		||||
        (EventVisibility.public, ResponseVisibility.followers, 'anon', False),
 | 
			
		||||
        (EventVisibility.public, ResponseVisibility.followers, 'unknown', False),
 | 
			
		||||
        (EventVisibility.public, ResponseVisibility.followers, 'follower', True),
 | 
			
		||||
        (EventVisibility.public, ResponseVisibility.followers, 'friend', True),
 | 
			
		||||
        (EventVisibility.public, ResponseVisibility.followers, 'attendee', True),
 | 
			
		||||
        (EventVisibility.public, ResponseVisibility.followers, 'organiser', True),
 | 
			
		||||
        (EventVisibility.public, ResponseVisibility.followers, 'respondent', True),
 | 
			
		||||
 | 
			
		||||
        (EventVisibility.public, ResponseVisibility.friends, 'anon', False),
 | 
			
		||||
        (EventVisibility.public, ResponseVisibility.friends, 'unknown', False),
 | 
			
		||||
        (EventVisibility.public, ResponseVisibility.friends, 'follower', False),
 | 
			
		||||
        (EventVisibility.public, ResponseVisibility.friends, 'friend', True),
 | 
			
		||||
        (EventVisibility.public, ResponseVisibility.friends, 'attendee', True),
 | 
			
		||||
        (EventVisibility.public, ResponseVisibility.friends, 'organiser', True),
 | 
			
		||||
        (EventVisibility.public, ResponseVisibility.friends, 'respondent', True),
 | 
			
		||||
 | 
			
		||||
        (EventVisibility.public, ResponseVisibility.attendees, 'anon', False),
 | 
			
		||||
        (EventVisibility.public, ResponseVisibility.attendees, 'unknown', False),
 | 
			
		||||
        (EventVisibility.public, ResponseVisibility.attendees, 'follower', False),
 | 
			
		||||
        (EventVisibility.public, ResponseVisibility.attendees, 'friend', False),
 | 
			
		||||
        (EventVisibility.public, ResponseVisibility.attendees, 'attendee', True),
 | 
			
		||||
        (EventVisibility.public, ResponseVisibility.attendees, 'organiser', True),
 | 
			
		||||
        (EventVisibility.public, ResponseVisibility.attendees, 'respondent', True),
 | 
			
		||||
 | 
			
		||||
        (EventVisibility.public, ResponseVisibility.organisers, 'anon', False),
 | 
			
		||||
        (EventVisibility.public, ResponseVisibility.organisers, 'unknown', False),
 | 
			
		||||
        (EventVisibility.public, ResponseVisibility.organisers, 'follower', False),
 | 
			
		||||
        (EventVisibility.public, ResponseVisibility.organisers, 'friend', False),
 | 
			
		||||
        (EventVisibility.public, ResponseVisibility.organisers, 'attendee', False),
 | 
			
		||||
        (EventVisibility.public, ResponseVisibility.organisers, 'organiser', True),
 | 
			
		||||
        (EventVisibility.public, ResponseVisibility.organisers, 'respondent', True),
 | 
			
		||||
 | 
			
		||||
        (EventVisibility.public, ResponseVisibility.private, 'anon', False),
 | 
			
		||||
        (EventVisibility.public, ResponseVisibility.private, 'unknown', False),
 | 
			
		||||
        (EventVisibility.public, ResponseVisibility.private, 'follower', False),
 | 
			
		||||
        (EventVisibility.public, ResponseVisibility.private, 'friend', False),
 | 
			
		||||
        (EventVisibility.public, ResponseVisibility.private, 'attendee', False),
 | 
			
		||||
        (EventVisibility.public, ResponseVisibility.private, 'organiser', False),
 | 
			
		||||
        (EventVisibility.public, ResponseVisibility.private, 'respondent', True),
 | 
			
		||||
 | 
			
		||||
        (EventVisibility.private, ResponseVisibility.public, 'anon', False),
 | 
			
		||||
        (EventVisibility.private, ResponseVisibility.public, 'unknown', False),
 | 
			
		||||
        (EventVisibility.private, ResponseVisibility.public, 'follower', False),
 | 
			
		||||
        (EventVisibility.private, ResponseVisibility.public, 'friend', False),
 | 
			
		||||
        (EventVisibility.private, ResponseVisibility.public, 'attendee', True),
 | 
			
		||||
        (EventVisibility.private, ResponseVisibility.public, 'organiser', True),
 | 
			
		||||
        (EventVisibility.private, ResponseVisibility.public, 'respondent', True),
 | 
			
		||||
 | 
			
		||||
        (EventVisibility.private, ResponseVisibility.followers, 'anon', False),
 | 
			
		||||
        (EventVisibility.private, ResponseVisibility.followers, 'unknown', False),
 | 
			
		||||
        (EventVisibility.private, ResponseVisibility.followers, 'follower', False),
 | 
			
		||||
        (EventVisibility.private, ResponseVisibility.followers, 'friend', False),
 | 
			
		||||
        (EventVisibility.private, ResponseVisibility.followers, 'attendee', True),
 | 
			
		||||
        (EventVisibility.private, ResponseVisibility.followers, 'organiser', True),
 | 
			
		||||
        (EventVisibility.private, ResponseVisibility.followers, 'respondent', True),
 | 
			
		||||
 | 
			
		||||
        (EventVisibility.private, ResponseVisibility.friends, 'anon', False),
 | 
			
		||||
        (EventVisibility.private, ResponseVisibility.friends, 'unknown', False),
 | 
			
		||||
        (EventVisibility.private, ResponseVisibility.friends, 'follower', False),
 | 
			
		||||
        (EventVisibility.private, ResponseVisibility.friends, 'friend', False),
 | 
			
		||||
        (EventVisibility.private, ResponseVisibility.friends, 'attendee', True),
 | 
			
		||||
        (EventVisibility.private, ResponseVisibility.friends, 'organiser', True),
 | 
			
		||||
        (EventVisibility.private, ResponseVisibility.friends, 'respondent', True),
 | 
			
		||||
 | 
			
		||||
        (EventVisibility.private, ResponseVisibility.attendees, 'anon', False),
 | 
			
		||||
        (EventVisibility.private, ResponseVisibility.attendees, 'unknown', False),
 | 
			
		||||
        (EventVisibility.private, ResponseVisibility.attendees, 'follower', False),
 | 
			
		||||
        (EventVisibility.private, ResponseVisibility.attendees, 'friend', False),
 | 
			
		||||
        (EventVisibility.private, ResponseVisibility.attendees, 'attendee', True),
 | 
			
		||||
        (EventVisibility.private, ResponseVisibility.attendees, 'organiser', True),
 | 
			
		||||
        (EventVisibility.private, ResponseVisibility.attendees, 'respondent', True),
 | 
			
		||||
 | 
			
		||||
        (EventVisibility.private, ResponseVisibility.organisers, 'anon', False),
 | 
			
		||||
        (EventVisibility.private, ResponseVisibility.organisers, 'unknown', False),
 | 
			
		||||
        (EventVisibility.private, ResponseVisibility.organisers, 'follower', False),
 | 
			
		||||
        (EventVisibility.private, ResponseVisibility.organisers, 'friend', False),
 | 
			
		||||
        (EventVisibility.private, ResponseVisibility.organisers, 'attendee', False),
 | 
			
		||||
        (EventVisibility.private, ResponseVisibility.organisers, 'organiser', True),
 | 
			
		||||
        (EventVisibility.private, ResponseVisibility.organisers, 'respondent', True),
 | 
			
		||||
 | 
			
		||||
        (EventVisibility.private, ResponseVisibility.private, 'anon', False),
 | 
			
		||||
        (EventVisibility.private, ResponseVisibility.private, 'unknown', False),
 | 
			
		||||
        (EventVisibility.private, ResponseVisibility.private, 'follower', False),
 | 
			
		||||
        (EventVisibility.private, ResponseVisibility.private, 'friend', False),
 | 
			
		||||
        (EventVisibility.private, ResponseVisibility.private, 'attendee', False),
 | 
			
		||||
        (EventVisibility.private, ResponseVisibility.private, 'organiser', False),
 | 
			
		||||
        (EventVisibility.private, ResponseVisibility.private, 'respondent', True),
 | 
			
		||||
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    for evt_vis, resp_vis, relation, exp_ret in test_data:
 | 
			
		||||
        organiser = Profile(display_name='organiser')
 | 
			
		||||
        event = Event(profile=organiser, visibility=evt_vis, title='Test Event', time_zone='UTC', start_time=datetime.utcnow(), end_time=datetime.utcnow())
 | 
			
		||||
        respondent = Profile(display_name='Respondent')
 | 
			
		||||
        response = Response(event=event, visibility=resp_vis, profile=respondent, response=ResponseType.going)
 | 
			
		||||
 | 
			
		||||
        db.session.add_all([event, response])
 | 
			
		||||
 | 
			
		||||
        if relation is 'anon':
 | 
			
		||||
            spectator = None
 | 
			
		||||
        elif relation == 'respondent':
 | 
			
		||||
            spectator = respondent
 | 
			
		||||
        elif relation == 'organiser':
 | 
			
		||||
            spectator = organiser
 | 
			
		||||
        else:
 | 
			
		||||
            spectator = Profile(display_name='Spectator')
 | 
			
		||||
            db.session.add(spectator)
 | 
			
		||||
 | 
			
		||||
            if relation == 'follower' or relation == 'friend':
 | 
			
		||||
                follow = UserFollow(follower=spectator, followed=respondent)
 | 
			
		||||
                db.session.add(follow)
 | 
			
		||||
 | 
			
		||||
            if relation == 'friend':
 | 
			
		||||
                follow = UserFollow(follower=respondent, followed=spectator)
 | 
			
		||||
                db.session.add(follow)
 | 
			
		||||
 | 
			
		||||
            if relation == 'attendee':
 | 
			
		||||
                att_response = Response(profile=spectator,
 | 
			
		||||
                                    event=event,
 | 
			
		||||
                                    response=ResponseType.going,
 | 
			
		||||
                                    visibility=ResponseVisibility.public)
 | 
			
		||||
                db.session.add(att_response)
 | 
			
		||||
 | 
			
		||||
        db.session.commit()
 | 
			
		||||
 | 
			
		||||
        notvis = ' not' if exp_ret else ''
 | 
			
		||||
        assert_message = f'Response is{notvis} visible to {spectator} ({evt_vis}, {resp_vis}, {relation})'
 | 
			
		||||
        assert response.visible_to(spectator) is exp_ret, assert_message
 | 
			
		||||
		Reference in New Issue
	
	Block a user