mirror of
https://github.com/djohnlewis/stackdump
synced 2025-04-08 02:33:27 +00:00
Started implementing the search results view.
This commit is contained in:
parent
f8a6e7c455
commit
098a4f2fa9
python
@ -52,36 +52,91 @@
|
|||||||
padding-right: 60px;
|
padding-right: 60px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.available-sites-container {
|
.site-list-container {
|
||||||
margin-top: 40px;
|
margin-top: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.available-sites {
|
.site-list {
|
||||||
list-style-image: none;
|
list-style-image: none;
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.available-sites li {
|
.site-list li {
|
||||||
clear: both;
|
clear: both;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.available-sites img {
|
.site-list img {
|
||||||
float: left;
|
float: left;
|
||||||
margin-right: 7px;
|
margin-right: 7px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.available-sites h6 {
|
.site-list h6 {
|
||||||
padding-top: 7px;
|
padding-top: 7px;
|
||||||
line-height: normal;
|
line-height: normal;
|
||||||
margin-left: 55px; /* 48px for the logo, 7px for the padding */
|
margin-left: 55px; /* 48px for the logo, 7px for the padding */
|
||||||
}
|
}
|
||||||
|
|
||||||
.available-sites .tagline {
|
.site-list .tagline {
|
||||||
padding-top: 3px;
|
padding-top: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.total-hits {
|
||||||
|
color: #999999;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.total-hits p {
|
||||||
|
font-size: 16pt;
|
||||||
|
line-height: 20pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-value {
|
||||||
|
font-size: 36pt;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
#search-results {
|
||||||
|
list-style-image: none;
|
||||||
|
list-style-type: none;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#search-results li {
|
||||||
|
clear: both;
|
||||||
|
border-top: 1px solid #CCCCCC;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-stats-vertical {
|
||||||
|
float: left;
|
||||||
|
width: 64px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-stat {
|
||||||
|
text-align: center;
|
||||||
|
background-color: #CCCCCC;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
padding: 4px 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-stat p {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-stat-value {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 16pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-summary {
|
||||||
|
padding-left: 72px; /* 64px for post-stats-vertical + 8px for padding */
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-details {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
#footer {
|
#footer {
|
||||||
margin-top: 50px;
|
margin-top: 50px;
|
||||||
border-top: solid 1px #999999;
|
border-top: solid 1px #999999;
|
||||||
|
@ -2,6 +2,14 @@ import sys
|
|||||||
import os
|
import os
|
||||||
import threading
|
import threading
|
||||||
import functools
|
import functools
|
||||||
|
import re
|
||||||
|
|
||||||
|
try:
|
||||||
|
# For Python < 2.6 or people using a newer version of simplejson
|
||||||
|
import simplejson as json
|
||||||
|
except ImportError:
|
||||||
|
# For Python >= 2.6
|
||||||
|
import json
|
||||||
|
|
||||||
from bottle import route, run, static_file, debug, abort, request, redirect
|
from bottle import route, run, static_file, debug, abort, request, redirect
|
||||||
from jinja2 import Environment, PackageLoader
|
from jinja2 import Environment, PackageLoader
|
||||||
@ -19,6 +27,21 @@ MEDIA_ROOT = os.path.abspath(BOTTLE_ROOT + '/../../media')
|
|||||||
thread_locals = threading.local()
|
thread_locals = threading.local()
|
||||||
|
|
||||||
|
|
||||||
|
# CUSTOM TEMPLATE TAGS AND FILTERS
|
||||||
|
|
||||||
|
def parse_se_tags(value):
|
||||||
|
'''\
|
||||||
|
Parses the string of tags as given in the StackExchange XML site dump. The
|
||||||
|
format is:
|
||||||
|
|
||||||
|
<feature-request><filter>
|
||||||
|
'''
|
||||||
|
# if it isn't a string, just do nothing
|
||||||
|
if not isinstance(value, basestring):
|
||||||
|
return value
|
||||||
|
|
||||||
|
return re.findall(r'<([^>]+)>', value)
|
||||||
|
|
||||||
# RESOURCE DECORATORS
|
# RESOURCE DECORATORS
|
||||||
|
|
||||||
def uses_templates(fn):
|
def uses_templates(fn):
|
||||||
@ -39,6 +62,7 @@ def uses_templates(fn):
|
|||||||
# template.
|
# template.
|
||||||
extensions=['jinja2.ext.autoescape']
|
extensions=['jinja2.ext.autoescape']
|
||||||
)
|
)
|
||||||
|
thread_locals.template_env.filters['parse_se_tags'] = parse_se_tags
|
||||||
|
|
||||||
if not fn:
|
if not fn:
|
||||||
init_templates()
|
init_templates()
|
||||||
@ -153,6 +177,7 @@ def site_index(site_key):
|
|||||||
@route('/search')
|
@route('/search')
|
||||||
@uses_templates
|
@uses_templates
|
||||||
@uses_solr
|
@uses_solr
|
||||||
|
@uses_db
|
||||||
def search():
|
def search():
|
||||||
# TODO: scrub this first to avoid Solr injection attacks?
|
# TODO: scrub this first to avoid Solr injection attacks?
|
||||||
query = request.GET.get('q')
|
query = request.GET.get('q')
|
||||||
@ -164,11 +189,16 @@ def search():
|
|||||||
|
|
||||||
# perform search
|
# perform search
|
||||||
results = solr_conn().search(query, start=page*rows_per_page, rows=rows_per_page)
|
results = solr_conn().search(query, start=page*rows_per_page, rows=rows_per_page)
|
||||||
|
decode_json_fields(results)
|
||||||
|
|
||||||
context = { }
|
context = { }
|
||||||
|
context['site_root_path'] = ''
|
||||||
|
context['sites'] = Site.select()
|
||||||
|
|
||||||
# TODO: scrub this first to avoid HTML injection attacks?
|
# TODO: scrub this first to avoid HTML injection attacks?
|
||||||
context['query'] = query
|
context['query'] = query
|
||||||
context['results'] = results
|
context['results'] = results
|
||||||
|
context['total_hits'] = results.hits
|
||||||
|
|
||||||
return render_template('results.html', context)
|
return render_template('results.html', context)
|
||||||
|
|
||||||
@ -194,6 +224,7 @@ def site_search(site_key):
|
|||||||
|
|
||||||
# perform search
|
# perform search
|
||||||
results = solr_conn().search(query, start=page*rows_per_page, rows=rows_per_page)
|
results = solr_conn().search(query, start=page*rows_per_page, rows=rows_per_page)
|
||||||
|
decode_json_fields(results)
|
||||||
|
|
||||||
# TODO: scrub this first to avoid HTML injection attacks?
|
# TODO: scrub this first to avoid HTML injection attacks?
|
||||||
context['query'] = query
|
context['query'] = query
|
||||||
@ -239,6 +270,40 @@ def get_template_settings():
|
|||||||
|
|
||||||
return template_settings
|
return template_settings
|
||||||
|
|
||||||
|
def decode_json_fields(obj):
|
||||||
|
'''\
|
||||||
|
Looks for keys in obj that end in -json, decodes the corresponding value and
|
||||||
|
stores that in the key minus -json suffix.
|
||||||
|
|
||||||
|
If the obj is only a dict, then wrap it in a list because the we also want
|
||||||
|
to process list of dicts. If it is not a dict, it is assumed to be a list.\
|
||||||
|
'''
|
||||||
|
if obj == None:
|
||||||
|
return obj
|
||||||
|
|
||||||
|
if isinstance(obj, dict):
|
||||||
|
objs = [ obj ]
|
||||||
|
else:
|
||||||
|
objs = obj
|
||||||
|
|
||||||
|
for o in objs:
|
||||||
|
for k in o.keys():
|
||||||
|
if k.endswith('-json'):
|
||||||
|
decoded_key = k[:-len('-json')]
|
||||||
|
|
||||||
|
json_value = o[k]
|
||||||
|
if isinstance(json_value, list):
|
||||||
|
decoded_list = [ ]
|
||||||
|
for j in json_value:
|
||||||
|
decoded_list.append(json.loads(j))
|
||||||
|
|
||||||
|
o[decoded_key] = decoded_list
|
||||||
|
else: # assume it is a JSON string
|
||||||
|
o[decoded_key] = json.loads(json_value)
|
||||||
|
|
||||||
|
# remove the JSON string from the dict-object
|
||||||
|
del o[k]
|
||||||
|
|
||||||
# END VIEW HELPERS
|
# END VIEW HELPERS
|
||||||
|
|
||||||
# INITIALISATION
|
# INITIALISATION
|
||||||
|
@ -32,9 +32,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="span4">
|
<div class="span4">
|
||||||
<div class="well available-sites-container">
|
<div class="well site-list-container">
|
||||||
<h3>Available sites</h3>
|
<h3>Available sites</h3>
|
||||||
<ul class="available-sites">
|
<ul class="site-list">
|
||||||
{% for s in sites %}
|
{% for s in sites %}
|
||||||
<li>
|
<li>
|
||||||
<img src="{{ SETTINGS.APP_URL_ROOT }}media/logos/{{ s.key }}.png" alt="{{ s.name }} logo" />
|
<img src="{{ SETTINGS.APP_URL_ROOT }}media/logos/{{ s.key }}.png" alt="{{ s.name }} logo" />
|
||||||
|
@ -5,11 +5,75 @@
|
|||||||
{% block body %}
|
{% block body %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="span16">
|
<div class="span16">
|
||||||
|
<form id="search" method="get" action="{{ SETTINGS.APP_URL_ROOT }}search">
|
||||||
|
<input type="text" class="xlarge" name="q" value="{{ query }}" />
|
||||||
|
<input type="submit" class="btn primary" value="Search" />
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<ul class="tabs">
|
||||||
|
<li class="active"><a href="#">newest</a></li>
|
||||||
|
<li><a href="#">votes</a></li>
|
||||||
|
<li><a href="#">relevance</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="span12">
|
||||||
<ul id="search-results">
|
<ul id="search-results">
|
||||||
{% for r in results %}
|
{% for r in results %}
|
||||||
<li>{{ r }}</li>
|
<li>
|
||||||
|
<div class="post-stats-vertical">
|
||||||
|
<div class="post-stat">
|
||||||
|
<p class="post-stat-value">{{ r.question.score }}</p>
|
||||||
|
<p>vote{% if r.question.score != 1 %}s{% endif %}</p>
|
||||||
|
</div>
|
||||||
|
<div class="post-stat">
|
||||||
|
<p class="post-stat-value">{{ r.answer|length }}</p>
|
||||||
|
<p>answer{% if r.answer|length != 1 %}s{% endif %}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="post-summary">
|
||||||
|
<h3>{{ r.question.title }}</h3>
|
||||||
|
<p>{{ r.question.body|striptags|truncate(256) }}</p>
|
||||||
|
<p class="post-details">
|
||||||
|
Asked by <strong>{{ r.question.ownerUserId }}</strong> on
|
||||||
|
<strong>{{ r.question.creationDate }}</strong>.
|
||||||
|
</p>
|
||||||
|
<div class="post-tags">
|
||||||
|
{% for t in r.question.tags|parse_se_tags %}
|
||||||
|
<span class="label">{{ t }}</span>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="span4">
|
||||||
|
<div class="total-hits">
|
||||||
|
<p>
|
||||||
|
<span class="stat-value">{{ total_hits }}</span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
posts matched your query <em>"{{ query }}"</em>.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="well site-list-container">
|
||||||
|
<h3>Narrow by site</h3>
|
||||||
|
<ul class="site-list">
|
||||||
|
{% for s in sites %}
|
||||||
|
<li>
|
||||||
|
<img src="{{ SETTINGS.APP_URL_ROOT }}media/logos/{{ s.key }}.png" alt="{{ s.name }} logo" />
|
||||||
|
<h6>
|
||||||
|
<a href="{{ SETTINGS.APP_URL_ROOT }}{{ s.key }}/search?q={{ query }}">{{ s.name }}</a>
|
||||||
|
<small class="tagline">{{ s.dump_date }}</small>
|
||||||
|
</h6>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
Loading…
x
Reference in New Issue
Block a user