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".', })
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_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".', })
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': [], })
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)
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_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', }, ], })
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".', })
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',) }), )
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})
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, })
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.'), }])
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
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'), }), )
class MyConfigForm(IntegrationConfigForm): my_conditions = ConditionsField(choices=ReviewRequestConditionChoices)
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', ) }), )