Browse Source

Merge pull request #534 from kvch/preferences-refactor

new preferences handling
Adam Tauber 9 years ago
parent
commit
149b08a062
8 changed files with 525 additions and 168 deletions
  1. 269
    0
      searx/preferences.py
  2. 5
    10
      searx/search.py
  3. 1
    1
      searx/settings_robot.yml
  4. 0
    23
      searx/utils.py
  5. 36
    133
      searx/webapp.py
  6. 108
    0
      tests/robot/test_basic.robot
  7. 101
    0
      tests/unit/test_preferences.py
  8. 5
    1
      tests/unit/test_webapp.py

+ 269
- 0
searx/preferences.py View File

@@ -0,0 +1,269 @@
1
+from searx import settings, autocomplete
2
+from searx.languages import language_codes as languages
3
+
4
+
5
+COOKIE_MAX_AGE = 60 * 60 * 24 * 365 * 5  # 5 years
6
+LANGUAGE_CODES = [l[0] for l in languages]
7
+LANGUAGE_CODES.append('all')
8
+DISABLED = 0
9
+ENABLED = 1
10
+
11
+
12
+class MissingArgumentException(Exception):
13
+    pass
14
+
15
+
16
+class ValidationException(Exception):
17
+    pass
18
+
19
+
20
+class Setting(object):
21
+    """Base class of user settings"""
22
+
23
+    def __init__(self, default_value, **kwargs):
24
+        super(Setting, self).__init__()
25
+        self.value = default_value
26
+        for key, value in kwargs.iteritems():
27
+            setattr(self, key, value)
28
+
29
+        self._post_init()
30
+
31
+    def _post_init(self):
32
+        pass
33
+
34
+    def parse(self, data):
35
+        self.value = data
36
+
37
+    def get_value(self):
38
+        return self.value
39
+
40
+    def save(self, name, resp):
41
+        resp.set_cookie(name, bytes(self.value), max_age=COOKIE_MAX_AGE)
42
+
43
+
44
+class StringSetting(Setting):
45
+    """Setting of plain string values"""
46
+    pass
47
+
48
+
49
+class EnumStringSetting(Setting):
50
+    """Setting of a value which can only come from the given choices"""
51
+
52
+    def _post_init(self):
53
+        if not hasattr(self, 'choices'):
54
+            raise MissingArgumentException('Missing argument: choices')
55
+
56
+        if self.value != '' and self.value not in self.choices:
57
+            raise ValidationException('Invalid default value: {0}'.format(self.value))
58
+
59
+    def parse(self, data):
60
+        if data not in self.choices and data != self.value:
61
+            raise ValidationException('Invalid choice: {0}'.format(data))
62
+        self.value = data
63
+
64
+
65
+class MultipleChoiceSetting(EnumStringSetting):
66
+    """Setting of values which can only come from the given choices"""
67
+
68
+    def _post_init(self):
69
+        if not hasattr(self, 'choices'):
70
+            raise MissingArgumentException('Missing argument: choices')
71
+        for item in self.value:
72
+            if item not in self.choices:
73
+                raise ValidationException('Invalid default value: {0}'.format(self.value))
74
+
75
+    def parse(self, data):
76
+        if data == '':
77
+            self.value = []
78
+            return
79
+
80
+        elements = data.split(',')
81
+        for item in elements:
82
+            if item not in self.choices:
83
+                raise ValidationException('Invalid choice: {0}'.format(item))
84
+        self.value = elements
85
+
86
+    def parse_form(self, data):
87
+        self.value = []
88
+        for choice in data:
89
+            if choice in self.choices and choice not in self.value:
90
+                self.value.append(choice)
91
+
92
+    def save(self, name, resp):
93
+        resp.set_cookie(name, ','.join(self.value), max_age=COOKIE_MAX_AGE)
94
+
95
+
96
+class MapSetting(Setting):
97
+    """Setting of a value that has to be translated in order to be storable"""
98
+
99
+    def _post_init(self):
100
+        if not hasattr(self, 'map'):
101
+            raise MissingArgumentException('missing argument: map')
102
+        if self.value not in self.map.values():
103
+            raise ValidationException('Invalid default value')
104
+
105
+    def parse(self, data):
106
+        if data not in self.map:
107
+            raise ValidationException('Invalid choice: {0}'.format(data))
108
+        self.value = self.map[data]
109
+        self.key = data
110
+
111
+    def save(self, name, resp):
112
+        resp.set_cookie(name, bytes(self.key), max_age=COOKIE_MAX_AGE)
113
+
114
+
115
+class SwitchableSetting(Setting):
116
+    """ Base class for settings that can be turned on && off"""
117
+
118
+    def _post_init(self):
119
+        self.disabled = set()
120
+        self.enabled = set()
121
+        if not hasattr(self, 'choices'):
122
+            raise MissingArgumentException('missing argument: choices')
123
+
124
+    def transform_form_items(self, items):
125
+        return items
126
+
127
+    def transform_values(self, values):
128
+        return values
129
+
130
+    def parse_cookie(self, data):
131
+        if data[DISABLED] != '':
132
+            self.disabled = set(data[DISABLED].split(','))
133
+        if data[ENABLED] != '':
134
+            self.enabled = set(data[ENABLED].split(','))
135
+
136
+    def parse_form(self, items):
137
+        items = self.transform_form_items(items)
138
+
139
+        self.disabled = set()
140
+        self.enabled = set()
141
+        for choice in self.choices:
142
+            if choice['default_on']:
143
+                if choice['id'] in items:
144
+                    self.disabled.add(choice['id'])
145
+            else:
146
+                if choice['id'] not in items:
147
+                    self.enabled.add(choice['id'])
148
+
149
+    def save(self, resp):
150
+        resp.set_cookie('disabled_{0}'.format(self.value), ','.join(self.disabled), max_age=COOKIE_MAX_AGE)
151
+        resp.set_cookie('enabled_{0}'.format(self.value), ','.join(self.enabled), max_age=COOKIE_MAX_AGE)
152
+
153
+    def get_disabled(self):
154
+        disabled = self.disabled
155
+        for choice in self.choices:
156
+            if not choice['default_on'] and choice['id'] not in self.enabled:
157
+                disabled.add(choice['id'])
158
+        return self.transform_values(disabled)
159
+
160
+    def get_enabled(self):
161
+        enabled = self.enabled
162
+        for choice in self.choices:
163
+            if choice['default_on'] and choice['id'] not in self.disabled:
164
+                enabled.add(choice['id'])
165
+        return self.transform_values(enabled)
166
+
167
+
168
+class EnginesSetting(SwitchableSetting):
169
+    def _post_init(self):
170
+        super(EnginesSetting, self)._post_init()
171
+        transformed_choices = []
172
+        for engine_name, engine in self.choices.iteritems():
173
+            for category in engine.categories:
174
+                transformed_choice = dict()
175
+                transformed_choice['default_on'] = not engine.disabled
176
+                transformed_choice['id'] = '{}__{}'.format(engine_name, category)
177
+                transformed_choices.append(transformed_choice)
178
+        self.choices = transformed_choices
179
+
180
+    def transform_form_items(self, items):
181
+        return [item[len('engine_'):].replace('_', ' ').replace('  ', '__') for item in items]
182
+
183
+    def transform_values(self, values):
184
+        if len(values) == 1 and values[0] == '':
185
+            return list()
186
+        transformed_values = []
187
+        for value in values:
188
+            engine, category = value.split('__')
189
+            transformed_values.append((engine, category))
190
+        return transformed_values
191
+
192
+
193
+class PluginsSetting(SwitchableSetting):
194
+    def _post_init(self):
195
+        super(PluginsSetting, self)._post_init()
196
+        transformed_choices = []
197
+        for plugin in self.choices:
198
+            transformed_choice = dict()
199
+            transformed_choice['default_on'] = plugin.default_on
200
+            transformed_choice['id'] = plugin.id
201
+            transformed_choices.append(transformed_choice)
202
+        self.choices = transformed_choices
203
+
204
+    def transform_form_items(self, items):
205
+        return [item[len('plugin_'):] for item in items]
206
+
207
+
208
+class Preferences(object):
209
+    """Stores, validates and saves preferences to cookies"""
210
+
211
+    def __init__(self, themes, categories, engines, plugins):
212
+        super(Preferences, self).__init__()
213
+
214
+        self.key_value_settings = {'categories': MultipleChoiceSetting(['general'], choices=categories),
215
+                                   'language': EnumStringSetting('all', choices=LANGUAGE_CODES),
216
+                                   'locale': EnumStringSetting(settings['ui']['default_locale'],
217
+                                                               choices=settings['locales'].keys()),
218
+                                   'autocomplete': EnumStringSetting(settings['search']['autocomplete'],
219
+                                                                     choices=autocomplete.backends.keys()),
220
+                                   'image_proxy': MapSetting(settings['server']['image_proxy'],
221
+                                                             map={'': settings['server']['image_proxy'],
222
+                                                                  '0': False,
223
+                                                                  '1': True}),
224
+                                   'method': EnumStringSetting('POST', choices=('GET', 'POST')),
225
+                                   'safesearch': MapSetting(settings['search']['safe_search'], map={'0': 0,
226
+                                                                                                    '1': 1,
227
+                                                                                                    '2': 2}),
228
+                                   'theme': EnumStringSetting(settings['ui']['default_theme'], choices=themes)}
229
+
230
+        self.engines = EnginesSetting('engines', choices=engines)
231
+        self.plugins = PluginsSetting('plugins', choices=plugins)
232
+
233
+    def parse_cookies(self, input_data):
234
+        for user_setting_name, user_setting in input_data.iteritems():
235
+            if user_setting_name in self.key_value_settings:
236
+                self.key_value_settings[user_setting_name].parse(user_setting)
237
+            elif user_setting_name == 'disabled_engines':
238
+                self.engines.parse_cookie([input_data['disabled_engines'], input_data['enabled_engines']])
239
+            elif user_setting_name == 'disabled_plugins':
240
+                self.plugins.parse_cookie([input_data['disabled_plugins'], input_data['enabled_plugins']])
241
+
242
+    def parse_form(self, input_data):
243
+        disabled_engines = []
244
+        enabled_categories = []
245
+        disabled_plugins = []
246
+        for user_setting_name, user_setting in input_data.iteritems():
247
+            if user_setting_name in self.key_value_settings:
248
+                self.key_value_settings[user_setting_name].parse(user_setting)
249
+            elif user_setting_name.startswith('engine_'):
250
+                disabled_engines.append(user_setting_name)
251
+            elif user_setting_name.startswith('category_'):
252
+                enabled_categories.append(user_setting_name[len('category_'):])
253
+            elif user_setting_name.startswith('plugin_'):
254
+                disabled_plugins.append(user_setting_name)
255
+        self.key_value_settings['categories'].parse_form(enabled_categories)
256
+        self.engines.parse_form(disabled_engines)
257
+        self.plugins.parse_form(disabled_plugins)
258
+
259
+    # cannot be used in case of engines or plugins
260
+    def get_value(self, user_setting_name):
261
+        if user_setting_name in self.key_value_settings:
262
+            return self.key_value_settings[user_setting_name].get_value()
263
+
264
+    def save(self, resp):
265
+        for user_setting_name, user_setting in self.key_value_settings.iteritems():
266
+            user_setting.save(user_setting_name, resp)
267
+        self.engines.save(resp)
268
+        self.plugins.save(resp)
269
+        return resp

