def test_to_python_with_invalid_value_error(self):
        """Testing ConditionsField.to_python with invalid value error"""
        class MyChoice(BaseConditionIntegerChoice):
            choice_id = 'my-choice'

        choices = ConditionChoices([MyChoice])
        field = ConditionsField(choices=choices)

        with self.assertRaises(ValidationError) as cm:
            field.to_python({
                'mode':
                'any',
                'conditions': [{
                    'choice': 'my-choice',
                    'op': 'is',
                    'value': 'invalid-value',
                }],
            })

        self.assertEqual(cm.exception.messages,
                         ['There was an error with one of your conditions.'])
        self.assertEqual(cm.exception.code, 'condition_errors')
        self.assertEqual(field.widget.condition_errors, {
            0: 'Enter a whole number.',
        })
    def test_to_python_with_operator_not_found_error(self):
        """Testing ConditionsField.to_python with operator not found error"""
        class MyChoice(BaseConditionStringChoice):
            choice_id = 'my-choice'

        choices = ConditionChoices([MyChoice])
        field = ConditionsField(choices=choices)

        with self.assertRaises(ValidationError) as cm:
            field.to_python({
                'mode':
                'any',
                'conditions': [{
                    'choice': 'my-choice',
                    'op': 'invalid-op',
                    'value': 'my-value',
                }],
            })

        self.assertEqual(cm.exception.messages,
                         ['There was an error with one of your conditions.'])
        self.assertEqual(cm.exception.code, 'condition_errors')
        self.assertEqual(field.widget.condition_errors, {
            0: 'No operator was found matching "invalid-op".',
        })
Example #3
0
    def test_to_python_with_value_required_error(self):
        """Testing ConditionsField.to_python with value required error"""
        class MyChoice(BaseConditionStringChoice):
            choice_id = 'my-choice'

        choices = ConditionChoices([MyChoice])
        field = ConditionsField(choices=choices)

        with self.assertRaises(ValidationError) as cm:
            field.to_python({
                'mode': 'any',
                'conditions': [{
                    'choice': 'my-choice',
                    'op': 'is',
                }],
            })

        self.assertEqual(cm.exception.messages,
                         ['There was an error with one of your conditions.'])
        self.assertEqual(cm.exception.code, 'condition_errors')
        self.assertEqual(
            field.widget.condition_errors,
            {
                0: 'A value is required.',
            })
Example #4
0
    def test_to_python_with_invalid_value_error(self):
        """Testing ConditionsField.to_python with invalid value error"""
        class MyChoice(BaseConditionIntegerChoice):
            choice_id = 'my-choice'

        choices = ConditionChoices([MyChoice])
        field = ConditionsField(choices=choices)

        with self.assertRaises(ValidationError) as cm:
            field.to_python({
                'mode': 'any',
                'conditions': [{
                    'choice': 'my-choice',
                    'op': 'is',
                    'value': 'invalid-value',
                }],
            })

        self.assertEqual(cm.exception.messages,
                         ['There was an error with one of your conditions.'])
        self.assertEqual(cm.exception.code, 'condition_errors')
        self.assertEqual(
            field.widget.condition_errors,
            {
                0: 'Enter a whole number.',
            })
Example #5
0
    def test_to_python_with_operator_not_found_error(self):
        """Testing ConditionsField.to_python with operator not found error"""
        class MyChoice(BaseConditionStringChoice):
            choice_id = 'my-choice'

        choices = ConditionChoices([MyChoice])
        field = ConditionsField(choices=choices)

        with self.assertRaises(ValidationError) as cm:
            field.to_python({
                'mode': 'any',
                'conditions': [{
                    'choice': 'my-choice',
                    'op': 'invalid-op',
                    'value': 'my-value',
                }],
            })

        self.assertEqual(cm.exception.messages,
                         ['There was an error with one of your conditions.'])
        self.assertEqual(cm.exception.code, 'condition_errors')
        self.assertEqual(
            field.widget.condition_errors,
            {
                0: 'No operator was found matching "invalid-op".',
            })
    def test_to_python(self):
        """Testing ConditionsField.to_python"""
        class MyChoice(BaseConditionStringChoice):
            choice_id = 'my-choice'

        choices = ConditionChoices([MyChoice])
        field = ConditionsField(choices=choices)

        condition_set = field.to_python({
            'mode':
            'any',
            'conditions': [{
                'choice': 'my-choice',
                'op': 'is',
                'value': 'my-value',
            }]
        })

        self.assertEqual(condition_set.mode, ConditionSet.MODE_ANY)
        self.assertEqual(len(condition_set.conditions), 1)

        condition = condition_set.conditions[0]
        self.assertEqual(condition.choice.choice_id, 'my-choice')
        self.assertEqual(condition.operator.operator_id, 'is')
        self.assertEqual(condition.value, 'my-value')
    def test_prepare_value_with_condition_set(self):
        """Testing ConditionsField.prepare_value with ConditionSet"""
        choices = ConditionChoices([BaseConditionStringChoice])
        field = ConditionsField(choices=choices)

        self.assertEqual(field.prepare_value(ConditionSet()), {
            'mode': 'all',
            'conditions': [],
        })
Example #8
0
    def test_prepare_value_with_serialized_data(self):
        """Testing ConditionsField.prepare_value with serialized data"""
        choices = ConditionChoices([BaseConditionStringChoice])
        field = ConditionsField(choices=choices)

        data = {
            'mode': 'all',
            'conditions': [],
        }

        self.assertEqual(field.prepare_value(data), data)
