Show images in search results.

master
Pacman Ghost 4 years ago
parent 9d564be449
commit 238f18d24f
  1. 9
      asl_articles/articles.py
  2. 14
      asl_articles/models.py
  3. 9
      asl_articles/publications.py
  4. 9
      asl_articles/publishers.py
  5. 13
      asl_articles/tests/test_articles.py
  6. 13
      asl_articles/tests/test_publications.py
  7. 13
      asl_articles/tests/test_publishers.py
  8. 9
      web/src/App.js
  9. 7
      web/src/ArticleSearchResult.js
  10. 5
      web/src/PublicationSearchResult.js
  11. 5
      web/src/PublisherSearchResult.js
  12. 1
      web/src/SearchResults.css
  13. 4
      web/src/utils.js

@ -42,6 +42,7 @@ def get_article_vals( article ):
"article_id": article.article_id,
"article_title": article.article_title,
"article_subtitle": article.article_subtitle,
"article_image_id": article.article_id if article.article_image else None,
"article_authors": [ a.author_id for a in authors ],
"article_snippet": article.article_snippet,
"article_url": article.article_url,
@ -72,7 +73,7 @@ def create_article():
new_article_id = article.article_id
_save_authors( article, updated )
_save_scenarios( article, updated )
_save_image( article )
_save_image( article, updated )
db.session.commit()
_logger.debug( "- New ID: %d", new_article_id )
@ -156,7 +157,7 @@ def _save_scenarios( article, updated_fields ):
# yup - let the caller know about them
updated_fields[ "article_scenarios"] = scenario_ids
def _save_image( article ):
def _save_image( article, updated ):
"""Save the article's image."""
# check if a new image was provided
@ -168,6 +169,7 @@ def _save_image( article ):
ArticleImage.query.filter( ArticleImage.article_id == article.article_id ).delete()
if image_data == "{remove}":
# NOTE: The front-end sends this if it wants the article to have no image.
updated[ "article_image_id" ] = None
return
# add the new image to the database
@ -177,6 +179,7 @@ def _save_image( article ):
db.session.add( img )
db.session.flush()
_logger.debug( "Created new image: %s, #bytes=%d", fname, len(image_data) )
updated[ "article_image_id" ] = article.article_id
# ---------------------------------------------------------------------
@ -200,7 +203,7 @@ def update_article():
apply_attrs( article, vals )
_save_authors( article, updated )
_save_scenarios( article, updated )
_save_image( article )
_save_image( article, updated )
vals[ "time_updated" ] = datetime.datetime.now()
db.session.commit()

@ -2,6 +2,7 @@
# NOTE: Don't forget to keep the list of tables in init_db() in sync with the models defined here.
from sqlalchemy.orm import deferred
from sqlalchemy.schema import UniqueConstraint
from asl_articles import db
@ -20,7 +21,8 @@ class Publisher( db.Model ):
time_created = db.Column( db.TIMESTAMP(timezone=True) )
time_updated = db.Column( db.TIMESTAMP(timezone=True) )
#
publications = db.relationship( "Publication", backref="parent", passive_deletes=True )
publ_image = db.relationship( "PublisherImage", backref="parent_publ", passive_deletes=True )
publications = db.relationship( "Publication", backref="parent_publ", passive_deletes=True )
def __repr__( self ):
return "<Publisher:{}|{}>".format( self.publ_id, self.publ_name )
@ -44,7 +46,8 @@ class Publication( db.Model ):
time_created = db.Column( db.TIMESTAMP(timezone=True) )
time_updated = db.Column( db.TIMESTAMP(timezone=True) )
#
articles = db.relationship( "Article", backref="parent", passive_deletes=True )
pub_image = db.relationship( "PublicationImage", backref="parent_pub", passive_deletes=True )
articles = db.relationship( "Article", backref="parent_pub", passive_deletes=True )
def __repr__( self ):
return "<Publication:{}|{}>".format( self.pub_id, self.pub_name )
@ -68,6 +71,7 @@ class Article( db.Model ):
time_created = db.Column( db.TIMESTAMP(timezone=True) )
time_updated = db.Column( db.TIMESTAMP(timezone=True) )
#
article_image = db.relationship( "ArticleImage", backref="parent_article", passive_deletes=True )
article_authors = db.relationship( "ArticleAuthor", backref="parent_article", passive_deletes=True )
article_scenarios = db.relationship( "ArticleScenario", backref="parent_article", passive_deletes=True )
@ -121,7 +125,7 @@ class PublisherImage( db.Model ):
primary_key = True
)
image_filename = db.Column( db.String(500), nullable=False )
image_data = db.Column( db.LargeBinary, nullable=False )
image_data = deferred( db.Column( db.LargeBinary, nullable=False ) )
def __repr__( self ):
return "<PublisherImage:{}|{}>".format( self.publ_id, len(self.image_data) )
@ -134,7 +138,7 @@ class PublicationImage( db.Model ):
primary_key = True
)
image_filename = db.Column( db.String(500), nullable=False )
image_data = db.Column( db.LargeBinary, nullable=False )
image_data = deferred( db.Column( db.LargeBinary, nullable=False ) )
def __repr__( self ):
return "<PublicationImage:{}|{}>".format( self.pub_id, len(self.image_data) )
@ -147,7 +151,7 @@ class ArticleImage( db.Model ):
primary_key = True
)
image_filename = db.Column( db.String(500), nullable=False )
image_data = db.Column( db.LargeBinary, nullable=False )
image_data = deferred( db.Column( db.LargeBinary, nullable=False ) )
def __repr__( self ):
return "<ArticleImage:{}|{}>".format( self.article_id, len(self.image_data) )

