First commit

pull/1/head
Sean Redmond 2019-07-13 17:21:31 -04:00
commit 3b338c24ee
53 changed files with 20170 additions and 0 deletions

16
.gitignore vendored Normal file
View File

@ -0,0 +1,16 @@
venv/
*.pyc
__pycache__/
instance/
.pytest_cache/
.coverage
htmlcov/
dist/
build/
*.egg-info/
*~

44
cce_search/__init__.py Normal file
View File

@ -0,0 +1,44 @@
import os
from flask import Flask, send_from_directory
def create_app(test_config=None):
# create and configure the app
app = Flask(__name__, instance_relative_config=True)
app.config.from_mapping(
SECRET_KEY='dev',
#DATABASE=os.path.join(app.instance_path, 'flaskr.sqlite'),
API='http://sfr-bardo-copyright-development.us-east-1.elasticbeanstalk.com',
)
if test_config is None:
# load the instance config, if it exists, when not testing
app.config.from_pyfile('config.py', silent=True)
else:
# load the test config if passed in
app.config.from_mapping(test_config)
# ensure the instance folder exists
try:
os.makedirs(app.instance_path)
except OSError:
pass
from . import api
from . import search
app.register_blueprint(search.bp)
app.add_url_rule('/', endpoint='index')
# a simple page that says hello
@app.route('/hello')
def hello():
return api.search()
@app.route('/fonts/<path:path>')
def send_fonts(path):
return send_from_directory('fonts', path)
return app

43
cce_search/api.py Normal file
View File

@ -0,0 +1,43 @@
from flask import current_app, g
import requests
def search(term, page=0, per_page=10):
r = requests.get(current_app.config['API'] + '/search/fulltext',
params={'query': term,
'source': 'true',
'page': page,
'per_page': per_page})
return r.json()
def reg_search(term, page=0, per_page=10):
api = current_app.config['API']
r = requests.get("{}/search/registration/{}".format(api, term),
params={'source': 'true',
'page': page,
'per_page': per_page})
return r.json()
def ren_search(term, page=0, per_page=10):
api = current_app.config['API']
r = requests.get("{}/search/renewal/{}".format(api, term),
params={'source': 'true',
'page': page,
'per_page': per_page})
return r.json()
def registration(cceid):
r = requests.get('{}/registration/{}'.format(current_app.config['API'],
cceid))
r.raise_for_status()
return r.json()
def renewal(cceid):
r = requests.get('{}/renewal/{}'.format(current_app.config['API'],
cceid))
r.raise_for_status()
return r.json()

124
cce_search/search.py Normal file
View File

@ -0,0 +1,124 @@
from cce_search.api import search, reg_search, ren_search, registration, renewal
from flask import (
Blueprint, flash, g, redirect, render_template, request, url_for
)
import re
from urllib.parse import urlparse, parse_qs, parse_qsl, urlunparse, urlencode
from werkzeug.exceptions import abort
from requests import HTTPError
bp = Blueprint('search', __name__)
@bp.route('/')
def index():
results = None
term = None
paging=None
search_type = "ft"
if request.args:
term = request.args['term']
if request.args['type'] == 'ft':
response = search(request.args['term'], request.args.get('page'),
request.args.get('per_page'))
elif request.args['type'] == 'reg':
search_type = "reg"
response = reg_search(request.args['term'],
request.args.get('page'),
request.args.get('per_page'))
else:
search_type = "ren"
response = ren_search(request.args['term'],
request.args.get('page'),
request.args.get('per_page'))
paging = proc_pagination(response['data']['paging'],
request.args.get('page'))
results = proc_results(response)
return render_template('search/index.html', results=results, term=term,
paging=paging, search_type=search_type)
def proc_results(r):
return [enhance_results(res) for res in r['data']['results']]
def enhance_results(r):
if r.get('type') == 'renewal':
return r
return {**r, **{'original': strip_tags(r.get('xml')),
'source_url': ia_url(r.get('source', {}))}}
def strip_tags(xml):
if xml:
return re.sub(r"</?.+?>", "", xml).replace("\n", "")
return ""
def ia_url(src):
#return src
return "{}#page/{:d}/mode/1up".format(src.get('url', ''),
src.get('page', 0))
def proc_pagination(pg, current):
if not pg['next'] and not pg['previous']:
return {**pg, **{'has_pages': False}}
per_page = extract_per_page(pg)
if current is None:
current = 1
else:
current = int(current) + 1
return {**pg, **{'has_pages': True,
'current_page': current,
'last_page': extract_last(pg),
'pages': dict([(p, extract_pg(pg.get(p), per_page))
for p in ['first', 'next', 'last',
'previous']])}}
def extract_pg(pg, per_page):
if pg:
oq = dict(parse_qsl(urlparse(pg).query))
t = urlparse(request.url)
return urlunparse(
t._replace(query=urlencode({**dict(parse_qsl(t.query)),
**{'page': oq['page'],
'per_page': oq['per_page']}})))
return None
def extract_per_page(pg):
return [parse_qs(urlparse(v).query)["per_page"][0]
for v in pg.values() if v][0]
def extract_last(pg):
last = pg.get('last')
if last is None:
return "1"
else:
return int(parse_qs(urlparse(last).query)["page"][0]) + 1
@bp.route('/cceid/<cceid>')
def cceid(cceid):
try:
results = registration(cceid)
return render_template('search/cceid.html', results=results)
except HTTPError:
try:
results = renewal(cceid)
return render_template('search/cceid.html', results=results)
except HTTPError:
pass
return render_template('search/cceid.html', results=None, error=1)