Example #9
0
    def test_prepare_value_with_condition_set(self):
        """Testing ConditionsField.prepare_value with ConditionSet"""
        choices = ConditionChoices([BaseConditionStringChoice])
        field = ConditionsField(choices=choices)

        self.assertEqual(
            field.prepare_value(ConditionSet()),
            {
                'mode': 'all',
                'conditions': [],
            })
    def test_prepare_value_with_serialized_data(self):
        """Testing ConditionsField.prepare_value with serialized data"""
        choices = ConditionChoices([BaseConditionStringChoice])
        field = ConditionsField(choices=choices)

        data = {
            'mode': 'all',
            'conditions': [],
        }

        self.assertEqual(field.prepare_value(data), data)
Example #11
0
    def test_to_python_with_mode_error(self):
        """Testing ConditionsField.to_python with mode error"""
        choices = ConditionChoices()
        field = ConditionsField(choices=choices)

        with self.assertRaises(ValidationError) as cm:
            field.to_python({
                'mode': 'invalid',
                'conditions': [],
            })

        self.assertEqual(cm.exception.messages,
                         ['"invalid" is not a valid condition mode.'])
        self.assertEqual(cm.exception.code, 'invalid_mode')
    def test_to_python_with_mode_error(self):
        """Testing ConditionsField.to_python with mode error"""
        choices = ConditionChoices()
        field = ConditionsField(choices=choices)

        with self.assertRaises(ValidationError) as cm:
            field.to_python({
                'mode': 'invalid',
                'conditions': [],
            })

        self.assertEqual(cm.exception.messages,
                         ['"invalid" is not a valid condition mode.'])
        self.assertEqual(cm.exception.code, 'invalid_mode')
    def test_value_from_datadict_with_missing_choice_rows(self):
        """Testing ConditionsWidget.value_from_datadict with missing choice
        rows
        """
        class MyChoice(BaseConditionIntegerChoice):
            choice_id = 'my-choice'

        choices = ConditionChoices([MyChoice])
        field = ConditionsField(choices=choices)

        data = MultiValueDict('')
        data.update({
            'my_conditions_mode': 'any',
            'my_conditions_last_id': '5',
            'my_conditions_choice[5]': 'my-choice',
            'my_conditions_operator[5]': 'is-not',
            'my_conditions_value[5]': 'my-value',
        })

        self.assertEqual(
            field.widget.value_from_datadict(data, MultiValueDict(''),
                                             'my_conditions'),
            {
                'mode': 'any',
                'conditions': [
                    {
                        'choice': 'my-choice',
                        'op': 'is-not',
                        'value': 'my-value',
                    },
                ],
            })
Example #14
0
    def test_get_context_with_condition_errors(self):
        """Testing ConditionsWidget.get_context with condition errors"""
        class MyOperator1(BaseConditionOperator):
            operator_id = 'my-op-1'
            name = 'My Op 1'
            value_field = ConditionValueIntegerField()

        class MyChoice1(BaseConditionChoice):
            choice_id = 'my-choice'
            name = 'My Choice'
            operators = ConditionOperators([MyOperator1])

        choices = ConditionChoices([MyChoice1])
        field = ConditionsField(choices=choices)
        field.widget.condition_errors[0] = 'This is an error.'

        result = field.widget.get_context(
            'my_conditions', {
                'mode':
                'any',
                'conditions': [
                    {
                        'choice': 'my-choice',
                        'op': 'my-op-1',
                        'value': 'my-value-1',
                    },
                ],
            }, {
                'id': 'my-conditions',
            })

        rendered_rows = result['rendered_rows']
        self.assertEqual(rendered_rows, [{
            'choice': ('<select id="my-conditions_choice_0"'
                       ' name="my_conditions_choice[0]">\n'
                       '<option value="my-choice" selected="selected">'
                       'My Choice</option>\n'
                       '</select>'),
            'operator': ('<select id="my-conditions_operator_0"'
                         ' name="my_conditions_operator[0]">\n'
                         '<option value="my-op-1" selected="selected">'
                         'My Op 1</option>\n'
                         '</select>'),
            'error':
            'This is an error.',
        }])

        serialized_choices = result['serialized_choices']
        self.assertEqual(len(serialized_choices), 1)
        self.assertEqual(serialized_choices[0]['id'], 'my-choice')

        serialized_rows = result['serialized_rows']
        self.assertEqual(serialized_rows, [{
            'choiceID': 'my-choice',
            'operatorID': 'my-op-1',
            'valid': True,
            'value': 'my-value-1',
            'error': 'This is an error.',
        }])
    def test_init_with_choices_subclass(self):
        """Testing ConditionsField initialization with choices subclass"""
        class MyChoices(ConditionChoices):
            choice_classes = [BaseConditionStringChoice]

        field = ConditionsField(choices=MyChoices)

        self.assertIs(field.choices.__class__, MyChoices)
    def test_init_with_missing_operators(self):
        """Testing ConditionsField initialization with choices missing
        operators
        """
        class MyChoice(BaseConditionChoice):
            choice_id = 'my-choice'

        choices = ConditionChoices([MyChoice])
        msg = 'MyChoice must define a non-empty "operators" attribute.'

        with self.assertRaisesMessage(ValueError, msg):
            ConditionsField(choices=choices)
    def test_to_python_with_choice_not_found_error(self):
        """Testing ConditionsField.to_python with choice not found error"""
        choices = ConditionChoices()
        field = ConditionsField(choices=choices)

        with self.assertRaises(ValidationError) as cm:
            field.to_python({
                'mode':
                'any',
                'conditions': [{
                    'choice': 'invalid-choice',
                    'op': 'is',
                    'value': 'my-value',
                }],
            })

        self.assertEqual(cm.exception.messages,
                         ['There was an error with one of your conditions.'])
        self.assertEqual(cm.exception.code, 'condition_errors')
        self.assertEqual(
            field.widget.condition_errors, {
                0: 'No condition choice was found matching "invalid-choice".',
            })