+ 5
- 10
searx/search.py View File

@@ -23,7 +23,7 @@ from searx.engines import (
23 23
     categories, engines
24 24
 )
25 25
 from searx.languages import language_codes
26
-from searx.utils import gen_useragent, get_blocked_engines
26
+from searx.utils import gen_useragent
27 27
 from searx.query import Query
28 28
 from searx.results import ResultContainer
29 29
 from searx import logger
@@ -140,15 +140,13 @@ class Search(object):
140 140
         self.lang = 'all'
141 141
 
142 142
         # set blocked engines
143
-        self.blocked_engines = get_blocked_engines(engines, request.cookies)
143
+        self.blocked_engines = request.preferences.engines.get_disabled()
144 144
 
145 145
         self.result_container = ResultContainer()
146 146
         self.request_data = {}
147 147
 
148 148
         # set specific language if set
149
-        if request.cookies.get('language')\
150
-           and request.cookies['language'] in (x[0] for x in language_codes):
151
-            self.lang = request.cookies['language']
149
+        self.lang = request.preferences.get_value('language')
152 150
 
153 151
         # set request method
154 152
         if request.method == 'POST':
@@ -294,11 +292,8 @@ class Search(object):
294 292
             else:
295 293
                 request_params['language'] = self.lang
296 294
 
