def _resize_image(cls, image, image_handler, truck, size): session = Client().session() (width, height) = image_handler.size (new_width, new_height) = cls._new_dimensions(width, height, RESIZES[size]) print 'resizing to %s' % size resized = image_handler.resize((new_width, new_height), ImageHandler.ANTIALIAS) (_, contents_file) = tempfile.mkstemp() try: resized.save(contents_file, image_handler.format) with open(contents_file, 'r') as contents: print 'uploading resized image' truck.upload_resize(contents.read(), size) finally: os.unlink(contents_file) resize = ImageResize(image_id=image.image_id, width=new_width, height=new_height, suffix=size) session.add(resize) session.flush()
def test_edit_title(self): session = Client().session() silly = Image(filename="silly", title="Silly Picture") session.add(silly) session.flush() self.visit_url('/image/{0}'.format(silly.image_id)) self.browser.click_link_by_text('Edit') caption = self.browser.find_by_id('caption') assert not caption.visible, "Caption header didn't disappear!" title_field = self.browser.find_by_css('input[name="title"]') assert title_field.visible, "No title-edit field!" eq_(title_field.value, 'Silly Picture') title_field.fill('Goofy Picture\n') self.browser.click_link_by_text('Stop Editing') assert not title_field.visible, "Title field didn't go away!" eq_(caption.text, 'Goofy Picture') eq_(self.browser.title, 'Goofy Picture - Catsnap') session.refresh(silly) eq_(silly.title, 'Goofy Picture')
def test_edit_description(self): session = Client().session() silly = Image(filename="silly", title="Silly Picture") session.add(silly) session.flush() self.visit_url('/image/{0}'.format(silly.image_id)) self.browser.click_link_by_text('Edit') description_field = self.browser.find_by_id('description') assert description_field.visible, "No description-edit field!" description_field.fill('This is silly to do.\nWhy is it done?') self.browser.click_link_by_text('Stop Editing') session.refresh(silly) eq_(silly.description, 'This is silly to do.\nWhy is it done?') description_paras = self.browser.find_by_css('p.image-description') eq_([p.text for p in description_paras], [ 'This is silly to do.', 'Why is it done?' ])
def test_view_an_image(self): session = Client().session() album = Album(name="tophos") session.add(album) session.flush() silly = Image(album_id=album.album_id, filename="silly") session.add(silly) session.flush() self.visit_url('/image/{0}'.format(silly.image_id)) images = self.browser.find_by_tag('img') eq_(map(lambda i: i['src'], images), [ 'https://s3.amazonaws.com/humptydump/silly', ]) eq_(map(lambda i: i['alt'], images), ['silly']) assert self.browser.is_text_present('silly') edit_button = self.browser.find_by_id('edit') assert not edit_button, "Edit button visible to logged-out user!" title_field = self.browser.find_by_css('input[name="title"]') assert not title_field.visible, "Edit controls visible to logged-out user!"
def test_add_tag_to_an_untagged_image(self): session = Client().session() pic = Image(filename="tagless", title="Untagged Picture") session.add(pic) session.flush() self.visit_url('/image/{0}'.format(pic.image_id)) tag_button = self.browser.find_by_id('tag-button') assert tag_button, "Couldn't find a button for listing tags!" tag_button.click() tag_list = self.browser.find_by_css('ul.view-tags') assert not tag_list.visible, "Popped up an empty tag dropdown!" self.browser.click_link_by_text('Edit') add_tag = self.browser.find_by_css('a.add-tag') add_tag.click() tag_input = self.browser.find_by_id('tag')[0] tag_input.fill('untagged\n') self.browser.click_link_by_text('Stop Editing') tag_button.click() tags = self.browser.find_by_css('li.tag') assert all([t.visible for t in tags]), "Tag listing was not visible!" eq_([t.text for t in tags], ['untagged'])
def test_add_and_remove_a_tag(self): session = Client().session() image = Image(filename='bab1e5') session.add(image) session.flush() self.visit_url('/image/%d' % image.image_id) assert self.browser.is_text_present('(click to add a tag)'),\ "Didn't find tag-adder" add_tag = self.browser.find_by_css('#add-tag') add_tag.click() tag_name = self.browser.find_by_css('li input') tag_name.fill("booya\n") new_tag = self.browser.find_by_css('li span').first eq_(new_tag.text, 'booya') assert self.browser.is_text_present('(click to add a tag)'),\ "Didn't find new tag-adder" image_tag = session.query(ImageTag).\ join(Tag, Tag.tag_id == ImageTag.tag_id).\ filter(Tag.name == 'booya').\ one() eq_(image_tag.image_id, image.image_id) self.browser.find_link_by_text('x').first.click() assert not self.browser.is_text_present('booya'), \ "Tag wasn't deleted from the page" image_tags = session.query(ImageTag).all() eq_(image_tags, [])
def test_add_tag_to_an_untagged_image(self): session = Client().session() pic = Image(filename="tagless", title="Untagged Picture") session.add(pic) session.flush() self.visit_url('/image/{0}'.format(pic.image_id)) tag_button = self.browser.find_by_id('tag-button') assert tag_button, "Couldn't find a button for listing tags!" assert tag_button.has_class("disabled"), \ "Tag button enabled without tags!" self.browser.click_link_by_text('Edit') add_tag = self.browser.find_by_css('a.add-tag') add_tag.click() tag_input = self.browser.find_by_id('tag')[0] tag_input.type('untagged') tag_input.type(Keys.ENTER) self.browser.click_link_by_text('Stop Editing') tag_button.click() tags = self.browser.find_by_css('li.tag') assert all([t.visible for t in tags]), "Tag listing was not visible!" eq_([t.text for t in tags], ['untagged'])
def test_edit_the_description(self): session = Client().session() image = Image(filename='deafbeef') session.add(image) session.flush() self.visit_url('/image/%d' % image.image_id) assert self.browser.is_text_present('(click to add description)'), \ "Didn't find description placeholder" description = self.browser.find_by_css('#description span') assert description, "Didn't find description" description.click() description_edit = self.browser.find_by_css('#description textarea') assert description_edit, "Didn't find a description-edit field!" eq_(description_edit.value, '(click to add description)') description_edit.fill("this image fills me with glee\t") description = self.browser.find_by_css('#description span') assert description, "Didn't find description after editing" description.click() description_edit = self.browser.find_by_css('#description textarea') assert description_edit, "Didn't find a description-edit field after editing" eq_(description_edit.value, 'this image fills me with glee') description_edit.fill("this image makes me sad\t") image = session.query(Image).\ filter(Image.image_id == image.image_id).\ one() eq_(image.description, 'this image makes me sad')
def test_edit_the_title(self): session = Client().session() image = Image(filename='asdf', title='click on this!') session.add(image) session.flush() self.visit_url('/image/%d' % image.image_id) assert self.browser.is_text_present('click on this!'), \ "Didn't find expected title" title = self.browser.find_by_css('h2').first assert title, "Didn't find a title" title.click() title_edit_field = self.browser.find_by_css('header input') assert title_edit_field, "Didn't find a title-edit field!" eq_(title_edit_field.value, 'click on this!') title_edit_field.fill('I clicked\n') title = self.browser.find_by_css('h2').first assert title, "Didn't find a title after first edit" title.click() title_edit_field = self.browser.find_by_css('header input') assert title_edit_field, "didn't find a title-edit field after first edit" eq_(title_edit_field.value, 'I clicked') title_edit_field.fill("I clicked TWICE\n") image = session.query(Image).\ filter(Image.image_id == image.image_id).\ one() eq_(image.title, 'I clicked TWICE')
def test_update_an_image(self): session = Client().session() album = Album(name='cow shots') session.add(album) image = Image(filename='deadbeef', description='one time I saw a dead cow', title='dead beef') session.add(image) session.flush() response = self.app.patch('/image/%d.json' % image.image_id, data={ 'album_id': album.album_id, }) body = json.loads(response.data) eq_(body, { 'status': 'ok', 'image': { 'title': 'dead beef', 'description': 'one time I saw a dead cow', 'album_id': str(album.album_id), 'caption': 'dead beef', 'tags': [], } }) del image image = session.query(Image).one() eq_(image.album_id, album.album_id)
def file_type_test(self, bucket_method, test_file_name, content_type, resized_size): bucket = Mock() bucket_method.return_value = bucket new_key = Mock() bucket.new_key.return_value = new_key test_file = os.path.join(os.path.dirname(__file__), test_file_name) image_handler = ImageHandler.open(test_file) with open(test_file, 'r') as fh: truck = ImageTruck.new_from_stream(fh, content_type) session = Client().session() image = ImageTable(filename='badcafe') session.add(image) session.flush() ResizeImage._resize_image(image, image_handler, truck, 'thumbnail') new_key.set_metadata.assert_called_with('Content-Type', content_type) resized_contents = new_key.set_contents_from_string.call_args[0][0] image_handler = ImageHandler.open(StringIO.StringIO(resized_contents)) eq_(image_handler.size, resized_size)
def test_caption__defaults_to_title(self): session = Client().session() image = Image(title='the title', filename='') session.add(image) image.add_tags(['cat', 'awesome']) eq_(image.caption(), 'the title')
def file_type_test(self, bucket_method, test_file, content_type, resized_size): bucket = Mock() bucket_method.return_value = bucket new_key = Mock() bucket.new_key.return_value = new_key image_handler = ImageHandler(filename=test_file) truck = ImageTruck.new_from_file(test_file) session = Client().session() image = ImageTable(filename='badcafe') session.add(image) session.flush() after_upload = Mock() ResizeImage._resize_image(image, image_handler, truck, 'thumbnail', after_upload) new_key.set_metadata.assert_called_with('Content-Type', content_type) resized_contents = new_key.set_contents_from_string.call_args[0][0] image_handler = ImageHandler(blob=resized_contents) eq_(image_handler.size, resized_size) after_upload.assert_called_once_with('thumbnail')
def process_image(self, image_contents_id): session = Client().session() contents = session.query(ImageContents).\ filter(ImageContents.image_contents_id == image_contents_id).\ one() image = session.query(Image).\ filter(Image.image_id == contents.image_id).\ one() truck = ImageTruck( contents.contents, contents.content_type, image.source_url) truck.filename = image.filename metadata = ImageMetadata.image_metadata(truck.contents) truck.contents = ReorientImage.reorient_image(truck.contents) def after_upload(size): redis.publish(REDIS_CHANNEL, json.dumps({ 'task_id': self.request.id, 'suffix': size, })) ResizeImage.make_resizes(image, truck, after_upload) print "uploading original image" truck.upload() redis.publish(REDIS_CHANNEL, json.dumps({ 'task_id': self.request.id, 'suffix': '', })) delay(queued_tasks, Invalidate(), image.image_id) for attr, value in metadata.iteritems(): setattr(image, attr, value) session.add(image) session.delete(contents)
def test_get_image_info_as_json(self): session = Client().session() album = Album(name='cow shots') session.add(album) session.flush() image = Image(filename='deadbeef', description='one time I saw a dead cow', title='dead beef', album_id=album.album_id) session.add(image) image.add_tags(['cow', 'dead']) session.flush() response = self.app.get('/image/%d.json' % image.image_id) eq_(json.loads(response.data), { 'description': 'one time I saw a dead cow', 'title': 'dead beef', 'album_id': album.album_id, 'tags': [ 'cow', 'dead', ], 'source_url': 'https://s3.amazonaws.com/snapcats/deadbeef', 'camera': None, 'photographed_at': None, 'focal_length': None, 'aperture': None, 'shutter_speed': None, 'iso': None, })
def add(request_format): tag_names = request.form['tags'].split(' ') url = request.form['url'] if url: print 'fetching from remote url' truck = ImageTruck.new_from_url(url) elif request.files['file']: image = request.files['file'] truck = ImageTruck.new_from_stream(image.stream, image.mimetype) else: abort(400) metadata = ImageMetadata.image_metadata(truck.contents) print 'potentially reorienting' truck.contents = ReorientImage.reorient_image(truck.contents) print 'uploading to s3' truck.upload() session = Client().session() image = Image(filename=truck.calculate_filename(), source_url=url, description=request.form.get('description'), title=request.form.get('title'), **metadata) album_id = request.form['album'] if album_id: image.album_id = album_id session.add(image) image.add_tags(tag_names) ResizeImage.make_resizes(image, truck) if request_format == 'html': return redirect(url_for('show_image', image_id=image.image_id)) elif request_format == 'json': return {'url': truck.url()}
def test_find_by_filename(self): session = Client().session() session.add(Image(filename='deadbeef', source_url='example.com/foo')) session.flush() image = Image.find_by_filename('deadbeef') eq_(image.source_url, 'example.com/foo')
def test_find_by_filename__is_case_insensitive(self): session = Client().session() session.add(Image(filename='deadbeef', source_url='example.com/foo')) session.flush() image = Image.find_by_filename('DEADBEEF') eq_(image.source_url, 'example.com/foo')
def test_neighbors_sorts_by_photographed_at_if_present(self): session = Client().session() album = Album(name='Light me up') session.add(album) session.flush() album_id = album.album_id images = [ Image(filename='a', album_id=album_id, photographed_at='2015-03-20 15:00:00', image_id=0), Image(filename='b', album_id=album_id, photographed_at='2015-03-20 15:30:00', image_id=1), # should sort last Image(filename='c', album_id=album_id, photographed_at='2015-03-20 15:20:00', image_id=2), # should sort later Image(filename='d', album_id=album_id, photographed_at='2015-03-20 15:00:00', image_id=3), Image(filename='e', album_id=album_id, photographed_at='2015-03-20 15:00:00', image_id=4), ] for image in images: session.add(image) session.flush() eq_(images[0].neighbors(), (None, images[3])) eq_(images[1].neighbors(), (images[2], None)) eq_(images[2].neighbors(), (images[4], images[1])) eq_(images[3].neighbors(), (images[0], images[4])) eq_(images[4].neighbors(), (images[3], images[2]))
def test_upload_an_image(self, ImageTruck, delay): truck = Mock() ImageTruck.new_from_stream.return_value = truck truck.filename = 'CA7' truck.url.return_value = 'ess three' truck.contents = b'' truck.content_type = "image/jpeg" session = Client().session() album = Album(name='11:11 Eleven Eleven') session.add(album) session.flush() response = self.app.post('/add', data={ 'url': '', 'album_id': album.album_id, 'file': (StringIO(str('booya')), 'img.jpg')}) image = session.query(Image).one() eq_(image.filename, 'CA7') eq_(image.source_url, '') eq_(image.album_id, album.album_id) eq_(response.status_code, 302, response.data) eq_(response.headers['Location'], 'http://localhost/image/{0}'.format(image.image_id)) contents = session.query(ImageContents).one() eq_(image.image_id, contents.image_id) delay.assert_called_with( [], process_image, contents.image_contents_id)
def test_neighbors_is_both_neighbors_when_many_neighbors_are_present(self): session = Client().session() album = Album(name='Light me up') session.add(album) session.flush() images = [ Image(filename='cafebabe', album_id=album.album_id, created_at='2015-03-20 15:00:00'), Image(filename='1ace', album_id=album.album_id, created_at='2015-03-20 15:30:00'), Image(filename='f1acc1d', album_id=album.album_id, created_at='2015-03-20 15:20:00'), Image(filename='bab1e5', album_id=album.album_id, created_at='2015-03-20 15:00:00'), Image(filename='acebabe', album_id=album.album_id, created_at='2015-03-20 15:00:00'), ] for image in images: session.add(image) session.flush() eq_(images[0].neighbors(), (None, images[1])) # this awkward list(enumerate()) construct avoids the indexes being left-shifted from # their corresponding values (as would happen with `enumerate(images[1:-2])`) for (i, image) in list(enumerate(images))[1:-2]: eq_(image.neighbors(), (images[i - 1], images[i + 1])) eq_(images[-1].neighbors(), (images[-2], None))
def test_caption__falls_back_to_tags(self): session = Client().session() image = Image(title='', filename='') session.add(image) image.add_tags(['cat', 'awesome']) eq_(image.caption(), 'awesome cat')
def test_toomanyinvalidation_errors_cause_retry(self, MockClient): error = CloudFrontServerError(400, 'Bad Request') error.error_code = 'TooManyInvalidationsInProgress' cloudfront = Mock() cloudfront.create_invalidation_request.side_effect = error config = Client().config() client = Mock() client.config.return_value = config client.get_cloudfront.return_value = cloudfront MockClient.return_value = client session = Client().session() transaction_id = TaskTransaction.new_id() image = Image(filename='abad1dea') image.created_at = '2001-01-01 00:00:00' session.add(image) session.flush() invalidate = NeverCalledDirectlyInvalidate() invalidate.retry = Mock() invalidate(transaction_id, image.image_id) invalidate.retry.assert_called_with(exc=error)
def add(request_format): url = request.form.get("url") if url: try: trucks = [ImageTruck.new_from_url(url)] except RequestException: abort(request_format, 400, "That url is no good.") except TryHTTPError: abort( request_format, 400, "Catsnap couldn't establish an HTTPS connection to that " "image. An HTTP connection may succeed (this is a problem " "on Catsnap's end, not something you did wrong).", ) elif request.files.get("file[]"): trucks = [ImageTruck.new_from_stream(data.stream) for data in request.files.getlist("file[]")] elif request.files.get("file"): data = request.files["file"] trucks = [ImageTruck.new_from_stream(data.stream)] else: abort(request_format, 400, "Please submit either a file or a url.") # These loops are sorta awkwardly phrased to avoid lots of round-tripping # to the database. I hope you don't consider the optimization premature. session = Client().session() images = [] for truck in trucks: image = Image(filename=truck.filename, source_url=url) album_id = request.form.get("album_id") if album_id: image.album_id = int(album_id) session.add(image) images.append(image) session.flush() contentses = [] for i in xrange(0, len(images)): (truck, image) = trucks[i], images[i] contents = ImageContents(image_id=image.image_id, contents=truck.contents, content_type=truck.content_type) session.add(contents) contentses.append(contents) session.flush() task_ids = [] # Hey, this is a loop around a round-trip to redis. Although the Python # Redis library offers a way to send multiple requests in one thwack, # Celery doesn't appear to offer a way to use it. Nothing to be done. for contents in contentses: task_ids.append(delay(g.queued_tasks, process_image, contents.image_contents_id)) if request_format == "html": return redirect(url_for("show_image", image_id=image.image_id)) elif request_format == "json": return [ {"url": trucks[i].url(), "image_id": images[i].image_id, "task_id": task_ids[i]} for i in xrange(0, len(trucks)) ]
def test_on_view_album(self): session = Client().session() album = Album(name="fotozzzz") session.add(album) session.flush() self.visit_url('/album/{0}'.format(album.album_id)) assert self.browser.is_text_present('Log out'), \ "Page didn't act like logged in"
def test_on_view_image(self): session = Client().session() image = Image(filename='bab1eface', title='face of a baby') session.add(image) session.flush() self.visit_url('/image/{0}'.format(image.image_id)) assert self.browser.is_text_present('Log out'), \ "Page didn't act like logged in"
def test_creating_with_a_new_source_url_updates_existing_record(self): session = Client().session() session.add(Image(filename='badcafe', source_url='example.com')) session.flush() session.add(Image(filename='badcafe', source_url='examp.le')) session.flush() source_url = session.query(Image.source_url).first() eq_(source_url, ('examp.le',))
def add(request_format): url = request.form.get('url') if url: try: trucks = [ImageTruck.new_from_url(url)] except RequestException: abort(request_format, 400, "That url is no good.") elif request.files.get('file[]'): trucks = [ImageTruck.new_from_stream(data.stream) for data in request.files.getlist('file[]')] elif request.files.get('file'): data = request.files['file'] trucks = [ImageTruck.new_from_stream(data.stream)] else: abort(request_format, 400, "Please submit either a file or a url.") # These loops are sorta awkwardly phrased to avoid lots of round-tripping # to the database. I hope you don't consider the optimization premature. session = Client().session() images = [] for truck in trucks: image = Image(filename=truck.filename, source_url=url) album_id = request.form.get('album_id') if album_id: image.album_id = int(album_id) session.add(image) images.append(image) session.flush() contentses = [] for i in xrange(0, len(images)): (truck, image) = trucks[i], images[i] contents = ImageContents(image_id=image.image_id, contents=truck.contents, content_type=truck.content_type) session.add(contents) contentses.append(contents) session.flush() task_ids = [] # Hey, this is a loop around a round-trip to redis. Although the Python # Redis library offers a way to send multiple requests in one thwack, # Celery doesn't appear to offer a way to use it. Nothing to be done. for contents in contentses: task_ids.append(delay(g.queued_tasks, process_image, contents.image_contents_id)) if request_format == 'html': return redirect(url_for('show_image', image_id=image.image_id)) elif request_format == 'json': return [{ 'url': trucks[i].url(), 'image_id': images[i].image_id, 'task_id': task_ids[i], } for i in xrange(0, len(trucks))]
def create_album(request_format): session = Client().session() album = Album(name=request.form['name']) session.add(album) session.flush() if request_format == 'html': return redirect(url_for('show_add')) else: return {'album_id': album.album_id}
def test_created_at_is_set_at_creation_time(self, mock_time): now = time.strptime("2011-05-09 13:01:01", "%Y-%m-%d %H:%M:%S") mock_time.strftime = time.strftime mock_time.gmtime.return_value = now session = Client().session() album = Album(name="my pix") session.add(album) session.flush() eq_(album.created_at, "2011-05-09 13:01:01")