Example #18
0
class SlackIntegrationConfigForm(IntegrationConfigForm):
    """Form for configuring Slack.

    This allows an administrator to set up a Slack configuration for sending
    messages to a given Slack WebHook URL based on the specified conditions.
    """

    conditions = ConditionsField(ReviewRequestConditionChoices,
                                 label=_('Conditions'))

    webhook_url = forms.CharField(
        label=_('Webhook URL'),
        required=True,
        help_text=_('Your unique Slack webhook URL. This can be found in the '
                    '"Setup Instructions" box inside the Incoming WebHooks '
                    'integration.'),
        widget=forms.TextInput(attrs={
            'size': 80,
        }))

    channel = forms.CharField(
        label=_('Send to Channel'),
        required=False,
        help_text=_('The optional name of the channel review request updates '
                    'are sent to. By default, the configured channel on the '
                    'Incoming Webhook will be used.'),
        widget=forms.TextInput(attrs={
            'size': 40,
        }))

    class Meta:
        fieldsets = (
            ('What To Post', {
                'description': _(
                    'You can choose which review requests would be posted by '
                    'choosing the repositories and groups to match against.'
                ),
                'fields': ('conditions',),
            }),
            ('Where To Post', {
                'description': _(
                    'To start, add a new "Incoming WebHooks" service '
                    'integration on Slack. You can then provide the '
                    '"Unique WebHook URL" below, and optionally choose a '
                    'custom channel to send notifications to.'
                ),
                'fields': ('webhook_url', 'channel'),
                'classes': ('wide',)
            }),
        )
Example #19
0
    def test_to_python_with_choice_not_found_error(self):
        """Testing ConditionsField.to_python with choice not found error"""
        choices = ConditionChoices()
        field = ConditionsField(choices=choices)

        with self.assertRaises(ValidationError) as cm:
            field.to_python({
                'mode': 'any',
                'conditions': [{
                    'choice': 'invalid-choice',
                    'op': 'is',
                    'value': 'my-value',
                }],
            })

        self.assertEqual(cm.exception.messages,
                         ['There was an error with one of your conditions.'])
        self.assertEqual(cm.exception.code, 'condition_errors')
        self.assertEqual(
            field.widget.condition_errors,
            {
                0: 'No condition choice was found matching "invalid-choice".',
            })
    def test_to_python_with_value_required_error(self):
        """Testing ConditionsField.to_python with value required error"""
        class MyChoice(BaseConditionStringChoice):
            choice_id = 'my-choice'

        choices = ConditionChoices([MyChoice])
        field = ConditionsField(choices=choices)

        with self.assertRaises(ValidationError) as cm:
            field.to_python({
                'mode':
                'any',
                'conditions': [{
                    'choice': 'my-choice',
                    'op': 'is',
                }],
            })

        self.assertEqual(cm.exception.messages,
                         ['There was an error with one of your conditions.'])
        self.assertEqual(cm.exception.code, 'condition_errors')
        self.assertEqual(field.widget.condition_errors, {
            0: 'A value is required.',
        })
    def test_to_python_with_choice_kwargs(self):
        """Testing ConditionsField.to_python with choice_kwargs set"""
        class MyChoice(BaseConditionStringChoice):
            choice_id = 'my-choice'

        choices = ConditionChoices([MyChoice])
        field = ConditionsField(choices=choices, choice_kwargs={'abc': 123})

        condition_set = field.to_python({
            'mode':
            'any',
            'conditions': [{
                'choice': 'my-choice',
                'op': 'is',
                'value': 'my-value',
            }]
        })

        self.assertEqual(condition_set.mode, ConditionSet.MODE_ANY)
        self.assertEqual(len(condition_set.conditions), 1)

        choice = condition_set.conditions[0].choice
        self.assertEqual(choice.choice_id, 'my-choice')
        self.assertEqual(choice.extra_state, {'abc': 123})
