def add_youtube_photo(client_upload_id, storage_id, author, album, now, youtube_id): def get_next_album_index(album): album_index_q = Photo.objects.filter(album=album).aggregate(Max("album_index")) max_album_index = album_index_q["album_index__max"] if max_album_index is None: return 0 else: return max_album_index + 1 success = False while not success: try: with transaction.atomic(): next_album_index = get_next_album_index(album) p, created = Photo.objects.get_or_create( storage_id=storage_id, defaults={ "photo_id": Photo.generate_photo_id(), "media_type": Photo.MEDIA_TYPE_YOUTUBE, "client_upload_id": client_upload_id, "subdomain": Photo.choose_random_subdomain(), "date_created": now, "author": author, "album": album, "album_index": next_album_index, "youtube_id": youtube_id, }, ) except IntegrityError: # This will happen if there is a collision with a duplicate # 'album_index' from a concurrent request success = False else: success = True if created: if not in_testing_mode(): # Update the photo servers: for photo_server in PhotoServer.objects.filter(subdomain=p.subdomain, unreachable=False): # TODO We should use concurrent requests for this num_retries = 5 initial_retry_time = 4 try: request_with_n_retries( num_retries, initial_retry_time, lambda: photo_server_set_photos(photo_server.photos_update_url, photo_server.auth_key, [p]), ) except requests.exceptions.RequestException: # TODO Log this photo_server.set_unreachable() album.save_revision(now, True) photos_added_to_album.send(sender=None, photos=[p.photo_id], by_user=author, to_album=album)
def add_photos_to_db(self, photo_ids): """ Returns a dictionary from subdomain values to lists of Photo objects """ added_photos = {} album = Album.objects.get(pk=self.album_id) album_index_q = Photo.objects.filter(album=album).aggregate(Max("album_index")) max_album_index = album_index_q["album_index__max"] if max_album_index is None: next_album_index = 0 else: next_album_index = max_album_index + 1 for photo_id in photo_ids: try: pending_photo = PendingPhoto.objects.get(photo_id=photo_id) except PendingPhoto.DoesNotExist: try: Photo.objects.get(pk=photo_id) except Photo.DoesNotExist: raise InvalidPhotoIdAddPhotoException() else: pass else: try: with transaction.atomic(): chosen_subdomain = Photo.choose_random_subdomain() p = Photo.objects.create( photo_id=photo_id, media_type=Photo.MEDIA_TYPE_PHOTO, client_upload_id="", storage_id=pending_photo.storage_id, subdomain=chosen_subdomain, date_created=self.date_created, author=pending_photo.author, album=album, album_index=next_album_index, ) if chosen_subdomain in added_photos: added_photos[chosen_subdomain].append(p) else: added_photos[chosen_subdomain] = [p] except IntegrityError: t, v, tb = sys.exc_info() # Two possible scenarios: # # 1) The album_index we tried to add was already added (by a # concurrent request) # # 2) photo_id was already added (by a concurrent request) try: Photo.objects.get(pk=photo_id) except Photo.DoesNotExist: # This is most likely case (1). We let the original # exception bubble up (the calling code will retry this function) raise t, v, tb else: # This is case (2) # The photo is already added, so there is nothing to do pass else: # Notice that this is only incremented if a new object was # actually inserted next_album_index += 1 # This is safe to call even if it was already deleted by a # concurrent request (will be a nop) pending_photo.delete() return added_photos