class AccountSettingsForm(AccountPageForm): """Form for the Settings page for an account.""" form_id = 'settings' form_title = _('Settings') save_label = _('Save Settings') timezone = TimeZoneField( label=_('Time zone'), required=True, help_text=_("The time zone you're in.")) syntax_highlighting = forms.BooleanField( label=_('Enable syntax highlighting in the diff viewer'), required=False) open_an_issue = forms.BooleanField( label=_('Always open an issue when comment box opens'), required=False) should_send_email = forms.BooleanField( label=_('Get e-mail notification for review requests and reviews'), required=False) def load(self): self.set_initial({ 'open_an_issue': self.profile.open_an_issue, 'should_send_email': self.profile.should_send_email, 'syntax_highlighting': self.profile.syntax_highlighting, 'timezone': self.profile.timezone, }) siteconfig = SiteConfiguration.objects.get_current() if not siteconfig.get('diffviewer_syntax_highlighting'): del self.fields['syntax_highlighting'] def save(self): if 'syntax_highlighting' in self.cleaned_data: self.profile.syntax_highlighting = \ self.cleaned_data['syntax_highlighting'] self.profile.open_an_issue = self.cleaned_data['open_an_issue'] self.profile.should_send_email = self.cleaned_data['should_send_email'] self.profile.timezone = self.cleaned_data['timezone'] self.profile.save() messages.add_message(self.request, messages.INFO, _('Your settings have been saved.'))
class GeneralSettingsForm(SiteSettingsForm): """General settings for Review Board.""" CACHE_TYPE_CHOICES = ( ('memcached', _('Memcached')), ('file', _('File cache')), ) CACHE_BACKENDS_MAP = { 'file': 'django.core.cache.backends.filebased.FileBasedCache', 'memcached': 'django.core.cache.backends.memcached.MemcachedCache', 'locmem': 'django.core.cache.backends.locmem.LocMemCache', } CACHE_TYPES_MAP = { 'django.core.cache.backends.filebased.FileBasedCache': 'file', 'django.core.cache.backends.memcached.CacheClass': 'memcached', 'django.core.cache.backends.memcached.MemcachedCache': 'memcached', 'django.core.cache.backends.locmem.LocMemCache': 'locmem', } CACHE_LOCATION_FIELD_MAP = { 'file': 'cache_path', 'memcached': 'cache_host', } company = forms.CharField( label=_("Company/Organization"), help_text=_("The optional name of your company or organization. " "This will be displayed on your support page."), required=False, widget=forms.TextInput(attrs={'size': '30'})) server = forms.CharField( label=_("Server"), help_text=_("The URL of this Review Board server. This should not " "contain the subdirectory Review Board is installed in."), widget=forms.TextInput(attrs={'size': '30'})) site_media_url = forms.CharField( label=_("Media URL"), help_text=_("The URL to the media files. Leave blank to use the " "default media path on this server."), required=False, widget=forms.TextInput(attrs={'size': '30'})) site_admin_name = forms.CharField( label=_("Administrator Name"), required=True, widget=forms.TextInput(attrs={'size': '30'})) site_admin_email = forms.EmailField( label=_("Administrator E-Mail"), required=True, widget=forms.TextInput(attrs={'size': '30'})) locale_timezone = TimeZoneField( label=_("Time Zone"), required=True, help_text=_("The time zone used for all dates on this server.")) search_enable = forms.BooleanField( label=_("Enable search"), help_text=_("Provides a search field for quickly searching through " "review requests."), required=False) search_results_per_page = forms.IntegerField( label=_("Search results per page"), help_text=_("Number of search results to show per page."), min_value=1, required=False) search_index_file = forms.CharField( label=_("Search index directory"), help_text=_("The directory that search index data should be stored " "in."), required=False, widget=forms.TextInput(attrs={'size': '50'})) cache_type = forms.ChoiceField( label=_("Cache Backend"), choices=CACHE_TYPE_CHOICES, help_text=_('The type of server-side caching to use.'), required=True) cache_path = forms.CharField( label=_("Cache Path"), help_text=_('The file location for the cache.'), required=True, widget=forms.TextInput(attrs={'size': '50'})) cache_host = forms.CharField( label=_("Cache Hosts"), help_text=_('The host or hosts used for the cache, in hostname:port ' 'form. Multiple hosts can be specified by separating ' 'them with a semicolon (;).'), required=True, widget=forms.TextInput(attrs={'size': '50'})) integration_gravatars = forms.BooleanField( label=_("Use Gravatar images"), help_text=_("Use gravatar.com for user avatars"), required=False) def load(self): """Load the form.""" domain_method = self.siteconfig.get("site_domain_method") site = Site.objects.get_current() # Load the rest of the settings from the form. super(GeneralSettingsForm, self).load() # Load the cache settings. cache_backend_info = self.siteconfig.get('cache_backend') cache_backend = (normalize_cache_backend(cache_backend_info, DEFAULT_FORWARD_CACHE_ALIAS) or normalize_cache_backend(cache_backend_info)) cache_type = self.CACHE_TYPES_MAP.get(cache_backend['BACKEND'], 'custom') self.fields['cache_type'].initial = cache_type if settings.DEBUG: self.fields['cache_type'].choices += (('locmem', _('Local memory cache')), ) if cache_type == 'custom': self.fields['cache_type'].choices += (('custom', _('Custom')), ) cache_locations = [] elif cache_type != 'locmem': cache_locations = cache_backend['LOCATION'] if not isinstance(cache_locations, list): cache_locations = [cache_locations] location_field = self.CACHE_LOCATION_FIELD_MAP[cache_type] self.fields[location_field].initial = ';'.join(cache_locations) # This must come after we've loaded the general settings. self.fields['server'].initial = "%s://%s" % (domain_method, site.domain) def save(self): """Save the form.""" server = self.cleaned_data['server'] if "://" not in server: # urlparse doesn't properly handle URLs without a scheme. It # believes the domain is actually the path. So we apply a prefix. server = "http://" + server url_parts = urlparse(server) domain_method = url_parts[0] domain_name = url_parts[1] if domain_name.endswith("/"): domain_name = domain_name[:-1] site = Site.objects.get_current() site.domain = domain_name site.save() self.siteconfig.set("site_domain_method", domain_method) cache_type = self.cleaned_data['cache_type'] if cache_type != 'custom': if cache_type == 'locmem': # We want to specify a "reviewboard" location to keep items # separate from those in other caches. location = 'reviewboard' else: location_field = self.CACHE_LOCATION_FIELD_MAP[cache_type] location = self.cleaned_data[location_field] if cache_type == 'memcached': # memcached allows a list of servers, rather than just a # string representing one. location = location.split(';') self.siteconfig.set( 'cache_backend', { DEFAULT_FORWARD_CACHE_ALIAS: { 'BACKEND': self.CACHE_BACKENDS_MAP[cache_type], 'LOCATION': location, } }) super(GeneralSettingsForm, self).save() # Reload any important changes into the Django settings. load_site_config() def full_clean(self): """Clean and validate all form fields.""" cache_type = self['cache_type'].data or self['cache_type'].initial for iter_cache_type, field in six.iteritems( self.CACHE_LOCATION_FIELD_MAP): self.fields[field].required = (cache_type == iter_cache_type) return super(GeneralSettingsForm, self).full_clean() def clean_cache_host(self): """Validate that the cache_host field is provided if required.""" cache_host = self.cleaned_data['cache_host'].strip() if self.fields['cache_host'].required and not cache_host: raise ValidationError(_('A valid cache host must be provided.')) return cache_host def clean_cache_path(self): """Validate that the cache_path field is provided if required.""" cache_path = self.cleaned_data['cache_path'].strip() if self.fields['cache_path'].required and not cache_path: raise ValidationError(_('A valid cache path must be provided.')) return cache_path def clean_search_index_file(self): """Validate that the specified index file is valid. This checks to make sure that the provided file path is an absolute path, and that the directory is writable by the web server. """ index_file = self.cleaned_data['search_index_file'].strip() if index_file: if not os.path.isabs(index_file): raise ValidationError( _("The search index path must be absolute.")) if (os.path.exists(index_file) and not os.access(index_file, os.W_OK)): raise ValidationError( _('The search index path is not writable. Make sure the ' 'web server has write access to it and its parent ' 'directory.')) return index_file class Meta: title = _("General Settings") save_blacklist = ('server', 'cache_type', 'cache_host', 'cache_path') fieldsets = ( { 'classes': ('wide', ), 'title': _("Site Settings"), 'fields': ('company', 'server', 'site_media_url', 'site_admin_name', 'site_admin_email', 'locale_timezone'), }, { 'classes': ('wide', ), 'title': _('Cache Settings'), 'fields': ('cache_type', 'cache_path', 'cache_host'), }, { 'classes': ('wide', ), 'title': _("Search"), 'fields': ('search_enable', 'search_results_per_page', 'search_index_file'), }, { 'classes': ('wide', ), 'title': _("Third-party Integrations"), 'fields': ('integration_gravatars', ), }, )
class GeneralSettingsForm(SiteSettingsForm): """General settings for Review Board.""" _cache_backends = OrderedDict([ ('locmem', { 'name': _('Local memory cache (developer-only)'), 'available': not settings.PRODUCTION, 'backend_cls_path': 'django.core.cache.backends.locmem.LocMemCache', 'form_cls': LocalMemoryCacheSettingsForm, }), ('memcached', { 'name': _('Memcached (recommended)'), 'backend_cls_path': 'django.core.cache.backends.memcached.MemcachedCache', 'legacy_backend_cls_paths': [ 'django.core.cache.backends.memcached.CacheClass', ], 'form_cls': MemcachedSettingsForm, }), ('file', { 'name': _('Local file cache'), 'backend_cls_path': 'django.core.cache.backends.filebased.FileBasedCache', 'form_cls': FileCacheSettingsForm, }), ]) CACHE_VALIDATION_KEY = '__rb-cache-validation__' CACHE_VALIDATION_VALUE = 12345 company = forms.CharField( label=_('Company/Organization'), help_text=_('The optional name of your company or organization. ' 'This will be displayed on your support page.'), required=False, widget=forms.TextInput(attrs={'size': '30'})) server = forms.CharField( label=_('Server'), help_text=_('The URL of this Review Board server. This should not ' 'contain the subdirectory Review Board is installed in.'), widget=forms.TextInput(attrs={'size': '30'})) site_read_only = forms.BooleanField( label=_('Enable read-only mode'), help_text=_('Prevent non-superusers from making any changes to ' 'Review Board.'), required=False) read_only_message = forms.CharField( label=_('Read-only message'), help_text=_('A custom message displayed when the site is in ' 'read-only mode.'), required=False, widget=forms.TextInput(attrs={'size': '30'})) site_media_url = forms.CharField( label=_('Media URL'), help_text=(_('The URL to the media files. Set to ' '<code>%smedia/</code> to use the default media path on ' 'this server.') % settings.SITE_ROOT), required=True, widget=forms.TextInput(attrs={'size': '30'})) site_static_url = forms.CharField( label=_('Static URL'), help_text=(_('The URL to the static files, such as JavaScript files, ' 'CSS files, and images that are bundled with Review ' 'Board or third-party extensions. Set to ' '<code>%sstatic/</code> to use the default static path ' 'on this server.') % settings.SITE_ROOT), required=True, widget=forms.TextInput(attrs={'size': '30'})) site_admin_name = forms.CharField( label=_('Administrator Name'), required=True, widget=forms.TextInput(attrs={'size': '30'})) site_admin_email = forms.EmailField( label=_('Administrator E-Mail'), required=True, widget=forms.TextInput(attrs={'size': '30'})) locale_timezone = TimeZoneField( label=_('Time Zone'), required=True, help_text=_('The time zone used for all dates on this server.')) cache_type = forms.ChoiceField( label=_('Cache Backend'), help_text=_('The type of server-side caching to use.'), required=True, widget=forms.Select(attrs={ 'data-subform-group': 'cache-backend', })) def __init__(self, siteconfig, data=None, files=None, *args, **kwargs): """Initialize the settings form. Args: siteconfig (djblets.siteconfig.models.SiteConfiguration): The site configuration being changed. data (dict, optional): The submitted form data. files (dict, optional): The uploaded file data. *args (tuple): Additional positional arguments for the form. **kwargs (dict): Additional keyword arguments for the form. """ self.cache_backend_forms = { backend_id: backend_info['form_cls'](data=data, files=files) for backend_id, backend_info in six.iteritems(self._cache_backends) if backend_info.get('available', True) } super(GeneralSettingsForm, self).__init__(siteconfig, data=data, files=files, *args, **kwargs) def load(self): """Load settings from the form. This will populate initial fields based on the site configuration. It takes care to transition legacy (<= Review Board 1.7) cache backends, if still used in production, to a modern configuration. """ domain_method = self.siteconfig.get('site_domain_method') site = Site.objects.get_current() # Load the rest of the settings from the form. super(GeneralSettingsForm, self).load() # Load the cache settings. cache_backend_info = self.siteconfig.get('cache_backend') cache_backend = (normalize_cache_backend(cache_backend_info, DEFAULT_FORWARD_CACHE_ALIAS) or normalize_cache_backend(cache_backend_info)) cache_backend_path = cache_backend['BACKEND'] cache_type = 'custom' for _cache_type, backend_info in six.iteritems(self._cache_backends): if (cache_backend_path == backend_info['backend_cls_path'] or cache_backend_path in backend_info.get( 'legacy_backend_cls_paths', [])): cache_type = _cache_type break cache_type_choices = [ (backend_id, backend_info['name']) for backend_id, backend_info in six.iteritems(self._cache_backends) if backend_info.get('available', True) ] if cache_type == 'custom': cache_type_choices.append(('custom', ugettext('Custom'))) cache_type_field = self.fields['cache_type'] cache_type_field.choices = tuple(cache_type_choices) cache_type_field.initial = cache_type if cache_type in self.cache_backend_forms: self.cache_backend_forms[cache_type].load(cache_backend) # This must come after we've loaded the general settings. self.fields['server'].initial = '%s://%s' % (domain_method, site.domain) def save(self): """Save the form. This will write the new configuration to the database. It will then force a site configuration reload. """ server = self.cleaned_data['server'] if '://' not in server: # urlparse doesn't properly handle URLs without a scheme. It # believes the domain is actually the path. So we apply a prefix. server = 'http://' + server url_parts = urlparse(server) domain_method = url_parts[0] domain_name = url_parts[1] if domain_name.endswith('/'): domain_name = domain_name[:-1] site = Site.objects.get_current() if site.domain != domain_name: site.domain = domain_name site.save(update_fields=['domain']) self.siteconfig.set('site_domain_method', domain_method) cache_type = self.cleaned_data['cache_type'] if cache_type != 'custom': cache_backend_info = self._cache_backends[cache_type] cache_form = self.cache_backend_forms[cache_type] cache_backend_settings = cache_form.build_cache_backend_settings() self.siteconfig.set( 'cache_backend', { DEFAULT_FORWARD_CACHE_ALIAS: dict({ 'BACKEND': cache_backend_info['backend_cls_path'], }, **cache_backend_settings), }) super(GeneralSettingsForm, self).save() # Reload any important changes into the Django settings. load_site_config() def is_valid(self): """Return whether the form is valid. This will check that this form, and the form for any selected cache backend, is valid. Returns: bool: ``True`` if all form fields are valid. ``False`` if any are invalid. """ if not super(GeneralSettingsForm, self).is_valid(): return False cache_form = self.cache_backend_forms.get( self.cleaned_data['cache_type']) return cache_form is None or cache_form.is_valid() def clean(self): """Clean and validate the form fields. This is called after all individual fields are validated. It does the remaining work of checking to make sure the resulting configuration is valid. Returns: dict: The cleaned form data. """ cleaned_data = super(GeneralSettingsForm, self).clean() if 'cache_type' not in self.errors: cache_type = cleaned_data['cache_type'] cache_backend_info = self._cache_backends.get(cache_type) cache_form = self.cache_backend_forms.get(cache_type) if (cache_backend_info is not None and cache_form is not None and cache_form.is_valid()): cache_backend = None try: cache_backend_settings = \ cache_form.build_cache_backend_settings() cache_cls = import_string( cache_backend_info['backend_cls_path']) cache_backend = cache_cls( cache_backend_settings.get('LOCATION'), {}) cache_backend.set(self.CACHE_VALIDATION_KEY, self.CACHE_VALIDATION_VALUE) value = cache_backend.get(self.CACHE_VALIDATION_KEY) cache_backend.delete(self.CACHE_VALIDATION_KEY) if value != self.CACHE_VALIDATION_VALUE: self.errors['cache_type'] = self.error_class([ _('Unable to store and retrieve values from this ' 'caching backend. There may be a problem ' 'connecting.') ]) except Exception as e: self.errors['cache_type'] = self.error_class( [_('Error with this caching configuration: %s') % e]) # If the cache backend is open, try closing it. This may fail, # so we want to ignore any failures. if cache_backend is not None: try: cache_backend.close() except Exception: pass return cleaned_data class Meta: title = _('General Settings') save_blacklist = ('server', 'cache_type') subforms = ({ 'subforms_attr': 'cache_backend_forms', 'controller_field': 'cache_type', }, ) fieldsets = ( { 'title': _('Site Settings'), 'classes': ('wide', ), 'fields': ('company', 'server', 'site_media_url', 'site_static_url', 'site_admin_name', 'site_admin_email', 'locale_timezone', 'site_read_only', 'read_only_message'), }, { 'title': _('Cache Settings'), 'classes': ('wide', ), 'fields': ('cache_type', ), }, )
class AccountSettingsForm(AccountPageForm): """Form for the Settings page for an account.""" form_id = 'settings' form_title = _('Settings') timezone = TimeZoneField(label=_('Time zone'), required=True, help_text=_("The time zone you're in.")) syntax_highlighting = forms.BooleanField( label=_('Enable syntax highlighting in the diff viewer'), required=False) open_an_issue = forms.BooleanField( label=_('Always open an issue when comment box opens'), required=False) default_use_rich_text = forms.BooleanField( label=_('Always use Markdown for text fields'), required=False) should_send_email = forms.BooleanField( label=_('Get e-mail notification for review requests and reviews'), required=False) should_send_own_updates = forms.BooleanField( label=_('Get e-mail notifications for my own activity'), required=False) enable_desktop_notifications = forms.BooleanField( label=_('Show desktop notifications'), required=False) def load(self): """Load data for the form.""" profile = self.user.get_profile() siteconfig = SiteConfiguration.objects.get_current() diffviewer_syntax_highlighting = siteconfig.get( 'diffviewer_syntax_highlighting') self.set_initial({ 'open_an_issue': profile.open_an_issue, 'syntax_highlighting': (profile.syntax_highlighting and diffviewer_syntax_highlighting), 'timezone': profile.timezone, 'default_use_rich_text': profile.should_use_rich_text, 'should_send_email': profile.should_send_email, 'should_send_own_updates': profile.should_send_own_updates, 'enable_desktop_notifications': profile.should_enable_desktop_notifications, }) if not diffviewer_syntax_highlighting: self.fields['syntax_highlighting'].widget.attrs.update({ 'disabled': True, }) def save(self): """Save the form.""" profile = self.user.get_profile() siteconfig = SiteConfiguration.objects.get_current() if siteconfig.get('diffviewer_syntax_highlighting'): profile.syntax_highlighting = \ self.cleaned_data['syntax_highlighting'] profile.open_an_issue = self.cleaned_data['open_an_issue'] profile.default_use_rich_text = \ self.cleaned_data['default_use_rich_text'] profile.timezone = self.cleaned_data['timezone'] profile.should_send_email = self.cleaned_data['should_send_email'] profile.should_send_own_updates = \ self.cleaned_data['should_send_own_updates'] profile.settings['enable_desktop_notifications'] = \ self.cleaned_data['enable_desktop_notifications'] profile.save() messages.add_message(self.request, messages.INFO, _('Your settings have been saved.')) class Meta: fieldsets = ((_('General Settings'), { 'fields': ('form_target', 'timezone', 'syntax_highlighting', 'open_an_issue', 'default_use_rich_text'), }), (_('Notifications'), { 'fields': ('should_send_email', 'should_send_own_updates', 'enable_desktop_notifications'), }))
class GeneralSettingsForm(SiteSettingsForm): """General settings for Review Board.""" CACHE_TYPE_CHOICES = ( ('memcached', _('Memcached')), ('file', _('File cache')), ) CACHE_BACKENDS_MAP = { 'file': 'django.core.cache.backends.filebased.FileBasedCache', 'memcached': 'django.core.cache.backends.memcached.MemcachedCache', 'locmem': 'django.core.cache.backends.locmem.LocMemCache', } CACHE_TYPES_MAP = { 'django.core.cache.backends.filebased.FileBasedCache': 'file', 'django.core.cache.backends.memcached.CacheClass': 'memcached', 'django.core.cache.backends.memcached.MemcachedCache': 'memcached', 'django.core.cache.backends.locmem.LocMemCache': 'locmem', } CACHE_LOCATION_FIELD_MAP = { 'file': 'cache_path', 'memcached': 'cache_host', } CACHE_VALIDATION_KEY = '__rb-cache-validation__' CACHE_VALIDATION_VALUE = 12345 company = forms.CharField( label=_('Company/Organization'), help_text=_('The optional name of your company or organization. ' 'This will be displayed on your support page.'), required=False, widget=forms.TextInput(attrs={'size': '30'})) server = forms.CharField( label=_('Server'), help_text=_('The URL of this Review Board server. This should not ' 'contain the subdirectory Review Board is installed in.'), widget=forms.TextInput(attrs={'size': '30'})) site_read_only = forms.BooleanField( label=_('Enable read-only mode'), help_text=_('Prevent non-superusers from making any changes to ' 'Review Board.'), required=False) read_only_message = forms.CharField( label=_('Read-only message'), help_text=_('A custom message displayed when the site is in ' 'read-only mode.'), required=False, widget=forms.TextInput(attrs={'size': '30'})) site_media_url = forms.CharField( label=_('Media URL'), help_text=(_('The URL to the media files. Set to ' '<code>%smedia/</code> to use the default media path on ' 'this server.') % settings.SITE_ROOT), required=True, widget=forms.TextInput(attrs={'size': '30'})) site_static_url = forms.CharField( label=_('Static URL'), help_text=(_('The URL to the static files, such as JavaScript files, ' 'CSS files, and images that are bundled with Review ' 'Board or third-party extensions. Set to ' '<code>%sstatic/</code> to use the default static path ' 'on this server.') % settings.SITE_ROOT), required=True, widget=forms.TextInput(attrs={'size': '30'})) site_admin_name = forms.CharField( label=_('Administrator Name'), required=True, widget=forms.TextInput(attrs={'size': '30'})) site_admin_email = forms.EmailField( label=_('Administrator E-Mail'), required=True, widget=forms.TextInput(attrs={'size': '30'})) locale_timezone = TimeZoneField( label=_('Time Zone'), required=True, help_text=_('The time zone used for all dates on this server.')) cache_type = forms.ChoiceField( label=_('Cache Backend'), choices=CACHE_TYPE_CHOICES, help_text=_('The type of server-side caching to use.'), required=True) cache_path = forms.CharField( label=_('Cache Path'), help_text=_('The file location for the cache.'), required=True, widget=forms.TextInput(attrs={'size': '50'}), error_messages={ 'required': 'A valid cache path must be provided.' }) cache_host = forms.CharField( label=_('Cache Hosts'), help_text=_('The host or hosts used for the cache, in hostname:port ' 'form. Multiple hosts can be specified by separating ' 'them with a semicolon (;).'), required=True, widget=forms.TextInput(attrs={'size': '50'}), error_messages={ 'required': 'A valid cache host must be provided.' }) def load(self): """Load settings from the form. This will populate initial fields based on the site configuration. It takes care to transition legacy (<= Review Board 1.7) cache backends, if still used in production, to a modern configuration. """ domain_method = self.siteconfig.get('site_domain_method') site = Site.objects.get_current() # Load the rest of the settings from the form. super(GeneralSettingsForm, self).load() # Load the cache settings. cache_backend_info = self.siteconfig.get('cache_backend') cache_backend = ( normalize_cache_backend(cache_backend_info, DEFAULT_FORWARD_CACHE_ALIAS) or normalize_cache_backend(cache_backend_info)) cache_type = self.CACHE_TYPES_MAP.get(cache_backend['BACKEND'], 'custom') self.fields['cache_type'].initial = cache_type if settings.DEBUG: self.fields['cache_type'].choices += ( ('locmem', ugettext('Local memory cache')), ) if cache_type == 'custom': self.fields['cache_type'].choices += ( ('custom', ugettext('Custom')), ) cache_locations = [] elif cache_type != 'locmem': cache_locations = cache_backend['LOCATION'] if not isinstance(cache_locations, list): cache_locations = [cache_locations] location_field = self.CACHE_LOCATION_FIELD_MAP[cache_type] self.fields[location_field].initial = ';'.join(cache_locations) # This must come after we've loaded the general settings. self.fields['server'].initial = '%s://%s' % (domain_method, site.domain) def save(self): """Save the form. This will write the new configuration to the database. It will then force a site configuration reload. """ server = self.cleaned_data['server'] if '://' not in server: # urlparse doesn't properly handle URLs without a scheme. It # believes the domain is actually the path. So we apply a prefix. server = 'http://' + server url_parts = urlparse(server) domain_method = url_parts[0] domain_name = url_parts[1] if domain_name.endswith('/'): domain_name = domain_name[:-1] site = Site.objects.get_current() if site.domain != domain_name: site.domain = domain_name site.save(update_fields=['domain']) self.siteconfig.set('site_domain_method', domain_method) cache_type = self.cleaned_data['cache_type'] if cache_type != 'custom': if cache_type == 'locmem': # We want to specify a "reviewboard" location to keep items # separate from those in other caches. location = 'reviewboard' else: location_field = self.CACHE_LOCATION_FIELD_MAP[cache_type] location = self.cleaned_data[location_field] if cache_type == 'memcached': # memcached allows a list of servers, rather than just a # string representing one. location = location.split(';') self.siteconfig.set('cache_backend', { DEFAULT_FORWARD_CACHE_ALIAS: { 'BACKEND': self.CACHE_BACKENDS_MAP[cache_type], 'LOCATION': location, } }) super(GeneralSettingsForm, self).save() # Reload any important changes into the Django settings. load_site_config() def full_clean(self): """Begin cleaning and validating all form fields. This is the beginning of the form validation process. Before cleaning the fields, this will set the "required" states for the caching fields, based on the chosen caching type. This will enable or disable validation for those particular fields. Returns: dict: The cleaned form data. """ orig_required = {} cache_type = (self['cache_type'].data or self.fields['cache_type'].initial) for iter_cache_type, field in six.iteritems( self.CACHE_LOCATION_FIELD_MAP): orig_required[field] = self.fields[field].required self.fields[field].required = (cache_type == iter_cache_type) cleaned_data = super(GeneralSettingsForm, self).full_clean() # Reset the required flags for any modified field. for field, required in six.iteritems(orig_required): self.fields[field].required = required return cleaned_data def clean(self): """Clean and validate the form fields. This is called after all individual fields are validated. It does the remaining work of checking to make sure the resulting configuration is valid. Returns: dict: The cleaned form data. """ cleaned_data = super(GeneralSettingsForm, self).clean() if 'cache_type' not in self.errors: cache_type = cleaned_data['cache_type'] cache_location_field = \ self.CACHE_LOCATION_FIELD_MAP.get(cache_type) if cache_location_field not in self.errors: cache_backend = None try: cache_cls = import_string( self.CACHE_BACKENDS_MAP[cache_type]) cache_backend = cache_cls( cleaned_data.get(cache_location_field), {}) cache_backend.set(self.CACHE_VALIDATION_KEY, self.CACHE_VALIDATION_VALUE) value = cache_backend.get(self.CACHE_VALIDATION_KEY) cache_backend.delete(self.CACHE_VALIDATION_KEY) if value != self.CACHE_VALIDATION_VALUE: self.errors[cache_location_field] = self.error_class([ _('Unable to store and retrieve values from this ' 'caching backend. There may be a problem ' 'connecting.') ]) except Exception as e: self.errors[cache_location_field] = self.error_class([ _('Error with this caching configuration: %s') % e ]) # If the cache backend is open, try closing it. This may fail, # so we want to ignore any failures. if cache_backend is not None: try: cache_backend.close() except Exception: pass return cleaned_data def clean_cache_host(self): """Validate that the cache_host field is provided if required. If valid, this will strip whitespace around the ``cache_host`` field and return it. Returns: unicode: The cache host, with whitespace stripped. Raises: django.core.exceptions.ValidationError: A cache host was not provided, and is required by the backend. """ cache_host = self.cleaned_data['cache_host'].strip() if self.fields['cache_host'].required and not cache_host: raise ValidationError( ugettext('A valid cache host must be provided.')) return cache_host def clean_cache_path(self): """Validate that the cache_path field is provided if required. If valid, this will strip whitespace around the ``cache_path`` field and return it. Returns: unicode: The cache path, with whitespace stripped. Raises: django.core.exceptions.ValidationError: A cache path was not provided, and is required by the backend. """ cache_path = self.cleaned_data['cache_path'].strip() if self.fields['cache_path'].required and not cache_path: raise ValidationError( ugettext('A valid cache path must be provided.')) return cache_path class Meta: title = _('General Settings') save_blacklist = ('server', 'cache_type', 'cache_host', 'cache_path') fieldsets = ( { 'title': _('Site Settings'), 'classes': ('wide',), 'fields': ('company', 'server', 'site_media_url', 'site_static_url', 'site_admin_name', 'site_admin_email', 'locale_timezone', 'site_read_only', 'read_only_message'), }, { 'title': _('Cache Settings'), 'classes': ('wide',), 'fields': ('cache_type', 'cache_path', 'cache_host'), }, )
class PreferencesForm(forms.Form): redirect_to = forms.CharField(required=False, widget=forms.HiddenInput) groups = forms.MultipleChoiceField(widget=forms.CheckboxSelectMultiple, required=False) syntax_highlighting = forms.BooleanField( required=False, label=_("Enable syntax highlighting in the diff viewer")) profile_private = forms.BooleanField( required=False, label=_("Keep your user profile private")) open_an_issue = forms.BooleanField( required=False, label=_("Always open an issue when comment box opens")) first_name = forms.CharField(required=False) last_name = forms.CharField(required=False) email = forms.EmailField() password1 = forms.CharField(required=False, widget=widgets.PasswordInput()) password2 = forms.CharField(required=False, widget=widgets.PasswordInput()) timezone = TimeZoneField( label=_("Time Zone"), required=True, help_text=_("The time zone used for this account.")) def __init__(self, user, *args, **kwargs): from reviewboard.accounts.backends import get_auth_backends super(forms.Form, self).__init__(*args, **kwargs) auth_backends = get_auth_backends() choices = [] for g in Group.objects.accessible(user=user).order_by('display_name'): choices.append((g.id, g.display_name)) for site in user.local_site.all().order_by('name'): for g in Group.objects.accessible( user=user, local_site=site).order_by('display_name'): display_name = '%s / %s' % (g.local_site.name, g.display_name) choices.append((g.id, display_name)) self.fields['groups'].choices = choices self.fields['email'].required = auth_backends[0].supports_change_email def save(self, user): from reviewboard.accounts.backends import get_auth_backends auth_backends = get_auth_backends() primary_backend = auth_backends[0] password = self.cleaned_data['password1'] if primary_backend.supports_change_password and password: primary_backend.update_password(user, password) if primary_backend.supports_change_name: user.first_name = self.cleaned_data['first_name'] user.last_name = self.cleaned_data['last_name'] primary_backend.update_name(user) if primary_backend.supports_change_email: user.email = self.cleaned_data['email'] primary_backend.update_email(user) user.review_groups = self.cleaned_data['groups'] user.save() profile, is_new = Profile.objects.get_or_create(user=user) profile.first_time_setup_done = True profile.syntax_highlighting = self.cleaned_data['syntax_highlighting'] profile.is_private = self.cleaned_data['profile_private'] profile.open_an_issue = self.cleaned_data['open_an_issue'] profile.timezone = self.cleaned_data['timezone'] profile.save() def clean_password2(self): p1 = self.cleaned_data['password1'] p2 = self.cleaned_data['password2'] if p1 != p2: raise ValidationError(_('Passwords do not match')) return p2