Example #22
0
class CircleCIIntegrationConfigForm(IntegrationConfigForm):
    """Form for configuring Circle CI."""

    conditions = ConditionsField(ReviewRequestConditionChoices,
                                 label=_('Conditions'))

    circle_api_token = forms.CharField(
        label=_('API Token'),
        help_text=_('Your CircleCI API token. You can create these tokens '
                    'in the CircleCI repository settings under "API '
                    'Permissions".'))

    branch_name = forms.CharField(
        label=_('Build Branch'),
        required=False,
        help_text=_('An optional branch name to use for review request '
                    'builds within the CircleCI user interface.'))
    def test_value_from_datadict_with_missing_data(self):
        """Testing ConditionsWidget.value_from_datadict with missing data"""
        class MyChoice(BaseConditionIntegerChoice):
            choice_id = 'my-choice'

        choices = ConditionChoices([MyChoice])
        field = ConditionsField(choices=choices)

        data = MultiValueDict('')

        self.assertEqual(
            field.widget.value_from_datadict(data, MultiValueDict(''),
                                             'my_conditions'),
            {
                'mode': None,
                'conditions': [],
            })
    def test_render(self):
        """Testing ConditionsWidget.render"""
        class MyOperator(BaseConditionOperator):
            operator_id = 'my-op'
            name = 'My Op'
            value_field = ConditionValueIntegerField()

        class MyChoice(BaseConditionChoice):
            choice_id = 'my-choice'
            name = 'My Choice'
            operators = ConditionOperators([MyOperator])

        choices = ConditionChoices([MyChoice])
        field = ConditionsField(choices=choices)

        rendered = field.widget.render(
            'my_conditions',
            {
                'mode': 'any',
                'conditions': [
                    {
                        'choice': 'my-choice',
                        'op': 'my-op',
                        'value': 'my-value-1',
                    },
                ],
            },
            {
                'id': 'my-conditions',
            })

        self.assertIn('<div class="conditions-field" id="my-conditions">',
                      rendered)
        self.assertIn('<input type="hidden" name="my_conditions_last_id"'
                      ' value="1">',
                      rendered)
        self.assertRegexpMatches(
            rendered,
            '<option value="my-choice" selected(="selected")?>My Choice'
            '</option>')
        self.assertRegexpMatches(
            rendered,
            '<option value="my-op" selected(="selected")?>My Op</option>')
        self.assertIn('<span class="conditions-field-value"></span>',
                      rendered)
    def test_deepcopy(self):
        """Testing ConditionsWidget.__deepcopy__"""
        class MyChoice(BaseConditionIntegerChoice):
            choice_id = 'my-choice'

        choices = ConditionChoices([MyChoice])
        field = ConditionsField(choices=choices)
        widget1 = field.widget
        widget2 = copy.deepcopy(widget1)

        widget1.mode_widget.attrs['foo'] = True
        widget1.choice_widget.attrs['foo'] = True
        widget1.operator_widget.attrs['foo'] = True
        widget1.condition_errors[0] = 'This is a test.'

        self.assertEqual(widget2.mode_widget.attrs, {})
        self.assertEqual(widget2.choice_widget.attrs, {})
        self.assertEqual(widget2.operator_widget.attrs, {})
        self.assertEqual(widget2.condition_errors, {})

        # Choices won't be modified between copies, and is a shared object,
        # so both should have the same instance.
        self.assertIs(widget1.choices, widget2.choices)
 class MyForm(Form):
     conditions = ConditionsField(
         choices=ConditionChoices([BaseConditionStringChoice]))
    def test_init_with_choice_kwargs(self):
        """Testing ConditionsField initialization with choice_kwargs"""
        choices = ConditionChoices([BaseConditionStringChoice])
        field = ConditionsField(choices=choices, choice_kwargs={'abc': 123})

        self.assertEqual(field.widget.choice_kwargs, field.choice_kwargs)
    def test_get_context(self):
        """Testing ConditionsWidget.get_context"""
        class MyOperator1(BaseConditionOperator):
            operator_id = 'my-op-1'
            name = 'My Op 1'
            value_field = ConditionValueIntegerField()

        class MyOperator2(BaseConditionOperator):
            operator_id = 'my-op-2'
            name = 'My Op 2'
            value_field = ConditionValueCharField()

        class MyOperator3(BaseConditionOperator):
            operator_id = 'my-op-3'
            name = 'My Op 3'

        class MyChoice1(BaseConditionChoice):
            choice_id = 'my-choice-1'
            name = 'My Choice 1'
            operators = ConditionOperators([MyOperator1, MyOperator2])

        class MyChoice2(BaseConditionChoice):
            choice_id = 'my-choice-2'
            name = 'My Choice 2'
            operators = ConditionOperators([MyOperator3])

        choices = ConditionChoices([MyChoice1, MyChoice2])
        field = ConditionsField(choices=choices)

        result = field.widget.get_context(
            'my_conditions',
            {
                'mode': 'any',
                'conditions': [
                    {
                        'choice': 'my-choice-1',
                        'op': 'my-op-1',
                        'value': 'my-value-1',
                    },
                    {
                        'choice': 'my-choice-2',
                        'op': 'my-op-3',
                    },
                ],
            },
            {
                'id': 'my-conditions',
            })

        self.assertEqual(
            set(result),
            {
                'field_id', 'field_name', 'rendered_mode', 'rendered_rows',
                'serialized_choices', 'serialized_rows',
            })
        self.assertEqual(result['field_id'], 'my-conditions')
        self.assertEqual(result['field_name'], 'my_conditions')
        self.assertHTMLEqual(
            result['rendered_mode'],
            '<ul id="my_conditions_mode">\n'
            '<li><label for="my_conditions_mode_0">'
            '<input id="my_conditions_mode_0" name="my_conditions_mode"'
            ' type="radio" value="always" />'
            ' Always match</label></li>\n'
            '<li><label for="my_conditions_mode_1">'
            '<input id="my_conditions_mode_1" name="my_conditions_mode"'
            ' type="radio" value="all" />'
            ' Match <b>all</b> of the following:</label></li>\n'
            '<li><label for="my_conditions_mode_2">'
            '<input checked="checked" id="my_conditions_mode_2" '
            'name="my_conditions_mode" type="radio" value="any" />'
            ' Match <b>any</b> of the following:</label></li>\n'
            '</ul>')

        rendered_rows = result['rendered_rows']
        self.assertEqual(len(rendered_rows), 2)

        # Check the first rendered row.
        rendered_row = rendered_rows[0]
        self.assertEqual(set(six.iterkeys(rendered_row)),
                         {'choice', 'operator', 'error'})

        self.assertHTMLEqual(
            rendered_row['choice'],
            '<select id="my-conditions_choice_0"'
            ' name="my_conditions_choice[0]">\n'
            '<option value="my-choice-1" selected="selected">'
            'My Choice 1</option>\n'
            '<option value="my-choice-2">'
            'My Choice 2</option>\n'
            '</select>')

        self.assertHTMLEqual(
            rendered_row['operator'],
            '<select id="my-conditions_operator_0"'
            ' name="my_conditions_operator[0]">\n'
            '<option value="my-op-1" selected="selected">'
            'My Op 1</option>\n'
            '<option value="my-op-2">My Op 2</option>\n'
            '</select>')

        self.assertIsNone(rendered_row['error'])

        # Check the second rendered row.
        rendered_row = rendered_rows[1]
        self.assertEqual(set(six.iterkeys(rendered_row)),
                         {'choice', 'operator', 'error'})

        self.assertHTMLEqual(
            rendered_row['choice'],
            '<select id="my-conditions_choice_1"'
            ' name="my_conditions_choice[1]">\n'
            '<option value="my-choice-1">'
            'My Choice 1</option>\n'
            '<option value="my-choice-2" selected="selected">'
            'My Choice 2</option>\n'
            '</select>')

        self.assertHTMLEqual(
            rendered_row['operator'],
            '<select id="my-conditions_operator_1"'
            ' name="my_conditions_operator[1]">\n'
            '<option value="my-op-3" selected="selected">'
            'My Op 3</option>\n'
            '</select>')

        self.assertIsNone(rendered_row['error'])

        serialized_choices = result['serialized_choices']
        self.assertEqual(len(serialized_choices), 2)

        # Check the first serialized choice.
        serialized_choice = serialized_choices[0]
        self.assertEqual(set(six.iterkeys(serialized_choice)),
                         {'id', 'name', 'valueField', 'operators'})
        self.assertEqual(serialized_choice['id'], 'my-choice-1')
        self.assertEqual(serialized_choice['name'], 'My Choice 1')
        self.assertEqual(serialized_choice['valueField'], {})
        self.assertEqual(serialized_choice['valueField'], {})

        serialized_operators = serialized_choice['operators']
        self.assertEqual(len(serialized_operators), 2)

        # Check the first operator in the first serialized choice.
        serialized_operator = serialized_operators[0]
        self.assertEqual(set(six.iterkeys(serialized_operator)),
                         {'id', 'name', 'useValue', 'valueField'})
        self.assertEqual(serialized_operator['id'], 'my-op-1')
        self.assertEqual(serialized_operator['name'], 'My Op 1')
        self.assertTrue(serialized_operator['useValue'])

        serialized_value_field = serialized_operator['valueField']
        self.assertEqual(set(six.iterkeys(serialized_value_field)),
                         {'model', 'view'})

        serialized_value_model = serialized_value_field['model']
        self.assertEqual(serialized_value_model['className'],
                         'Djblets.Forms.ConditionValueField')
        self.assertHTMLEqual(serialized_value_model['data']['fieldHTML'],
                             '<input name="XXX" type="number" />')

        serialized_value_view = serialized_value_field['view']
        self.assertEqual(serialized_value_view['className'],
                         'Djblets.Forms.ConditionValueFormFieldView')
        self.assertEqual(serialized_value_view['data'], {})

        # Check the second operator in the first serialized choice.
        serialized_operator = serialized_operators[1]
        self.assertEqual(set(six.iterkeys(serialized_operator)),
                         {'id', 'name', 'useValue', 'valueField'})
        self.assertEqual(serialized_operator['id'], 'my-op-2')
        self.assertEqual(serialized_operator['name'], 'My Op 2')
        self.assertTrue(serialized_operator['useValue'])

        serialized_value_field = serialized_operator['valueField']
        self.assertEqual(set(six.iterkeys(serialized_value_field)),
                         {'model', 'view'})

        serialized_value_model = serialized_value_field['model']
        self.assertEqual(serialized_value_model['className'],
                         'Djblets.Forms.ConditionValueField')
        self.assertHTMLEqual(serialized_value_model['data']['fieldHTML'],
                             '<input name="XXX" type="text" />')

        serialized_value_view = serialized_value_field['view']
        self.assertEqual(serialized_value_view['className'],
                         'Djblets.Forms.ConditionValueFormFieldView')
        self.assertEqual(serialized_value_view['data'], {})

        self.assertEqual(
            serialized_choices[1],
            {
                'id': 'my-choice-2',
                'name': 'My Choice 2',
                'operators': [
                    {
                        'id': 'my-op-3',
                        'name': 'My Op 3',
                        'useValue': False,
                    },
                ],
                'valueField': {},
            })

        serialized_rows = result['serialized_rows']
        self.assertEqual(len(serialized_rows), 2)
        self.assertEqual(
            serialized_rows[0],
            {
                'choiceID': 'my-choice-1',
                'operatorID': 'my-op-1',
                'valid': True,
                'value': 'my-value-1',
            })
        self.assertEqual(
            serialized_rows[1],
            {
                'choiceID': 'my-choice-2',
                'operatorID': 'my-op-3',
                'valid': True,
                'value': None,
            })
