Ejemplo n.º 1
0
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.'))
Ejemplo n.º 2
0
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', ),
            },
        )
Ejemplo n.º 3
0
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', ),
            },
        )
Ejemplo n.º 4
0
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'),
        }))
Ejemplo n.º 5
0
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'),
            },
        )
Ejemplo n.º 6
0
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