@ -54,6 +54,7 @@ def get_publication_vals( pub ):
"pub_edition": pub.pub_edition,
"pub_description": pub.pub_description,
"pub_url": pub.pub_url,
"pub_image_id": pub.pub_id if pub.pub_image else None,
"pub_tags": decode_tags( pub.pub_tags ),
"publ_id": pub.publ_id,
}
@ -76,7 +77,7 @@ def create_publication():
vals[ "time_created" ] = datetime.datetime.now()
pub = Publication( **vals )
db.session.add( pub )
_save_image( pub )
_save_image( pub, updated )
db.session.commit()
_logger.debug( "- New ID: %d", pub.pub_id )
@ -87,7 +88,7 @@ def create_publication():
extras[ "tags" ] = do_get_tags()
return make_ok_response( updated=updated, extras=extras, warnings=warnings )
def _save_image( pub ):
def _save_image( pub, updated ):
"""Save the publication's image."""
# check if a new image was provided
@ -99,6 +100,7 @@ def _save_image( pub ):
PublicationImage.query.filter( PublicationImage.pub_id == pub.pub_id ).delete()
if image_data == "{remove}":
# NOTE: The front-end sends this if it wants the publication to have no image.
updated[ "pub_image_id" ] = None
return
# add the new image to the database
@ -108,6 +110,7 @@ def _save_image( pub ):
db.session.add( img )
db.session.flush()
_logger.debug( "Created new image: %s, #bytes=%d", fname, len(image_data) )
updated[ "pub_image_id" ] = pub.pub_id
# ---------------------------------------------------------------------
@ -129,7 +132,7 @@ def update_publication():
if not pub:
abort( 404 )
apply_attrs( pub, vals )
_save_image( pub )
_save_image( pub, updated )
vals[ "time_updated" ] = datetime.datetime.now()
db.session.commit()

@ -58,6 +58,7 @@ def get_publisher_vals( publ ):
"publ_name": publ.publ_name,
"publ_description": publ.publ_description,
"publ_url": publ.publ_url,
"publ_image_id": publ.publ_id if publ.publ_image else None,
}
# ---------------------------------------------------------------------
@ -77,7 +78,7 @@ def create_publisher():
vals[ "time_created" ] = datetime.datetime.now()
publ = Publisher( **vals )
db.session.add( publ )
_save_image( publ )
_save_image( publ, updated )
db.session.commit()
_logger.debug( "- New ID: %d", publ.publ_id )
@ -87,7 +88,7 @@ def create_publisher():
extras[ "publishers" ] = _do_get_publishers()
return make_ok_response( updated=updated, extras=extras, warnings=warnings )
def _save_image( publ ):
def _save_image( publ, updated ):
"""Save the publisher's image."""
# check if a new image was provided
@ -99,6 +100,7 @@ def _save_image( publ ):
PublisherImage.query.filter( PublisherImage.publ_id == publ.publ_id ).delete()
if image_data == "{remove}":
# NOTE: The front-end sends this if it wants the publisher to have no image.
updated[ "publ_image_id" ] = None
return
# add the new image to the database
@ -108,6 +110,7 @@ def _save_image( publ ):
db.session.add( img )
db.session.flush()
_logger.debug( "Created new image: %s, #bytes=%d", fname, len(image_data) )
updated[ "publ_image_id" ] = publ.publ_id
# ---------------------------------------------------------------------
@ -127,7 +130,7 @@ def update_publisher():
publ = Publisher.query.get( publ_id )
if not publ:
abort( 404 )
_save_image( publ )
_save_image( publ, updated )
apply_attrs( publ, vals )
vals[ "time_updated" ] = datetime.datetime.now()
db.session.commit()