Example #29
0
class TravisCIIntegrationConfigForm(IntegrationConfigForm):
    """Form for configuring Travis CI."""

    conditions = ConditionsField(
        GitHubOnlyConditionChoices,
        label=_('Conditions'),
        help_text=_('You can choose which review requests will be built using '
                    'this Travis CI configuration.'))

    travis_endpoint = forms.ChoiceField(
        label=_('Travis CI'),
        choices=TravisAPI.ENDPOINT_CHOICES,
        help_text=_('The Travis CI endpoint for your project.'))

    travis_custom_endpoint = forms.URLField(
        label=_('CI Server'),
        required=False,
        help_text=_('The URL to your enterprise Travis CI server. For '
                    'example, <code>https://travis.example.com/</code>.'))

    travis_ci_token = forms.CharField(
        label=_('API Token'),
        help_text=(
            _('The Travis CI API token. To get an API token, follow the '
              'instructions at <a href="%(url)s">%(url)s</a>.') % {
                  'url': 'https://developer.travis-ci.com/authentication'
              }))

    travis_yml = forms.CharField(
        label=_('Build Config'),
        help_text=_('The configuration needed to do a test build, without '
                    'any notification or deploy stages.'),
        widget=forms.Textarea(attrs={'cols': '80'}))

    branch_name = forms.CharField(
        label=_('Build Branch'),
        required=False,
        help_text=_('An optional branch name to use for review request '
                    'builds within the Travis CI user interface.'))

    def __init__(self, *args, **kwargs):
        """Initialize the form.

        Args:
            *args (tuple):
                Arguments for the form.

            **kwargs (dict):
                Keyword arguments for the form.
        """
        super(TravisCIIntegrationConfigForm, self).__init__(*args, **kwargs)

        from rbintegrations.extension import RBIntegrationsExtension
        extension = RBIntegrationsExtension.instance

        travis_integration_config_bundle = \
            extension.get_bundle_id('travis-ci-integration-config')
        self.css_bundle_names = [travis_integration_config_bundle]
        self.js_bundle_names = [travis_integration_config_bundle]

    def clean(self):
        """Clean the form.

        This validates that the configured settings are correct. It checks that
        the API token works, and uses Travis' lint API to validate the
        ``travis_yml`` field.

        Returns:
            dict:
            The cleaned data.
        """
        cleaned_data = super(TravisCIIntegrationConfigForm, self).clean()

        if self._errors:
            # If individual form field validation already failed, don't try to
            # do any of the below.
            return cleaned_data

        endpoint = cleaned_data['travis_endpoint']

        if (endpoint == TravisAPI.ENTERPRISE_ENDPOINT
                and not cleaned_data['travis_custom_endpoint']):
            self._errors['travis_custom_endpoint'] = self.error_class([
                _('The server URL is required when using an enterprise '
                  'Travis CI server.')
            ])
            return cleaned_data

        try:
            api = TravisAPI(cleaned_data)
        except ValueError as e:
            self._errors['travis_endpoint'] = self.error_class(
                [six.text_type(e)])

        # First try fetching the "user" endpoint. We don't actually do anything
        # with the data returned by this, but it's a good check to see if the
        # API token is correct because it requires authentication.
        try:
            api.get_user()
        except HTTPError as e:
            if e.code == 403:
                message = _('Unable to authenticate with this API token.')
            else:
                message = six.text_type(e)

            self._errors['travis_ci_token'] = self.error_class([message])

            return cleaned_data
        except URLError as e:
            self._errors['travis_endpoint'] = self.error_class([e])
            return cleaned_data

        # Use the Travis API's "lint" endpoint to verify that the provided
        # config is valid.
        try:
            lint_results = api.lint(cleaned_data['travis_yml'])

            for warning in lint_results['warnings']:
                if warning['key']:
                    if isinstance(warning['key'], list):
                        key = '.'.join(warning['key'])
                    else:
                        key = warning['key']

                    message = (_('In %s section: %s') %
                               (key, warning['message']))
                else:
                    message = warning['message']

                self._errors['travis_yml'] = self.error_class([message])
        except URLError as e:
            logger.exception(
                'Unexpected error when trying to lint Travis CI '
                'config: %s',
                e,
                request=self.request)
            self._errors['travis_endpoint'] = self.error_class(
                [_('Unable to communicate with Travis CI server.')])
        except Exception as e:
            logger.exception(
                'Unexpected error when trying to lint Travis CI '
                'config: %s',
                e,
                request=self.request)
            self._errors['travis_endpoint'] = self.error_class([e])

        return cleaned_data

    class Meta:
        fieldsets = (
            (_('What To Build'), {
                'description':
                _('You can choose which review requests to build using this '
                  'configuration by setting conditions here. At a minimum, '
                  'this should include the specific repository to use '
                  'this configuration for.'),
                'fields': ('conditions', ),
            }),
            (_('Where To Build'), {
                'description':
                _('Travis CI offers several different servers depending on '
                  'your project. Select that here and set up your API key '
                  'for the correct server.'),
                'fields': ('travis_endpoint', 'travis_custom_endpoint',
                           'travis_ci_token'),
            }),
            (_('How To Build'), {
                'description':
                _("Builds performed on the code in review requests will use "
                  "a completely separate configuration from commits which "
                  "are pushed to the GitHub repository. The configuration "
                  "listed here will be used instead of the contents of the "
                  "repository's <code>.travis.yml</code> file. Note that "
                  "this should not contain any secret environment "
                  "variables.\n"
                  "It's also recommended to create a special branch head "
                  "in the GitHub repository to use for these builds, so "
                  "they don't appear to be happening on "
                  "<code>master</code>. This branch can contain anything "
                  "(or even be empty), since the code will come from the "
                  "review request."),
                'fields': ('travis_yml', 'branch_name'),
                'classes': ('wide', )
            }),
        )
    def test_init_with_choices_instance(self):
        """Testing ConditionsField initialization with choices instance"""
        choices = ConditionChoices([BaseConditionStringChoice])
        field = ConditionsField(choices=choices)

        self.assertIs(field.choices, choices)
    def test_get_context_with_invalid_operator(self):
        """Testing ConditionsWidget.get_context with invalid operator"""
        class MyOperator1(BaseConditionOperator):
            operator_id = 'my-op-1'
            name = 'My Op 1'
            value_field = ConditionValueIntegerField()

        class MyChoice1(BaseConditionChoice):
            choice_id = 'my-choice'
            name = 'My Choice'
            operators = ConditionOperators([MyOperator1])

        choices = ConditionChoices([MyChoice1])
        field = ConditionsField(choices=choices)

        result = field.widget.get_context(
            'my_conditions',
            {
                'mode': 'any',
                'conditions': [
                    {
                        'choice': 'my-choice',
                        'op': 'invalid-op',
                        'value': 'my-value-1',
                    },
                ],
            },
            {
                'id': 'my-conditions',
            })

        rendered_rows = result['rendered_rows']
        self.assertEqual(len(rendered_rows), 1)

        rendered_row = rendered_rows[0]
        self.assertEqual(set(six.iterkeys(rendered_row)),
                         {'choice', 'operator', 'error'})
        self.assertHTMLEqual(
            rendered_row['choice'],
            '<select disabled="disabled" id="my-conditions_choice_0"'
            ' name="my_conditions_choice[0]">\n'
            '<option value="my-choice" selected="selected">'
            'My Choice</option>\n'
            '</select>'
            '<input name="my_conditions_choice[0]" type="hidden"'
            ' value="my-choice" />')

        self.assertHTMLEqual(
            rendered_row['operator'],
            '<select disabled="disabled" id="my-conditions_operator_0"'
            ' name="my_conditions_operator[0]">\n'
            '<option value="invalid-op" selected="selected">'
            'invalid-op</option>\n'
            '</select>'
            '<input name="my_conditions_operator[0]" type="hidden"'
            ' value="invalid-op" />')

        self.assertEqual(
            rendered_row['error'],
            'This operator no longer exists. You will need to '
            'delete the condition in order to make changes.')

        serialized_choices = result['serialized_choices']
        self.assertEqual(len(serialized_choices), 1)
        self.assertEqual(serialized_choices[0]['id'], 'my-choice')

        serialized_rows = result['serialized_rows']
        self.assertEqual(
            serialized_rows,
            [{
                'choiceID': 'my-choice',
                'operatorID': 'invalid-op',
                'valid': False,
                'value': 'my-value-1',
                'error': ('This operator no longer exists. You will need to '
                          'delete the condition in order to make changes.'),
            }])
