mirror of
https://github.com/djohnlewis/stackdump
synced 2024-12-04 23:17:37 +00:00
Started implementing the search results view.
This commit is contained in:
parent
f8a6e7c455
commit
098a4f2fa9
@ -52,36 +52,91 @@
|
||||
padding-right: 60px;
|
||||
}
|
||||
|
||||
.available-sites-container {
|
||||
.site-list-container {
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.available-sites {
|
||||
.site-list {
|
||||
list-style-image: none;
|
||||
list-style-type: none;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.available-sites li {
|
||||
.site-list li {
|
||||
clear: both;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.available-sites img {
|
||||
.site-list img {
|
||||
float: left;
|
||||
margin-right: 7px;
|
||||
}
|
||||
|
||||
.available-sites h6 {
|
||||
.site-list h6 {
|
||||
padding-top: 7px;
|
||||
line-height: normal;
|
||||
margin-left: 55px; /* 48px for the logo, 7px for the padding */
|
||||
}
|
||||
|
||||
.available-sites .tagline {
|
||||
.site-list .tagline {
|
||||
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 {
|
||||
margin-top: 50px;
|
||||
border-top: solid 1px #999999;
|
||||
|
@ -2,6 +2,14 @@ import sys
|
||||
import os
|
||||
import threading
|
||||
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 jinja2 import Environment, PackageLoader
|
||||
@ -19,6 +27,21 @@ MEDIA_ROOT = os.path.abspath(BOTTLE_ROOT + '/../../media')
|
||||
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
|
||||
|
||||
def uses_templates(fn):
|
||||
@ -39,6 +62,7 @@ def uses_templates(fn):
|
||||
# template.
|
||||
extensions=['jinja2.ext.autoescape']
|
||||
)
|
||||
thread_locals.template_env.filters['parse_se_tags'] = parse_se_tags
|
||||
|
||||
if not fn:
|
||||
init_templates()
|
||||
@ -153,6 +177,7 @@ def site_index(site_key):
|
||||
@route('/search')
|
||||
@uses_templates
|
||||
@uses_solr
|
||||
@uses_db
|
||||
def search():
|
||||
# TODO: scrub this first to avoid Solr injection attacks?
|
||||
query = request.GET.get('q')
|
||||
@ -164,11 +189,16 @@ def search():
|
||||
|
||||
# perform search
|
||||
results = solr_conn().search(query, start=page*rows_per_page, rows=rows_per_page)
|
||||
decode_json_fields(results)
|
||||
|
||||
context = { }
|
||||
context['site_root_path'] = ''
|
||||
context['sites'] = Site.select()
|
||||
|
||||
# TODO: scrub this first to avoid HTML injection attacks?
|
||||
context['query'] = query
|
||||
context['results'] = results
|
||||
context['total_hits'] = results.hits
|
||||
|
||||
return render_template('results.html', context)
|
||||
|
||||
@ -194,6 +224,7 @@ def site_search(site_key):
|
||||
|
||||
# perform search
|
||||
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?
|
||||
context['query'] = query
|
||||
@ -239,6 +270,40 @@ def get_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
|
||||
|
||||
# INITIALISATION
|
||||
|
@ -32,9 +32,9 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="span4">
|
||||
<div class="well available-sites-container">
|
||||
<div class="well site-list-container">
|
||||
<h3>Available sites</h3>
|
||||
<ul class="available-sites">
|
||||
<ul class="site-list">
|
||||
{% for s in sites %}
|
||||
<li>
|
||||
<img src="{{ SETTINGS.APP_URL_ROOT }}media/logos/{{ s.key }}.png" alt="{{ s.name }} logo" />
|
||||
|
@ -5,11 +5,75 @@
|
||||
{% block body %}
|
||||
<div class="row">
|
||||
<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">
|
||||
{% 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 %}
|
||||
</ul>
|
||||
</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 %}
|
Loading…
Reference in New Issue
Block a user