Provide a nicer selector for cache backends compatible with settings.CACHES.

Review Request #3603 — Created Dec. 1, 2012 and submitted — Latest diff uploaded

Information

Review Board
release-1.7.x

Reviewers

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.

Diff Revision 2 (Latest)

orig
1
2
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
Loading...