Example #32
0
class MyConfigForm(IntegrationConfigForm):
    my_conditions = ConditionsField(ReviewRequestConditionChoices)
    group = forms.ModelChoiceField(queryset=Group.objects.order_by('pk'))

    def serialize_group_field(self, group):
        return group.name
Example #33
0
class ReviewBotConfigForm(IntegrationConfigForm):
    """Form for configuring Review Bot.

    This allows administrators to set up a Review Bot configuration for running
    tools against code changes based on the specified conditions.
    """

    COMMENT_ON_UNMODIFIED_CODE_DEFAULT = False
    OPEN_ISSUES_DEFAULT = True
    DROP_OLD_ISSUES_DEFAULT = True
    RUN_MANUALLY_DEFAULT = False
    MAX_COMMENTS_DEFAULT = 30

    #: When to run this configuration.
    conditions = ConditionsField(
        ReviewRequestConditionChoices,
        label=_('Conditions'),
        required=False)

    #: What to run when this configuration matches.
    tool = forms.ModelChoiceField(
        queryset=Tool.objects.filter(enabled=True),
        label=_('Tool'))

    #: Whether the tool should comment on code which hasn't been modified.
    comment_on_unmodified_code = forms.BooleanField(
        label=_('Comment on unmodified code'),
        required=COMMENT_ON_UNMODIFIED_CODE_DEFAULT)

    #: Whether this tool should open issues.
    open_issues = forms.BooleanField(
        label=_('Open issues'),
        required=False,
        initial=OPEN_ISSUES_DEFAULT)

    #: Whether new runs of this tool should drop open issues from previous
    #: runs.
    drop_old_issues = forms.BooleanField(
        label=_('Drop old issues'),
        help_text=_('When running this tool against new revisions of a '
                    'change, drop open issues from previous runs.'),
        required=False,
        initial=DROP_OLD_ISSUES_DEFAULT)

    #: Whether this tool should be run manually
    run_manually = forms.BooleanField(
        label=_('Run this tool manually'),
        required=False,
        help_text=_('Wait to run this tool until manually started. This will '
                    'add a "Run" button on the tool\'s result entry.'),
        initial=RUN_MANUALLY_DEFAULT)

    #: Maximum number of comments to make.
    max_comments = forms.IntegerField(
        label=_('Maximum comments'),
        help_text=_('The maximum number of comments to make at one time. If '
                    'the tool generates more than this number, a warning will '
                    'be shown in the review. If this is set to a large value '
                    'and a tool generates a large number of comments, the '
                    'resulting page can be very slow in some browsers.'),
        initial=MAX_COMMENTS_DEFAULT)

    def __init__(self, *args, **kwargs):
        """Initialize the form.

        Args:
            *args (tuple):
                Arguments for the form.

            **kwargs (dict):
                Keyword arguments for the form.
        """
        super(ReviewBotConfigForm, self).__init__(*args, **kwargs)

        from reviewbotext.extension import ReviewBotExtension
        extension = ReviewBotExtension.instance

        self.css_bundle_names = [extension.get_bundle_id('integration-config')]
        self.js_bundle_names = [extension.get_bundle_id('integration-config')]

    def load(self):
        """Load the form."""
        if 'tool_options' not in self.fields:
            self.fields['tool_options'] = forms.CharField(
                widget=ToolOptionsWidget(self.fields['tool'].queryset))

        # Supporting APIs for these features were added in RB 3.0.19.
        if not hasattr(StatusUpdate, 'drop_open_issues'):
            self.disabled_fields = ['drop_old_issues', 'run_manually']
            self.disabled_reasons = {
                'drop_old_issues': ugettext(
                    'Requires Review Board 3.0.19 or newer.'),
                'run_manually': ugettext(
                    'Requires Review Board 3.0.19 or newer.'),
            }
            self.fields['drop_old_issues'].initial = False
            self.fields['run_manually'].initial = False

        super(ReviewBotConfigForm, self).load()

    def serialize_tool_field(self, value):
        """Serialize the tool field.

        This takes the value from the :py:attr:`tool field <tool>` and
        converts it to a JSON-serializable format.

        Args:
            value (reviewbotext.models.Tool):
                The value to serialize.

        Returns:
            int:
            The primary key of the selected tool.
        """
        return value.pk

    def deserialize_tool_field(self, value):
        """Deserialize the tool field.

        This takes the serialized version (pks) and turns it back into a Tool
        object.

        Args:
            value (list of int):
                The serialized value.

        Returns:
            reviewbotext.models.Tool:
            The deserialized value.
        """
        try:
            return Tool.objects.get(pk=value)
        except Tool.DoesNotExist:
            raise ValidationError('Tool with pk %s does not exist' % value)

    class Meta:
        fieldsets = (
            (_('What review requests should be reviewed?'), {
                'description': _(
                    'You can choose which review requests would be reviewed '
                    'by choosing the repositories and groups to match '
                    'against.'
                ),
                'fields': ('conditions',),
            }),
            (_('What tool should be run?'), {
                'fields': ('tool',),
            }),
            (_('Tool options'), {
                'fields': ('comment_on_unmodified_code',
                           'open_issues',
                           'drop_old_issues',
                           'max_comments',
                           'tool_options',
                           'run_manually'),
            }),
        )
