def video_delete(self, request): """ API Endpoint to delete a project video """ project = self.get_project(request.project_id, assigned_only=True) video = get_obj_or_api_404(Video.non_trashed_objects.all(), project=project, youtube_id=request.youtube_id) if (not self.current_user.is_superuser and not project.is_owner_or_admin(self.current_user) and self.current_user.pk != video.user_id): raise ForbiddenException video_id = video.pk video.delete(trash=False) remove_video_list_cache(project) publish_appevent(EventKind.VIDEODELETED, object_id=video_id, video_id=video_id, project_id=project.pk, user=self.current_user) return message_types.VoidMessage()
def delete_reply(self, request): """ Deletes a video comment reply """ project = self.get_project(request.project_id, assigned_only=True) video = get_obj_or_api_404( Video.objects.select_related('youtube_video'), project=project, youtube_id=request.youtube_id) reply = get_obj_or_api_404( TimedVideoComment.objects.select_related('user'), tagged_object_id=video.pk, pk=request.reply_id) if (self.current_user != reply.user and not self.current_user.is_superuser): raise ForbiddenException reply.delete() publish_appevent( EventKind.TIMEDVIDEOREPLYCOMMENTDELETED, object_id=reply.pk, video_id=video.pk, project_id=project.pk, meta=request.comment_id, user=self.current_user) return message_types.VoidMessage()
def project_remove_user(self, request): """ Removes a user from the project id: The ProjectUser ID """ project = self.get_project( request.project_id, check_fn=lambda p: p.is_owner_or_admin(self.current_user)) projectuser = get_obj_or_api_404(ProjectUser, pk=request.id) projectuser.delete() if projectuser.user_id: eventbus.publish_appevent(kind=EventKind.USERREMOVED, object_id=projectuser.user_id, project_id=project.pk, user=self.current_user) elif projectuser.pending_user_id: eventbus.publish_appevent(kind=EventKind.PENDINGUSERREMOVED, object_id=projectuser.pending_user_id, project_id=project.pk, user=self.current_user) return message_types.VoidMessage()
def create_comment_reply(self, request): """ Creates a reply to a root comment """ project = self.get_project(request.project_id, assigned_only=True) video = get_obj_or_api_404( Video.objects.select_related('youtube_video'), project=project, youtube_id=request.youtube_id) comment = get_obj_or_api_404( TimedVideoComment.objects.select_related('user'), tagged_object_id=video.pk, pk=request.comment_id) if not comment.is_root(): raise BadRequestException( "Replies can only be added to root comments") reply = comment.add_reply( request.text, self.current_user) publish_appevent( EventKind.TIMEDVIDEOREPLYCOMMENTCREATED, object_id=reply.pk, video_id=video.pk, project_id=project.pk, meta=comment.pk, user=self.current_user ) return self.slim_mapper.map(reply, project=project, video=video)
def create_root_comment(self, request): """ Creates a new root level comment on the video """ project = self.get_project(request.project_id, assigned_only=True) video = get_obj_or_api_404( Video.objects.select_related('youtube_video'), project=project, youtube_id=request.youtube_id) comment = TimedVideoComment.add_root( video=video, start_seconds=request.start_seconds, text=request.text, user=self.current_user) publish_appevent( EventKind.TIMEDVIDEOROOTCOMMENTCREATED, object_id=comment.pk, video_id=video.pk, project_id=project.pk, user=self.current_user ) return self.mapper.map(comment, project=project, video=video)
def video_batch_archive(self, request): """ Archive or unarchive a batch of videos Pass unarchive=true to unarchive """ project = self.get_project(request.project_id, assigned_only=True) is_owner_or_admin = project.is_owner_or_admin(self.current_user) qs = Video.archived_objects if request.unarchive else Video.objects videos = { v.youtube_id: v for v in qs.filter(project_id=project.pk, youtube_id__in=request.youtube_ids) } does_not_exist = [ make_batch_response_message(yt_id, not_found=True) for yt_id in set(request.youtube_ids) - set(videos.keys()) ] video_ids_to_update, permission_denied, success = [], [], [] for yt_id, video in videos.items(): if self.current_user.id != video.user_id and not ( self.current_user.is_superuser or is_owner_or_admin): permission_denied.append( make_batch_response_message(yt_id, forbidden=True)) else: video_ids_to_update.append(video.pk) success.append(make_batch_response_message(yt_id, success=True)) (qs.filter(project=project, pk__in=video_ids_to_update).update( archived_at=None if request.unarchive else timezone.now())) remove_video_list_cache(project) for video_id in video_ids_to_update: publish_appevent(kind=EventKind.VIDEOUNARCHIVED if request.unarchive else EventKind.VIDEOARCHIVED, object_id=video_id, video_id=video_id, project_id=project.pk, user=self.current_user) videos = (Video.all_objects.select_related("youtube_video").filter( pk__in=video_ids_to_update) if video_ids_to_update else []) return VideoBatchListResponseMessage( items=success + does_not_exist + permission_denied, videos=map(self.mapper.map, videos), is_list=True)
def filter_expired_collaborators(self, collaborators): now = datetime.datetime.utcnow() for k, v in collaborators.items(): if (now - v['timestamp']) > self.collaborator_expiry: publish_appevent( self.offline_event_kind, object_id=collaborators[k]['id'], project_id=self.object_id, meta=collaborators[k]['id'], user_id=collaborators[k]['id']) del collaborators[k] return collaborators
def patch_comment(self, request): """ Patches any video comment """ project = self.get_project(request.project_id, assigned_only=True) video = get_obj_or_api_404( Video.objects.select_related('youtube_video'), project=project, youtube_id=request.youtube_id) comment = get_obj_or_api_404( TimedVideoComment.objects.select_related('user'), tagged_object_id=video.pk, pk=request.comment_id) if (self.current_user != comment.user and not self.current_user.is_superuser): raise ForbiddenException if request.text is not None: comment.text = request.text if (request.start_seconds is not None and comment.start_seconds != request.start_seconds and comment.is_root()): # update start_seconds on all children comment.start_seconds = request.start_seconds reply_ids = list( comment.get_children().values_list('pk', flat=True)) (TimedVideoComment.objects .filter(pk__in=reply_ids) .update(start_seconds=request.start_seconds)) if request.text is not None or request.start_seconds is not None: comment.save() publish_appevent( EventKind.TIMEDVIDEOCOMMENTUPDATED, object_id=comment.pk, video_id=video.pk, project_id=project.pk, user=self.current_user) return self.mapper.map(comment, project=project, video=video)
def dispatch(self, request, **kwargs): """ Override disptach to validate export format and set user. While generally not good practice, we set CSRF as exempt because no POST requests handled by this view modify any server side data. """ self.user = users.get_current_user() self.format = request.POST.get('format', 'csv').lower() eventbus.publish_appevent(kind=EventKind.USEREXPORTEDVIDEOS, object_id=request.user.pk, project_id=kwargs['project_id'], user=request.user, meta=self.format) if self.format not in ('csv', 'kml'): return HttpResponseBadRequest("Format not valid") return super(BaseExportView, self).dispatch(request, **kwargs)
def video_unarchive(self, request): """ Unarchives the given video """ project = self.get_project(request.project_id, assigned_only=True) video = get_obj_or_api_404(Video.archived_objects.all(), youtube_id=request.youtube_id, project=project) video.unarchive() remove_video_list_cache(project) publish_appevent(kind=EventKind.VIDEOUNARCHIVED, object_id=video.pk, video_id=video.pk, project_id=project.pk, user=self.current_user) return message_types.VoidMessage()
def video_patch(self, request): """ API Endpoint to patch a project video """ project = self.get_project(request.project_id, assigned_only=True) video = get_obj_or_api_404( Video.objects.select_related("youtube_video"), youtube_id=request.youtube_id, project_id=project.pk) if request.location_overridden is not None: video.location_overridden = request.location_overridden if request.recorded_date_overridden is not None: video.recorded_date_overridden = request.recorded_date_overridden if request.precise_location is not None: video.precise_location = request.precise_location if video.location_overridden: if request.latitude is not None: video.latitude = request.latitude if request.longitude is not None: video.longitude = request.longitude else: video.latitude = video.longitude = None if video.recorded_date_overridden: video.recorded_date = request.recorded_date else: video.recorded_date = None video.save() publish_appevent(EventKind.VIDEOUPDATED, object_id=video.pk, video_id=video.pk, project_id=project.pk, user=self.current_user) return self.mapper.map(video)
def _remove_collaborator(): collaborators = self.get_collaborators() if token in collaborators: user_id = collaborators[token]['id'] del collaborators[token] ret = self.client.cas( self.key, collaborators, namespace=self.namespace ) if ret: publish_appevent( self.offline_event_kind, object_id=user_id, project_id=self.object_id, meta=user_id, user_id=user_id) return ret
def _add_collaborator(): collaborators = self.get_collaborators() # prevent duplicate collaborator entries if a user has multiple windows open for k, v in collaborators.items(): if v['id'] == user.pk: del collaborators[k] collaborators[token] = user_dict ret = self.client.cas( self.key, collaborators, namespace=self.namespace ) if ret: publish_appevent( self.online_event_kind, object_id=user.pk, project_id=self.object_id, meta=user.pk, user=user) return ret
def video_batch_create(self, request): project = self.get_project(request.project_id, assigned_only=True) duplicate_videos = { v.youtube_video.youtube_id: v for v in Video.objects.filter( project=project, youtube_video__youtube_id__in=request.youtube_ids) } success, error, videos = [], [], [] for youtube_id in request.youtube_ids: if youtube_id in duplicate_videos: video = duplicate_videos[youtube_id] error.append( make_batch_response_message( video.youtube_id, error="The video '{0}' already exists in this project". format(youtube_id))) videos.append(duplicate_videos[youtube_id]) continue youtube_ids_of_videos_to_add = [ yt_id for yt_id in request.youtube_ids if yt_id not in duplicate_videos ] youtube_client = YouTubeClient() youtube_videos = { v.youtube_id: v for v in youtube_client.update_or_create_videos( youtube_ids_of_videos_to_add) } for yt_id, yt_video in youtube_videos.items(): try: video = (Video.all_objects.get(youtube_video__youtube_id=yt_id, project_id=project.pk)) except Video.DoesNotExist: video = Video( project_id=project.pk, youtube_video=yt_video, youtube_id=yt_video.youtube_id, user_id=self.current_user.pk, ) else: if not (video.trashed_at or video.archived_at): error.append( make_batch_response_message( None, error="The video '{0}' already exists in " "this project".format(yt_id))) else: # if it's trashed or archived then restore it video.archived_at = video.trashed_at = None video.save() # attach youtube video for mapping response video.youtube_video = yt_video videos.append(video) success.append( make_batch_response_message(video.youtube_id, success=True)) publish_appevent(EventKind.VIDEOCREATED, object_id=video.pk, video_id=video.pk, project_id=project.pk, user=self.current_user) remove_video_list_cache(project) return VideoBatchListResponseMessage(items=success + error, videos=map( self.mapper.map, videos), is_list=True)
def project_add_user(self, request): project_users = [] for email in get_emails(request.email): """ Adds a user to the project """ project = self.get_project( request.project_id, check_fn=lambda p: p.is_owner_or_admin(self.current_user)) def send_invite_email(): send_email( "You've been invited to collaborate on a Montage project", EXISTING_USER_INVITED.format( project_name=project.name, home_link='https://montage.storyful.com'), email) User = get_user_model() project_user = None try: user = User.objects.get(email=email) project_user = (project.add_admin if request.as_admin else project.add_assigned)(user) eventbus.publish_appevent( kind=EventKind.USERINVITEDASPROJECTADMIN if request.as_admin else EventKind.USERINVITEDASPROJECTUSER, object_id=user.pk, project_id=project.pk, user=self.current_user) send_invite_email() except User.DoesNotExist: pending_user, created = User.objects.get_or_create( email=email, username=email) project_user = (project.add_admin if request.as_admin else project.add_assigned)(pending_user) eventbus.publish_appevent( kind=EventKind.PENDINGUSERINVITEDASPROJECTADMIN if request.as_admin else EventKind.PENDINGUSERINVITEDASPROJECTUSER, object_id=pending_user.pk, project_id=project.pk, meta=pending_user.email, user=self.current_user) if created: send_email( "You've been invited to join Montage", NEW_USER_INVITED.format( project_name=project.name, home_link='https://montage.storyful.com'), pending_user.email) else: send_invite_email() project_users.append(project_user) return ProjectUserListResponse(items=map(self.user_mapper.map, project_users), is_list=True)