297
-            try:
298
-                # 0 = None, 1 = Moderate, 2 = Strict
299
-                request_params['safesearch'] = int(request.cookies.get('safesearch'))
300
-            except Exception:
301
-                request_params['safesearch'] = settings['search']['safe_search']
295
+            # 0 = None, 1 = Moderate, 2 = Strict
296
+            request_params['safesearch'] = request.preferences.get_value('safesearch')
302 297
 
303 298
             # update request parameters dependent on
304 299
             # search-engine (contained in engines folder)

+ 1
- 1
searx/settings_robot.yml View File

@@ -4,7 +4,7 @@ general:
4 4
 
5 5
 search:
6 6
     safe_search : 0
7
-    autocomplete : 0
7
+    autocomplete : ""
8 8
 
9 9
 server:
10 10
     port : 11111

+ 0
- 23
searx/utils.py View File

@@ -230,26 +230,3 @@ def list_get(a_list, index, default=None):
230 230
         return a_list[index]
231 231
     else:
232 232
         return default
233
-
234
-
235
-def get_blocked_engines(engines, cookies):
236
-    if 'blocked_engines' not in cookies:
237
-        return [(engine_name, category) for engine_name in engines
238
-                for category in engines[engine_name].categories if engines[engine_name].disabled]
239
-
240
-    blocked_engine_strings = cookies.get('blocked_engines', '').split(',')
241
-    blocked_engines = []
242
-
243
-    if not blocked_engine_strings:
244
-        return blocked_engines
245
-
246
-    for engine_string in blocked_engine_strings:
247
-        if engine_string.find('__') > -1:
248
-            engine, category = engine_string.split('__', 1)
249
-            if engine in engines and category in engines[engine].categories:
250
-                blocked_engines.append((engine, category))
251
-        elif engine_string in engines:
252
-            for category in engines[engine_string].categories:
253
-                blocked_engines.append((engine_string, category))
254
-
255
-    return blocked_engines