Example #34
0
class MyConfigForm(IntegrationConfigForm):
    my_conditions = ConditionsField(choices=ReviewRequestConditionChoices)
Example #35
0
class IDoneThisIntegrationConfigForm(IntegrationConfigForm):
    """Admin configuration form for I Done This.

    This allows an administrator to set up a configuration for posting 'done'
    entries to a given I Done This team based on the specified conditions.
    """

    conditions = ConditionsField(ReviewRequestConditionChoices,
                                 label=_('Conditions'))

    team_id = forms.CharField(
        label=_('Team ID'),
        required=True,
        help_text=_('The identifier of the team to receive posts. This can '
                    'be found at the end of the team URL, e.g. '
                    '<code>https://beta.idonethis.com/t/'
                    '<strong>123456abcdef</strong></code>'),
        widget=forms.TextInput(attrs={
            'size': 15,
        }))

    def clean_team_id(self):
        """Clean and validate the 'team_id' field.

        Returns:
            unicode:
            Team ID with leading and trailing whitespace removed.

        Raises:
            django.core.exceptions.ValidationError:
                Raised if the team ID contains any slashes.
        """
        team_id = self.cleaned_data['team_id'].strip()

        if '/' in team_id:
            raise forms.ValidationError(
                ugettext('Team ID cannot contain slashes.'))

        return team_id

    class Meta:
        fieldsets = (
            (_('What to Post'), {
                'description':
                _('You can choose which review request activity would be '
                  'posted by selecting the conditions to match.'),
                'fields': ('conditions', ),
            }),
            (_('Where to Post'), {
                'description':
                _('Posts are made to the specified I Done This team on '
                  'behalf of individual users who belong to that team. '
                  'A separate configuration is required for each team, '
                  'and multiple configurations may use the same team to '
                  'specify alternative sets of conditions.\n'
                  'To enable posting, each user has to provide their '
                  'personal I Done This API Token on their Review Board '
                  'account page.'),
                'fields': ('team_id', ),
                'classes': ('wide', )
            }),
        )