Skip to content
This repository was archived by the owner on Jun 4, 2021. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added .booksearch.py.swp
Binary file not shown.
8 changes: 8 additions & 0 deletions .idea/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 28 additions & 0 deletions .idea/google-books-search-webapp.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/inspectionProfiles/profiles_settings.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions .idea/modules.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 0 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,3 @@ The installation instructions above will allow you to run this app. I've include
if __name__ == '__main__':
app.run(debug=True)
```
This will allow you to see errors in the browser if the app encounters one.

## Next Steps
### pagination
I plan to add pagination -- currently, each request to the google books api only returns 10 results, and can only return up to 40 results. however, searches can have thousands of results. I plan to add forward and backwards buttons that allow new requests to be sent for the same query, that way the user can navigate through the entire list of results.
### Design
I'd also like to make the app more visually appealing. It's currently very barebones on html and css.

### Code Refactor
the BookSearch class is currently doing a lot. It would be better to have an APIQuery class, can be extended by separate classes for each api the app uses currently (google books and goodreads). This makes the code a little more future-proof.
12 changes: 7 additions & 5 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,17 @@
import sys

app = Flask(__name__)
app.secret_key = 'QVwI5uhPuU'

# app.secret_key = 'QVwI5uhPuU'
app.secret_key = 'AIzaSyBKFOVhps_PAJaA5mq9n440F_ILdj8BCMM'
app.logger.addHandler(logging.StreamHandler(sys.stdout))
app.logger.setLevel(logging.ERROR)

@app.route('/', methods=["GET", "POST"])

@app.route("/", methods=["GET", "POST"])
def home():
form = SearchForm()
results = []
if request.method == 'POST': #if submitting the form
if request.method == 'POST':
if form.validate() == False:
return render_template("index.html", form=form)
else:
Expand All @@ -24,8 +25,9 @@ def home():
results = book_search.get_search_results()
return render_template("index.html", form=form, results=results)

elif request.method == 'GET': #if returning results
elif request.method == 'GET':
return render_template("index.html", form=form, results=results)


if __name__ == '__main__':
app.run()
109 changes: 83 additions & 26 deletions booksearch.py
Original file line number Diff line number Diff line change
@@ -1,87 +1,144 @@
import requests
import dateutil.parser as dparser


class BookSearch:

books_api = 'https://www.googleapis.com/books/v1/volumes'
parameters = { 'q' : '',
'fields' : 'kind,totalItems,items(kind,volumeInfo(title,subtitle,authors,publisher,industryIdentifiers,imageLinks/thumbnail))'
}
search = '' #user's search query, populated in __init__
results = '' #response from google books, populated by parse_results()
parameters = dict(q='')
startIndex = 0
search = '' # user's search query, populated in __init__
results = '' # response from google books, populated by parse_results()
responseTime = 0
maxDate = None
minDate = None
mostProlific = ''

def __init__(self, search=''):
self.search = search




def make_a_search(self):
self.construct_request()
self.send_request()
# self.send_all_requests()
self.parse_results()
if self.results['items']:
self.analyze_results()

# adds user's search phrase to parameters
def construct_request(self):
self.parameters['q'] = self.search

def send_request(self):
self.search = requests.get(self.books_api, params=self.parameters)
self.responseTime = self.search.elapsed.total_seconds()

#store the results in a python dictionary
def send_all_requests(self):
self.parameters['startIndex'] = 0
self.parameters['maxResults'] = 40
self.search = requests.get(self.books_api, params=self.parameters)
if 'totalItems' in self.search.json() and self.search.json()['totalItems'] > 0:
self.results = self.search.json()
num_results = self.results['totalItems']
while self.parameters['startIndex'] < num_results:
self.parameters['startIndex'] += 40
next_results = requests.get(self.books_api, params=self.parameters).json()
if 'items' in next_results:
for item in next_results['items']:
self.results['items'].append(item)

# store the results in a python dictionary
def parse_results(self):
self.results = self.search.json()




def get_search_results(self):
search_results = []

if self.results['totalItems'] == 0:
return 'no results'

search_results = {
'totalResults': self.results['totalItems'],
'maxDate': self.maxDate,
'minDate': self.minDate,
'mostProlific': self.mostProlific,
'responseTime': self.responseTime,
'items': []
}

num_results = len(self.results['items'])
for result in range(num_results):

# grab results by index from each of the resulting lists
# send these back to the app for rendering front end
for result in range(num_results):
formatted_result = {
'title': self.get_result_title(result),
'description': self.get_result_description(result),
'authors': self.get_result_authors(result),
'publisher': self.get_result_publisher(result),
'thumbnail': self.get_thumbnail_url(result),
'goodreads': self.make_goodreads_url(result)
}
search_results.append(formatted_result)

search_results['items'].append(formatted_result)
return search_results

# do a bit of processing on each json object
def analyze_results(self):
authors = {}
dates = []
for result in self.results['items']:
if 'authors' in result['volumeInfo']:
for author in result['volumeInfo']['authors']:
if author in authors:
authors[author] += 1
else:
authors[author] = 1
if 'publishedDate' in result['volumeInfo']:
dates.append(dparser.parse(result['volumeInfo']['publishedDate']))

# sort dates to get the earliest and latest
dates = sorted(dates)
self.maxDate = dates[-1]
self.minDate = dates[0]

# having counted our authors in this traunch, sort and grab the highest count
if authors:
sorted_authors = sorted(authors, key=authors.get, reverse=True)
# self.mostProlific = sorted(authors, key=authors.get, reverse=True)[0]
plurality = " work." if authors.get(sorted_authors[0]) is 1 else " works."
self.mostProlific = sorted_authors[0] + " who has contributed to " + str(authors.get(sorted_authors[0])) + plurality

# collate title and subtitles per volume
def get_result_title(self, result):
title = self.results['items'][result]['volumeInfo']['title']

if 'subtitle' in self.results['items'][result]['volumeInfo']:
title += ': ' + self.results['items'][result]['volumeInfo']['subtitle']
return title

return 'title: ' + title
# check for and return a description if present, message otherwise
def get_result_description(self, result):
desc = 'No description provided.'
if 'description' in self.results['items'][result]['volumeInfo']:
desc = self.results['items'][result]['volumeInfo']['description']
return desc

# join all authors with a comma per volume
def get_result_authors(self, result):
authors = 'unkown'
authors = 'unknown'
if 'authors' in self.results['items'][result]['volumeInfo']:
authors = ', '.join(self.results['items'][result]['volumeInfo']['authors'])
return 'authors: ' + authors
return authors

# null check publisher, return if present
def get_result_publisher(self, result):
publisher = 'unknown'

if 'publisher' in self.results['items'][result]['volumeInfo']:
publisher = self.results['items'][result]['volumeInfo']['publisher']

return 'publisher: ' + publisher
return publisher

def get_thumbnail_url(self, result):
thumbnail = ''
if 'imageLinks' in self.results['items'][result]['volumeInfo']:
thumbnail = self.results['items'][result]['volumeInfo']['imageLinks']['thumbnail']
return thumbnail

# unclear if relevant, consider removal
def make_goodreads_url(self, result):
goodreads = 'https://www.goodreads.com/book/show/'
id = str(self.get_goodreads_id(result))
Expand Down
Binary file added booksearch.pyc
Binary file not shown.
6 changes: 6 additions & 0 deletions form.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,9 @@
class SearchForm(Form):
search_query = StringField('Search', validators=[DataRequired("Enter a search phrase."), Length(max=68)])
submit = SubmitField('search')


class PaginateForm(Form):
search_query = StringField('Search', validators=[DataRequired("Enter a search phrase."), Length(max=68)])
submit = SubmitField('nextResults')

Binary file added form.pyc
Binary file not shown.
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ requests==2.21.0
urllib3==1.24.1
Werkzeug==0.15.3
WTForms==2.2.1
python-dateutil==2.8.1
37 changes: 18 additions & 19 deletions templates/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,47 +4,46 @@

<form method="POST" action="/" class="pure-form">
{{ form.hidden_tag() }}

<div class="form-group">
{% if form.search_query.errors %}
{% for error in form.search_query.errors %}
<p class="error-message">{{ error }}</p>
{% endfor %}
{% endif %}

{{ form.search_query }}
</div>

{{ form.submit(class="btn-primary pure-button pure-button-primary") }}
</form>


<div class="results">
{% if results == 'no results' %}
<div class="pure-u-1-4"></div>
<div class="pure-u-3-4">
<p class="no-results">Oops! your search didn't turn up any results!</p>
</div>
{% else %}
{% for result in results %}
<div class="metrics">
<h2>Search Metrics</h2>
<ul>
<li>Earliest Published Date: {{ results['minDate'] }}</li>
<li>Most Recent Published Date: {{ results['maxDate'] }}</li>
<li>Most Prolific Author: {{ results['mostProlific'] }}</li>
<li>Server Response Time: {{ results['responseTime'] }}</li>
<li>Total Results: {{ results['totalResults'] }}</li>
</ul>
</div>
{% for item in results['items'] %}
<article class='pure-g'>
<div class="pure-u-1-4">
{% if result['thumbnail'] =='' %}
<p class = thumbnail>this result has no thumbnail</p>
{% else %}
<img src="{{ result['thumbnail'] }}" style="margin:auto">
{% endif %}
</div>
<div class="pure-u-3-4">
<p class="title" target="_blank">{{ result['title'] }}</p>
<p class="authors">{{ result['authors'] }}</p>
<p class="publisher">{{ result['publisher'] }}</p>
<a href="{{ result['goodreads'] }}">View on GoodReads</a>
</div>
<button class="collapsible">{{ item['authors'] }} - {{ item['title'] }}</button>
{% if item['description'] != '' %}
<div class="description">
<p>Description: {{ item['description'] }}</p>
</div>
{% endif %}
</article>
{% endfor %}
{% endif %}
</div>


{% endblock %}
{% endblock %}
Loading