View File

@ -0,0 +1,31 @@
.info-card {
border: 2px solid black;
border-radius: 1em;
margin-bottom: 1em;
padding-bottom: 1em;
}
.info-card dt {
font-weight: bold;
}
.info-card dd {
padding-bottom: 0.5em;
}
.info-card ul.child-renewal {
padding-left: 0;
}
fieldset.usa-fieldset legend {
margin-bottom: 8px;
}
.pagination {
text-align: center;
}
nav.pagination li {
display: inline-block;
padding: 0 1em;
}

19473
cce_search/static/uswds.css Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,46 @@
<!doctype html>
<html lang="en">
<meta charset="utf-8">
<title>{% block title %}{% endblock %} - CCE Search</title>
<link rel="stylesheet" href="{{ url_for('static', filename='uswds.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
</head>
<body>
<div class="usa-banner">
<header class="usa-banner__header">
<nav>
<h1>CCE Search</h1>
<ul>
<li><a href="{{ url_for('search.index') }}">Search</a>
</ul>
</nav>
</header>
</div>
<main id="main-content" class="grid-container">
{% block header %}{% endblock %}
{% for message in get_flashed_messages() %}
<div class="flash">{{ message }}</div>
{% endfor %}
{% block content %}{% endblock %}
</main>
<footer class="usa-footer usa-footer--slim" role="contentinfo">
<div class="grid-container usa-footer__return-to-top">
<a href="#">Return to top</a>
</div>
<div class="usa-footer__primary-section">
<div class="grid-row grid-row maxw-desktop margin-x-auto desktop:padding-x-4">
<div class="mobile-lg:grid-col-8">
<nav class="usa-footer__nav">
<ul class="add-list-reset grid-row grid-gap">
<li class="mobile-lg:grid-col-6 desktop:grid-col-auto usa-footer__primary-content">
Footer stuff
</li>
</ul>
</nav>
</div>
</div>
</div>
</footer>
</body>

View File

@ -0,0 +1,58 @@
{% if result['registrations'] %}
<dl>
<dt>Title</dt>
<dd>{{ result['title'] }}</dd>
<dt>Registrations</dt>
<dd>
<ul>
{% for reg in result['registrations'] %}
<li>
<dl>
<dt>ID</dt>
<dd>{{ reg['number'] }}</dd>
<dt>Date</dt>
<dd>{{ reg['date'] }}</dd>
</dl>
</li>
{% endfor %}
</ul>
</dd>
is
{% if result['renewals'] %}
<dt>Renewals</dt>
<dd>
<ul>
{% for ren in result['renewals'] %}
<li>
<dl>
<dt>ID</dt>
<dd>{{ ren['renewal_num'] }}</dd>
<dt>Date</dt>
<dd>{{ ren['renewal_date'] }}</dd>
<dt>JSON</dt>
<dd>{{ ren }}</dd>
</dl>
</li>
{% endfor %}
</ul>
</dd>
{% endif %}
<dt>Source</dt>
<dd><a href="{{ result['source']['url'] }}#page/{{ result['source']['page'] }}">src</a></dd>
<dt>JSON</dt>
<dd>{{ result }}</dd>
</dl>
{% else %}
RENEWAL
<dl>
<dt>JSON</dt>
<dd>{{ result }}</dd>
</dl>
{% endif %}

View File

@ -0,0 +1 @@
sean@lugdunum.local.13184

View File

@ -0,0 +1,10 @@
{% extends 'base.html' %}
{% block header %}
<h2>{% block title %}Find Copyright Entries{% endblock %}</h2>
{% endblock %}
{% block content %}
{{ results }}
{% endblock %}

View File

