Compare commits
	
		
			2 Commits
		
	
	
		
			master
			...
			response-v
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 45eb411eb2 | |||
| 69a1efcd93 | 
| @@ -70,6 +70,18 @@ class NotificationAction(Enum): | ||||
|     #: A user has been invited to an event | ||||
|     invite = 2 | ||||
|  | ||||
|     def __hash__(self): | ||||
|         return Enum.__hash__(self) | ||||
|  | ||||
|     def __eq__(self, other): | ||||
|         if isinstance(other, str): | ||||
|             return self.name.lower() == other.lower()  # pylint: disable=no-member | ||||
|  | ||||
|         if isinstance(other, (int, float)): | ||||
|             return self.value == other | ||||
|  | ||||
|         return Enum.__eq__(self, other) | ||||
|  | ||||
|  | ||||
| NOTIFICATION_ACTION_MESSAGES = { | ||||
|     NotificationAction.follow: (_('%(actor)s followed you'), _('%(actor)s followed %(item)s')), | ||||
| @@ -123,6 +135,80 @@ 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 EventAvailability(Enum): | ||||
|     """Enumeration of event availabilities | ||||
|     """ | ||||
|  | ||||
|     free = 0 | ||||
|     busy = 1 | ||||
|  | ||||
|     def __hash__(self): | ||||
|         return Enum.__hash__(self) | ||||
|  | ||||
|     def __eq__(self, other): | ||||
|         if isinstance(other, str): | ||||
|             return self.name.lower() == other.lower()  # pylint: disable=no-member | ||||
|  | ||||
|         if isinstance(other, (int, float)): | ||||
|             return self.value == other | ||||
|  | ||||
|         return Enum.__eq__(self, other) | ||||
|  | ||||
|  | ||||
| class UserAvailability(Enum): | ||||
|     """Enumeration of user availabilities | ||||
|     """ | ||||
|  | ||||
|     free = 0 | ||||
|     busy = 1 | ||||
|     tentative = 2 | ||||
|  | ||||
|     def __hash__(self): | ||||
|         return Enum.__hash__(self) | ||||
|  | ||||
|     def __eq__(self, other): | ||||
|         if isinstance(other, str): | ||||
|             return self.name.lower() == other.lower()  # pylint: disable=no-member | ||||
|  | ||||
|         if isinstance(other, (int, float)): | ||||
|             return self.value == other | ||||
|  | ||||
|         return Enum.__eq__(self, other) | ||||
|  | ||||
|  | ||||
| class SettingsProxy: | ||||
|     """Proxy object to get settings for a user | ||||
|     """ | ||||
| @@ -404,6 +490,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 +916,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