@ -142,13 +142,24 @@ def test_delete_article( webdriver, flask_app, dbconn ):
# ---------------------------------------------------------------------
def test_images( webdriver, flask_app, dbconn ):
def test_images( webdriver, flask_app, dbconn ): #pylint: disable=too-many-statements
"""Test article images."""
# initialize
init_tests( webdriver, flask_app, dbconn, max_image_upload_size=2*1024 )
def check_image( expected ):
# check the image in the article's search result
img = find_child( "img.image", article_sr )
if expected:
expected_url = flask_app.url_for( "get_image", image_type="article", image_id=article_id )
image_url = img.get_attribute( "src" ).split( "?" )[0]
assert image_url == expected_url
else:
assert not img
# check the image in the article's config
find_child( ".edit", article_sr ).click()
dlg = wait_for_elem( 2, "#modal-form" )
if expected:

@ -144,13 +144,24 @@ def test_delete_publication( webdriver, flask_app, dbconn ):
# ---------------------------------------------------------------------
def test_images( webdriver, flask_app, dbconn ):
def test_images( webdriver, flask_app, dbconn ): #pylint: disable=too-many-statements
"""Test publication images."""
# initialize
init_tests( webdriver, flask_app, dbconn, max_image_upload_size=2*1024 )
def check_image( expected ):
# check the image in the publication's search result
img = find_child( "img.image", pub_sr )
if expected:
expected_url = flask_app.url_for( "get_image", image_type="publication", image_id=pub_id )
image_url = img.get_attribute( "src" ).split( "?" )[0]
assert image_url == expected_url
else:
assert not img
# check the image in the publisher's config
find_child( ".edit", pub_sr ).click()
dlg = wait_for_elem( 2, "#modal-form" )
if expected:

@ -131,13 +131,24 @@ def test_delete_publisher( webdriver, flask_app, dbconn ):
# ---------------------------------------------------------------------
def test_images( webdriver, flask_app, dbconn ):
def test_images( webdriver, flask_app, dbconn ): #pylint: disable=too-many-statements
"""Test publisher images."""
# initialize
init_tests( webdriver, flask_app, dbconn, max_image_upload_size=2*1024 )
def check_image( expected ):
# check the image in the publisher's search result
img = find_child( "img.image", publ_sr )
if expected:
expected_image_url = flask_app.url_for( "get_image", image_type="publisher", image_id=publ_id )
image_url = img.get_attribute( "src" ).split( "?" )[0]
assert image_url == expected_image_url
else:
assert not img
# check the image in the publisher's config
find_child( ".edit", publ_sr ).click()
dlg = wait_for_elem( 2, "#modal-form" )
if expected:

@ -247,6 +247,15 @@ export default class App extends React.Component
}
return url ;
}
makeFlaskImageUrl( type, image_id, force ) {
// generate an image URL for the Flask backend server
if ( ! image_id )
return null ;
let url = this.makeFlaskUrl( "/images/" + type + "/" + image_id ) ;
if ( force )
url += "?foo=" + Math.random() ; // FUDGE! To bypass the cache :-/
return url ;
}
_onStartupTask( taskId ) {
// flag that the specified startup task has completed

@ -12,6 +12,7 @@ export class ArticleSearchResult extends React.Component
render() {
const pub = gAppRef.caches.publications[ this.props.data.pub_id ] ;
const image_url = gAppRef.makeFlaskImageUrl( "article", this.props.data.article_image_id, true ) ;
let tags = [] ;
if ( this.props.data.article_tags )
this.props.data.article_tags.map( t => tags.push( <div key={t} className="tag"> {t} </div> ) ) ;
@ -20,8 +21,10 @@ export class ArticleSearchResult extends React.Component
return ( <div className="search-result article"
ref = { r => gAppRef.setTestAttribute( r, "article_id", this.props.data.article_id ) }
>
<div className="title name"> { makeOptionalLink( this.props.data.article_title, this.props.data.article_url ) }
{ pub && <span className="publication"> ({pub.pub_name}) </span> }
<div className="title name">
{ image_url && <img src={image_url} className="image" alt="Article." /> }
{ makeOptionalLink( this.props.data.article_title, this.props.data.article_url ) }
{ pub && <span className="publication"> ({pub.pub_name}) </span> }
<img src="/images/edit.png" className="edit" onClick={this.onEditArticle.bind(this)} alt="Edit this article." />
<img src="/images/delete.png" className="delete" onClick={this.onDeleteArticle.bind(this)} alt="Delete this article." />
{ this.props.data.article_subtitle && <div className="subtitle" dangerouslySetInnerHTML={{ __html: this.props.data.article_subtitle }} /> }

@ -12,13 +12,16 @@ export class PublicationSearchResult extends React.Component
render() {
const publ = gAppRef.caches.publishers[ this.props.data.publ_id ] ;
const image_url = gAppRef.makeFlaskImageUrl( "publication", this.props.data.pub_image_id, true ) ;
let tags = [] ;
if ( this.props.data.pub_tags )
this.props.data.pub_tags.map( t => tags.push( <div key={t} className="tag"> {t} </div> ) ) ;
return ( <div className="search-result publication"
ref = { r => gAppRef.setTestAttribute( r, "pub_id", this.props.data.pub_id ) }
>
<div className="name"> { makeOptionalLink( this._makeDisplayName(), this.props.data.pub_url ) }
<div className="name">
{ image_url && <img src={image_url} className="image" alt="Publication." /> }
{ makeOptionalLink( this._makeDisplayName(), this.props.data.pub_url ) }
{ publ && <span className="publisher"> ({publ.publ_name}) </span> }
<img src="/images/edit.png" className="edit" onClick={this.onEditPublication.bind(this)} alt="Edit this publication." />
<img src="/images/delete.png" className="delete" onClick={this.onDeletePublication.bind(this)} alt="Delete this publication." />

@ -11,10 +11,13 @@ export class PublisherSearchResult extends React.Component
{
render() {
const image_url = gAppRef.makeFlaskImageUrl( "publisher", this.props.data.publ_image_id, true ) ;
return ( <div className="search-result publisher"
ref = { r => gAppRef.setTestAttribute( r, "publ_id", this.props.data.publ_id ) }
>
<div className="name"> { makeOptionalLink( this.props.data.publ_name, this.props.data.publ_url ) }
<div className="name">
{ image_url && <img src={image_url} className="image" alt="Publisher." /> }
{ makeOptionalLink( this.props.data.publ_name, this.props.data.publ_url ) }
<img src="/images/edit.png" className="edit" onClick={this.onEditPublisher.bind(this)} alt="Edit this publisher." />
<img src="/images/delete.png" className="delete" onClick={this.onDeletePublisher.bind(this)} alt="Delete this publisher." />
</div>

@ -7,6 +7,7 @@
}
.search-result .name { padding: 2px 5px ; }
.search-result .image { float: left ; margin-right: 0.25em ; height: 1em ; }
.search-result .name a { font-weight: bold ; text-decoration: none ; }
.search-result .name .publisher { font-size: 80% ; font-style: italic ; }
.search-result .name .publication { font-size: 80% ; font-style: italic ; }

@ -27,6 +27,10 @@ export function applyUpdatedVals( vals, newVals, updated, refs ) {
// into the original table of values.
for ( let r in refs )
vals[ r ] = (updated && updated[r] !== undefined) ? updated[r] : newVals[r] ;
// NOTE: We sometimes want to force an entry into the vals that doesn't have
// an associated ref (i.e. UI element) e.g. XXX_image_id.
for ( let key in updated )
vals[ key ] = updated[ key ] ;
}
// --------------------------------------------------------------------

Loading…
Cancel
Save