add multi theming support

This commit is contained in:
Matej Cotman 2014-04-24 23:46:40 +00:00
parent 3386e21cdf
commit 08eaffe245
41 changed files with 104 additions and 29 deletions

View File

@ -44,13 +44,13 @@ minimal: bin/buildout minimal.cfg setup.py
bin/buildout -c minimal.cfg $(options) bin/buildout -c minimal.cfg $(options)
styles: styles:
@lessc -x searx/static/less/style.less > searx/static/css/style.css @lessc -x searx/static/default/less/style.less > searx/static/default/css/style.css
locales: locales:
@pybabel compile -d searx/translations @pybabel compile -d searx/translations
clean: clean:
@rm -rf .installed.cfg .mr.developer.cfg bin parts develop-eggs \ @rm -rf .installed.cfg .mr.developer.cfg bin parts develop-eggs \
searx.egg-info lib include .coverage coverage searx/static/css/*.css searx.egg-info lib include .coverage coverage searx/static/default/css/*.css
.PHONY: all tests robot flake8 coverage production minimal styles locales clean .PHONY: all tests robot flake8 coverage production minimal styles locales clean

View File

@ -4,6 +4,8 @@ server:
debug : True debug : True
request_timeout : 2.0 # seconds request_timeout : 2.0 # seconds
base_url : False base_url : False
themes_path : ""
default_theme : default
engines: engines:
- name : wikipedia - name : wikipedia

View File

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 837 B

After

Width:  |  Height:  |  Size: 837 B

View File

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

View File

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 7.1 KiB

View File

@ -1,6 +1,6 @@
{% extends 'base.html' %} {% extends 'default/base.html' %}
{% block content %} {% block content %}
{% include 'github_ribbon.html' %} {% include 'default/github_ribbon.html' %}
<div class="row"> <div class="row">
<h1>About <a href="{{ url_for('index') }}">searx</a></h1> <h1>About <a href="{{ url_for('index') }}">searx</a></h1>

View File

@ -1,8 +1,8 @@
{% extends "base.html" %} {% extends "default/base.html" %}
{% block content %} {% block content %}
<div class="center"> <div class="center">
<div class="title"><h1>searx</h1></div> <div class="title"><h1>searx</h1></div>
{% include 'search.html' %} {% include 'default/search.html' %}
<p class="top_margin"> <p class="top_margin">
<a href="{{ url_for('about') }}" class="hmarg">{{ _('about') }}</a> <a href="{{ url_for('about') }}" class="hmarg">{{ _('about') }}</a>
<a href="{{ url_for('preferences') }}" class="hmarg">{{ _('preferences') }}</a> <a href="{{ url_for('preferences') }}" class="hmarg">{{ _('preferences') }}</a>

View File

@ -1,4 +1,4 @@
{% extends "base.html" %} {% extends "default/base.html" %}
{% block head %} {% endblock %} {% block head %} {% endblock %}
{% block content %} {% block content %}
<div class="row"> <div class="row">
@ -8,7 +8,7 @@
<fieldset> <fieldset>
<legend>{{ _('Default categories') }}</legend> <legend>{{ _('Default categories') }}</legend>
<p> <p>
{% include 'categories.html' %} {% include 'default/categories.html' %}
</p> </p>
</fieldset> </fieldset>
<fieldset> <fieldset>
@ -52,6 +52,16 @@
</select> </select>
</p> </p>
</fieldset> </fieldset>
<fieldset>
<legend>{{ _('Themes') }}</legend>
<p>
<select name="theme">
{% for name in themes %}
<option value="{{ name }}" {% if name == theme %}selected="selected"{% endif %}>{{ name }}</option>
{% endfor %}
</select>
</p>
</fieldset>
<fieldset> <fieldset>
<legend>{{ _('Currently used search engines') }}</legend> <legend>{{ _('Currently used search engines') }}</legend>

View File

@ -1,7 +1,7 @@
<div class="result {{ result.class }}"> <div class="result {{ result.class }}">
{% if result['favicon'] %} {% if result['favicon'] %}
<img width="14" height="14" class="favicon" src="static/img/icon_{{result['favicon']}}.ico" /> <img width="14" height="14" class="favicon" src="static/{{theme}}/img/icon_{{result['favicon']}}.ico" />
{% endif %} {% endif %}
<div> <div>

View File

@ -1,6 +1,6 @@
<div class="result"> <div class="result">
{% if result['favicon'] %} {% if result['favicon'] %}
<img width="14" height="14" class="favicon" src="static/img/icon_{{result['favicon']}}.ico" /> <img width="14" height="14" class="favicon" src="static/{{theme}}/img/icon_{{result['favicon']}}.ico" />
{% endif %} {% endif %}
<p> <p>

View File

@ -1,9 +1,9 @@
{% extends "base.html" %} {% extends "default/base.html" %}
{% block title %}{{ q }} - {% endblock %} {% block title %}{{ q }} - {% endblock %}
{% block content %} {% block content %}
<div class="right"><a href="{{ url_for('preferences') }}" id="preferences"><span>preferences</span></a></div> <div class="right"><a href="{{ url_for('preferences') }}" id="preferences"><span>preferences</span></a></div>
<div class="small search center"> <div class="small search center">
{% include 'search.html' %} {% include 'default/search.html' %}
</div> </div>
<div id="results"> <div id="results">
<div id="sidebar"> <div id="sidebar">
@ -43,9 +43,9 @@
{% for result in results %} {% for result in results %}
{% if result['template'] %} {% if result['template'] %}
{% include 'result_templates/'+result['template'] %} {% include 'default/result_templates/'+result['template'] %}
{% else %} {% else %}
{% include 'result_templates/default.html' %} {% include 'default/result_templates/default.html' %}
{% endif %} {% endif %}
{% endfor %} {% endfor %}

View File

@ -3,5 +3,5 @@
<input type="text" placeholder="{{ _('Search for...') }}" id="q" class="q" name="q" tabindex="1" autocomplete="off" {% if q %}value="{{ q }}"{% endif %}/> <input type="text" placeholder="{{ _('Search for...') }}" id="q" class="q" name="q" tabindex="1" autocomplete="off" {% if q %}value="{{ q }}"{% endif %}/>
<input type="submit" value="search" id="search_submit" /> <input type="submit" value="search" id="search_submit" />
</div> </div>
{% include 'categories.html' %} {% include 'default/categories.html' %}
</form> </form>

View File

@ -1,4 +1,4 @@
{% extends "base.html" %} {% extends "default/base.html" %}
{% block head %} {% endblock %} {% block head %} {% endblock %}
{% block content %} {% block content %}
<h2>{{ _('Engine stats') }}</h2> <h2>{{ _('Engine stats') }}</h2>

View File

@ -1,11 +1,13 @@
from HTMLParser import HTMLParser
#import htmlentitydefs #import htmlentitydefs
import csv
from codecs import getincrementalencoder from codecs import getincrementalencoder
import cStringIO from HTMLParser import HTMLParser
import re
from random import choice from random import choice
import cStringIO
import csv
import os
import re
ua_versions = ('26.0', '27.0', '28.0') ua_versions = ('26.0', '27.0', '28.0')
ua_os = ('Windows NT 6.3; WOW64', ua_os = ('Windows NT 6.3; WOW64',
'X11; Linux x86_64', 'X11; Linux x86_64',
@ -110,3 +112,17 @@ class UnicodeWriter:
def writerows(self, rows): def writerows(self, rows):
for row in rows: for row in rows:
self.writerow(row) self.writerow(row)
def get_themes(root):
"""Returns available themes list."""
static_path = os.path.join(root, 'static')
static_names = set(os.listdir(static_path))
templates_path = os.path.join(root, 'templates')
templates_names = set(os.listdir(templates_path))
themes = []
for name in static_names.intersection(templates_names):
themes += [name]
return static_path, templates_path, themes

View File

@ -38,16 +38,23 @@ from searx.engines import (
search as do_search, categories, engines, get_engines_stats, search as do_search, categories, engines, get_engines_stats,
engine_shortcuts engine_shortcuts
) )
from searx.utils import UnicodeWriter, highlight_content, html_to_text from searx.utils import (
UnicodeWriter, highlight_content, html_to_text, get_themes
)
from searx.languages import language_codes from searx.languages import language_codes
from searx.search import Search from searx.search import Search
from searx.autocomplete import backends as autocomplete_backends from searx.autocomplete import backends as autocomplete_backends
static_path, templates_path, themes = get_themes(settings['themes_path'] if \
settings.get('themes_path', None) else searx_dir)
default_theme = settings['default_theme'] if \
settings.get('default_theme', None) else 'default'
app = Flask( app = Flask(
__name__, __name__,
static_folder=os.path.join(searx_dir, 'static'), static_folder=static_path,
template_folder=os.path.join(searx_dir, 'templates') template_folder=templates_path
) )
app.secret_key = settings['server']['secret_key'] app.secret_key = settings['server']['secret_key']
@ -90,7 +97,30 @@ def get_base_url():
return hostname return hostname
def render(template_name, **kwargs): def get_current_theme_name(override=None):
"""Returns theme name.
Checks in this order:
1. override
2. cookies
3. settings"""
if override and override in themes:
return override
theme_name = request.cookies.get('theme', default_theme)
if theme_name not in themes:
theme_name = default_theme
return theme_name
def url_for_theme(endpoint, override_theme=None, **values):
if endpoint == 'static' and values.get('filename', None):
theme_name = get_current_theme_name(override=override_theme)
values['filename'] = "{}/{}".format(theme_name, values['filename'])
return url_for(endpoint, **values)
def render(template_name, override_theme=None, **kwargs):
blocked_engines = request.cookies.get('blocked_engines', '').split(',') blocked_engines = request.cookies.get('blocked_engines', '').split(',')
autocomplete = request.cookies.get('autocomplete') autocomplete = request.cookies.get('autocomplete')
@ -125,7 +155,13 @@ def render(template_name, **kwargs):
kwargs['method'] = request.cookies.get('method', 'POST') kwargs['method'] = request.cookies.get('method', 'POST')
return render_template(template_name, **kwargs) # override url_for function in templates
kwargs['url_for'] = url_for_theme
kwargs['theme'] = get_current_theme_name(override=override_theme)
return render_template(
'{}/{}'.format(kwargs['theme'], template_name), **kwargs)
@app.route('/search', methods=['GET', 'POST']) @app.route('/search', methods=['GET', 'POST'])
@ -232,7 +268,8 @@ def index():
paging=search.paging, paging=search.paging,
pageno=search.pageno, pageno=search.pageno,
base_url=get_base_url(), base_url=get_base_url(),
suggestions=search.suggestions suggestions=search.suggestions,
theme=get_current_theme_name()
) )
@ -290,7 +327,7 @@ def preferences():
if request.method == 'GET': if request.method == 'GET':
blocked_engines = request.cookies.get('blocked_engines', '').split(',') blocked_engines = request.cookies.get('blocked_engines', '').split(',')
else: else: # on save
selected_categories = [] selected_categories = []
locale = None locale = None
autocomplete = '' autocomplete = ''
@ -315,6 +352,8 @@ def preferences():
engine_name = pd_name.replace('engine_', '', 1) engine_name = pd_name.replace('engine_', '', 1)
if engine_name in engines: if engine_name in engines:
blocked_engines.append(engine_name) blocked_engines.append(engine_name)
elif pd_name == 'theme':
theme = pd if pd in themes else default_theme
resp = make_response(redirect(url_for('index'))) resp = make_response(redirect(url_for('index')))
@ -352,6 +391,9 @@ def preferences():
resp.set_cookie('method', method, max_age=cookie_max_age) resp.set_cookie('method', method, max_age=cookie_max_age)
resp.set_cookie(
'theme', theme, max_age=cookie_max_age)
return resp return resp
return render('preferences.html', return render('preferences.html',
locales=settings['locales'], locales=settings['locales'],
@ -361,7 +403,9 @@ def preferences():
categs=categories.items(), categs=categories.items(),
blocked_engines=blocked_engines, blocked_engines=blocked_engines,
autocomplete_backends=autocomplete_backends, autocomplete_backends=autocomplete_backends,
shortcuts={y: x for x, y in engine_shortcuts.items()}) shortcuts={y: x for x, y in engine_shortcuts.items()},
themes=themes,
theme=get_current_theme_name())
@app.route('/stats', methods=['GET']) @app.route('/stats', methods=['GET'])
@ -404,7 +448,10 @@ def opensearch():
@app.route('/favicon.ico') @app.route('/favicon.ico')
def favicon(): def favicon():
return send_from_directory(os.path.join(app.root_path, 'static/img'), return send_from_directory(os.path.join(app.root_path,
'static',
get_current_theme_name(),
'img'),
'favicon.png', 'favicon.png',
mimetype='image/vnd.microsoft.icon') mimetype='image/vnd.microsoft.icon')