mirror of
https://github.com/djohnlewis/stackdump
synced 2024-12-04 23:17:37 +00:00
Fleshed initial HTML views and pages.
This commit is contained in:
parent
5e930bbc08
commit
dc7a923fa9
88
python/media/css/main.css
Normal file
88
python/media/css/main.css
Normal file
@ -0,0 +1,88 @@
|
||||
/* don't want it to have a fixed position; so we'll override it */
|
||||
.topbar {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.topbar-text {
|
||||
float: left;
|
||||
display: block;
|
||||
padding: 8px 2px 12px;
|
||||
color: grey;
|
||||
font-size: 20px;
|
||||
font-weight: 200;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.topbar-text a {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.topbar-divider {
|
||||
padding-left: 3px;
|
||||
padding-right: 3px;
|
||||
}
|
||||
|
||||
/* leave enough gap for the topbar */
|
||||
#content {
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.search-container {
|
||||
padding: 60px;
|
||||
}
|
||||
|
||||
.site-logo {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.site-title {
|
||||
padding-left: 64px; /* 48px for logo + 16px for padding */
|
||||
}
|
||||
|
||||
#search {
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.tagline {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.random-questions-container {
|
||||
padding-left: 60px;
|
||||
padding-right: 60px;
|
||||
}
|
||||
|
||||
.available-sites-container {
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.available-sites {
|
||||
list-style-image: none;
|
||||
list-style-type: none;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.available-sites li {
|
||||
clear: both;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.available-sites img {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.available-sites h6 {
|
||||
padding-top: 7px;
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
.available-sites .tagline {
|
||||
padding-top: 3px;
|
||||
}
|
||||
|
||||
#footer {
|
||||
margin-top: 50px;
|
||||
border-top: solid 1px #999999;
|
||||
background-color: #CCCCCC;
|
||||
padding: 7px 10px;
|
||||
}
|
@ -1,14 +1,19 @@
|
||||
from bottle import route, run, static_file
|
||||
from jinja2 import Environment, PackageLoader
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
from bottle import route, run, static_file, debug, abort, request, redirect
|
||||
from jinja2 import Environment, PackageLoader
|
||||
from sqlobject import sqlhub, connectionForURI, AND, OR, SQLObjectNotFound
|
||||
from pysolr import Solr
|
||||
|
||||
from stackdump.models import Site, Badge, Comment, User
|
||||
|
||||
# STATIC VARIABLES
|
||||
BOTTLE_ROOT = os.path.abspath(os.path.dirname(sys.argv[0]))
|
||||
MEDIA_ROOT = os.path.abspath(BOTTLE_ROOT + '/../../media')
|
||||
|
||||
# hopefully this is thread-safe; not sure though. Will need to experiment/check.
|
||||
# TODO: thread-safe?
|
||||
TEMPLATE_ENV = Environment(loader=PackageLoader('stackdump', 'templates'))
|
||||
|
||||
# WEB REQUEST METHODS
|
||||
@ -18,13 +23,70 @@ TEMPLATE_ENV = Environment(loader=PackageLoader('stackdump', 'templates'))
|
||||
def serve_static(filename):
|
||||
return static_file(filename, root=MEDIA_ROOT)
|
||||
|
||||
@route('/hello')
|
||||
def hello():
|
||||
return "Hello World!"
|
||||
|
||||
@route('/')
|
||||
def index():
|
||||
return render_template('index.html')
|
||||
context = { }
|
||||
context['site_root_path'] = ''
|
||||
context['sites'] = Site.select()
|
||||
return render_template('index.html', context)
|
||||
|
||||
@route('/:site_key#\w+#')
|
||||
@route('/:site_key#\w+#/')
|
||||
def site_index(site_key):
|
||||
context = { }
|
||||
context['site_root_path'] = '%s/' % site_key
|
||||
|
||||
try:
|
||||
context['site'] = Site.selectBy(key=site_key).getOne()
|
||||
except SQLObjectNotFound:
|
||||
abort(code=404, output='No site exists with the key %s.' % site_key)
|
||||
|
||||
return render_template('site_index.html', context)
|
||||
|
||||
@route('/search')
|
||||
def search():
|
||||
query = request.GET.get('q')
|
||||
if not query:
|
||||
redirect(settings.APP_URL_ROOT)
|
||||
|
||||
page = request.GET.get('p', 0)
|
||||
rows_per_page = request.GET.get('r', 10)
|
||||
|
||||
# perform search
|
||||
results = solr.search(query, start=page*rows_per_page, rows=rows_per_page)
|
||||
|
||||
context = { }
|
||||
# TODO: scrub this first to avoid injection attacks?
|
||||
context['query'] = query
|
||||
context['results'] = results
|
||||
|
||||
return render_template('results.html', context)
|
||||
|
||||
@route('/:site_key#\w+#/search')
|
||||
def site_search(site_key):
|
||||
context = { }
|
||||
context['site_root_path'] = '%s/' % site_key
|
||||
|
||||
try:
|
||||
context['site'] = Site.selectBy(key=site_key).getOne()
|
||||
except SQLObjectNotFound:
|
||||
raise HTTPError(code=404, output='No site exists with the key %s.' % site_key)
|
||||
|
||||
query = request.GET.get('q')
|
||||
if not query:
|
||||
redirect(settings.APP_URL_ROOT)
|
||||
|
||||
page = request.GET.get('p', 0)
|
||||
rows_per_page = request.GET.get('r', 10)
|
||||
|
||||
# perform search
|
||||
results = solr.search(query, start=page*rows_per_page, rows=rows_per_page)
|
||||
|
||||
# TODO: scrub this first to avoid injection attacks?
|
||||
context['query'] = query
|
||||
context['results'] = results
|
||||
|
||||
return render_template('site_results.html', context)
|
||||
|
||||
# END WEB REQUEST METHODS
|
||||
|
||||
@ -51,11 +113,27 @@ def get_template_settings():
|
||||
# INITIALISATION
|
||||
|
||||
if __name__ == '__main__':
|
||||
# only print this on the parent process, not the child ones. Applies when
|
||||
# only do these things in the child processes, not the parents. Applies when
|
||||
# the auto-reload option is on (reloader=True). When it is on, the
|
||||
# BOTTLE_CHILD env var is True if this is the child process.
|
||||
if not os.environ.get('BOTTLE_CHILD', True):
|
||||
if os.environ.get('BOTTLE_CHILD', True):
|
||||
print('Serving media from: %s' % MEDIA_ROOT)
|
||||
|
||||
# connect to the data sources
|
||||
db_path = os.path.abspath(os.path.join(BOTTLE_ROOT, '../../../data/stackdump.sqlite'))
|
||||
|
||||
# connect to the database
|
||||
# TODO: thread-safe?
|
||||
print('Connecting to the database...')
|
||||
conn_str = 'sqlite://' + db_path
|
||||
sqlhub.processConnection = connectionForURI(conn_str)
|
||||
print('Connected.\n')
|
||||
|
||||
# connect to solr
|
||||
# TODO: thread-safe?
|
||||
print('Connecting to solr...')
|
||||
solr = Solr("http://localhost:8983/solr/")
|
||||
print('Connected.\n')
|
||||
|
||||
# load the settings file
|
||||
__import__('settings')
|
||||
@ -65,6 +143,9 @@ if __name__ == '__main__':
|
||||
else:
|
||||
settings = { }
|
||||
|
||||
if settings.get('DEBUG', False):
|
||||
debug(True)
|
||||
|
||||
# run the server!
|
||||
server = settings.get('SERVER_ADAPTER', 'wsgiref')
|
||||
|
||||
|
@ -3,6 +3,8 @@
|
||||
# It is modelled after the Django settings file. This file is just like any
|
||||
# other Python file, except the local variables form the settings dictionary.
|
||||
|
||||
DEBUG = True
|
||||
|
||||
# see http://bottlepy.org/docs/dev/tutorial.html#multi-threaded-server
|
||||
SERVER_ADAPTER = 'cherrypy'
|
||||
SERVER_HOST = '0.0.0.0'
|
||||
|
@ -10,7 +10,38 @@
|
||||
</head>
|
||||
|
||||
<body>
|
||||
{%- block body %}
|
||||
{% endblock -%}
|
||||
<div class="topbar">
|
||||
<div class="fill">
|
||||
<div class="container">
|
||||
<span class="topbar-text">
|
||||
<a href="{{ SETTINGS.APP_URL_ROOT }}">Stackdump</a>
|
||||
{% if site %}
|
||||
<span class="topbar-divider">//</span>
|
||||
<a href="{{ SETTINGS.APP_URL_ROOT }}{{ site_root_path }}">{{ site.name }}</a>
|
||||
from {{ site.dump_date }}
|
||||
{% endif %}
|
||||
</span>
|
||||
|
||||
|
||||
<form method="get" action="{{ SETTINGS.APP_URL_ROOT }}{{ site_root_path }}search" class="pull-right">
|
||||
<input type="text" placeholder="Search" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="content">
|
||||
<div class="container">
|
||||
{%- block body %}
|
||||
{% endblock -%}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="footer">
|
||||
Stackdump is licensed under
|
||||
<a href="http://en.wikipedia.org/wiki/MIT_License">MIT License</a>. The
|
||||
contents is licensed under <a href="http://creativecommons.org/licenses/by-sa/3.0/">
|
||||
Creative Commons [cc-wiki]</a>.
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -3,5 +3,49 @@
|
||||
{% block title %}Stackdump Home{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
Welcome to stackdump.
|
||||
<div class="row">
|
||||
<div class="span12">
|
||||
<div class="search-container">
|
||||
<h1>
|
||||
Welcome to Stackdump,
|
||||
<small class="tagline">the offline browser for StackExchange sites.</small>
|
||||
</h1>
|
||||
<form id="search" method="get" action="{{ SETTINGS.APP_URL_ROOT }}search">
|
||||
<input type="text" class="xlarge" name="q" />
|
||||
<input type="submit" class="btn primary" value="Search" />
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="random-questions-container">
|
||||
<h3>Random questions</h3>
|
||||
<ul class="random-questions">
|
||||
<li>
|
||||
Question 1
|
||||
</li>
|
||||
<li>
|
||||
Question 2
|
||||
</li>
|
||||
<li>
|
||||
Question 3
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="span4">
|
||||
<div class="well available-sites-container">
|
||||
<h3>Available sites</h3>
|
||||
<ul class="available-sites">
|
||||
{% for s in sites %}
|
||||
<li>
|
||||
<img src="{{ SETTINGS.APP_URL_ROOT }}media/images/logos/{{ s.key }}.png" alt="{{ s.name }} logo" />
|
||||
<h6>
|
||||
<a href="{{ SETTINGS.APP_URL_ROOT }}{{ s.key }}/">{{ s.name }}</a>
|
||||
<small class="tagline">{{ s.dump_date }}</small>
|
||||
</h6>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
15
python/src/stackdump/templates/results.html
Normal file
15
python/src/stackdump/templates/results.html
Normal file
@ -0,0 +1,15 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %}Stackdump search for "{{ query }}"{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="row">
|
||||
<div class="span16">
|
||||
<ul id="search-results">
|
||||
{% for r in results %}
|
||||
<li>{{ r }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
36
python/src/stackdump/templates/site_index.html
Normal file
36
python/src/stackdump/templates/site_index.html
Normal file
@ -0,0 +1,36 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %}Stackdump // {{ site.name }} Home{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="row">
|
||||
<div class="span16">
|
||||
<div class="search-container">
|
||||
<img class="site-logo" src="{{ SETTINGS.APP_URL_ROOT }}media/images/logos/{{ site.key }}.png" alt="{{ site.name }} logo" />
|
||||
<h1 class="site-title">
|
||||
{{ site.name }}
|
||||
<small class="tagline">{{ site.desc }}</small>
|
||||
</h1>
|
||||
<form id="search" method="post" action="{{ SETTINGS.APP_URL_ROOT }}{{ site_root_path }}search">
|
||||
<input type="text" class="xlarge" name="q" />
|
||||
<input type="submit" class="btn primary" value="Search" />
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="random-questions-container">
|
||||
<h3>Random questions</h3>
|
||||
<ul class="random-questions">
|
||||
<li>
|
||||
Question 1
|
||||
</li>
|
||||
<li>
|
||||
Question 2
|
||||
</li>
|
||||
<li>
|
||||
Question 3
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
Loading…
Reference in New Issue
Block a user