diff options
author | softwarefactory-project-zuul[bot] <33884098+softwarefactory-project-zuul[bot]@users.noreply.github.com> | 2019-07-16 00:56:22 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-07-16 00:56:22 +0200 |
commit | b8b98b136b26e038e277e4333c62fe0d34a36ad6 (patch) | |
tree | c5de9b3312910ce85c3a29a45e993749a233cf90 | |
parent | Merge pull request #4323 from keithjgrant/4248-tooltip-permissions-list (diff) | |
parent | Ensure that the Postgres client is installed (diff) | |
download | awx-b8b98b136b26e038e277e4333c62fe0d34a36ad6.tar.xz awx-b8b98b136b26e038e277e4333c62fe0d34a36ad6.zip |
Merge pull request #4070 from jbradberry/upgrade-django-2.2
Upgrade Django to 2.2
Reviewed-by: https://github.com/softwarefactory-project-zuul[bot]
58 files changed, 293 insertions, 376 deletions
diff --git a/awx/__init__.py b/awx/__init__.py index 0963e7fc91..8583d5bbf6 100644 --- a/awx/__init__.py +++ b/awx/__init__.py @@ -25,9 +25,8 @@ import hashlib try: import django - from django.utils.encoding import force_bytes - from django.db.backends.base.schema import BaseDatabaseSchemaEditor from django.db.backends.base import schema + from django.db.backends.utils import names_digest HAS_DJANGO = True except ImportError: HAS_DJANGO = False @@ -37,30 +36,33 @@ if HAS_DJANGO is True: # This line exists to make sure we don't regress on FIPS support if we # upgrade Django; if you're upgrading Django and see this error, # update the version check below, and confirm that FIPS still works. - if django.__version__ != '1.11.20': - raise RuntimeError("Django version other than 1.11.20 detected {}. \ - Subclassing BaseDatabaseSchemaEditor is known to work for Django 1.11.20 \ - and may not work in newer Django versions.".format(django.__version__)) + # If operating in a FIPS environment, `hashlib.md5()` will raise a `ValueError`, + # but will support the `usedforsecurity` keyword on RHEL and Centos systems. + + # Keep an eye on https://code.djangoproject.com/ticket/28401 + target_version = '2.2.2' + if django.__version__ != target_version: + raise RuntimeError( + "Django version other than {target} detected: {current}. " + "Overriding `names_digest` is known to work for Django {target} " + "and may not work in other Django versions.".format(target=target_version, + current=django.__version__) + ) - - class FipsBaseDatabaseSchemaEditor(BaseDatabaseSchemaEditor): - - @classmethod - def _digest(cls, *args): + try: + names_digest('foo', 'bar', 'baz', length=8) + except ValueError: + def names_digest(*args, length): """ - Generates a 32-bit digest of a set of arguments that can be used to - shorten identifying names. + Generate a 32-bit digest of a set of arguments that can be used to shorten + identifying names. Support for use in FIPS environments. """ - try: - h = hashlib.md5() - except ValueError: - h = hashlib.md5(usedforsecurity=False) + h = hashlib.md5(usedforsecurity=False) for arg in args: - h.update(force_bytes(arg)) - return h.hexdigest()[:8] - + h.update(arg.encode()) + return h.hexdigest()[:length] - schema.BaseDatabaseSchemaEditor = FipsBaseDatabaseSchemaEditor + schema.names_digest = names_digest def find_commands(management_dir): diff --git a/awx/api/generics.py b/awx/api/generics.py index 7c17799d11..c66c9b7348 100644 --- a/awx/api/generics.py +++ b/awx/api/generics.py @@ -401,21 +401,21 @@ class ListAPIView(generics.ListAPIView, GenericAPIView): continue if getattr(field, 'related_model', None): fields.add('{}__search'.format(field.name)) - for rel in self.model._meta.related_objects: - name = rel.related_name - if isinstance(rel, OneToOneRel) and self.model._meta.verbose_name.startswith('unified'): + for related in self.model._meta.related_objects: + name = related.related_name + if isinstance(related, OneToOneRel) and self.model._meta.verbose_name.startswith('unified'): # Add underscores for polymorphic subclasses for user utility - name = rel.related_model._meta.verbose_name.replace(" ", "_") + name = related.related_model._meta.verbose_name.replace(" ", "_") if skip_related_name(name) or name.endswith('+'): continue fields.add('{}__search'.format(name)) - m2m_rel = [] - m2m_rel += self.model._meta.local_many_to_many + m2m_related = [] + m2m_related += self.model._meta.local_many_to_many if issubclass(self.model, UnifiedJobTemplate) and self.model != UnifiedJobTemplate: - m2m_rel += UnifiedJobTemplate._meta.local_many_to_many + m2m_related += UnifiedJobTemplate._meta.local_many_to_many if issubclass(self.model, UnifiedJob) and self.model != UnifiedJob: - m2m_rel += UnifiedJob._meta.local_many_to_many - for relationship in m2m_rel: + m2m_related += UnifiedJob._meta.local_many_to_many + for relationship in m2m_related: if skip_related_name(relationship.name): continue if relationship.related_model._meta.app_label != 'main': diff --git a/awx/api/permissions.py b/awx/api/permissions.py index 867388a08d..3c4de0ad27 100644 --- a/awx/api/permissions.py +++ b/awx/api/permissions.py @@ -95,7 +95,7 @@ class ModelAccessPermission(permissions.BasePermission): ''' # Don't allow anonymous users. 401, not 403, hence no raised exception. - if not request.user or request.user.is_anonymous(): + if not request.user or request.user.is_anonymous: return False # Always allow superusers diff --git a/awx/api/serializers.py b/awx/api/serializers.py index 60db5cf9f3..d336587de0 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -2560,7 +2560,7 @@ class CredentialSerializer(BaseSerializer): def validate_credential_type(self, credential_type): if self.instance and credential_type.pk != self.instance.credential_type.pk: - for rel in ( + for related_objects in ( 'ad_hoc_commands', 'insights_inventories', 'unifiedjobs', @@ -2569,7 +2569,7 @@ class CredentialSerializer(BaseSerializer): 'projectupdates', 'workflowjobnodes' ): - if getattr(self.instance, rel).count() > 0: + if getattr(self.instance, related_objects).count() > 0: raise ValidationError( _('You cannot change the credential type of the credential, as it may break the functionality' ' of the resources using it.'), @@ -4640,37 +4640,37 @@ class ActivityStreamSerializer(BaseSerializer): return "" def get_related(self, obj): - rel = {} + data = {} if obj.actor is not None: - rel['actor'] = self.reverse('api:user_detail', kwargs={'pk': obj.actor.pk}) + data['actor'] = self.reverse('api:user_detail', kwargs={'pk': obj.actor.pk}) for fk, __ in self._local_summarizable_fk_fields: if not hasattr(obj, fk): continue - m2m_list = self._get_rel(obj, fk) + m2m_list = self._get_related_objects(obj, fk) if m2m_list: - rel[fk] = [] + data[fk] = [] id_list = [] - for thisItem in m2m_list: - if getattr(thisItem, 'id', None) in id_list: + for item in m2m_list: + if getattr(item, 'id', None) in id_list: continue - id_list.append(getattr(thisItem, 'id', None)) - if hasattr(thisItem, 'get_absolute_url'): - rel_url = thisItem.get_absolute_url(self.context.get('request')) + id_list.append(getattr(item, 'id', None)) + if hasattr(item, 'get_absolute_url'): + url = item.get_absolute_url(self.context.get('request')) else: view_name = fk + '_detail' - rel_url = self.reverse('api:' + view_name, kwargs={'pk': thisItem.id}) - rel[fk].append(rel_url) + url = self.reverse('api:' + view_name, kwargs={'pk': item.id}) + data[fk].append(url) if fk == 'schedule': - rel['unified_job_template'] = thisItem.unified_job_template.get_absolute_url(self.context.get('request')) + data['unified_job_template'] = item.unified_job_template.get_absolute_url(self.context.get('request')) if obj.setting and obj.setting.get('category', None): - rel['setting'] = self.reverse( + data['setting'] = self.reverse( 'api:setting_singleton_detail', kwargs={'category_slug': obj.setting['category']} ) - return rel + return data - def _get_rel(self, obj, fk): + def _get_related_objects(self, obj, fk): related_model = ActivityStream._meta.get_field(fk).related_model related_manager = getattr(obj, fk) if issubclass(related_model, PolymorphicModel) and hasattr(obj, '_prefetched_objects_cache'): @@ -4703,7 +4703,7 @@ class ActivityStreamSerializer(BaseSerializer): try: if not hasattr(obj, fk): continue - m2m_list = self._get_rel(obj, fk) + m2m_list = self._get_related_objects(obj, fk) if m2m_list: summary_fields[fk] = [] for thisItem in m2m_list: diff --git a/awx/api/views/__init__.py b/awx/api/views/__init__.py index 092675267d..1c57c35bd8 100644 --- a/awx/api/views/__init__.py +++ b/awx/api/views/__init__.py @@ -2997,7 +2997,7 @@ class WorkflowJobTemplateNodeChildrenBaseList(EnforceParentRelationshipMixin, Su relationships = ['success_nodes', 'failure_nodes', 'always_nodes'] relationships.remove(self.relationship) qs = functools.reduce(lambda x, y: (x | y), - (Q(**{'{}__in'.format(rel): [sub.id]}) for rel in relationships)) + (Q(**{'{}__in'.format(r): [sub.id]}) for r in relationships)) if models.WorkflowJobTemplateNode.objects.filter(Q(pk=parent.id) & qs).exists(): return {"Error": _("Relationship not allowed.")} diff --git a/awx/conf/migrations/0001_initial.py b/awx/conf/migrations/0001_initial.py index f9613b15d1..22330e330c 100644 --- a/awx/conf/migrations/0001_initial.py +++ b/awx/conf/migrations/0001_initial.py @@ -21,7 +21,8 @@ class Migration(migrations.Migration): ('modified', models.DateTimeField(default=None, editable=False)), ('key', models.CharField(max_length=255)), ('value', jsonfield.fields.JSONField(null=True)), - ('user', models.ForeignKey(related_name='settings', default=None, editable=False, to=settings.AUTH_USER_MODEL, null=True)), + ('user', models.ForeignKey(related_name='settings', default=None, editable=False, + to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE, null=True)), ], options={ 'abstract': False, diff --git a/awx/main/consumers.py b/awx/main/consumers.py index aee25e8ff9..a4fcdc96a6 100644 --- a/awx/main/consumers.py +++ b/awx/main/consumers.py @@ -24,7 +24,7 @@ def ws_connect(message): headers = dict(message.content.get('headers', '')) message.reply_channel.send({"accept": True}) message.content['method'] = 'FAKE' - if message.user.is_authenticated(): + if message.user.is_authenticated: message.reply_channel.send( {"text": json.dumps({"accept": True, "user": message.user.id})} ) diff --git a/awx/main/fields.py b/awx/main/fields.py index 70ee365086..110492b173 100644 --- a/awx/main/fields.py +++ b/awx/main/fields.py @@ -11,8 +11,9 @@ from jinja2 import Environment, StrictUndefined from jinja2.exceptions import UndefinedError, TemplateSyntaxError # Django -import django +from django.contrib.postgres.fields import JSONField as upstream_JSONBField from django.core import exceptions as django_exceptions +from django.core.serializers.json import DjangoJSONEncoder from django.db.models.signals import ( post_save, post_delete, @@ -37,7 +38,6 @@ import jsonschema.exceptions # Django-JSONField from jsonfield import JSONField as upstream_JSONField -from jsonbfield.fields import JSONField as upstream_JSONBField # DRF from rest_framework import serializers @@ -76,10 +76,10 @@ class JSONField(upstream_JSONField): def db_type(self, connection): return 'text' - def from_db_value(self, value, expression, connection, context): + def from_db_value(self, value, expression, connection): if value in {'', None} and not self.null: return {} - return super(JSONField, self).from_db_value(value, expression, connection, context) + return super(JSONField, self).from_db_value(value, expression, connection) class JSONBField(upstream_JSONBField): @@ -91,12 +91,12 @@ class JSONBField(upstream_JSONBField): def get_db_prep_value(self, value, connection, prepared=False): if connection.vendor == 'sqlite': # sqlite (which we use for tests) does not support jsonb; - return json.dumps(value) + return json.dumps(value, cls=DjangoJSONEncoder) return super(JSONBField, self).get_db_prep_value( value, connection, prepared ) - def from_db_value(self, value, expression, connection, context): + def from_db_value(self, value, expression, connection): # Work around a bug in django-jsonfield # https://bitbucket.org/schinckel/django-jsonfield/issues/57/cannot-use-in-the-same-project-as-djangos if isinstance(value, str): @@ -112,14 +112,9 @@ class AutoSingleRelatedObjectDescriptor(ReverseOneToOneDescriptor): def __get__(self, instance, instance_type=None): try: - return super(AutoSingleRelatedObjectDescriptor, - self).__get__(instance, instance_type) + return super(AutoSingleRelatedObjectDescriptor, self).__get__(instance, instance_type) except self.related.related_model.DoesNotExist: obj = self.related.related_model(**{self.related.field.name: instance}) - if self.related.field.rel.parent_link: - raise NotImplementedError('not supported with polymorphic!') - for f in instance._meta.local_fields: - setattr(obj, f.name, getattr(instance, f.name)) obj.save() return obj @@ -453,21 +448,6 @@ class JSONSchemaField(JSONBField): params={'value': value}, ) - def get_db_prep_value(self, value, connection, prepared=False): - if connection.vendor == 'sqlite': - # sqlite (which we use for tests) does not support jsonb; - return json.dumps(value) - return super(JSONSchemaField, self).get_db_prep_value( - value, connection, prepared - ) - - def from_db_value(self, value, expression, connection, context): - # Work around a bug in django-jsonfield - # https://bitbucket.org/schinckel/django-jsonfield/issues/57/cannot-use-in-the-same-project-as-djangos - if isinstance(value, str): - return json.loads(value) - return value - @JSONSchemaField.format_checker.checks('vault_id') def format_vault_id(value): @@ -986,7 +966,7 @@ class OAuth2ClientSecretField(models.CharField): encrypt_value(value), connection, prepared ) - def from_db_value(self, value, expression, connection, context): + def from_db_value(self, value, expression, connection): if value and value.startswith('$encrypted$'): return decrypt_value(get_encryption_key('value', pk=None), value) return value @@ -1022,38 +1002,6 @@ class OrderedManyToManyDescriptor(ManyToManyDescriptor): '%s__position' % self.through._meta.model_name ) - def add(self, *objs): - # Django < 2 doesn't support this method on - # ManyToManyFields w/ an intermediary model - # We should be able to remove this code snippet when we - # upgrade Django. - # see: https://github.com/django/django/blob/stable/1.11.x/django/db/models/fields/related_descriptors.py#L926 - if not django.__version__.startswith('1.'): - raise RuntimeError( - 'This method is no longer necessary in Django>=2' - ) - try: - self.through._meta.auto_created = True - super(OrderedManyRelatedManager, self).add(*objs) - finally: - self.through._meta.auto_created = False - - def remove(self, *objs): - # Django < 2 doesn't support this method on - # ManyToManyFields w/ an intermediary model - # We should be able to remove this code snippet when we - # upgrade Django. - # see: https://github.com/django/django/blob/stable/1.11.x/django/db/models/fields/related_descriptors.py#L944 - if not django.__version__.startswith('1.'): - raise RuntimeError( - 'This method is no longer necessary in Django>=2' - ) - try: - self.through._meta.auto_created = True - super(OrderedManyRelatedManager, self).remove(*objs) - finally: - self.through._meta.auto_created = False - return OrderedManyRelatedManager return add_custom_queryset_to_many_related_manager( diff --git a/awx/main/middleware.py b/awx/main/middleware.py index 86bb58e33e..b1a03c9a38 100644 --- a/awx/main/middleware.py +++ b/awx/main/middleware.py @@ -73,7 +73,7 @@ class ActivityStreamMiddleware(threading.local, MiddlewareMixin): super().__init__(get_response) def process_request(self, request): - if hasattr(request, 'user') and hasattr(request.user, 'is_authenticated') and request.user.is_authenticated(): + if hasattr(request, 'user') and request.user.is_authenticated: user = request.user else: user = None diff --git a/awx/main/migrations/0001_initial.py b/awx/main/migrations/0001_initial.py index fb8a88e676..e14624911a 100644 --- a/awx/main/migrations/0001_initial.py +++ b/awx/main/migrations/0001_initial.py @@ -44,7 +44,7 @@ class Migration(migrations.Migration): ('modified', models.DateTimeField(default=None, editable=False)), ('host_name', models.CharField(default='', max_length=1024, editable=False)), ('event', models.CharField(max_length=100, choices=[('runner_on_failed', 'Host Failed'), ('runner_on_ok', 'Host OK'), ('runner_on_unreachable', 'Host Unreachable'), ('runner_on_skipped', 'Host Skipped')])), - ('event_data', jsonfield.fields.JSONField(default={}, blank=True)), + ('event_data', jsonfield.fields.JSONField(default=dict, blank=True)), ('failed', models.BooleanField(default=False, editable=False)), ('changed', models.BooleanField(default=False, editable=False)), ('counter', models.PositiveIntegerField(default=0)), @@ -62,7 +62,7 @@ class Migration(migrations.Migration): ('expires', models.DateTimeField(default=django.utils.timezone.now)), ('request_hash', models.CharField(default='', max_length=40, blank=True)), ('reason', models.CharField(default='', help_text='Reason the auth token was invalidated.', max_length=1024, blank=True)), - ('user', models.ForeignKey(related_name='auth_tokens', to=settings.AUTH_USER_MODEL)), + ('user', models.ForeignKey(related_name='auth_tokens', on_delete=models.CASCADE, to=settings.AUTH_USER_MODEL)), ], ), migrations.CreateModel( @@ -198,7 +198,7 @@ class Migration(migrations.Migration): ('created', models.DateTimeField(default=None, editable=False)), ('modified', models.DateTimeField(default=None, editable=False)), ('event', models.CharField(max_length=100, choices=[('runner_on_failed', 'Host Failed'), ('runner_on_ok', 'Host OK'), ('runner_on_error', 'Host Failure'), ('runner_on_skipped', 'Host Skipped'), ('runner_on_unreachable', 'Host Unreachable'), ('runner_on_no_hosts', 'No Hosts Remaining'), ('runner_on_async_poll', 'Host Polling'), ('runner_on_async_ok', 'Host Async OK'), ('runner_on_async_failed', 'Host Async Failure'), ('runner_on_file_diff', 'File Difference'), ('playbook_on_start', 'Playbook Started'), ('playbook_on_notify', 'Running Handlers'), ('playbook_on_no_hosts_matched', 'No Hosts Matched'), ('playbook_on_no_hosts_remaining', 'No Hosts Remaining'), ('playbook_on_task_start', 'Task Started'), ('playbook_on_vars_prompt', 'Variables Prompted'), ('playbook_on_setup', 'Gathering Facts'), ('playbook_on_import_for_host', 'internal: on Import for Host'), ('playbook_on_not_import_for_host', 'internal: on Not Import for Host'), ('playbook_on_play_start', 'Play Started'), ('playbook_on_stats', 'Playbook Complete')])), - ('event_data', jsonfield.fields.JSONField(default={}, blank=True)), + ('event_data', jsonfield.fields.JSONField(default=dict, blank=True)), ('failed', models.BooleanField(default=False, editable=False)), ('changed', models.BooleanField(default=False, editable=False)), ('host_name', models.CharField(default='', max_length=1024, editable=False)), @@ -241,7 +241,7 @@ class Migration(migrations.Migration): ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('created', models.DateTimeField(auto_now_add=True)), ('modified', models.DateTimeField(auto_now=True)), - ('instance', models.ForeignKey(to='main.Instance')), + ('instance', models.ForeignKey(on_delete=models.CASCADE, to='main.Instance')), ], ), migrations.CreateModel( @@ -287,7 +287,7 @@ class Migration(migrations.Migration): ('created', models.DateTimeField(default=None, editable=False)), ('modified', models.DateTimeField(default=None, editable=False)), ('ldap_dn', models.CharField(default='', max_length=1024)), - ('user', awx.main.fields.AutoOneToOneField(related_name='profile', editable=False, to=settings.AUTH_USER_MODEL)), + ('user', awx.main.fields.AutoOneToOneField(related_name='profile', editable=False, on_delete=models.CASCADE, to=settings.AUTH_USER_MODEL)), ], ), migrations.CreateModel( @@ -304,7 +304,7 @@ class Migration(migrations.Migration): ('dtend', models.DateTimeField(default=None, null=True, editable=False)), ('rrule', models.CharField(max_length=255)), ('next_run', models.DateTimeField(default=None, null=True, editable=False)), - ('extra_data', jsonfield.fields.JSONField(default={}, blank=True)), + ('extra_data', jsonfield.fields.JSONField(default=dict, blank=True)), ('created_by', models.ForeignKey(related_name="{u'class': 'schedule', u'app_label': 'main'}(class)s_created+", on_delete=django.db.models.deletion.SET_NULL, default=None, editable=False, to=settings.AUTH_USER_MODEL, null=True)), ('modified_by', models.ForeignKey(related_name="{u'class': 'schedule', u'app_label': 'main'}(class)s_modified+", on_delete=django.db.models.deletion.SET_NULL, default=None, editable=False, to=settings.AUTH_USER_MODEL, null=True)), ('tags', taggit.managers.TaggableManager(to='taggit.Tag', through='taggit.TaggedItem', blank=True, help_text='A comma-separated list of tags.', verbose_name='Tags')), @@ -343,7 +343,7 @@ class Migration(migrations.Migration): ('name', models.CharField(max_length=512)), ('old_pk', models.PositiveIntegerField(default=None, null=True, editable=False)), ('launch_type', models.CharField(default='manual', max_length=20, editable=False, choices=[('manual', 'Manual'), ('relaunch', 'Relaunch'), ('callback', 'Callback'), ('scheduled', 'Scheduled'), ('dependency', 'Dependency')])), - ('cancel_flag', models.BooleanField(default=False, editable=False)), + ('cancel_flag', models.BooleanField(blank=True, default=False, editable=False)), ('status', models.CharField(default='new', max_length=20, editable=False, choices=[('new', 'New'), ('pending', 'Pending'), ('waiting', 'Waiting'), ('running', 'Running'), ('successful', 'Successful'), ('failed', 'Failed'), ('error', 'Error'), ('canceled', 'Canceled')])), ('failed', models.BooleanField(default=False, editable=False)), ('started', models.DateTimeField(default=None, null=True, editable=False)), @@ -351,7 +351,7 @@ class Migration(migrations.Migration): ('elapsed', models.DecimalField(editable=False, max_digits=12, decimal_places=3)), ('job_args', models.TextField(default='', editable=False, blank=True)), ('job_cwd', models.CharField(default='', max_length=1024, editable=False, blank=True)), - ('job_env', jsonfield.fields.JSONField(default={}, editable=False, blank=True)), + ('job_env', jsonfield.fields.JSONField(default=dict, editable=False, blank=True)), ('job_explanation', models.TextField(default='', editable=False, blank=True)), ('start_args', models.TextField(default='', editable=False, blank=True)), ('result_stdout_text', models.TextField(default='', editable=False, blank=True)), @@ -380,7 +380,7 @@ class Migration(migrations.Migration): migrations.CreateModel( name='AdHocCommand', fields=[ - ('unifiedjob_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='main.UnifiedJob')), + ('unifiedjob_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, on_delete=django.db.models.deletion.CASCADE, serialize=False, to='main.UnifiedJob')), ('job_type', models.CharField(default='run', max_length=64, choices=[('run', 'Run'), ('check', 'Check')])), ('limit', models.CharField(default='', max_length=1024, blank=True)), ('module_name', models.CharField(default='', max_length=1024, blank=True)), @@ -394,7 +394,7 @@ class Migration(migrations.Migration): migrations.CreateModel( name='InventorySource', fields=[ - ('unifiedjobtemplate_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='main.UnifiedJobTemplate')), + ('unifiedjobtemplate_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, on_delete=django.db.models.deletion.CASCADE, serialize=False, to='main.UnifiedJobTemplate')), ('source', models.CharField(default='', max_length=32, blank=True, choices=[('', 'Manual'), ('file', 'Local File, Directory or Script'), ('rax', 'Rackspace Cloud Servers'), ('ec2', 'Amazon EC2'), ('gce', 'Google Compute Engine'), ('azure', 'Microsoft Azure'), ('vmware', 'VMware vCenter'), ('openstack', 'OpenStack'), ('custom', 'Custom Script')])), ('source_path', models.CharField(default='', max_length=1024, editable=False, blank=True)), ('source_vars', models.TextField(default='', help_text='Inventory source variables in YAML or JSON format.', blank=True)), @@ -411,7 +411,7 @@ class Migration(migrations.Migration): migrations.CreateModel( name='InventoryUpdate', fields=[ - ('unifiedjob_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='main.UnifiedJob')), + ('unifiedjob_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, on_delete=django.db.models.deletion.CASCADE, serialize=False, to='main.UnifiedJob')), ('source', models.CharField(default='', max_length=32, blank=True, choices=[('', 'Manual'), ('file', 'Local File, Directory or Script'), ('rax', 'Rackspace Cloud Servers'), ('ec2', 'Amazon EC2'), ('gce', 'Google Compute Engine'), ('azure', 'Microsoft Azure'), ('vmware', 'VMware vCenter'), ('openstack', 'OpenStack'), ('custom', 'Custom Script')])), ('source_path', models.CharField(default='', max_length=1024, editable=False, blank=True)), ('source_vars', models.TextField(default='', help_text='Inventory source variables in YAML or JSON format.', blank=True)), @@ -427,7 +427,7 @@ class Migration(migrations.Migration): migrations.CreateModel( name='Job', fields=[ - ('unifiedjob_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='main.UnifiedJob')), + ('unifiedjob_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, on_delete=django.db.models.deletion.CASCADE, serialize=False, to='main.UnifiedJob')), ('job_type', models.CharField(default='run', max_length=64, choices=[('run', 'Run'), ('check', 'Check'), ('scan', 'Scan')])), ('playbook', models.CharField(default='', max_length=1024, blank=True)), ('forks', models.PositiveIntegerField(default=0, blank=True)), @@ -435,7 +435,7 @@ class Migration(migrations.Migration): ('verbosity', models.PositiveIntegerField(default=0, blank=True, choices=[(0, '0 (Normal)'), (1, '1 (Verbose)'), (2, '2 (More Verbose)'), (3, '3 (Debug)'), (4, '4 (Connection Debug)'), (5, '5 (WinRM Debug)')])), ('extra_vars', models.TextField(default='', blank=True)), ('job_tags', models.CharField(default='', max_length=1024, blank=True)), - ('force_handlers', models.BooleanField(default=False)), + ('force_handlers', models.BooleanField(blank=True, default=False)), ('skip_tags', models.CharField(default='', max_length=1024, blank=True)), ('start_at_task', models.CharField(default='', max_length=1024, blank=True)), ('become_enabled', models.BooleanField(default=False)), @@ -448,7 +448,7 @@ class Migration(migrations.Migration): migrations.CreateModel( name='JobTemplate', fields=[ - ('unifiedjobtemplate_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='main.UnifiedJobTemplate')), + ('unifiedjobtemplate_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, on_delete=django.db.models.deletion.CASCADE, serialize=False, to='main.UnifiedJobTemplate')), ('job_type', models.CharField(default='run', max_length=64, choices=[('run', 'Run'), ('check', 'Check'), ('scan', 'Scan')])), ('playbook', models.CharField(default='', max_length=1024, blank=True)), ('forks', models.PositiveIntegerField(default=0, blank=True)), @@ -456,14 +456,14 @@ class Migration(migrations.Migration): ('verbosity', models.PositiveIntegerField(default=0, blank=True, choices=[(0, '0 (Normal)'), (1, '1 (Verbose)'), (2, '2 (More Verbose)'), (3, '3 (Debug)'), (4, '4 (Connection Debug)'), (5, '5 (WinRM Debug)')])), ('extra_vars', models.TextField(default='', blank=True)), ('job_tags', models.CharField(default='', max_length=1024, blank=True)), - ('force_handlers', models.BooleanField(default=False)), + ('force_handlers', models.BooleanField(blank=True, default=False)), ('skip_tags', models.CharField(default='', max_length=1024, blank=True)), ('start_at_task', models.CharField(default='', max_length=1024, blank=True)), ('become_enabled', models.BooleanField(default=False)), ('host_config_key', models.CharField(default='', max_length=1024, blank=True)), ('ask_variables_on_launch', models.BooleanField(default=False)), ('survey_enabled', models.BooleanField(default=False)), - ('survey_spec', jsonfield.fields.JSONField(default={}, blank=True)), + ('survey_spec', jsonfield.fields.JSONField(default=dict, blank=True)), ], options={ 'ordering': ('name',), @@ -473,7 +473,7 @@ class Migration(migrations.Migration): migrations.CreateModel( name='Project', fields=[ - ('unifiedjobtemplate_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='main.UnifiedJobTemplate')), + ('unifiedjobtemplate_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, on_delete=django.db.models.deletion.CASCADE, serialize=False, to='main.UnifiedJobTemplate')), ('local_path', models.CharField(help_text='Local path (relative to PROJECTS_ROOT) containing playbooks and related files for this project.', max_length=1024, blank=True)), ('scm_type', models.CharField(default='', max_length=8, verbose_name='SCM Type', blank=True, choices=[('', 'Manual'), ('git', 'Git'), ('hg', 'Mercurial'), ('svn', 'Subversion')])), ('scm_url', models.CharField(default='', max_length=1024, verbose_name='SCM URL', blank=True)), @@ -492,7 +492,7 @@ class Migration(migrations.Migration): migrations.CreateModel( name='ProjectUpdate', fields=[ - ('unifiedjob_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='main.UnifiedJob')), + ('unifiedjob_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, on_delete=django.db.models.deletion.CASCADE, serialize=False, to='main.UnifiedJob')), ('local_path', models.CharField(help_text='Local path (relative to PROJECTS_ROOT) containing playbooks and related files for this project.', max_length=1024, blank=True)), ('scm_type', models.CharField(default='', max_length=8, verbose_name='SCM Type', blank=True, choices=[('', 'Manual'), ('git', 'Git'), ('hg', 'Mercurial'), ('svn', 'Subversion')])), ('scm_url', models.CharField(default='', max_length=1024, verbose_name='SCM URL', blank=True)), @@ -505,7 +505,7 @@ class Migration(migrations.Migration): migrations.CreateModel( name='SystemJob', fields=[ - ('unifiedjob_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='main.UnifiedJob')), + ('unifiedjob_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, on_delete=django.db.models.deletion.CASCADE, serialize=False, to='main.UnifiedJob')), ('job_type', models.CharField(default='', max_length=32, blank=True, choices=[('cleanup_jobs', 'Remove jobs older than a certain number of days'), ('cleanup_activitystream', 'Remove activity stream entries older than a certain number of days'), ('cleanup_deleted', 'Purge previously deleted items from the database'), ('cleanup_facts', 'Purge and/or reduce the granularity of system tracking data')])), ('extra_vars', models.TextField(default='', blank=True)), ], @@ -517,7 +517,7 @@ class Migration(migrations.Migration): migrations.CreateModel( name='SystemJobTemplate', fields=[ - ('unifiedjobtemplate_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='main.UnifiedJobTemplate')), + ('unifiedjobtemplate_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, on_delete=django.db.models.deletion.CASCADE, serialize=False, to='main.UnifiedJobTemplate')), ('job_type', models.CharField(default='', max_length=32, blank=True, choices=[('cleanup_jobs', 'Remove jobs older than a certain number of days'), ('cleanup_activitystream', 'Remove activity stream entries older than a certain number of days'), ('cleanup_deleted', 'Purge previously deleted items from the database'), ('cleanup_facts', 'Purge and/or reduce the granularity of system tracking data')])), ], bases=('main.unifiedjobtemplate', models.Model), @@ -550,7 +550,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='unifiedjobtemplate', name='polymorphic_ctype', - field=models.ForeignKey(related_name='polymorphic_main.unifiedjobtemplate_set+', editable=False, to='contenttypes.ContentType', null=True), + field=models.ForeignKey(related_name='polymorphic_main.unifiedjobtemplate_set+', editable=False, on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType', null=True), ), migrations.AddField( model_name='unifiedjobtemplate', @@ -575,7 +575,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='unifiedjob', name='polymorphic_ctype', - field=models.ForeignKey(related_name='polymorphic_main.unifiedjob_set+', editable=False, to='contenttypes.ContentType', null=True), + field=models.ForeignKey(related_name='polymorphic_main.unifiedjob_set+', editable=False, on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType', null=True), ), migrations.AddField( model_name='unifiedjob', @@ -595,7 +595,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='schedule', name='unified_job_template', - field=models.ForeignKey(related_name='schedules', to='main.UnifiedJobTemplate'), + field=models.ForeignKey(related_name='schedules', on_delete=django.db.models.deletion.CASCADE, to='main.UnifiedJobTemplate'), ), migrations.AddField( model_name='permission', @@ -610,12 +610,12 @@ class Migration(migrations.Migration): migrations.AddField( model_name='joborigin', name='unified_job', - field=models.OneToOneField(related_name='job_origin', to='main.UnifiedJob'), + field=models.OneToOneField(related_name='job_origin', on_delete=django.db.models.deletion.CASCADE, to='main.UnifiedJob'), ), migrations.AddField( model_name='inventory', name='organization', - field=models.ForeignKey(related_name='inventories', to='main.Organization', help_text='Organization containing this inventory.'), + field=models.ForeignKey(related_name='inventories', on_delete=django.db.models.deletion.CASCADE, to='main.Organization', help_text='Organization containing this inventory.'), ), migrations.AddField( model_name='inventory', @@ -625,7 +625,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='host', name='inventory', - field=models.ForeignKey(related_name='hosts', to='main.Inventory'), + field=models.ForeignKey(related_name='hosts', on_delete=django.db.models.deletion.CASCADE, to='main.Inventory'), ), migrations.AddField( model_name='host', @@ -650,7 +650,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='group', name='inventory', - field=models.ForeignKey(related_name='groups', to='main.Inventory'), + field=models.ForeignKey(related_name='groups', on_delete=django.db.models.deletion.CASCADE, to='main.Inventory'), ), migrations.AddField( model_name='group', @@ -680,12 +680,12 @@ class Migration(migrations.Migration): migrations.AddField( model_name='credential', name='team', - field=models.ForeignKey(related_name='credentials', default=None, blank=True, to='main.Team', null=True), + field=models.ForeignKey(related_name='credentials', on_delete=django.db.models.deletion.SET_NULL, default=None, blank=True, to='main.Team', null=True), ), migrations.AddField( model_name='credential', name='user', - field=models.ForeignKey(related_name='credentials', default=None, blank=True, to=settings.AUTH_USER_MODEL, null=True), + field=models.ForeignKey(related_name='credentials', on_delete=django.db.models.deletion.SET_NULL, default=None, blank=True, to=settings.AUTH_USER_MODEL, null=True), ), migrations.AddField( model_name='adhoccommandevent', @@ -774,7 +774,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='projectupdate', name='project', - field=models.ForeignKey(related_name='project_updates', editable=False, to='main.Project'), + field=models.ForeignKey(related_name='project_updates', on_delete=django.db.models.deletion.CASCADE, editable=False, to='main.Project'), ), migrations.AddField( model_name='project', @@ -814,12 +814,12 @@ class Migration(migrations.Migration): migrations.AddField( model_name='jobhostsummary', name='job', - field=models.ForeignKey(related_name='job_host_summaries', editable=False, to='main.Job'), + field=models.ForeignKey(related_name='job_host_summaries', on_delete=django.db.models.deletion.CASCADE, editable=False, to='main.Job'), ), migrations.AddField( model_name='jobevent', name='job', - field=models.ForeignKey(related_name='job_events', editable=False, to='main.Job'), + field=models.ForeignKey(related_name='job_events', on_delete=django.db.models.deletion.CASCADE, editable=False, to='main.Job'), ), migrations.AddField( model_name='job', @@ -859,7 +859,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='inventoryupdate', name='inventory_source', - field=models.ForeignKey(related_name='inventory_updates', editable=False, to='main.InventorySource'), + field=models.ForeignKey(related_name='inventory_updates', on_delete=django.db.models.deletion.CASCADE, editable=False, to='main.InventorySource'), ), migrations.AddField( model_name='inventoryupdate', @@ -874,12 +874,12 @@ class Migration(migrations.Migration): migrations.AddField( model_name='inventorysource', name='group', - field=awx.main.fields.AutoOneToOneField(related_name='inventory_source', null=True, default=None, editable=False, to='main.Group'), + field=awx.main.fields.AutoOneToOneField(related_name='inventory_source', on_delete=django.db.models.deletion.SET_NULL, null=True, default=None, editable=False, to='main.Group'), ), migrations.AddField( model_name='inventorysource', name='inventory', - field=models.ForeignKey(related_name='inventory_sources', default=None, editable=False, to='main.Inventory', null=True), + field=models.ForeignKey(related_name='inventory_sources', on_delete=django.db.models.deletion.SET_NULL, default=None, editable=False, to='main.Inventory', null=True), ), migrations.AddField( model_name='inventorysource', @@ -916,7 +916,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='adhoccommandevent', name='ad_hoc_command', - field=models.ForeignKey(related_name='ad_hoc_command_events', editable=False, to='main.AdHocCommand'), + field=models.ForeignKey(related_name='ad_hoc_command_events', on_delete=django.db.models.deletion.CASCADE, editable=False, to='main.AdHocCommand'), ), migrations.AddField( model_name='adhoccommand', diff --git a/awx/main/migrations/0002_squashed_v300_release.py b/awx/main/migrations/0002_squashed_v300_release.py index 11190a20da..89fce679ea 100644 --- a/awx/main/migrations/0002_squashed_v300_release.py +++ b/awx/main/migrations/0002_squashed_v300_release.py @@ -13,7 +13,6 @@ from django.conf import settings from django.utils.timezone import now import jsonfield.fields -import jsonbfield.fields import taggit.managers @@ -144,7 +143,7 @@ class Migration(migrations.Migration): ('category', models.CharField(max_length=128)), ('value', models.TextField(blank=True)), ('value_type', models.CharField(max_length=12, choices=[('string', 'String'), ('int', 'Integer'), ('float', 'Decimal'), ('json', 'JSON'), ('bool', 'Boolean'), ('password', 'Password'), ('list', 'List')])), - ('user', models.ForeignKey(related_name='settings', default=None, editable=False, to=settings.AUTH_USER_MODEL, null=True)), + ('user', models.ForeignKey(related_name='settings', default=None, editable=False, to=settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True)), ], ), # Notification changes @@ -185,7 +184,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='notification', name='notification_template', - field=models.ForeignKey(related_name='notifications', editable=False, to='main.NotificationTemplate'), + field=models.ForeignKey(related_name='notifications', editable=False, on_delete=models.CASCADE, to='main.NotificationTemplate'), ), migrations.AddField( model_name='activitystream', @@ -239,8 +238,8 @@ class Migration(migrations.Migration): ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('timestamp', models.DateTimeField(default=None, help_text='Date and time of the corresponding fact scan gathering time.', editable=False)), ('module', models.CharField(max_length=128)), - ('facts', jsonbfield.fields.JSONField(default={}, help_text='Arbitrary JSON structure of module facts captured at timestamp for a single host.', blank=True)), - ('host', models.ForeignKey(related_name='facts', to='main.Host', help_text='Host for the facts that the fact scan captured.')), + ('facts', awx.main.fields.JSONBField(default=dict, help_text='Arbitrary JSON structure of module facts captured at timestamp for a single host.', blank=True)), + ('host', models.ForeignKey(related_name='facts', to='main.Host', on_delete=models.CASCADE, help_text='Host for the facts that the fact scan captured.')), ], ), migrations.AlterIndexTogether( @@ -318,7 +317,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='project', name='organization', - field=models.ForeignKey(related_name='projects', to='main.Organization', blank=True, null=True), + field=models.ForeignKey(related_name='projects', to='main.Organization', on_delete=models.CASCADE, blank=True, null=True), ), migrations.AlterField( model_name='team', @@ -367,7 +366,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='credential', name='organization', - field=models.ForeignKey(related_name='credentials', default=None, blank=True, to='main.Organization', null=True), + field=models.ForeignKey(related_name='credentials', on_delete=models.CASCADE, default=None, blank=True, to='main.Organization', null=True), ), # @@ -382,7 +381,7 @@ class Migration(migrations.Migration): ('members', models.ManyToManyField(related_name='roles', to=settings.AUTH_USER_MODEL)), ('parents', models.ManyToManyField(related_name='children', to='main.Role')), ('implicit_parents', models.TextField(default='[]')), - ('content_type', models.ForeignKey(default=None, to='contenttypes.ContentType', null=True)), + ('content_type', models.ForeignKey(default=None, to='contenttypes.ContentType', on_delete=models.CASCADE, null=True)), ('object_id', models.PositiveIntegerField(default=None, null=True)), ], @@ -398,8 +397,8 @@ class Migration(migrations.Migration): ('role_field', models.TextField()), ('content_type_id', models.PositiveIntegerField()), ('object_id', models.PositiveIntegerField()), - ('ancestor', models.ForeignKey(related_name='+', to='main.Role')), - ('descendent', models.ForeignKey(related_name='+', to='main.Role')), + ('ancestor', models.ForeignKey(on_delete=models.CASCADE, related_name='+', to='main.Role')), + ('descendent', models.ForeignKey(on_delete=models.CASCADE, related_name='+', to='main.Role')), ], options={ 'db_table': 'main_rbac_role_ancestors', @@ -569,7 +568,7 @@ class Migration(migrations.Migration): ('name', models.CharField(max_length=512)), ('created_by', models.ForeignKey(related_name="{u'class': 'label', u'app_label': 'main'}(class)s_created+", on_delete=django.db.models.deletion.SET_NULL, default=None, editable=False, to=settings.AUTH_USER_MODEL, null=True)), ('modified_by', models.ForeignKey(related_name="{u'class': 'label', u'app_label': 'main'}(class)s_modified+", on_delete=django.db.models.deletion.SET_NULL, default=None, editable=False, to=settings.AUTH_USER_MODEL, null=True)), - ('organization', models.ForeignKey(related_name='labels', to='main.Organization', help_text='Organization this label belongs to.')), + ('organization', models.ForeignKey(related_name='labels', on_delete=django.db.models.deletion.CASCADE, to='main.Organization', help_text='Organization this label belongs to.')), ('tags', taggit.managers.TaggableManager(to='taggit.Tag', through='taggit.TaggedItem', blank=True, help_text='A comma-separated list of tags.', verbose_name='Tags')), ], options={ @@ -599,12 +598,12 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='label', name='organization', - field=models.ForeignKey(related_name='labels', on_delete=django.db.models.deletion.SET_NULL, default=None, blank=True, to='main.Organization', help_text='Organization this label belongs to.', null=True), + field=models.ForeignKey(related_name='labels', on_delete=django.db.models.deletion.CASCADE, default=None, blank=True, to='main.Organization', help_text='Organization this label belongs to.', null=True), ), migrations.AlterField( model_name='label', name='organization', - field=models.ForeignKey(related_name='labels', to='main.Organization', help_text='Organization this label belongs to.'), + field=models.ForeignKey(related_name='labels', on_delete=django.db.models.deletion.CASCADE, to='main.Organization', help_text='Organization this label belongs to.'), ), # InventorySource Credential migrations.AddField( @@ -630,12 +629,12 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='credential', name='deprecated_team', - field=models.ForeignKey(related_name='deprecated_credentials', default=None, blank=True, to='main.Team', null=True), + field=models.ForeignKey(related_name='deprecated_credentials', on_delete=django.db.models.deletion.SET_NULL, default=None, blank=True, to='main.Team', null=True), ), migrations.AlterField( model_name='credential', name='deprecated_user', - field=models.ForeignKey(related_name='deprecated_credentials', default=None, blank=True, to=settings.AUTH_USER_MODEL, null=True), + field=models.ForeignKey(related_name='deprecated_credentials', on_delete=django.db.models.deletion.SET_NULL, default=None, blank=True, to=settings.AUTH_USER_MODEL, null=True), ), migrations.AlterField( model_name='credential', diff --git a/awx/main/migrations/0003_squashed_v300_v303_updates.py b/awx/main/migrations/0003_squashed_v300_v303_updates.py index 3c3680a4e7..d58ffc2dfe 100644 --- a/awx/main/migrations/0003_squashed_v300_v303_updates.py +++ b/awx/main/migrations/0003_squashed_v300_v303_updates.py @@ -116,7 +116,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='team', name='organization', - field=models.ForeignKey(related_name='teams', to='main.Organization'), + field=models.ForeignKey(related_name='teams', on_delete=models.CASCADE, to='main.Organization'), preserve_default=False, ), ] + _squashed.operations(SQUASHED_30, applied=True) diff --git a/awx/main/migrations/0004_squashed_v310_release.py b/awx/main/migrations/0004_squashed_v310_release.py index 965b5184d7..88a33d146c 100644 --- a/awx/main/migrations/0004_squashed_v310_release.py +++ b/awx/main/migrations/0004_squashed_v310_release.py @@ -74,7 +74,7 @@ class Migration(migrations.Migration): migrations.CreateModel( name='WorkflowJob', fields=[ - ('unifiedjob_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='main.UnifiedJob')), + ('unifiedjob_ptr', models.OneToOneField(parent_link=True, auto_created=True, on_delete=models.CASCADE, primary_key=True, serialize=False, to='main.UnifiedJob')), ('extra_vars', models.TextField(default='', blank=True)), ], options={ @@ -100,7 +100,7 @@ class Migration(migrations.Migration): migrations.CreateModel( name='WorkflowJobTemplate', fields=[ - ('unifiedjobtemplate_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='main.UnifiedJobTemplate')), + ('unifiedjobtemplate_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, on_delete=models.CASCADE, serialize=False, to='main.UnifiedJobTemplate')), ('extra_vars', models.TextField(default='', blank=True)), ('admin_role', awx.main.fields.ImplicitRoleField(related_name='+', parent_role='singleton:system_administrator', to='main.Role', null='True')), ], @@ -116,7 +116,7 @@ class Migration(migrations.Migration): ('failure_nodes', models.ManyToManyField(related_name='workflowjobtemplatenodes_failure', to='main.WorkflowJobTemplateNode', blank=True)), ('success_nodes', models.ManyToManyField(related_name='workflowjobtemplatenodes_success', to='main.WorkflowJobTemplateNode', blank=True)), ('unified_job_template', models.ForeignKey(related_name='workflowjobtemplatenodes', on_delete=django.db.models.deletion.SET_NULL, default=None, blank=True, to='main.UnifiedJobTemplate', null=True)), - ('workflow_job_template', models.ForeignKey(related_name='workflow_job_template_nodes', default=None, blank=True, to='main.WorkflowJobTemplate', null=True)), + ('workflow_job_template', models.ForeignKey(related_name='workflow_job_template_nodes', on_delete=models.SET_NULL, default=None, blank=True, to='main.WorkflowJobTemplate', null=True)), ], options={ 'abstract': False, @@ -161,7 +161,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='workflowjobnode', name='char_prompts', - field=jsonfield.fields.JSONField(default={}, blank=True), + field=jsonfield.fields.JSONField(default=dict, blank=True), ), migrations.AddField( model_name='workflowjobnode', @@ -191,7 +191,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='workflowjobtemplatenode', name='char_prompts', - field=jsonfield.fields.JSONField(default={}, blank=True), + field=jsonfield.fields.JSONField(default=dict, blank=True), ), migrations.AddField( model_name='workflowjobtemplatenode', @@ -211,7 +211,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='workflowjobnode', name='workflow_job', - field=models.ForeignKey(related_name='workflow_job_nodes', default=None, blank=True, to='main.WorkflowJob', null=True), + field=models.ForeignKey(related_name='workflow_job_nodes', on_delete=django.db.models.deletion.CASCADE, default=None, blank=True, to='main.WorkflowJob', null=True), ), migrations.AlterField( model_name='workflowjobtemplate', @@ -227,12 +227,12 @@ class Migration(migrations.Migration): migrations.AddField( model_name='job', name='artifacts', - field=jsonfield.fields.JSONField(default={}, editable=False, blank=True), + field=jsonfield.fields.JSONField(default=dict, editable=False, blank=True), ), migrations.AddField( model_name='workflowjobnode', name='ancestor_artifacts', - field=jsonfield.fields.JSONField(default={}, editable=False, blank=True), + field=jsonfield.fields.JSONField(default=dict, editable=False, blank=True), ), # Job timeout settings migrations.AddField( @@ -397,7 +397,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='workflowjob', name='survey_passwords', - field=jsonfield.fields.JSONField(default={}, editable=False, blank=True), + field=jsonfield.fields.JSONField(default=dict, editable=False, blank=True), ), migrations.AddField( model_name='workflowjobtemplate', @@ -407,33 +407,33 @@ class Migration(migrations.Migration): migrations.AddField( model_name='workflowjobtemplate', name='survey_spec', - field=jsonfield.fields.JSONField(default={}, blank=True), + field=jsonfield.fields.JSONField(default=dict, blank=True), ), # JSON field changes migrations.AlterField( model_name='adhoccommandevent', name='event_data', - field=awx.main.fields.JSONField(default={}, blank=True), + field=awx.main.fields.JSONField(default=dict, blank=True), ), migrations.AlterField( model_name='job', name='artifacts', - field=awx.main.fields.JSONField(default={}, editable=False, blank=True), + field=awx.main.fields.JSONField(default=dict, editable=False, blank=True), ), migrations.AlterField( model_name='job', name='survey_passwords', - field=awx.main.fields.JSONField(default={}, editable=False, blank=True), + field=awx.main.fields.JSONField(default=dict, editable=False, blank=True), ), migrations.AlterField( model_name='jobevent', name='event_data', - field=awx.main.fields.JSONField(default={}, blank=True), + field=awx.main.fields.JSONField(default=dict, blank=True), ), migrations.AlterField( model_name='jobtemplate', name='survey_spec', - field=awx.main.fields.JSONField(default={}, blank=True), + field=awx.main.fields.JSONField(default=dict, blank=True), ), migrations.AlterField( model_name='notification', @@ -453,37 +453,37 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='schedule', name='extra_data', - field=awx.main.fields.JSONField(default={}, blank=True), + field=awx.main.fields.JSONField(default=dict, blank=True), ), migrations.AlterField( model_name='unifiedjob', name='job_env', - field=awx.main.fields.JSONField(default={}, editable=False, blank=True), + field=awx.main.fields.JSONField(default=dict, editable=False, blank=True), ), migrations.AlterField( model_name='workflowjob', name='survey_passwords', - field=awx.main.fields.JSONField(default={}, editable=False, blank=True), + field=awx.main.fields.JSONField(default=dict, editable=False, blank=True), ), migrations.AlterField( model_name='workflowjobnode', name='ancestor_artifacts', - field=awx.main.fields.JSONField(default={}, editable=False, blank=True), + field=awx.main.fields.JSONField(default=dict, editable=False, blank=True), ), migrations.AlterField( model_name='workflowjobnode', name='char_prompts', - field=awx.main.fields.JSONField(default={}, blank=True), + field=awx.main.fields.JSONField(default=dict, blank=True), ), migrations.AlterField( model_name='workflowjobtemplate', name='survey_spec', - field=awx.main.fields.JSONField(default={}, blank=True), + field=awx.main.fields.JSONField(default=dict, blank=True), ), migrations.AlterField( model_name='workflowjobtemplatenode', name='char_prompts', - field=awx.main.fields.JSONField(default={}, blank=True), + field=awx.main.fields.JSONField(default=dict, blank=True), ), # Job Project Update migrations.AddField( diff --git a/awx/main/migrations/0006_v320_release.py b/awx/main/migrations/0006_v320_release.py index cda08b98c3..d5712f8e0a 100644 --- a/awx/main/migrations/0006_v320_release.py +++ b/awx/main/migrations/0006_v320_release.py @@ -55,12 +55,12 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='inventorysource', name='deprecated_group', - field=models.OneToOneField(related_name='deprecated_inventory_source', null=True, default=None, to='main.Group'), + field=models.OneToOneField(related_name='deprecated_inventory_source', on_delete=models.CASCADE, null=True, default=None, to='main.Group'), ), migrations.AlterField( model_name='inventorysource', name='inventory', - field=models.ForeignKey(related_name='inventory_sources', default=None, to='main.Inventory', null=True), + field=models.ForeignKey(related_name='inventory_sources', default=None, to='main.Inventory', on_delete=models.CASCADE, null=True), ), # Smart Inventory @@ -78,13 +78,13 @@ class Migration(migrations.Migration): name='SmartInventoryMembership', fields=[ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('host', models.ForeignKey(related_name='+', to='main.Host')), + ('host', models.ForeignKey(related_name='+', on_delete=models.CASCADE, to='main.Host')), ], ), migrations.AddField( model_name='smartinventorymembership', name='inventory', - field=models.ForeignKey(related_name='+', to='main.Inventory'), + field=models.ForeignKey(on_delete=models.CASCADE, related_name='+', to='main.Inventory'), ), migrations.AddField( model_name='host', @@ -105,19 +105,19 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='inventory', name='organization', - field=models.ForeignKey(related_name='inventories', on_delete=models.deletion.SET_NULL, to='main.Organization', help_text='Organization containing this inventory.', null=True), + field=models.ForeignKey(related_name='inventories', on_delete=models.SET_NULL, to='main.Organization', help_text='Organization containing this inventory.', null=True), ), # Facts migrations.AlterField( model_name='fact', name='facts', - field=awx.main.fields.JSONBField(default={}, help_text='Arbitrary JSON structure of module facts captured at timestamp for a single host.', blank=True), + field=awx.main.fields.JSONBField(default=dict, help_text='Arbitrary JSON structure of module facts captured at timestamp for a single host.', blank=True), ), migrations.AddField( model_name='host', name='ansible_facts', - field=awx.main.fields.JSONBField(default={}, help_text='Arbitrary JSON structure of most recent ansible_facts, per-host.', blank=True), + field=awx.main.fields.JSONBField(default=dict, help_text='Arbitrary JSON structure of most recent ansible_facts, per-host.', blank=True), ), migrations.AddField( model_name='host', @@ -148,12 +148,12 @@ class Migration(migrations.Migration): migrations.AddField( model_name='inventorysource', name='source_project', - field=models.ForeignKey(related_name='scm_inventory_sources', default=None, blank=True, to='main.Project', help_text='Project containing inventory file used as source.', null=True), + field=models.ForeignKey(related_name='scm_inventory_sources', on_delete=models.CASCADE, default=None, blank=True, to='main.Project', help_text='Project containing inventory file used as source.', null=True), ), migrations.AddField( model_name='inventoryupdate', name='source_project_update', - field=models.ForeignKey(related_name='scm_inventory_updates', default=None, blank=True, to='main.ProjectUpdate', help_text='Inventory files from this Project Update were used for the inventory update.', null=True), + field=models.ForeignKey(related_name='scm_inventory_updates', on_delete=models.CASCADE, default=None, blank=True, to='main.ProjectUpdate', help_text='Inventory files from this Project Update were used for the inventory update.', null=True), ), migrations.AddField( model_name='project', @@ -200,7 +200,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='notificationtemplate', name='organization', - field=models.ForeignKey(related_name='notification_templates', to='main.Organization', null=True), + field=models.ForeignKey(related_name='notification_templates', on_delete=models.CASCADE, to='main.Organization', null=True), ), migrations.AlterUniqueTogether( name='notificationtemplate', @@ -312,7 +312,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='inventory', name='insights_credential', - field=models.ForeignKey(related_name='insights_inventories', on_delete=models.deletion.SET_NULL, default=None, blank=True, to='main.Credential', help_text='Credentials to be used by hosts belonging to this inventory when accessing Red Hat Insights API.', null=True), + field=models.ForeignKey(related_name='insights_inventories', on_delete=models.SET_NULL, default=None, blank=True, to='main.Credential', help_text='Credentials to be used by hosts belonging to this inventory when accessing Red Hat Insights API.', null=True), ), migrations.AlterField( model_name='inventory', @@ -382,10 +382,10 @@ class Migration(migrations.Migration): ('name', models.CharField(max_length=512)), ('kind', models.CharField(max_length=32, choices=[('ssh', 'Machine'), ('vault', 'Vault'), ('net', 'Network'), ('scm', 'Source Control'), ('cloud', 'Cloud'), ('insights', 'Insights')])), ('managed_by_tower', models.BooleanField(default=False, editable=False)), - ('inputs', awx.main.fields.CredentialTypeInputField(default={}, blank=True, help_text='Enter inputs using either JSON or YAML syntax. Use the radio button to toggle between the two. Refer to the Ansible Tower documentation for example syntax.')), - ('injectors', awx.main.fields.CredentialTypeInjectorField(default={}, blank=True, help_text='Enter injectors using either JSON or YAML syntax. Use the radio button to toggle between the two. Refer to the Ansible Tower documentation for example syntax.')), - ('created_by', models.ForeignKey(related_name="{u'class': 'credentialtype', u'app_label': 'main'}(class)s_created+", on_delete=models.deletion.SET_NULL, default=None, editable=False, to=settings.AUTH_USER_MODEL, null=True)), - ('modified_by', models.ForeignKey(related_name="{u'class': 'credentialtype', u'app_label': 'main'}(class)s_modified+", on_delete=models.deletion.SET_NULL, default=None, editable=False, to=settings.AUTH_USER_MODEL, null=True)), + ('inputs', awx.main.fields.CredentialTypeInputField(default=dict, blank=True, help_text='Enter inputs using either JSON or YAML syntax. Use the radio button to toggle between the two. Refer to the Ansible Tower documentation for example syntax.')), + ('injectors', awx.main.fields.CredentialTypeInjectorField(default=dict, blank=True, help_text='Enter injectors using either JSON or YAML syntax. Use the radio button to toggle between the two. Refer to the Ansible Tower documentation for example syntax.')), + ('created_by', models.ForeignKey(related_name="{u'class': 'credentialtype', u'app_label': 'main'}(class)s_created+", on_delete=models.SET_NULL, default=None, editable=False, to=settings.AUTH_USER_MODEL, null=True)), + ('modified_by', models.ForeignKey(related_name="{u'class': 'credentialtype', u'app_label': 'main'}(class)s_modified+", on_delete=models.SET_NULL, default=None, editable=False, to=settings.AUTH_USER_MODEL, null=True)), ('tags', taggit.managers.TaggableManager(to='taggit.Tag', through='taggit.TaggedItem', blank=True, help_text='A comma-separated list of tags.', verbose_name='Tags')), ], options={ @@ -399,23 +399,23 @@ class Migration(migrations.Migration): migrations.AddField( model_name='credential', name='inputs', - field=awx.main.fields.CredentialInputField(default={}, blank=True), + field=awx.main.fields.CredentialInputField(default=dict, blank=True), ), migrations.AddField( model_name='credential', name='credential_type', - field=models.ForeignKey(related_name='credentials', to='main.CredentialType', null=True), + field=models.ForeignKey(related_name='credentials', on_delete=models.CASCADE, to='main.CredentialType', null=True), preserve_default=False, ), migrations.AddField( model_name='job', name='vault_credential', - field=models.ForeignKey(related_name='jobs_as_vault_credential+', on_delete=models.deletion.SET_NULL, default=None, blank=True, to='main.Credential', null=True), + field=models.ForeignKey(related_name='jobs_as_vault_credential+', on_delete=models.SET_NULL, default=None, blank=True, to='main.Credential', null=True), ), migrations.AddField( model_name='jobtemplate', name='vault_credential', - field=models.ForeignKey(related_name='jobtemplates_as_vault_credential+', on_delete=models.deletion.SET_NULL, default=None, blank=True, to='main.Credential', null=True), + field=models.ForeignKey(related_name='jobtemplates_as_vault_credential+', on_delete=models.SET_NULL, default=None, blank=True, to='main.Credential', null=True), ), migrations.AddField( model_name='job', @@ -452,7 +452,7 @@ class Migration(migrations.Migration): ('name', models.CharField(unique=True, max_length=250)), ('created', models.DateTimeField(auto_now_add=True)), ('modified', models.DateTimeField(auto_now=True)), - ('controller', models.ForeignKey(related_name='controlled_groups', default=None, editable=False, to='main.InstanceGroup', help_text='Instance Group to remotely control this group.', null=True)), + ('controller', models.ForeignKey(related_name='controlled_groups', on_delete=models.CASCADE, default=None, editable=False, to='main.InstanceGroup', help_text='Instance Group to remotely control this group.', null=True)), ('instances', models.ManyToManyField(help_text='Instances that are members of this InstanceGroup', related_name='rampart_groups', editable=False, to='main.Instance')), ], ), @@ -464,7 +464,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='unifiedjob', name='instance_group', - field=models.ForeignKey(on_delete=models.deletion.SET_NULL, default=None, blank=True, to='main.InstanceGroup', help_text='The Rampart/Instance group the job was run under', null=True), + field=models.ForeignKey(on_delete=models.SET_NULL, default=None, blank=True, to='main.InstanceGroup', help_text='The Rampart/Instance group the job was run under', null=True), ), migrations.AddField( model_name='unifiedjobtemplate', diff --git a/awx/main/migrations/0008_v320_drop_v1_credential_fields.py b/awx/main/migrations/0008_v320_drop_v1_credential_fields.py index ed45bc4b8e..f2fc44397e 100644 --- a/awx/main/migrations/0008_v320_drop_v1_credential_fields.py +++ b/awx/main/migrations/0008_v320_drop_v1_credential_fields.py @@ -103,12 +103,12 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='credential', name='credential_type', - field=models.ForeignKey(related_name='credentials', to='main.CredentialType', null=False, help_text='Specify the type of credential you want to create. Refer to the Ansible Tower documentation for details on each type.') + field=models.ForeignKey(related_name='credentials', to='main.CredentialType', on_delete=models.CASCADE, null=False, help_text='Specify the type of credential you want to create. Refer to the Ansible Tower documentation for details on each type.') ), migrations.AlterField( model_name='credential', name='inputs', - field=awx.main.fields.CredentialInputField(default={}, help_text='Enter inputs using either JSON or YAML syntax. Use the radio button to toggle between the two. Refer to the Ansible Tower documentation for example syntax.', blank=True), + field=awx.main.fields.CredentialInputField(default=dict, help_text='Enter inputs using either JSON or YAML syntax. Use the radio button to toggle between the two. Refer to the Ansible Tower documentation for example syntax.', blank=True), ), migrations.RemoveField( model_name='job', diff --git a/awx/main/migrations/0014_v330_saved_launchtime_configs.py b/awx/main/migrations/0014_v330_saved_launchtime_configs.py index fbd26eec1b..d9c7b105d9 100644 --- a/awx/main/migrations/0014_v330_saved_launchtime_configs.py +++ b/awx/main/migrations/0014_v330_saved_launchtime_configs.py @@ -20,7 +20,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='schedule', name='char_prompts', - field=awx.main.fields.JSONField(default={}, blank=True), + field=awx.main.fields.JSONField(default=dict, blank=True), ), migrations.AddField( model_name='schedule', @@ -35,7 +35,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='schedule', name='survey_passwords', - field=awx.main.fields.JSONField(default={}, editable=False, blank=True), + field=awx.main.fields.JSONField(default=dict, editable=False, blank=True), ), migrations.AddField( model_name='workflowjobnode', @@ -45,12 +45,12 @@ class Migration(migrations.Migration): migrations.AddField( model_name='workflowjobnode', name='extra_data', - field=awx.main.fields.JSONField(default={}, blank=True), + field=awx.main.fields.JSONField(default=dict, blank=True), ), migrations.AddField( model_name='workflowjobnode', name='survey_passwords', - field=awx.main.fields.JSONField(default={}, editable=False, blank=True), + field=awx.main.fields.JSONField(default=dict, editable=False, blank=True), ), migrations.AddField( model_name='workflowjobtemplatenode', @@ -60,12 +60,12 @@ class Migration(migrations.Migration): migrations.AddField( model_name='workflowjobtemplatenode', name='extra_data', - field=awx.main.fields.JSONField(default={}, blank=True), + field=awx.main.fields.JSONField(default=dict, blank=True), ), migrations.AddField( model_name='workflowjobtemplatenode', name='survey_passwords', - field=awx.main.fields.JSONField(default={}, editable=False, blank=True), + field=awx.main.fields.JSONField(default=dict, editable=False, blank=True), ), # Run data migration before removing the old credential field migrations.RunPython(migration_utils.set_current_apps_for_migrations, migrations.RunPython.noop), @@ -83,9 +83,9 @@ class Migration(migrations.Migration): name='JobLaunchConfig', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('extra_data', awx.main.fields.JSONField(blank=True, default={})), - ('survey_passwords', awx.main.fields.JSONField(blank=True, default={}, editable=False)), - ('char_prompts', awx.main.fields.JSONField(blank=True, default={})), + ('extra_data', awx.main.fields.JSONField(blank=True, default=dict)), + ('survey_passwords', awx.main.fields.JSONField(blank=True, default=dict, editable=False)), + ('char_prompts', awx.main.fields.JSONField(blank=True, default=dict)), ('credentials', models.ManyToManyField(related_name='joblaunchconfigs', to='main.Credential')), ('inventory', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='joblaunchconfigs', to='main.Inventory')), ('job', models.OneToOneField(editable=False, on_delete=django.db.models.deletion.CASCADE, related_name='launch_config', to='main.UnifiedJob')), @@ -94,51 +94,51 @@ class Migration(migrations.Migration): migrations.AddField( model_name='workflowjobtemplate', name='ask_variables_on_launch', - field=awx.main.fields.AskForField(default=False), + field=awx.main.fields.AskForField(blank=True, default=False), ), migrations.AlterField( model_name='jobtemplate', name='ask_credential_on_launch', - field=awx.main.fields.AskForField(default=False), + field=awx.main.fields.AskForField(blank=True, default=False), ), migrations.AlterField( model_name='jobtemplate', name='ask_diff_mode_on_launch', - field=awx.main.fields.AskForField(default=False), + field=awx.main.fields.AskForField(blank=True, default=False), ), migrations.AlterField( model_name='jobtemplate', name='ask_inventory_on_launch', - field=awx.main.fields.AskForField(default=False), + field=awx.main.fields.AskForField(blank=True, default=False), ), migrations.AlterField( model_name='jobtemplate', name='ask_job_type_on_launch', - field=awx.main.fields.AskForField(default=False), + field=awx.main.fields.AskForField(blank=True, default=False), ), migrations.AlterField( model_name='jobtemplate', name='ask_limit_on_launch', - field=awx.main.fields.AskForField(default=False), + field=awx.main.fields.AskForField(blank=True, default=False), ), migrations.AlterField( model_name='jobtemplate', name='ask_skip_tags_on_launch', - field=awx.main.fields.AskForField(default=False), + field=awx.main.fields.AskForField(blank=True, default=False), ), migrations.AlterField( model_name='jobtemplate', name='ask_tags_on_launch', - field=awx.main.fields.AskForField(default=False), + field=awx.main.fields.AskForField(blank=True, default=False), ), migrations.AlterField( model_name='jobtemplate', name='ask_variables_on_launch', - field=awx.main.fields.AskForField(default=False), + field=awx.main.fields.AskForField(blank=True, default=False), ), migrations.AlterField( model_name='jobtemplate', name='ask_verbosity_on_launch', - field=awx.main.fields.AskForField(default=False), + field=awx.main.fields.AskForField(blank=True, default=False), ), ] diff --git a/awx/main/migrations/0018_v330_add_additional_stdout_events.py b/awx/main/migrations/0018_v330_add_additional_stdout_events.py index 80fdbe3bf7..33abaf0eee 100644 --- a/awx/main/migrations/0018_v330_add_additional_stdout_events.py +++ b/awx/main/migrations/0018_v330_add_additional_stdout_events.py @@ -20,7 +20,7 @@ class Migration(migrations.Migration): ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('created', models.DateTimeField(default=None, editable=False)), ('modified', models.DateTimeField(default=None, editable=False)), - ('event_data', awx.main.fields.JSONField(blank=True, default={})), + ('event_data', awx.main.fields.JSONField(blank=True, default=dict)), ('uuid', models.CharField(default='', editable=False, max_length=1024)), ('counter', models.PositiveIntegerField(default=0, editable=False)), ('stdout', models.TextField(default='', editable=False)), @@ -40,7 +40,7 @@ class Migration(migrations.Migration): ('created', models.DateTimeField(default=None, editable=False)), ('modified', models.DateTimeField(default=None, editable=False)), ('event', models.CharField(choices=[('runner_on_failed', 'Host Failed'), ('runner_on_ok', 'Host OK'), ('runner_on_error', 'Host Failure'), ('runner_on_skipped', 'Host Skipped'), ('runner_on_unreachable', 'Host Unreachable'), ('runner_on_no_hosts', 'No Hosts Remaining'), ('runner_on_async_poll', 'Host Polling'), ('runner_on_async_ok', 'Host Async OK'), ('runner_on_async_failed', 'Host Async Failure'), ('runner_item_on_ok', 'Item OK'), ('runner_item_on_failed', 'Item Failed'), ('runner_item_on_skipped', 'Item Skipped'), ('runner_retry', 'Host Retry'), ('runner_on_file_diff', 'File Difference'), ('playbook_on_start', 'Playbook Started'), ('playbook_on_notify', 'Running Handlers'), ('playbook_on_include', 'Including File'), ('playbook_on_no_hosts_matched', 'No Hosts Matched'), ('playbook_on_no_hosts_remaining', 'No Hosts Remaining'), ('playbook_on_task_start', 'Task Started'), ('playbook_on_vars_prompt', 'Variables Prompted'), ('playbook_on_setup', 'Gathering Facts'), ('playbook_on_import_for_host', 'internal: on Import for Host'), ('playbook_on_not_import_for_host', 'internal: on Not Import for Host'), ('playbook_on_play_start', 'Play Started'), ('playbook_on_stats', 'Playbook Complete'), ('debug', 'Debug'), ('verbose', 'Verbose'), ('deprecated', 'Deprecated'), ('warning', 'Warning'), ('system_warning', 'System Warning'), ('error', 'Error')], max_length=100)), - ('event_data', awx.main.fields.JSONField(blank=True, default={})), + ('event_data', awx.main.fields.JSONField(blank=True, default=dict)), ('failed', models.BooleanField(default=False, editable=False)), ('changed', models.BooleanField(default=False, editable=False)), ('uuid', models.CharField(default='', editable=False, max_length=1024)), @@ -65,7 +65,7 @@ class Migration(migrations.Migration): ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('created', models.DateTimeField(default=None, editable=False)), ('modified', models.DateTimeField(default=None, editable=False)), - ('event_data', awx.main.fields.JSONField(blank=True, default={})), + ('event_data', awx.main.fields.JSONField(blank=True, default=dict)), ('uuid', models.CharField(default='', editable=False, max_length=1024)), ('counter', models.PositiveIntegerField(default=0, editable=False)), ('stdout', models.TextField(default='', editable=False)), diff --git a/awx/main/migrations/0053_v340_workflow_inventory.py b/awx/main/migrations/0053_v340_workflow_inventory.py index 285b4262fe..c519a27e25 100644 --- a/awx/main/migrations/0053_v340_workflow_inventory.py +++ b/awx/main/migrations/0053_v340_workflow_inventory.py @@ -17,7 +17,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='workflowjob', name='char_prompts', - field=awx.main.fields.JSONField(blank=True, default={}), + field=awx.main.fields.JSONField(blank=True, default=dict), ), migrations.AddField( model_name='workflowjob', @@ -27,7 +27,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='workflowjobtemplate', name='ask_inventory_on_launch', - field=awx.main.fields.AskForField(default=False), + field=awx.main.fields.AskForField(blank=True, default=False), ), migrations.AddField( model_name='workflowjobtemplate', diff --git a/awx/main/migrations/0067_v350_credential_plugins.py b/awx/main/migrations/0067_v350_credential_plugins.py index e80e9f543b..32190b2bf2 100644 --- a/awx/main/migrations/0067_v350_credential_plugins.py +++ b/awx/main/migrations/0067_v350_credential_plugins.py @@ -34,7 +34,7 @@ class Migration(migrations.Migration): ('modified', models.DateTimeField(default=None, editable=False)), ('description', models.TextField(blank=True, default='')), ('input_field_name', models.CharField(max_length=1024)), - ('metadata', awx.main.fields.DynamicCredentialInputField(blank=True, default={})), + ('metadata', awx.main.fields.DynamicCredentialInputField(blank=True, default=dict)), ('created_by', models.ForeignKey(default=None, editable=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name="{'class': 'credentialinputsource', 'model_name': 'credentialinputsource', 'app_label': 'main'}(class)s_created+", to=settings.AUTH_USER_MODEL)), ('modified_by', models.ForeignKey(default=None, editable=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name="{'class': 'credentialinputsource', 'model_name': 'credentialinputsource', 'app_label': 'main'}(class)s_modified+", to=settings.AUTH_USER_MODEL)), ('source_credential', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='target_input_sources', to='main.Credential')), diff --git a/awx/main/migrations/_squashed_30.py b/awx/main/migrations/_squashed_30.py index 910be80287..31ea44e885 100644 --- a/awx/main/migrations/_squashed_30.py +++ b/awx/main/migrations/_squashed_30.py @@ -30,7 +30,7 @@ SQUASHED_30 = { migrations.AddField( model_name='job', name='survey_passwords', - field=jsonfield.fields.JSONField(default={}, editable=False, blank=True), + field=jsonfield.fields.JSONField(default=dict, editable=False, blank=True), ), ], '0031_v302_migrate_survey_passwords': [ diff --git a/awx/main/models/__init__.py b/awx/main/models/__init__.py index 974aca40c8..01a44c58b7 100644 --- a/awx/main/models/__init__.py +++ b/awx/main/models/__init__.py @@ -62,24 +62,6 @@ from awx.main.models.oauth import ( # noqa from oauth2_provider.models import Grant, RefreshToken # noqa -- needed django-oauth-toolkit model migrations - -# Monkeypatch Django serializer to ignore django-taggit fields (which break -# the dumpdata command; see https://github.com/alex/django-taggit/issues/155). -from django.core.serializers.python import Serializer as _PythonSerializer -_original_handle_m2m_field = _PythonSerializer.handle_m2m_field - - -def _new_handle_m2m_field(self, obj, field): - try: - field.rel.through._meta - except AttributeError: - return - return _original_handle_m2m_field(self, obj, field) - - -_PythonSerializer.handle_m2m_field = _new_handle_m2m_field - - # Add custom methods to User model for permissions checks. from django.contrib.auth.models import User # noqa from awx.main.access import ( # noqa @@ -158,7 +140,7 @@ User.add_to_class('is_system_auditor', user_is_system_auditor) def user_is_in_enterprise_category(user, category): - ret = (category,) in user.enterprise_auth.all().values_list('provider') and not user.has_usable_password() + ret = (category,) in user.enterprise_auth.values_list('provider') and not user.has_usable_password() # NOTE: this if-else block ensures existing enterprise users are still able to # log in. Remove it in a future release if category == 'radius': diff --git a/awx/main/models/credential/__init__.py b/awx/main/models/credential/__init__.py index e99401ad7c..d34cac27e3 100644 --- a/awx/main/models/credential/__init__.py +++ b/awx/main/models/credential/__init__.py @@ -105,7 +105,7 @@ class Credential(PasswordFieldsModel, CommonModelNameNotUnique, ResourceMixin): ) inputs = CredentialInputField( blank=True, - default={}, + default=dict, help_text=_('Enter inputs using either JSON or YAML syntax. Use the ' 'radio button to toggle between the two. Refer to the ' 'Ansible Tower documentation for example syntax.') @@ -343,14 +343,14 @@ class CredentialType(CommonModelNameNotUnique): ) inputs = CredentialTypeInputField( blank=True, - default={}, + default=dict, help_text=_('Enter inputs using either JSON or YAML syntax. Use the ' 'radio button to toggle between the two. Refer to the ' 'Ansible Tower documentation for example syntax.') ) injectors = CredentialTypeInjectorField( blank=True, - default={}, + default=dict, help_text=_('Enter injectors using either JSON or YAML syntax. Use the ' 'radio button to toggle between the two. Refer to the ' 'Ansible Tower documentation for example syntax.') @@ -1117,7 +1117,7 @@ class CredentialInputSource(PrimordialModel): ) metadata = DynamicCredentialInputField( blank=True, - default={} + default=dict ) def clean_target_credential(self): diff --git a/awx/main/models/events.py b/awx/main/models/events.py index 5b424353eb..9a70501474 100644 --- a/awx/main/models/events.py +++ b/awx/main/models/events.py @@ -149,7 +149,7 @@ class BasePlaybookEvent(CreatedModifiedModel): ) event_data = JSONField( blank=True, - default={}, + default=dict, ) failed = models.BooleanField( default=False, @@ -567,7 +567,7 @@ class BaseCommandEvent(CreatedModifiedModel): event_data = JSONField( blank=True, - default={}, + default=dict, ) uuid = models.CharField( max_length=1024, diff --git a/awx/main/models/inventory.py b/awx/main/models/inventory.py index a32b2da052..eec1c5ae11 100644 --- a/awx/main/models/inventory.py +++ b/awx/main/models/inventory.py @@ -650,7 +650,7 @@ class Host(CommonModelNameNotUnique, RelatedJobsMixin): ) ansible_facts = JSONBField( blank=True, - default={}, + default=dict, help_text=_('Arbitrary JSON structure of most recent ansible_facts, per-host.'), ) ansible_facts_modified = models.DateTimeField( diff --git a/awx/main/models/jobs.py b/awx/main/models/jobs.py index 59b26c66e5..12c691d195 100644 --- a/awx/main/models/jobs.py +++ b/awx/main/models/jobs.py @@ -485,7 +485,7 @@ class Job(UnifiedJob, JobOptions, SurveyJobMixin, JobNotificationMixin, TaskMana ) artifacts = JSONField( blank=True, - default={}, + default=dict, editable=False, ) scm_revision = models.CharField( @@ -847,7 +847,7 @@ class LaunchTimeConfigBase(BaseModel): # This is a solution to the nullable CharField problem, specific to prompting char_prompts = JSONField( blank=True, - default={} + default=dict ) def prompts_dict(self, display=False): @@ -927,11 +927,11 @@ class LaunchTimeConfig(LaunchTimeConfigBase): # Special case prompting fields, even more special than the other ones extra_data = JSONField( blank=True, - default={} + default=dict ) survey_passwords = prevent_search(JSONField( blank=True, - default={}, + default=dict, editable=False, )) # Credentials needed for non-unified job / unified JT models diff --git a/awx/main/models/mixins.py b/awx/main/models/mixins.py index 437388664a..47176f2550 100644 --- a/awx/main/models/mixins.py +++ b/awx/main/models/mixins.py @@ -100,7 +100,7 @@ class SurveyJobTemplateMixin(models.Model): ) survey_spec = prevent_search(JSONField( blank=True, - default={}, + default=dict, )) ask_variables_on_launch = AskForField( blank=True, @@ -360,7 +360,7 @@ class SurveyJobMixin(models.Model): survey_passwords = prevent_search(JSONField( blank=True, - default={}, + default=dict, editable=False, )) diff --git a/awx/main/models/unified_jobs.py b/awx/main/models/unified_jobs.py index 438cc5f513..e0de362e8e 100644 --- a/awx/main/models/unified_jobs.py +++ b/awx/main/models/unified_jobs.py @@ -641,7 +641,7 @@ class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique ) job_env = prevent_search(JSONField( blank=True, - default={}, + default=dict, editable=False, )) job_explanation = models.TextField( diff --git a/awx/main/models/workflow.py b/awx/main/models/workflow.py index e5f36054ff..d413f05666 100644 --- a/awx/main/models/workflow.py +++ b/awx/main/models/workflow.py @@ -180,7 +180,7 @@ class WorkflowJobNode(WorkflowNodeBase): ) ancestor_artifacts = JSONField( blank=True, - default={}, + default=dict, editable=False, ) do_not_run = models.BooleanField( diff --git a/awx/main/tests/functional/api/test_instance_group.py b/awx/main/tests/functional/api/test_instance_group.py index e43bf5f2c4..381e6b349e 100644 --- a/awx/main/tests/functional/api/test_instance_group.py +++ b/awx/main/tests/functional/api/test_instance_group.py @@ -101,7 +101,7 @@ def test_instance_group_is_isolated(instance_group, isolated_instance_group): assert not instance_group.is_isolated assert isolated_instance_group.is_isolated - isolated_instance_group.instances = [] + isolated_instance_group.instances.set([]) assert isolated_instance_group.is_isolated diff --git a/awx/main/tests/functional/commands/test_commands.py b/awx/main/tests/functional/commands/test_commands.py index 471e5f1be6..078ca96f2d 100644 --- a/awx/main/tests/functional/commands/test_commands.py +++ b/awx/main/tests/functional/commands/test_commands.py @@ -12,7 +12,6 @@ def run_command(name, *args, **options): command_runner = options.pop('command_runner', call_command) stdin_fileobj = options.pop('stdin_fileobj', None) options.setdefault('verbosity', 1) - options.setdefault('interactive', False) original_stdin = sys.stdin original_stdout = sys.stdout original_stderr = sys.stderr diff --git a/awx/main/tests/functional/conftest.py b/awx/main/tests/functional/conftest.py index 5a0efa37ae..c924034bdd 100644 --- a/awx/main/tests/functional/conftest.py +++ b/awx/main/tests/functional/conftest.py @@ -11,9 +11,9 @@ from django.urls import resolve from django.contrib.auth.models import User from django.core.serializers.json import DjangoJSONEncoder from django.db.backends.sqlite3.base import SQLiteCursorWrapper -from jsonbfield.fields import JSONField # AWX +from awx.main.fields import JSONBField from awx.main.models.projects import Project from awx.main.models.ha import Instance @@ -737,7 +737,7 @@ def get_db_prep_save(self, value, connection, **kwargs): @pytest.fixture def monkeypatch_jsonbfield_get_db_prep_save(mocker): - JSONField.get_db_prep_save = get_db_prep_save + JSONBField.get_db_prep_save = get_db_prep_save @pytest.fixture diff --git a/awx/main/tests/functional/models/test_schedule.py b/awx/main/tests/functional/models/test_schedule.py index aee73e50eb..525fdd3022 100644 --- a/awx/main/tests/functional/models/test_schedule.py +++ b/awx/main/tests/functional/models/test_schedule.py @@ -457,4 +457,4 @@ def test_duplicate_name_within_template(job_template): with pytest.raises(IntegrityError) as ierror: s2.save() - assert str(ierror.value) == "columns unified_job_template_id, name are not unique" + assert str(ierror.value) == "UNIQUE constraint failed: main_schedule.unified_job_template_id, main_schedule.name" diff --git a/awx/main/tests/functional/models/test_workflow.py b/awx/main/tests/functional/models/test_workflow.py index 3b61e6c29b..cc4c42ecfe 100644 --- a/awx/main/tests/functional/models/test_workflow.py +++ b/awx/main/tests/functional/models/test_workflow.py @@ -20,7 +20,6 @@ from django.test import TransactionTestCase from django.core.exceptions import ValidationError -@pytest.mark.django_db class TestWorkflowDAGFunctional(TransactionTestCase): def workflow_job(self, states=['new', 'new', 'new', 'new', 'new']): """ diff --git a/awx/main/tests/functional/task_management/test_capacity.py b/awx/main/tests/functional/task_management/test_capacity.py index 7b7b7d7dc0..b3be1a3a77 100644 --- a/awx/main/tests/functional/task_management/test_capacity.py +++ b/awx/main/tests/functional/task_management/test_capacity.py @@ -1,5 +1,3 @@ -import pytest - from django.test import TransactionTestCase from awx.main.models import ( @@ -8,7 +6,6 @@ from awx.main.models import ( ) -@pytest.mark.django_db class TestCapacityMapping(TransactionTestCase): def sample_cluster(self): diff --git a/awx/main/tests/functional/test_jobs.py b/awx/main/tests/functional/test_jobs.py index b04813b278..e847544931 100644 --- a/awx/main/tests/functional/test_jobs.py +++ b/awx/main/tests/functional/test_jobs.py @@ -42,7 +42,7 @@ def test_job_notification_data(inventory, machine_credential, project): survey_passwords={"SSN": encrypted_str}, project=project, ) - job.credentials = [machine_credential] + job.credentials.set([machine_credential]) notification_data = job.notification_data(block=0) assert json.loads(notification_data['extra_vars'])['SSN'] == encrypted_str diff --git a/awx/main/tests/functional/test_rbac_user.py b/awx/main/tests/functional/test_rbac_user.py index 403767749f..c161a79c2f 100644 --- a/awx/main/tests/functional/test_rbac_user.py +++ b/awx/main/tests/functional/test_rbac_user.py @@ -7,7 +7,6 @@ from awx.main.access import UserAccess, RoleAccess, TeamAccess from awx.main.models import User, Organization, Inventory -@pytest.mark.django_db class TestSysAuditorTransactional(TransactionTestCase): def rando(self): return User.objects.create(username='rando', password='rando', email='rando@com.com') diff --git a/awx/main/tests/unit/api/test_generics.py b/awx/main/tests/unit/api/test_generics.py index de7f8ab4c8..caac45bc3b 100644 --- a/awx/main/tests/unit/api/test_generics.py +++ b/awx/main/tests/unit/api/test_generics.py @@ -186,11 +186,8 @@ class TestResourceAccessList: def mock_request(self): return mock.MagicMock( - user=mock.MagicMock( - is_anonymous=mock.MagicMock(return_value=False), - is_superuser=False - ), method='GET') - + user=mock.MagicMock(is_anonymous=False, is_superuser=False), + method='GET') def mock_view(self, parent=None): view = ResourceAccessList() @@ -200,7 +197,6 @@ class TestResourceAccessList: view.get_parent_object = lambda: parent return view - def test_parent_access_check_failed(self, mocker, mock_organization): mock_access = mocker.MagicMock(__name__='for logger', return_value=False) with mocker.patch('awx.main.access.BaseAccess.can_read', mock_access): @@ -208,7 +204,6 @@ class TestResourceAccessList: self.mock_view(parent=mock_organization).check_permissions(self.mock_request()) mock_access.assert_called_once_with(mock_organization) - def test_parent_access_check_worked(self, mocker, mock_organization): mock_access = mocker.MagicMock(__name__='for logger', return_value=True) with mocker.patch('awx.main.access.BaseAccess.can_read', mock_access): diff --git a/awx/main/tests/unit/conftest.py b/awx/main/tests/unit/conftest.py index 729f14ecaf..26b7049477 100644 --- a/awx/main/tests/unit/conftest.py +++ b/awx/main/tests/unit/conftest.py @@ -6,7 +6,7 @@ from unittest.mock import PropertyMock from awx.api.urls import urlpatterns as api_patterns # Django -from django.urls import RegexURLResolver, RegexURLPattern +from django.urls import URLResolver, URLPattern @pytest.fixture(autouse=True) @@ -20,24 +20,24 @@ def all_views(): ''' returns a set of all views in the app ''' - patterns = set([]) - url_views = set([]) + patterns = set() + url_views = set() # Add recursive URL patterns unprocessed = set(api_patterns) while unprocessed: to_process = unprocessed.copy() - unprocessed = set([]) + unprocessed = set() for pattern in to_process: if hasattr(pattern, 'lookup_str') and not pattern.lookup_str.startswith('awx.api'): continue patterns.add(pattern) - if isinstance(pattern, RegexURLResolver): + if isinstance(pattern, URLResolver): for sub_pattern in pattern.url_patterns: if sub_pattern not in patterns: unprocessed.add(sub_pattern) # Get view classes for pattern in patterns: - if isinstance(pattern, RegexURLPattern) and hasattr(pattern.callback, 'view_class'): + if isinstance(pattern, URLPattern) and hasattr(pattern.callback, 'view_class'): url_views.add(pattern.callback.view_class) return url_views diff --git a/awx/main/tests/unit/models/test_events.py b/awx/main/tests/unit/models/test_events.py index 734d38f449..79d23a3757 100644 --- a/awx/main/tests/unit/models/test_events.py +++ b/awx/main/tests/unit/models/test_events.py @@ -57,5 +57,5 @@ def test_really_long_event_fields(field): }) manager.create.assert_called_with(**{ 'job_id': 123, - 'event_data': {field: 'X' * 1021 + '...'} + 'event_data': {field: 'X' * 1023 + '…'} }) diff --git a/awx/main/utils/named_url_graph.py b/awx/main/utils/named_url_graph.py index d21da9e7a1..f17c503d4c 100644 --- a/awx/main/utils/named_url_graph.py +++ b/awx/main/utils/named_url_graph.py @@ -208,7 +208,7 @@ def _check_unique_together_fields(model, ut): field = model._meta.get_field(field_name) if field_name == 'name': has_name = True - elif type(field) == models.ForeignKey and field.rel.to != model: + elif type(field) == models.ForeignKey and field.related_model != model: fk_names.append(field_name) elif issubclass(type(field), models.CharField) and field.choices: fields.append(field_name) @@ -256,7 +256,7 @@ def _dfs(configuration, model, graph, dead_ends, new_deadends, parents): fields, fk_names = configuration[model][0][:], configuration[model][1][:] adj_list = [] for fk_name in fk_names: - next_model = model._meta.get_field(fk_name).rel.to + next_model = model._meta.get_field(fk_name).related_model if issubclass(next_model, ContentType): continue if next_model not in configuration or\ diff --git a/awx/main/views.py b/awx/main/views.py index 13230d0846..1947bd001c 100644 --- a/awx/main/views.py +++ b/awx/main/views.py @@ -60,7 +60,7 @@ def handle_error(request, status=404, **kwargs): return render(request, 'error.html', kwargs, status=status) -def handle_400(request): +def handle_400(request, exception): kwargs = { 'name': _('Bad Request'), 'content': _('The request could not be understood by the server.'), @@ -68,7 +68,7 @@ def handle_400(request): return handle_error(request, 400, **kwargs) -def handle_403(request): +def handle_403(request, exception): kwargs = { 'name': _('Forbidden'), 'content': _('You don\'t have permission to access the requested resource.'), @@ -76,7 +76,7 @@ def handle_403(request): return handle_error(request, 403, **kwargs) -def handle_404(request): +def handle_404(request, exception): kwargs = { 'name': _('Not Found'), 'content': _('The requested resource could not be found.'), diff --git a/awx/sso/backends.py b/awx/sso/backends.py index df9df8a57e..35990e4622 100644 --- a/awx/sso/backends.py +++ b/awx/sso/backends.py @@ -13,6 +13,7 @@ from django.dispatch import receiver from django.contrib.auth.models import User from django.conf import settings as django_settings from django.core.signals import setting_changed +from django.utils.encoding import force_text # django-auth-ldap from django_auth_ldap.backend import LDAPSettings as BaseLDAPSettings @@ -98,7 +99,7 @@ class LDAPBackend(BaseLDAPBackend): settings = property(_get_settings, _set_settings) - def authenticate(self, username, password): + def authenticate(self, request, username, password): if self.settings.START_TLS and ldap.OPT_X_TLS_REQUIRE_CERT in self.settings.CONNECTION_OPTIONS: # with python-ldap, if you want to set connection-specific TLS # parameters, you must also specify OPT_X_TLS_NEWCTX = 0 @@ -124,7 +125,7 @@ class LDAPBackend(BaseLDAPBackend): raise ImproperlyConfigured( "{} must be an {} instance.".format(setting_name, type_) ) - return super(LDAPBackend, self).authenticate(None, username, password) + return super(LDAPBackend, self).authenticate(request, username, password) except Exception: logger.exception("Encountered an error authenticating to LDAP") return None @@ -179,7 +180,7 @@ def _decorate_enterprise_user(user, provider): def _get_or_set_enterprise_user(username, password, provider): created = False try: - user = User.objects.all().prefetch_related('enterprise_auth').get(username=username) + user = User.objects.prefetch_related('enterprise_auth').get(username=username) except User.DoesNotExist: user = User(username=username) enterprise_auth = _decorate_enterprise_user(user, provider) @@ -196,10 +197,10 @@ class RADIUSBackend(BaseRADIUSBackend): Custom Radius backend to verify license status ''' - def authenticate(self, username, password): + def authenticate(self, request, username, password): if not django_settings.RADIUS_SERVER: return None - return super(RADIUSBackend, self).authenticate(None, username, password) + return super(RADIUSBackend, self).authenticate(request, username, password) def get_user(self, user_id): if not django_settings.RADIUS_SERVER: @@ -209,7 +210,7 @@ class RADIUSBackend(BaseRADIUSBackend): return user def get_django_user(self, username, password=None): - return _get_or_set_enterprise_user(username, password, 'radius') + return _get_or_set_enterprise_user(force_text(username), force_text(password), 'radius') class TACACSPlusBackend(object): @@ -217,7 +218,7 @@ class TACACSPlusBackend(object): Custom TACACS+ auth backend for AWX ''' - def authenticate(self, username, password): + def authenticate(self, request, username, password): if not django_settings.TACACSPLUS_HOST: return None try: @@ -284,13 +285,13 @@ class SAMLAuth(BaseSAMLAuth): idp_config = self.setting('ENABLED_IDPS')[idp_name] return TowerSAMLIdentityProvider(idp_name, **idp_config) - def authenticate(self, *args, **kwargs): + def authenticate(self, request, *args, **kwargs): if not all([django_settings.SOCIAL_AUTH_SAML_SP_ENTITY_ID, django_settings.SOCIAL_AUTH_SAML_SP_PUBLIC_CERT, django_settings.SOCIAL_AUTH_SAML_SP_PRIVATE_KEY, django_settings.SOCIAL_AUTH_SAML_ORG_INFO, django_settings.SOCIAL_AUTH_SAML_TECHNICAL_CONTACT, django_settings.SOCIAL_AUTH_SAML_SUPPORT_CONTACT, django_settings.SOCIAL_AUTH_SAML_ENABLED_IDPS]): return None - user = super(SAMLAuth, self).authenticate(*args, **kwargs) + user = super(SAMLAuth, self).authenticate(request, *args, **kwargs) # Comes from https://github.com/omab/python-social-auth/blob/v0.2.21/social/backends/base.py#L91 if getattr(user, 'is_new', False): _decorate_enterprise_user(user, 'saml') @@ -307,7 +308,7 @@ class SAMLAuth(BaseSAMLAuth): return super(SAMLAuth, self).get_user(user_id) -def _update_m2m_from_groups(user, ldap_user, rel, opts, remove=True): +def _update_m2m_from_groups(user, ldap_user, related, opts, remove=True): ''' Hepler function to update m2m relationship based on LDAP group membership. ''' @@ -328,10 +329,10 @@ def _update_m2m_from_groups(user, ldap_user, rel, opts, remove=True): should_add = True if should_add: user.save() - rel.add(user) - elif remove and user in rel.all(): + related.add(user) + elif remove and user in related.all(): user.save() - rel.remove(user) + related.remove(user) @receiver(populate_user, dispatch_uid='populate-ldap-user') diff --git a/awx/sso/middleware.py b/awx/sso/middleware.py index 4edd4c4a2e..5ed1e5a9e2 100644 --- a/awx/sso/middleware.py +++ b/awx/sso/middleware.py @@ -39,7 +39,7 @@ class SocialAuthMiddleware(SocialAuthExceptionMiddleware): request.successful_authenticator = None if not request.path.startswith('/sso/') and 'migrations_notran' not in request.path: - if request.user and request.user.is_authenticated(): + if request.user and request.user.is_authenticated: # The rest of the code base rely hevily on type/inheritance checks, # LazyObject sent from Django auth middleware can be buggy if not # converted back to its original object. diff --git a/awx/sso/migrations/0001_initial.py b/awx/sso/migrations/0001_initial.py index 540215cf7f..69bc5ec7c7 100644 --- a/awx/sso/migrations/0001_initial.py +++ b/awx/sso/migrations/0001_initial.py @@ -17,7 +17,7 @@ class Migration(migrations.Migration): fields=[ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('provider', models.CharField(max_length=32, choices=[(b'radius', 'RADIUS'), (b'tacacs+', 'TACACS+')])), - ('user', models.ForeignKey(related_name='enterprise_auth', to=settings.AUTH_USER_MODEL)), + ('user', models.ForeignKey(related_name='enterprise_auth', on_delete=models.CASCADE, to=settings.AUTH_USER_MODEL)), ], ), migrations.AlterUniqueTogether( diff --git a/awx/sso/pipeline.py b/awx/sso/pipeline.py index 1343f2e75e..635787ef7a 100644 --- a/awx/sso/pipeline.py +++ b/awx/sso/pipeline.py @@ -50,7 +50,7 @@ def prevent_inactive_login(backend, details, user=None, *args, **kwargs): raise AuthInactive(backend) -def _update_m2m_from_expression(user, rel, expr, remove=True): +def _update_m2m_from_expression(user, related, expr, remove=True): ''' Helper function to update m2m relationship based on user matching one or more expressions. @@ -73,12 +73,12 @@ def _update_m2m_from_expression(user, rel, expr, remove=True): if ex.match(user.username) or ex.match(user.email): should_add = True if should_add: - rel.add(user) + related.add(user) elif remove: - rel.remove(user) + related.remove(user) -def _update_org_from_attr(user, rel, attr, remove, remove_admins): +def _update_org_from_attr(user, related, attr, remove, remove_admins): from awx.main.models import Organization org_ids = [] @@ -87,7 +87,7 @@ def _update_org_from_attr(user, rel, attr, remove, remove_admins): org = Organization.objects.get_or_create(name=org_name)[0] org_ids.append(org.id) - getattr(org, rel).members.add(user) + getattr(org, related).members.add(user) if remove: [o.member_role.members.remove(user) for o in diff --git a/awx/sso/tests/conftest.py b/awx/sso/tests/conftest.py index aa31bb0453..f94b1c528f 100644 --- a/awx/sso/tests/conftest.py +++ b/awx/sso/tests/conftest.py @@ -27,6 +27,7 @@ def existing_tacacsplus_user(): user = User.objects.get(username="foo") except User.DoesNotExist: user = User(username="foo") + user.set_unusable_password() user.save() enterprise_auth = UserEnterpriseAuth(user=user, provider='tacacs+') enterprise_auth.save() diff --git a/awx/sso/tests/functional/test_get_or_set_enterprise_user.py b/awx/sso/tests/functional/test_get_or_set_enterprise_user.py index b15c0b4e9a..9844c17295 100644 --- a/awx/sso/tests/functional/test_get_or_set_enterprise_user.py +++ b/awx/sso/tests/functional/test_get_or_set_enterprise_user.py @@ -8,8 +8,11 @@ from awx.sso.backends import _get_or_set_enterprise_user @pytest.mark.django_db def test_fetch_user_if_exist(existing_tacacsplus_user): - new_user = _get_or_set_enterprise_user("foo", "password", "tacacs+") - assert new_user == existing_tacacsplus_user + with mock.patch('awx.sso.backends.logger') as mocked_logger: + new_user = _get_or_set_enterprise_user("foo", "password", "tacacs+") + mocked_logger.debug.assert_not_called() + mocked_logger.warn.assert_not_called() + assert new_user == existing_tacacsplus_user @pytest.mark.django_db diff --git a/awx/sso/tests/unit/test_tacacsplus.py b/awx/sso/tests/unit/test_tacacsplus.py index c10cbd317e..e475694d63 100644 --- a/awx/sso/tests/unit/test_tacacsplus.py +++ b/awx/sso/tests/unit/test_tacacsplus.py @@ -4,7 +4,7 @@ from unittest import mock def test_empty_host_fails_auth(tacacsplus_backend): with mock.patch('awx.sso.backends.django_settings') as settings: settings.TACACSPLUS_HOST = '' - ret_user = tacacsplus_backend.authenticate(u"user", u"pass") + ret_user = tacacsplus_backend.authenticate(None, u"user", u"pass") assert ret_user is None @@ -16,7 +16,7 @@ def test_client_raises_exception(tacacsplus_backend): mock.patch('tacacs_plus.TACACSClient', return_value=client): settings.TACACSPLUS_HOST = 'localhost' settings.TACACSPLUS_AUTH_PROTOCOL = 'ascii' - ret_user = tacacsplus_backend.authenticate(u"user", u"pass") + ret_user = tacacsplus_backend.authenticate(None, u"user", u"pass") assert ret_user is None logger.exception.assert_called_once_with( "TACACS+ Authentication Error: foo" @@ -32,7 +32,7 @@ def test_client_return_invalid_fails_auth(tacacsplus_backend): mock.patch('tacacs_plus.TACACSClient', return_value=client): settings.TACACSPLUS_HOST = 'localhost' settings.TACACSPLUS_AUTH_PROTOCOL = 'ascii' - ret_user = tacacsplus_backend.authenticate(u"user", u"pass") + ret_user = tacacsplus_backend.authenticate(None, u"user", u"pass") assert ret_user is None @@ -48,5 +48,5 @@ def test_client_return_valid_passes_auth(tacacsplus_backend): mock.patch('awx.sso.backends._get_or_set_enterprise_user', return_value=user): settings.TACACSPLUS_HOST = 'localhost' settings.TACACSPLUS_AUTH_PROTOCOL = 'ascii' - ret_user = tacacsplus_backend.authenticate(u"user", u"pass") + ret_user = tacacsplus_backend.authenticate(None, u"user", u"pass") assert ret_user == user diff --git a/awx/sso/views.py b/awx/sso/views.py index 9eb0c33a95..fa248f634f 100644 --- a/awx/sso/views.py +++ b/awx/sso/views.py @@ -40,7 +40,7 @@ class CompleteView(BaseRedirectView): def dispatch(self, request, *args, **kwargs): response = super(CompleteView, self).dispatch(request, *args, **kwargs) - if self.request.user and self.request.user.is_authenticated(): + if self.request.user and self.request.user.is_authenticated: logger.info(smart_text(u"User {} logged in".format(self.request.user.username))) response.set_cookie('userLoggedIn', 'true') current_user = UserSerializer(self.request.user) diff --git a/awx/templates/error.html b/awx/templates/error.html index 69fade819e..81e54fe434 100644 --- a/awx/templates/error.html +++ b/awx/templates/error.html @@ -1,5 +1,5 @@ {% extends "rest_framework/api.html" %} -{% load i18n staticfiles %} +{% load i18n static %} {% block title %}{{ name }} · {% trans 'AWX' %}{% endblock %} diff --git a/awx/templates/rest_framework/login.html b/awx/templates/rest_framework/login.html index c9c5d64030..343d8a6ce0 100644 --- a/awx/templates/rest_framework/login.html +++ b/awx/templates/rest_framework/login.html @@ -1,6 +1,6 @@ {# Partial copy of login_base.html from rest_framework with AWX change. #} {% extends 'rest_framework/api.html' %} -{% load i18n staticfiles %} +{% load i18n static %} {% block breadcrumbs %} {% endblock %} diff --git a/awx/wsgi.py b/awx/wsgi.py index 13f8b08198..6d155ab6c3 100644 --- a/awx/wsgi.py +++ b/awx/wsgi.py @@ -40,12 +40,5 @@ if social_django.__version__ != '2.1.0': still works".format(social_django.__version__)) -if not django.__version__.startswith('1.'): - raise RuntimeError("Django version other than 1.XX detected {}. \ - Inherit from WSGIHandler to support short-circuit Django Middleware. \ - This is known to work for Django 1.XX and may not work with other, \ - even minor, versions.".format(django.__version__)) - - # Return the default Django WSGI application. application = get_wsgi_application() diff --git a/docs/licenses/django-jsonbfield.txt b/docs/licenses/django-jsonbfield.txt deleted file mode 100644 index 028fb0f561..0000000000 --- a/docs/licenses/django-jsonbfield.txt +++ /dev/null @@ -1,28 +0,0 @@ - -Copyright (c) Django Software Foundation and individual contributors. -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - 3. Neither the name of Django nor the names of its contributors may be used - to endorse or promote products derived from this software without - specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/licenses/sqlparse.txt b/docs/licenses/sqlparse.txt new file mode 100644 index 0000000000..de414c5579 --- /dev/null +++ b/docs/licenses/sqlparse.txt @@ -0,0 +1,25 @@ +Copyright (c) 2016, Andi Albrecht <albrecht.andi@gmail.com> +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the authors nor the names of its contributors may be + used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/requirements/requirements.in b/requirements/requirements.in index 3635efa7b5..5adae509d0 100644 --- a/requirements/requirements.in +++ b/requirements/requirements.in @@ -7,12 +7,12 @@ channels==1.1.8 celery==4.3.0 daphne==1.3.0 # Last before backwards-incompatible channels 2 upgrade twisted[tls]>=17.1 # from daphne, see https://github.com/django/daphne/pull/257 -Django==1.11.20 +Django==2.2.2 django-auth-ldap==1.7.0 django-cors-headers==2.4.0 django-crum==0.7.2 django-extensions==2.0.0 -django-jsonfield==1.0.1 +django-jsonfield==1.2.0 django-oauth-toolkit==1.1.3 django-polymorphic==2.0.2 django-pglocks==1.0.2 diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 56adbd6a82..24ae822d0f 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -27,7 +27,7 @@ django-auth-ldap==1.7.0 django-cors-headers==2.4.0 django-crum==0.7.2 django-extensions==2.0.0 -django-jsonfield==1.0.1 +django-jsonfield==1.2.0 django-oauth-toolkit==1.1.3 django-pglocks==1.0.2 django-polymorphic==2.0.2 @@ -35,7 +35,7 @@ django-radius==1.3.3 django-solo==1.1.3 django-split-settings==0.3.0 django-taggit==0.22.2 -django==1.11.20 +django==2.2.2 djangorestframework-yaml==1.0.3 djangorestframework==3.9.4 @@ -102,6 +102,7 @@ six==1.12.0 # via ansible-runner, asgi-amqp, asgiref, autobahn, au slackclient==1.1.2 social-auth-app-django==2.1.0 social-auth-core==3.0.0 +sqlparse==0.3.0 # via django tacacs_plus==1.0 tempora==1.14.1 # via irc, jaraco.logging twilio==6.10.4 diff --git a/requirements/requirements_git.txt b/requirements/requirements_git.txt index 46953643f7..9ce11ac140 100644 --- a/requirements/requirements_git.txt +++ b/requirements/requirements_git.txt @@ -1,3 +1,2 @@ git+https://github.com/ansible/ansiconv.git@tower_1.0.0#egg=ansiconv git+https://github.com/ansible/django-qsstats-magic.git@py3#egg=django-qsstats-magic -git+https://github.com/ansible/django-jsonbfield@fix-sqlite_serialization#egg=jsonbfield diff --git a/tools/docker-compose/Dockerfile b/tools/docker-compose/Dockerfile index 738daa305d..0d7671fe23 100644 --- a/tools/docker-compose/Dockerfile +++ b/tools/docker-compose/Dockerfile @@ -1,11 +1,9 @@ -FROM centos:7 +FROM fedora:27 ARG UID=0 -RUN yum -y update && yum -y install epel-release - # sync with installer/roles/image_build/templates/Dockerfile.j2 -RUN yum -y install acl \ +RUN dnf -y install acl \ alsa-lib \ ansible \ atk \ @@ -14,12 +12,15 @@ RUN yum -y install acl \ curl \ cyrus-sasl \ cyrus-sasl-devel \ + findutils \ gcc \ gcc-c++ \ GConf2 \ git \ + glibc-locale-source \ gtk3 \ ipa-gothic-fonts \ + iproute \ krb5-devel \ krb5-libs \ krb5-workstation \ @@ -45,16 +46,16 @@ RUN yum -y install acl \ nodejs \ openldap-devel \ openssh-server \ + postgresql-contrib \ postgresql-devel \ python-devel \ python-pip \ python-psutil \ python-psycopg2 \ python-setuptools \ - python36-devel \ - python36-setuptools \ + python3-devel \ + python3-setuptools \ rsync \ - setools-libs \ subversion \ sudo \ swig \ |