@ -0,0 +1,74 @@
{% extends 'base.html' %}
{% block header %}
<h2>{% block title %}Find Copyright Entries{% endblock %}</h2>
{% endblock %}
{% block content %}
<div class="grid-row">
<div class="grid-col-8">
<h3>Search</h3>
<form method="get" class="usa-form">
<fieldset class="usa-fieldset">
<legend>Search Type</legend>
<div class="usa-radio">
<input class="usa-radio__input" id="type_ft" type="radio"
{% if search_type == "ft" %}checked{% endif %} name="type"
value="ft">
<label class="usa-radio__label" for="type_ft">Full Text</label>
</div>
<div class="usa-radio">
<input class="usa-radio__input" id="type_registration" type="radio"
{% if search_type == "reg" %}checked{% endif %} name="type"
value="reg">
<label class="usa-radio__label" for="type_registration">Registration Number</label>
</div>
<div class="usa-radio">
<input class="usa-radio__input" id="type_renewal" type="radio"
{% if search_type == "ren" %}checked{% endif %} name="type"
value="ren">
<label class="usa-radio__label" for="type_renewal">Renewal Number</label>
</div>
</fieldset>
<label for="term" class="usa-label">Search For</label>
<input name="term" id="term" required class="usa-input"
{% if term %} value="{{ term }}" {% endif %} >
<input type="submit" value="Search" class="usa-button">
</form>
</div>
<div class="grid-col-4">
<h3>Search Hints</h3>
<ul>
<li>Use “Full Text” for authors, titles, and publishers</li>
<li>Use quotation marks to search phrases (for example &quot;Grapes of Wrath&quot;)</li>
<li>Searches are case insensitive</li>
<li>Renewal numbers look like <tt>R12345</tt> or <tt>RE12345</tt></li>
<li>Registration numbers look like <tt>A12345</tt>, <tt>BB12345</tt>, <tt>D12345</tt>. That is, they start with many letters or combination of letters, but not “R” or “RE”</li>
</ul>
</div>
</div>
{% if results %}
<h2>Results</h2>
{% include "search/paging.html" %}
<ul>
{% for result in results %}
{% if result['type'] %}
{% include "search/render_renewal.html" %}
{% else %}
{% include "search/render_registration.html" %}
{% endif %}
{% endfor %}
</ul>
{% endif %}
{% include "search/paging.html" %}
{% endblock %}

View File

@ -0,0 +1,27 @@
{% if paging["has_pages"] %}
<nav aria-label="pagination" class="pagination">
<ul class="usa-list usa-list--unstyled">
{% if paging['pages']['first'] %}
<li><a href="{{ paging['pages']['first'] }}">First<a></li>
{% endif %}
{% if paging['pages']['previous'] %}
<li><a href="{{ paging['pages']['previous'] }}">Previous<a></li>
{% endif %}
<li>Page {{ paging["current_page"] }} of {{ paging["last_page"] }}</li>
{% if paging['pages']['next'] %}
<li><a href="{{ paging['pages']['next'] }}">Next<a></li>
{% endif %}
{% if paging['pages']['last'] %}
<li><a href="{{ paging['pages']['last'] }}">Last<a></li>
{% endif %}
</ul>
</nav>
{% else %}
<div class="pagination">Page 1 of 1</div>
{% endif %}

View File

@ -0,0 +1,17 @@
<div class="grid-col-4">
<a href="/cceid/{{ renewal['uuid'] }}">
{{ renewal['renewal_num'] }} {{ renewal['renewal_date'] }}
</a>
</div>
<div class="grid-col-6">
<div>
{{ renewal['author'] }} {{ renewal['title'] }}
</div>
<ul class="usa-list usa-list--unstyled">
{% for cl in renewal['claimants'] %}
<li>{{ cl['name'] }} ({{ cl['type'] }})</li>
{% endfor %}
</ul>
</div>

View File

@ -0,0 +1,58 @@
{% if result['registrations'] %}
<dl>
<dt>Title</dt>
<dd>{{ result['title'] }}</dd>
<dt>Registrations</dt>
<dd>
<ul>
{% for reg in result['registrations'] %}
<li>
<dl>
<dt>ID</dt>
<dd>{{ reg['number'] }}</dd>
<dt>Date</dt>
<dd>{{ reg['date'] }}</dd>
</dl>
</li>
{% endfor %}
</ul>
</dd>
{% if result['renewals'] %}
<dt>Renewals</dt>
<dd>
<ul>
{% for ren in result['renewals'] %}
<li>
<dl>
<dt>ID</dt>
<dd>{{ ren['renewal_num'] }}</dd>
<dt>Date</dt>
<dd>{{ ren['renewal_date'] }}</dd>
<dt>JSON</dt>
<dd>{{ ren }}</dd>
</dl>
</li>
{% endfor %}
</ul>
</dd>
{% endif %}
<dt>Source</dt>
<dd><a href="{{ result['source']['url'] }}#page/{{ result['source']['page'] }}">src</a></dd>
<dt>JSON</dt>
<dd>{{ result }}</dd>
</dl>
{% else %}
RENEWAL
<dl>
<dt>JSON</dt>
<dd>{{ result }}</dd>
</dl>
{% endif %}

