diff --git a/.booksearch.py.swp b/.booksearch.py.swp new file mode 100644 index 0000000..fb7b596 Binary files /dev/null and b/.booksearch.py.swp differ diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..73f69e0 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/google-books-search-webapp.iml b/.idea/google-books-search-webapp.iml new file mode 100644 index 0000000..22cdd2b --- /dev/null +++ b/.idea/google-books-search-webapp.iml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..9037b5b --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..14e5437 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index a6c95a5..318b24d 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/app.py b/app.py index 1b31eff..3db1a8e 100644 --- a/app.py +++ b/app.py @@ -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: @@ -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() diff --git a/booksearch.py b/booksearch.py index 0496def..7df83f7 100644 --- a/booksearch.py +++ b/booksearch.py @@ -1,25 +1,29 @@ 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): @@ -27,54 +31,106 @@ def construct_request(self): 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 = '' @@ -82,6 +138,7 @@ def get_thumbnail_url(self, result): 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)) diff --git a/booksearch.pyc b/booksearch.pyc new file mode 100644 index 0000000..8741db6 Binary files /dev/null and b/booksearch.pyc differ diff --git a/form.py b/form.py index eefdd72..21d9ce9 100644 --- a/form.py +++ b/form.py @@ -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') + diff --git a/form.pyc b/form.pyc new file mode 100644 index 0000000..c9f66d9 Binary files /dev/null and b/form.pyc differ diff --git a/requirements.txt b/requirements.txt index e6267ac..111694f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -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 diff --git a/templates/index.html b/templates/index.html index a23e640..843640b 100644 --- a/templates/index.html +++ b/templates/index.html @@ -4,21 +4,17 @@
{{ form.hidden_tag() }} -
{% if form.search_query.errors %} {% for error in form.search_query.errors %}

{{ error }}

{% endfor %} {% endif %} - {{ form.search_query }}
- {{ form.submit(class="btn-primary pure-button pure-button-primary") }}
-
{% if results == 'no results' %}
@@ -26,25 +22,28 @@

Oops! your search didn't turn up any results!

{% else %} - {% for result in results %} +
+

Search Metrics

+ +
+ {% for item in results['items'] %}
- {% if result['thumbnail'] =='' %} -

this result has no thumbnail

- {% else %} - - {% endif %} -
-
-

{{ result['title'] }}

-

{{ result['authors'] }}

-

{{ result['publisher'] }}

- View on GoodReads
+ + {% if item['description'] != '' %} +
+

Description: {{ item['description'] }}

+
+ {% endif %}
{% endfor %} {% endif %} - - -{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/templates/outline.html b/templates/outline.html index bc63756..1c7f007 100644 --- a/templates/outline.html +++ b/templates/outline.html @@ -4,7 +4,33 @@ - Jack's Book Search app + Joshua's Book Search app + + @@ -13,7 +39,6 @@

Search Google Books

-

Jack's Python Web App

{% block content %} @@ -21,5 +46,22 @@

Jack's Python Web App

+