+ 36
- 133
searx/webapp.py View File

@@ -56,7 +56,7 @@ from searx.engines import (
56 56
 from searx.utils import (
57 57
     UnicodeWriter, highlight_content, html_to_text, get_themes,
58 58
     get_static_files, get_result_templates, gen_useragent, dict_subset,
59
-    prettify_url, get_blocked_engines
59
+    prettify_url
60 60
 )
61 61
 from searx.version import VERSION_STRING
62 62
 from searx.languages import language_codes
@@ -64,6 +64,7 @@ from searx.search import Search
64 64
 from searx.query import Query
65 65
 from searx.autocomplete import searx_bang, backends as autocomplete_backends
66 66
 from searx.plugins import plugins
67
+from searx.preferences import Preferences
67 68
 
68 69
 # check if the pyopenssl, ndg-httpsclient, pyasn1 packages are installed.
69 70
 # They are needed for SSL connection without trouble, see #298
@@ -109,8 +110,6 @@ for indice, theme in enumerate(themes):
109 110
     for (dirpath, dirnames, filenames) in os.walk(theme_img_path):
110 111
         global_favicons[indice].extend(filenames)
111 112
 
112
-cookie_max_age = 60 * 60 * 24 * 365 * 5  # 5 years
113
-
114 113
 _category_names = (gettext('files'),
115 114
                    gettext('general'),
116 115
                    gettext('music'),
@@ -222,9 +221,7 @@ def get_current_theme_name(override=None):
222 221
 
223 222
     if override and override in themes:
224 223
         return override
225
-    theme_name = request.args.get('theme',
226
-                                  request.cookies.get('theme',
227
-                                                      default_theme))
224
+    theme_name = request.args.get('theme', request.preferences.get_value('theme'))
228 225
     if theme_name not in themes:
229 226
         theme_name = default_theme
230 227
     return theme_name
@@ -262,12 +259,8 @@ def image_proxify(url):
262 259
 
263 260
 
264 261
 def render(template_name, override_theme=None, **kwargs):
265
-    blocked_engines = get_blocked_engines(engines, request.cookies)
266
-
267
-    autocomplete = request.cookies.get('autocomplete', settings['search']['autocomplete'])
268
-
269
-    if autocomplete not in autocomplete_backends:
270
-        autocomplete = None
262
+    blocked_engines = request.preferences.engines.get_disabled()
263
+    autocomplete = request.preferences.get_value('autocomplete')
271 264
 
272 265
     nonblocked_categories = set(category for engine_name in engines
273 266
                                 for category in engines[engine_name].categories
@@ -295,7 +288,7 @@ def render(template_name, override_theme=None, **kwargs):
295 288
                     kwargs['selected_categories'].append(c)
296 289
 
297 290
     if not kwargs['selected_categories']:
298
-        cookie_categories = request.cookies.get('categories', '').split(',')
291
+        cookie_categories = request.preferences.get_value('categories')
299 292
         for ccateg in cookie_categories:
300 293
             if ccateg in categories:
301 294
                 kwargs['selected_categories'].append(ccateg)
@@ -311,9 +304,9 @@ def render(template_name, override_theme=None, **kwargs):
311 304
 
312 305
     kwargs['searx_version'] = VERSION_STRING
313 306
 
314
-    kwargs['method'] = request.cookies.get('method', 'POST')
307
+    kwargs['method'] = request.preferences.get_value('method')
315 308
 
316
-    kwargs['safesearch'] = request.cookies.get('safesearch', str(settings['search']['safe_search']))
309
+    kwargs['safesearch'] = str(request.preferences.get_value('safesearch'))
317 310
 
318 311
     # override url_for function in templates
319 312
     kwargs['url_for'] = url_for_theme
@@ -347,14 +340,18 @@ def render(template_name, override_theme=None, **kwargs):
347 340
 @app.before_request
348 341
 def pre_request():
349 342
     # merge GET, POST vars
343
+    preferences = Preferences(themes, categories.keys(), engines, plugins)
344
+    preferences.parse_cookies(request.cookies)
345
+    request.preferences = preferences
346
+
350 347
     request.form = dict(request.form.items())
351 348
     for k, v in request.args.items():
352 349
         if k not in request.form:
353 350
             request.form[k] = v
354 351
 
355 352
     request.user_plugins = []
356
-    allowed_plugins = request.cookies.get('allowed_plugins', '').split(',')
357
-    disabled_plugins = request.cookies.get('disabled_plugins', '').split(',')
353
+    allowed_plugins = preferences.plugins.get_enabled()
354
+    disabled_plugins = preferences.plugins.get_disabled()
358 355
     for plugin in plugins:
359 356
         if ((plugin.default_on and plugin.id not in disabled_plugins)
360 357
                 or plugin.id in allowed_plugins):
@@ -486,7 +483,7 @@ def autocompleter():
486 483
         request_data = request.args
487 484
 
488 485
     # set blocked engines
489
-    blocked_engines = get_blocked_engines(engines, request.cookies)
486
+    blocked_engines = request.preferences.engines.get_disabled()
490 487
 
491 488
     # parse query
492 489
     query = Query(request_data.get('q', '').encode('utf-8'), blocked_engines)
@@ -496,8 +493,8 @@ def autocompleter():
496 493
     if not query.getSearchQuery():
497 494
         return '', 400
498 495
 
499
-    # get autocompleter
500
-    completer = autocomplete_backends.get(request.cookies.get('autocomplete', settings['search']['autocomplete']))
496
+    # run autocompleter
497
+    completer = autocomplete_backends.get(request.preferences.get_value('autocomplete'))
501 498
 
502 499
     # parse searx specific autocompleter results like !bang
503 500
     raw_results = searx_bang(query)
@@ -532,117 +529,23 @@ def autocompleter():
532 529
 
533 530
 @app.route('/preferences', methods=['GET', 'POST'])
534 531
 def preferences():
535
-    """Render preferences page.
536
-
537
-    Settings that are going to be saved as cookies."""
538
-    lang = None
539
-    image_proxy = request.cookies.get('image_proxy', settings['server'].get('image_proxy'))
540
-
541
-    if request.cookies.get('language')\
542
-       and request.cookies['language'] in (x[0] for x in language_codes):
543
-        lang = request.cookies['language']
544
-
545
-    blocked_engines = []
546
-
547
-    resp = make_response(redirect(urljoin(settings['server']['base_url'], url_for('index'))))
548
-
549
-    if request.method == 'GET':
550
-        blocked_engines = get_blocked_engines(engines, request.cookies)
551
-    else:  # on save
552
-        selected_categories = []
553
-        post_disabled_plugins = []
554
-        locale = None
555
-        autocomplete = ''
556
-        method = 'POST'
557
-        safesearch = settings['search']['safe_search']
558
-        for pd_name, pd in request.form.items():
559
-            if pd_name.startswith('category_'):
560
-                category = pd_name[9:]
561
-                if category not in categories:
562
-                    continue
563
-                selected_categories.append(category)
564
-            elif pd_name == 'locale' and pd in settings['locales']:
565
-                locale = pd
566
-            elif pd_name == 'image_proxy':
567
-                image_proxy = pd
568
-            elif pd_name == 'autocomplete':
569
-                autocomplete = pd
570
-            elif pd_name == 'language' and (pd == 'all' or
571
-                                            pd in (x[0] for
572
-                                                   x in language_codes)):
573
-                lang = pd
574
-            elif pd_name == 'method':
575
-                method = pd
576
-            elif pd_name == 'safesearch':
577
-                safesearch = pd
578
-            elif pd_name.startswith('engine_'):
579
-                if pd_name.find('__') > -1:
580
-                    # TODO fix underscore vs space
581
-                    engine_name, category = [x.replace('_', ' ') for x in
582
-                                             pd_name.replace('engine_', '', 1).split('__', 1)]
583
-                    if engine_name in engines and category in engines[engine_name].categories:
584
-                        blocked_engines.append((engine_name, category))
585
-            elif pd_name == 'theme':
586
-                theme = pd if pd in themes else default_theme
587
-            elif pd_name.startswith('plugin_'):
588
-                plugin_id = pd_name.replace('plugin_', '', 1)
589
-                if not any(plugin.id == plugin_id for plugin in plugins):
590
-                    continue
591
-                post_disabled_plugins.append(plugin_id)
592
-            else:
593
-                resp.set_cookie(pd_name, pd, max_age=cookie_max_age)
594
-
595
-        disabled_plugins = []
596
-        allowed_plugins = []
597
-        for plugin in plugins:
598
-            if plugin.default_on:
599
-                if plugin.id in post_disabled_plugins:
600
-                    disabled_plugins.append(plugin.id)
601
-            elif plugin.id not in post_disabled_plugins:
602
-                allowed_plugins.append(plugin.id)
603
-
604
-        resp.set_cookie('disabled_plugins', ','.join(disabled_plugins), max_age=cookie_max_age)
532
+    """Render preferences page && save user preferences"""
605 533
 
606
-        resp.set_cookie('allowed_plugins', ','.join(allowed_plugins), max_age=cookie_max_age)
607
-
608
-        resp.set_cookie(
609
-            'blocked_engines', ','.join('__'.join(e) for e in blocked_engines),
610
-            max_age=cookie_max_age
611
-        )
612
-
613
-        if locale:
614
-            resp.set_cookie(
615
-                'locale', locale,
616
-                max_age=cookie_max_age
617
-            )
618
-
619
-        if lang:
620
-            resp.set_cookie(
621
-                'language', lang,
622
-                max_age=cookie_max_age
623
-            )
624
-
625
-        if selected_categories:
626
-            # cookie max age: 4 weeks
627
-            resp.set_cookie(
628
-                'categories', ','.join(selected_categories),
629
-                max_age=cookie_max_age
630
-            )
631
-
632
-            resp.set_cookie(
633
-                'autocomplete', autocomplete,
634
-                max_age=cookie_max_age
635
-            )
636
-
637
-        resp.set_cookie('method', method, max_age=cookie_max_age)
638
-
639
-        resp.set_cookie('safesearch', str(safesearch), max_age=cookie_max_age)
640
-
641
-        resp.set_cookie('image_proxy', image_proxy, max_age=cookie_max_age)
642
-
643
-        resp.set_cookie('theme', theme, max_age=cookie_max_age)
644
-
645
-        return resp
534
+    # save preferences
535
+    if request.method == 'POST':
536
+        resp = make_response(redirect(urljoin(settings['server']['base_url'], url_for('index'))))
537
+        try:
538
+            request.preferences.parse_form(request.form)
539
+        except ValidationException:
540
+            # TODO use flash feature of flask
541
+            return resp
542
+        return request.preferences.save(resp)
543
+
544
+    # render preferences
545
+    image_proxy = request.preferences.get_value('image_proxy')
546
+    lang = request.preferences.get_value('language')
547
+    blocked_engines = request.preferences.engines.get_disabled()
548
+    allowed_plugins = request.preferences.plugins.get_enabled()
646 549
 
647 550
     # stats for preferences page
648 551
     stats = {}
@@ -664,7 +567,7 @@ def preferences():
664 567
     return render('preferences.html',
665 568
                   locales=settings['locales'],
666 569
                   current_locale=get_locale(),
667
-                  current_language=lang or 'all',
570
+                  current_language=lang,
668 571
                   image_proxy=image_proxy,
669 572
                   language_codes=language_codes,
670 573
                   engines_by_category=categories,
@@ -674,7 +577,7 @@ def preferences():
674 577
                   shortcuts={y: x for x, y in engine_shortcuts.items()},
675 578
                   themes=themes,
676 579
                   plugins=plugins,
677
-                  allowed_plugins=[plugin.id for plugin in request.user_plugins],
580
+                  allowed_plugins=allowed_plugins,
678 581
                   theme=get_current_theme_name())
679 582
 
680 583
 
@@ -750,7 +653,7 @@ Disallow: /preferences
750 653
 def opensearch():
751 654
     method = 'post'
752 655
 
753
-    if request.cookies.get('method', 'POST') == 'GET':
656
+    if request.preferences.get_value('method') == 'GET':
754 657
         method = 'get'
755 658
 
756 659
     # chrome/chromium only supports HTTP GET....

+ 108
- 0
tests/robot/test_basic.robot View File

@@ -42,3 +42,111 @@ Change language
42 42
     Location Should Be  http://localhost:11111/
43 43
     Page Should Contain  rólunk
44 44
     Page Should Contain  beállítások
45
+
46
+Change method
47
+    Page Should Contain  about
48
+    Page Should Contain  preferences
49
+    Go To  http://localhost:11111/preferences
50
+    Select From List  method  GET
51
+    Submit Form  id=search_form
52
+    Location Should Be  http://localhost:11111/
53
+    Go To  http://localhost:11111/preferences
54
+    List Selection Should Be  method  GET
55
+    Select From List  method  POST
56
+    Submit Form  id=search_form
57
+    Location Should Be  http://localhost:11111/
58
+    Go To  http://localhost:11111/preferences
59
+    List Selection Should Be  method  POST
60
+
61
+Change theme
62
+    Page Should Contain  about
63
+    Page Should Contain  preferences
64
+    Go To  http://localhost:11111/preferences
65
+    List Selection Should Be  theme  default
66
+    Select From List  theme  oscar
67
+    Submit Form  id=search_form
68
+    Location Should Be  http://localhost:11111/
69
+    Go To  http://localhost:11111/preferences
70
+    List Selection Should Be  theme  oscar
71
+
72
+Change safesearch
73
+    Page Should Contain  about
74
+    Page Should Contain  preferences
75
+    Go To  http://localhost:11111/preferences
76
+    List Selection Should Be  safesearch  None
77
+    Select From List  safesearch  Strict
78
+    Submit Form  id=search_form
79
+    Location Should Be  http://localhost:11111/
80
+    Go To  http://localhost:11111/preferences
81
+    List Selection Should Be  safesearch  Strict
82
+
83
+Change image proxy
84
+    Page Should Contain  about
85
+    Page Should Contain  preferences
86
+    Go To  http://localhost:11111/preferences
87
+    List Selection Should Be  image_proxy  Disabled
88
+    Select From List  image_proxy  Enabled
89
+    Submit Form  id=search_form
90
+    Location Should Be  http://localhost:11111/
91
+    Go To  http://localhost:11111/preferences
92
+    List Selection Should Be  image_proxy  Enabled
93
+
94
+Change search language
95
+    Page Should Contain  about
96
+    Page Should Contain  preferences
97
+    Go To  http://localhost:11111/preferences
98
+    List Selection Should Be  language  Automatic
99
+    Select From List  language  Turkish (Turkey) - tr_TR
100
+    Submit Form  id=search_form
101
+    Location Should Be  http://localhost:11111/
102
+    Go To  http://localhost:11111/preferences
103
+    List Selection Should Be  language  Turkish (Turkey) - tr_TR
104
+
105
+Change autocomplete
106
+    Page Should Contain  about
107
+    Page Should Contain  preferences
108
+    Go To  http://localhost:11111/preferences
109
+    List Selection Should Be  autocomplete  -
110
+    Select From List  autocomplete  google
111
+    Submit Form  id=search_form
112
+    Location Should Be  http://localhost:11111/
113
+    Go To  http://localhost:11111/preferences
114
+    List Selection Should Be  autocomplete  google
115
+
116
+Change allowed/disabled engines
117
+    Page Should Contain  about
118
+    Page Should Contain  preferences
119
+    Go To  http://localhost:11111/preferences
120
+    Page Should Contain  Engine name
121
+    Element Should Contain  xpath=//label[@class="deny"][@for='engine_dummy_dummy_dummy']  Block
122
+    Element Should Contain  xpath=//label[@class="deny"][@for='engine_general_general_dummy']  Block
123
+    Click Element  xpath=//label[@class="deny"][@for='engine_general_general_dummy']
124
+    Submit Form  id=search_form
125
+    Location Should Be  http://localhost:11111/
126
+    Page Should Contain  about
127
+    Page Should Contain  preferences
128
+    Go To  http://localhost:11111/preferences
129
+    Page Should Contain  Engine name
130
+    Element Should Contain  xpath=//label[@class="deny"][@for='engine_dummy_dummy_dummy']  Block
131
+    Element Should Contain  xpath=//label[@class="deny"][@for='engine_general_general_dummy']  \
132
+
133
+Block a plugin
134
+    Page Should Contain  about
135
+    Page Should Contain  preferences
136
+    Go To  http://localhost:11111/preferences
137
+    List Selection Should Be  theme  default
138
+    Select From List  theme  oscar
139
+    Submit Form  id=search_form
140
+    Location Should Be  http://localhost:11111/
141
+    Go To  http://localhost:11111/preferences
142
+    List Selection Should Be  theme  oscar
143
+    Page Should Contain  Plugins
144
+    Click Link  Plugins
145
+    Checkbox Should Not Be Selected  id=plugin_HTTPS_rewrite
146
+    Click Element  xpath=//label[@for='plugin_HTTPS_rewrite']
147
+    Submit Form  id=search_form
148
+    Location Should Be  http://localhost:11111/
149
+    Go To  http://localhost:11111/preferences
150
+    Page Should Contain  Plugins
151
+    Click Link  Plugins
152
+    Checkbox Should Be Selected  id=plugin_HTTPS_rewrite

+ 101
- 0
tests/unit/test_preferences.py View File

@@ -0,0 +1,101 @@
1
+from searx.preferences import (EnumStringSetting, MapSetting, MissingArgumentException,
2
+                               MultipleChoiceSetting, PluginsSetting, ValidationException)
3
+from searx.testing import SearxTestCase
4
+
5
+
6
+class PluginStub(object):
7
+    def __init__(self, id, default_on):
8
+        self.id = id
9
+        self.default_on = default_on
10
+
11
+
12
+class TestSettings(SearxTestCase):
13
+    # map settings
14
+    def test_map_setting_invalid_initialization(self):
15
+        with self.assertRaises(MissingArgumentException):
16
+            setting = MapSetting(3, wrong_argument={'0': 0})
17
+
18
+    def test_map_setting_invalid_default_value(self):
19
+        with self.assertRaises(ValidationException):
20
+            setting = MapSetting(3, map={'dog': 1, 'bat': 2})
21
+
22
+    def test_map_setting_invalid_choice(self):
23
+        setting = MapSetting(2, map={'dog': 1, 'bat': 2})
24
+        with self.assertRaises(ValidationException):
25
+            setting.parse('cat')
26
+
27
+    def test_map_setting_valid_default(self):
28
+        setting = MapSetting(3, map={'dog': 1, 'bat': 2, 'cat': 3})
29
+        self.assertEquals(setting.get_value(), 3)
30
+
31
+    def test_map_setting_valid_choice(self):
32
+        setting = MapSetting(3, map={'dog': 1, 'bat': 2, 'cat': 3})
33
+        self.assertEquals(setting.get_value(), 3)
34
+        setting.parse('bat')
35
+        self.assertEquals(setting.get_value(), 2)
36
+
37
+    def test_enum_setting_invalid_initialization(self):
38
+        with self.assertRaises(MissingArgumentException):
39
+            setting = EnumStringSetting('cat', wrong_argument=[0, 1, 2])
40
+
41
+    # enum settings
42
+    def test_enum_setting_invalid_initialization(self):
43
+        with self.assertRaises(MissingArgumentException):
44
+            setting = EnumStringSetting('cat', wrong_argument=[0, 1, 2])
45
+
46
+    def test_enum_setting_invalid_default_value(self):
47
+        with self.assertRaises(ValidationException):
48
+            setting = EnumStringSetting(3, choices=[0, 1, 2])
49
+
50
+    def test_enum_setting_invalid_choice(self):
51
+        setting = EnumStringSetting(0, choices=[0, 1, 2])
52
+        with self.assertRaises(ValidationException):
53
+            setting.parse(3)
54
+
55
+    def test_enum_setting_valid_default(self):
56
+        setting = EnumStringSetting(3, choices=[1, 2, 3])
57
+        self.assertEquals(setting.get_value(), 3)
58
+
59
+    def test_enum_setting_valid_choice(self):
60
+        setting = EnumStringSetting(3, choices=[1, 2, 3])
61
+        self.assertEquals(setting.get_value(), 3)
62
+        setting.parse(2)
63
+        self.assertEquals(setting.get_value(), 2)
64
+
65
+    # multiple choice settings
66
+    def test_multiple_setting_invalid_initialization(self):
67
+        with self.assertRaises(MissingArgumentException):
68
+            setting = MultipleChoiceSetting(['2'], wrong_argument=['0', '1', '2'])
69
+
70
+    def test_multiple_setting_invalid_default_value(self):
71
+        with self.assertRaises(ValidationException):
72
+            setting = MultipleChoiceSetting(['3', '4'], choices=['0', '1', '2'])
73
+
74
+    def test_multiple_setting_invalid_choice(self):
75
+        setting = MultipleChoiceSetting(['1', '2'], choices=['0', '1', '2'])
76
+        with self.assertRaises(ValidationException):
77
+            setting.parse('4, 3')
78
+
79
+    def test_multiple_setting_valid_default(self):
80
+        setting = MultipleChoiceSetting(['3'], choices=['1', '2', '3'])
81
+        self.assertEquals(setting.get_value(), ['3'])
82
+
83
+    def test_multiple_setting_valid_choice(self):
84
+        setting = MultipleChoiceSetting(['3'], choices=['1', '2', '3'])
85
+        self.assertEquals(setting.get_value(), ['3'])
86
+        setting.parse('2')
87
+        self.assertEquals(setting.get_value(), ['2'])
88
+
89
+    # plugins settings
90
+    def test_plugins_setting_all_default_enabled(self):
91
+        plugin1 = PluginStub('plugin1', True)
92
+        plugin2 = PluginStub('plugin2', True)
93
+        setting = PluginsSetting(['3'], choices=[plugin1, plugin2])
94
+        self.assertEquals(setting.get_enabled(), set(['plugin1', 'plugin2']))
95
+
96
+    def test_plugins_setting_few_default_enabled(self):
97
+        plugin1 = PluginStub('plugin1', True)
98
+        plugin2 = PluginStub('plugin2', False)
99
+        plugin3 = PluginStub('plugin3', True)
100
+        setting = PluginsSetting('name', choices=[plugin1, plugin2, plugin3])
101
+        self.assertEquals(setting.get_enabled(), set(['plugin1', 'plugin3']))

+ 5
- 1
tests/unit/test_webapp.py View File

@@ -12,7 +12,6 @@ class ViewsTestCase(SearxTestCase):
12 12
     def setUp(self):
13 13
         webapp.app.config['TESTING'] = True  # to get better error messages
14 14
         self.app = webapp.app.test_client()
15
-        webapp.default_theme = 'default'
16 15
 
17 16
         # set some defaults
18 17
         self.test_results = [
@@ -43,6 +42,11 @@ class ViewsTestCase(SearxTestCase):
43 42
 
44 43
         webapp.Search.search = search_mock
45 44
 
45
+        def get_current_theme_name_mock(override=None):
46
+            return 'default'
47
+
48
+        webapp.get_current_theme_name = get_current_theme_name_mock
49
+
46 50
         self.maxDiff = None  # to see full diffs
47 51
 
48 52
     def test_index_empty(self):