View File

@ -0,0 +1,94 @@
<div class="info-card registration grid-container">
<h3>Registration {{ result['registrations'][0]['number'] }}</h3>
<div>
<dl class="grid-row">
<dt class="grid-col-2">Title</dt>
<dd class="grid-col-9">{{ result['title'] }}</dd>
<dt class="grid-col-2">Authors</dt>
<dd class="grid-col-9">
<ul class="usa-list usa-list--unstyled">
{% for a in result['authors'] %}
<li>{{ a }}</li>
{% endfor %}
</ul>
</dd>
<dt class="grid-col-2">Registrations</dt>
<dd class="grid-col-9">
<ul class="usa-list usa-list--unstyled">
{% for r in result['registrations'] %}
<li>{{ r['number'] }} {{ r['date'] }}</li>
{% endfor %}
</ul>
</dd>
{% if result['copies'] %}
<dt class="grid-col-2">Copies</dt>
<dd class="grid-col-9">{{ result['copies'] }}</dd>
{% endif %}
{% if result['copy_date'] %}
<dt class="grid-col-2">Copy Date</dt>
<dd class="grid-col-9">{{ result['copy_date'] }}</dd>
{% endif %}
{% if result['description'] %}
<dt class="grid-col-2">Description</dt>
<dd class="grid-col-9">{{ result['description'] }}</dd>
{% endif %}
{% if result['pub_date'] %}
<dt class="grid-col-2">Publication Date</dt>
<dd class="grid-col-9">{{ result['pub_date'] }}</dd>
{% endif %}
{% if result['publishers'] %}
<dt class="grid-col-2">Publishers</dt>
<dd class="grid-col-9">
<ul class="usa-list usa-list--unstyled">
{% for p in result['publishers'] %}
<li>{{ p }}</li>
{% endfor %}
</ul>
</dd>
{% endif %}
<dt class="grid-col-2">Renewals</dt>
<dd class="grid-col-9">
{% if result['renewals'] %}
<ul class="child-renewal grid-container usa-list usa-list--unstyled">
{% for renewal in result['renewals'] %}
<li class="grid-row">
{% include "search/render_child_renewal.html" %}
</li>
{% endfor %}
</ul>
{% else %}
<em>No renewals found</em>
{% endif %}
</dd>
<dt class="grid-col-2">Source</dt>
<dd class="grid-col-9">
<a href="{{ result['source_url'] }}">
{{ result['source']['year'] }} p. {{ result['source']['page'] }}
</a>
</dd>
<dt class="grid-col-2">Original</dt>
<dd class="grid-col-9">{{ result['original'] }}</dd>
<dt class="grid-col-2">CCEID</dt>
<dd class="grid-col-9">
<a href="/cceid/{{ result['uuid'] }}">{{ result['uuid'] }}</a>
</dd>
</dl>
</div>
</div>

View File

@ -0,0 +1,48 @@
<div class="info-card renewal grid-container">
<h3>Renewal {{ result['renewal_num'] }}</h3>
<div>
<dl class="grid-row">
<dt class="grid-col-2">Title</dt>
<dd class="grid-col-9">{{ result['title'] }}</dd>
<dt class="grid-col-2">Authors</dt>
<dd class="grid-col-9">{{ result['author'] }}</dd>
<dt class="grid-col-2">Renewal</dt>
<dd class="grid-col-9">
{{ result['renewal_num'] }} {{ result['renewal_date'] }}
</dd>
<dt class="grid-col-2">Claimants</dt>
<dd class="grid-col-9">
<ul class="usa-list usa-list--unstyled">
{% for c in result['claimants'] %}
<li>{{ c['name'] }} ({{ c['type'] }})</li>
{% endfor %}
</ul>
</dd>
{% if result['new_matter'] %}
<dt class="grid-col-2">New Matter</dt>
<dd class="grid-col-9">{{ result['new_matter'] }}</dd>
{% endif %}
{% if result['notes'] %}
<dt class="grid-col-2">Notes</dt>
<dd class="grid-col-9">{{ result['notes'] }}</dd>
{% endif %}
{% if result['source'] %}
<dt class="grid-col-2">Original</dt>
<dd class="grid-col-9">{{ result['source'] }}</dd>
{% endif %}
<dt class="grid-col-2">CCEID</dt>
<dd class="grid-col-9">
<a href="/cceid/{{ result['uuid'] }}">{{ result['uuid'] }}</a>
</dd>
</dl>
</div>
</div>

6
requirements.txt Normal file
View File

@ -0,0 +1,6 @@
Click==7.0
Flask==1.1.1
itsdangerous==1.1.0
Jinja2==2.10.1
MarkupSafe==1.1.1
Werkzeug==0.15.4