Provide a nicer selector for cache backends compatible with settings.CACHES.
Review Request #3603 — Created Dec. 1, 2012 and submitted — Latest diff uploaded
Provide a nicer selector for cache backends compatible with settings.CACHES. The cache backend selector in General Settings is now much nicer, providing a dropdown of possible cache types and an appropriate field for entering the hosts or cache location. Two entries are provided by default: Memcached and File cache. If running with DEBUG = True, then "Local memory cache" will also be available, making it easier to test and debug things. If some other cache backend is used, then "Custom" will be an option as well, and will leave the cache settings untouched. This moves us away fully from the old Django CACHE_BACKEND setting.
Tested each cache backend and verified that the correct cache settings were stored in siteconfig and were being loaded out correctly.
reviewboard/admin/forms.py | |||
---|---|---|---|
Revision ea8edd60ed8f273f46af0da4a5cc6012c8b0abc2 | New Change | ||
29 lines | |||
30 |
import re |
30 |
import re |
31 |
import urlparse |
31 |
import urlparse |
32 | 32 | ||
33 |
from django import forms |
33 |
from django import forms |
34 |
from django.contrib.sites.models import Site |
34 |
from django.contrib.sites.models import Site |
35 |
from django.core.cache import parse_backend_uri, InvalidCacheBackendError |
35 |
from django.conf import settings |
36 |
from django.core.cache import parse_backend_uri, InvalidCacheBackendError, \ |
||
37 |
DEFAULT_CACHE_ALIAS
|
||
36 |
from django.utils.translation import ugettext as _ |
38 |
from django.utils.translation import ugettext as _ |
37 |
from djblets.log import restart_logging |
39 |
from djblets.log import restart_logging |
38 |
from djblets.siteconfig.forms import SiteSettingsForm |
40 |
from djblets.siteconfig.forms import SiteSettingsForm |
41 |
from djblets.util.cache import normalize_cache_backend |
||
39 |
from djblets.util.forms import TimeZoneField |
42 |
from djblets.util.forms import TimeZoneField |
40 | 43 | ||
41 |
from reviewboard.accounts.forms import LegacyAuthModuleSettingsForm |
44 |
from reviewboard.accounts.forms import LegacyAuthModuleSettingsForm |
42 |
from reviewboard.admin.checks import get_can_enable_search, \ |
45 |
from reviewboard.admin.checks import get_can_enable_search, \ |
43 |
get_can_enable_syntax_highlighting, \ |
46 |
get_can_enable_syntax_highlighting, \ |
44 |
get_can_use_amazon_s3, \ |
47 |
get_can_use_amazon_s3, \ |
45 |
get_can_use_couchdb
|
48 |
get_can_use_couchdb
|
46 |
from reviewboard.admin.siteconfig import load_site_config |
49 |
from reviewboard.admin.siteconfig import load_site_config |
47 |
from reviewboard.ssh.client import SSHClient |
50 |
from reviewboard.ssh.client import SSHClient |
48 | 51 | ||
49 | 52 | ||
50 |
class GeneralSettingsForm(SiteSettingsForm): |
53 |
class GeneralSettingsForm(SiteSettingsForm): |
51 |
"""General settings for Review Board.""" |
54 |
"""General settings for Review Board.""" |
55 |
CACHE_TYPE_CHOICES = ( |
||
56 |
('memcached', _('Memcached')), |
||
57 |
('file', _('File cache')), |
||
58 |
)
|
||
59 | |||
60 |
CACHE_BACKENDS_MAP = { |
||
61 |
'file': 'django.core.cache.backends.filebased.FileBasedCache', |
||
62 |
'memcached': 'django.core.cache.backends.memcached.CacheClass', |
||
63 |
'locmem': 'django.core.cache.backends.locmem.LocMemCache', |
||
64 |
}
|
||
65 | |||
66 |
CACHE_TYPES_MAP = { |
||
67 |
'django.core.cache.backends.filebased.FileBasedCache': 'file', |
||
68 |
'django.core.cache.backends.memcached.CacheClass': 'memcached', |
||
69 |
'django.core.cache.backends.locmem.LocMemCache': 'locmem', |
||
70 |
}
|
||
71 | |||
72 |
CACHE_LOCATION_FIELD_MAP = { |
||
73 |
'file': 'cache_path', |
||
74 |
'memcached': 'cache_host', |
||
75 |
}
|
||
76 | |||
52 |
server = forms.CharField( |
77 |
server = forms.CharField( |
53 |
label=_("Server"), |
78 |
label=_("Server"), |
54 |
help_text=_("The URL of this Review Board server. This should not " |
79 |
help_text=_("The URL of this Review Board server. This should not " |
55 |
"contain the subdirectory Review Board is installed in."), |
80 |
"contain the subdirectory Review Board is installed in."), |
56 |
widget=forms.TextInput(attrs={'size': '30'})) |
81 |
widget=forms.TextInput(attrs={'size': '30'})) |
30 lines | |||
class GeneralSettingsForm(SiteSettingsForm):
|
|||
87 |
help_text=_("The directory that search index data should be stored " |
112 |
help_text=_("The directory that search index data should be stored " |
88 |
"in."), |
113 |
"in."), |
89 |
required=False, |
114 |
required=False, |
90 |
widget=forms.TextInput(attrs={'size': '50'})) |
115 |
widget=forms.TextInput(attrs={'size': '50'})) |
91 | 116 | ||
92 |
cache_backend = forms.CharField( |
117 |
cache_type = forms.ChoiceField( |
93 |
label=_("Cache Backend"), |
118 |
label=_("Cache Backend"), |
94 |
help_text=_("The path to the cache backend." |
119 |
choices=CACHE_TYPE_CHOICES, |
95 |
"Example: 'memcached://127.0.0.1:11211/'"), |
120 |
help_text=_('The type of server-side caching to use.'), |
96 |
required=False, |
121 |
required=True) |
122 | |||
123 |
cache_path = forms.CharField( |
||
124 |
label=_("Cache Path"), |
||
125 |
help_text=_('The file location for the cache.'), |
||
126 |
required=True, |
||
127 |
widget=forms.TextInput(attrs={'size': '50'})) |
||
128 | |||
129 |
cache_host = forms.CharField( |
||
130 |
label=_("Cache Hosts"), |
||
131 |
help_text=_('The host or hosts used for the cache, in hostname:port ' |
||
132 |
'form. Multiple hosts can be specified by separating '
|
||
133 |
'them with a semicolon (;).'), |
||
134 |
required=True, |
||
97 |
widget=forms.TextInput(attrs={'size': '50'})) |
135 |
widget=forms.TextInput(attrs={'size': '50'})) |
98 | 136 | ||
99 |
def load(self): |
137 |
def load(self): |
100 |
domain_method = self.siteconfig.get("site_domain_method") |
138 |
domain_method = self.siteconfig.get("site_domain_method") |
101 |
site = Site.objects.get_current() |
139 |
site = Site.objects.get_current() |
102 | 140 | ||
103 |
can_enable_search, reason = get_can_enable_search() |
141 |
can_enable_search, reason = get_can_enable_search() |
104 |
if not can_enable_search: |
142 |
if not can_enable_search: |
105 |
self.disabled_fields['search_enable'] = True |
143 |
self.disabled_fields['search_enable'] = True |
106 |
self.disabled_fields['search_index_file'] = True |
144 |
self.disabled_fields['search_index_file'] = True |
107 |
self.disabled_reasons['search_enable'] = reason |
145 |
self.disabled_reasons['search_enable'] = reason |
108 | 146 | ||
147 |
# Load the rest of the settings from the form.
|
||
109 |
super(GeneralSettingsForm, self).load() |
148 |
super(GeneralSettingsForm, self).load() |
110 | 149 | ||
150 |
# Load the cache settings.
|
||
151 |
cache_backend = normalize_cache_backend( |
||
152 |
self.siteconfig.get('cache_backend')) |
||
153 | |||
154 |
cache_type = self.CACHE_TYPES_MAP.get(cache_backend['BACKEND'], |
||
155 |
'custom') |
||
156 |
self.fields['cache_type'].initial = cache_type |
||
157 | |||
158 |
if settings.DEBUG: |
||
159 |
self.fields['cache_type'].choices += ( |
||
160 |
('locmem', _('Local memory cache')), |
||
161 |
)
|
||
162 | |||
163 |
if cache_type == 'custom': |
||
164 |
self.fields['cache_type'].choices += ( |
||
165 |
('custom', _('Custom')), |
||
166 |
)
|
||
167 |
cache_locations = [] |
||
168 |
elif cache_type != 'locmem': |
||
169 |
cache_locations = cache_backend['LOCATION'] |
||
170 | |||
171 |
if not isinstance(cache_locations, list): |
||
172 |
cache_locations = [cache_locations] |
||
173 | |||
174 |
location_field = self.CACHE_LOCATION_FIELD_MAP[cache_type] |
||
175 |
self.fields[location_field].initial = ';'.join(cache_locations) |
||
176 | |||
111 |
# This must come after we've loaded the general settings.
|
177 |
# This must come after we've loaded the general settings.
|
112 |
self.fields['server'].initial = "%s://%s" % (domain_method, |
178 |
self.fields['server'].initial = "%s://%s" % (domain_method, |
113 |
site.domain) |
179 |
site.domain) |
114 | 180 | ||
115 |
def save(self): |
181 |
def save(self): |
15 lines | |||
def load(self):
|
|||
131 |
site.domain = domain_name |
197 |
site.domain = domain_name |
132 |
site.save() |
198 |
site.save() |
133 | 199 | ||
134 |
self.siteconfig.set("site_domain_method", domain_method) |
200 |
self.siteconfig.set("site_domain_method", domain_method) |
135 | 201 | ||
202 |
cache_type = self.cleaned_data['cache_type'] |
||
203 | |||
204 |
if cache_type != 'custom': |
||
205 |
if cache_type == 'locmem': |
||
206 |
# We want to specify a "reviewboard" location to keep items
|
||
207 |
# separate from those in other caches.
|
||
208 |
location = 'reviewboard' |
||
209 |
else: |
||
210 |
location_field = self.CACHE_LOCATION_FIELD_MAP[cache_type] |
||
211 |
location = self.cleaned_data[location_field] |
||
212 | |||
213 |
if cache_type == 'memcached': |
||
214 |
# memcached allows a list of servers, rather than just a
|
||
215 |
# string representing one.
|
||
216 |
location = location.split(';') |
||
217 | |||
218 |
self.siteconfig.set('cache_backend', { |
||
219 |
DEFAULT_CACHE_ALIAS: { |
||
220 |
'BACKEND': self.CACHE_BACKENDS_MAP[cache_type], |
||
221 |
'LOCATION': location, |
||
222 |
}
|
||
223 |
})
|
||
224 | |||
136 |
super(GeneralSettingsForm, self).save() |
225 |
super(GeneralSettingsForm, self).save() |
137 | 226 | ||
138 |
# Reload any important changes into the Django settings.
|
227 |
# Reload any important changes into the Django settings.
|
139 |
load_site_config() |
228 |
load_site_config() |
140 | 229 | ||
141 |
def clean_cache_backend(self): |
230 |
def full_clean(self): |
142 |
"""Validates that the specified cache backend is parseable by Django.""" |
231 |
cache_type = self['cache_type'].data or self['cache_type'].initial |
143 |
backend = self.cleaned_data['cache_backend'].strip() |
232 | |
144 |
if backend: |
233 |
for iter_cache_type, field in self.CACHE_LOCATION_FIELD_MAP.iteritems(): |
145 |
try: |
234 |
self.fields[field].required = (cache_type == iter_cache_type) |
146 |
parse_backend_uri(backend) |
235 | |
147 |
except InvalidCacheBackendError, e: |
236 |
return super(GeneralSettingsForm, self).full_clean() |
148 |
raise forms.ValidationError(e) |
||
149 | 237 | ||
150 |
return backend |
238 |
def clean_cache_host(self): |
239 |
cache_host = self.cleaned_data['cache_host'].strip() |
||
240 | |||
241 |
if self.fields['cache_host'].required and not cache_host: |
||
242 |
raise forms.ValidationError( |
||
243 |
_('A valid cache host must be provided.')) |
||
244 | |||
245 |
return cache_host |
||
246 | |||
247 |
def clean_cache_path(self): |
||
248 |
cache_path = self.cleaned_data['cache_path'].strip() |
||
249 | |||
250 |
if self.fields['cache_path'].required and not cache_path: |
||
251 |
raise forms.ValidationError( |
||
252 |
_('A valid cache path must be provided.')) |
||
253 | |||
254 |
return cache_path |
||
151 | 255 | ||
152 |
def clean_search_index_file(self): |
256 |
def clean_search_index_file(self): |
153 |
"""Validates that the specified index file is valid.""" |
257 |
"""Validates that the specified index file is valid.""" |
154 |
index_file = self.cleaned_data['search_index_file'].strip() |
258 |
index_file = self.cleaned_data['search_index_file'].strip() |
155 | 259 | ||
12 lines | |||
def clean_search_index_file(self):
|
|||
168 |
return index_file |
272 |
return index_file |
169 | 273 | ||
170 | 274 | ||
171 |
class Meta: |
275 |
class Meta: |
172 |
title = _("General Settings") |
276 |
title = _("General Settings") |
173 |
save_blacklist = ('server',) |
277 |
save_blacklist = ('server', 'cache_type', 'cache_host', 'cache_path') |
174 | 278 | ||
175 |
fieldsets = ( |
279 |
fieldsets = ( |
176 |
{
|
280 |
{
|
177 |
'classes': ('wide',), |
281 |
'classes': ('wide',), |
178 |
'title': _("Site Settings"), |
282 |
'title': _("Site Settings"), |
179 |
'fields': ('server', 'site_media_url', |
283 |
'fields': ('server', 'site_media_url', |
180 |
'site_admin_name', |
284 |
'site_admin_name', |
181 |
'site_admin_email', |
285 |
'site_admin_email', |
182 |
'locale_timezone', |
286 |
'locale_timezone'), |
183 |
'cache_backend'), |
287 |
},
|
288 |
{
|
||
289 |
'classes': ('wide',), |
||
290 |
'title': _('Cache Settings'), |
||
291 |
'fields': ('cache_type', 'cache_path', 'cache_host'), |
||
184 |
},
|
292 |
},
|
185 |
{
|
293 |
{
|
186 |
'classes': ('wide',), |
294 |
'classes': ('wide',), |
187 |
'title': _("Search"), |
295 |
'title': _("Search"), |
188 |
'fields': ('search_enable', 'search_index_file'), |
296 |
'fields': ('search_enable', 'search_index_file'), |
509 lines |
reviewboard/admin/urls.py |
---|
reviewboard/templates/admin/general_settings.html |
---|