diff --git a/.github/workflows/sanity.yml b/.github/workflows/sanity.yml new file mode 100644 index 000000000..5e6b29155 --- /dev/null +++ b/.github/workflows/sanity.yml @@ -0,0 +1,37 @@ +--- +name: Sanity +env: + LC_ALL: "C.UTF-8" # prevent ERROR: Ansible could not initialize the preferred locale: unsupported locale setting +on: + pull_request: + push: +jobs: + help_text: + name: Help Test Check + runs-on: ubuntu-latest + permissions: + packages: read + contents: read + strategy: + fail-fast: false + steps: + - name: Install make + run: sudo apt install make + + - uses: actions/checkout@v4 + with: + show-progress: false + + - name: Install build requirements + run: sudo apt-get update && sudo apt-get install -y libsasl2-dev libldap2-dev libssl-dev libxmlsec1-dev + + - name: Install python 3.11 + uses: actions/setup-python@v4 + with: + python-version: 3.11 + + - name: Install requirements + run: pip3.11 install -r requirements/requirements_all.txt -r requirements/requirements_dev.txt + + - name: Run help text check + run: ./manage.py help_text_check --applications=dab --ignore-file=./.help_text_check.ignore diff --git a/.help_text_check.ignore b/.help_text_check.ignore new file mode 100644 index 000000000..fc7630e5f --- /dev/null +++ b/.help_text_check.ignore @@ -0,0 +1,23 @@ +dab_authentication.AuthenticatorUser.created # Inherited from social auth +dab_authentication.AuthenticatorUser.extra_data # Inherited from social auth +dab_authentication.AuthenticatorUser.modified # Inherited from social auth +dab_authentication.AuthenticatorUser.uid # Inherited from social auth + +dab_oauth2_provider.OAuth2AccessToken.expires # Inherited from django-oauth-toolkit +dab_oauth2_provider.OAuth2AccessToken.id_token # Inherited from django-oauth-toolkit +dab_oauth2_provider.OAuth2AccessToken.source_refresh_token # Inherited from django-oauth-toolkit + +dab_oauth2_provider.OAuth2Application.algorithm # Inherited from django-oauth-toolkit +dab_oauth2_provider.OAuth2Application.client_id # Inherited from django-oauth-toolkit + +dab_oauth2_provider.OAuth2IDToken.application # Inherited from django-oauth-toolkit +dab_oauth2_provider.OAuth2IDToken.expires # Inherited from django-oauth-toolkit +dab_oauth2_provider.OAuth2IDToken.jti # Inherited from django-oauth-toolkit +dab_oauth2_provider.OAuth2IDToken.scope # Inherited from django-oauth-toolkit +dab_oauth2_provider.OAuth2IDToken.user # Inherited from django-oauth-toolkit + +dab_oauth2_provider.OAuth2RefreshToken.access_token # Inherited from django-oauth-toolkit +dab_oauth2_provider.OAuth2RefreshToken.application # Inherited from django-oauth-toolkit +dab_oauth2_provider.OAuth2RefreshToken.revoked # Inherited from django-oauth-toolkit +dab_oauth2_provider.OAuth2RefreshToken.user # Inherited from django-oauth-toolkit + diff --git a/ansible_base/activitystream/migrations/0005_alter_entry_changes_alter_entry_content_type_and_more.py b/ansible_base/activitystream/migrations/0005_alter_entry_changes_alter_entry_content_type_and_more.py new file mode 100644 index 000000000..021347e69 --- /dev/null +++ b/ansible_base/activitystream/migrations/0005_alter_entry_changes_alter_entry_content_type_and_more.py @@ -0,0 +1,50 @@ +# Generated by Django 4.2.16 on 2024-11-22 21:25 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('contenttypes', '0002_remove_content_type_name'), + ('dab_activitystream', '0004_alter_entry_created_alter_entry_created_by'), + ] + + operations = [ + migrations.AlterField( + model_name='entry', + name='changes', + field=models.JSONField(blank=True, help_text='The changes to the system recorded by this entry.', null=True), + ), + migrations.AlterField( + model_name='entry', + name='content_type', + field=models.ForeignKey(help_text='The content type which was changed in this entry.', on_delete=django.db.models.deletion.DO_NOTHING, to='contenttypes.contenttype'), + ), + migrations.AlterField( + model_name='entry', + name='object_id', + field=models.TextField(blank=True, help_text='The object id which was modified in this entry.', null=True), + ), + migrations.AlterField( + model_name='entry', + name='operation', + field=models.CharField(choices=[('create', 'Entity created'), ('update', 'Entity updated'), ('delete', 'Entity deleted'), ('associate', 'Entity was associated with another entity'), ('disassociate', 'Entity was disassociated with another entity')], help_text='The type of change recorded by the entry (i.e. create/update/delete/etc).', max_length=12), + ), + migrations.AlterField( + model_name='entry', + name='related_content_type', + field=models.ForeignKey(blank=True, help_text='The content type if this entry is recording an many to many (M2M) change.', null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='related_content_type', to='contenttypes.contenttype'), + ), + migrations.AlterField( + model_name='entry', + name='related_field_name', + field=models.CharField(blank=True, help_text='The related field name if this entry is for an many to many (M2M) relationship.', max_length=64, null=True), + ), + migrations.AlterField( + model_name='entry', + name='related_object_id', + field=models.TextField(blank=True, help_text='The object id of the related model if this entry is for an many to many (M2M) change.', null=True), + ), + ] diff --git a/ansible_base/activitystream/models/entry.py b/ansible_base/activitystream/models/entry.py index db0272e8a..6de1c5aae 100644 --- a/ansible_base/activitystream/models/entry.py +++ b/ansible_base/activitystream/models/entry.py @@ -32,17 +32,35 @@ class Meta: ('disassociate', _("Entity was disassociated with another entity")), ] - content_type = models.ForeignKey(ContentType, on_delete=models.DO_NOTHING) - object_id = models.TextField(null=True, blank=True) + content_type = models.ForeignKey(ContentType, on_delete=models.DO_NOTHING, help_text=_("The content type which was changed in this entry.")) + object_id = models.TextField(null=True, blank=True, help_text=_("The object id which was modified in this entry.")) content_object = GenericForeignKey('content_type', 'object_id') - operation = models.CharField(max_length=12, choices=OPERATION_CHOICES) - changes = models.JSONField(null=True, blank=True) + operation = models.CharField( + max_length=12, choices=OPERATION_CHOICES, help_text=_("The type of change recorded by the entry (i.e. create/update/delete/etc).") + ) + changes = models.JSONField(null=True, blank=True, help_text=_("The changes to the system recorded by this entry.")) # This is used for m2m (dis)associations - related_content_type = models.ForeignKey(ContentType, on_delete=models.DO_NOTHING, null=True, blank=True, related_name='related_content_type') - related_object_id = models.TextField(null=True, blank=True) + related_content_type = models.ForeignKey( + ContentType, + on_delete=models.DO_NOTHING, + null=True, + blank=True, + related_name='related_content_type', + help_text=_("The content type if this entry is recording an many to many (M2M) change."), + ) + related_object_id = models.TextField( + null=True, + blank=True, + help_text=_("The object id of the related model if this entry is for an many to many (M2M) change."), + ) related_content_object = GenericForeignKey('related_content_type', 'related_object_id') - related_field_name = models.CharField(max_length=64, null=True, blank=True) + related_field_name = models.CharField( + max_length=64, + null=True, + blank=True, + help_text=_("The related field name if this entry is for an many to many (M2M) relationship."), + ) def __str__(self): return f'[{self.created}] {self.get_operation_display()} by {self.created_by}: {self.content_type} {self.object_id}' diff --git a/ansible_base/authentication/migrations/0016_alter_authenticatoruser_access_allowed_and_more.py b/ansible_base/authentication/migrations/0016_alter_authenticatoruser_access_allowed_and_more.py new file mode 100644 index 000000000..7d4895d82 --- /dev/null +++ b/ansible_base/authentication/migrations/0016_alter_authenticatoruser_access_allowed_and_more.py @@ -0,0 +1,41 @@ +# Generated by Django 4.2.16 on 2024-11-22 21:25 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('dab_authentication', '0015_alter_authenticator_category_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='authenticatoruser', + name='access_allowed', + field=models.BooleanField(default=None, help_text='Tracks if this user was allowed access to the system from the authenticator maps.', null=True), + ), + migrations.AlterField( + model_name='authenticatoruser', + name='claims', + field=models.JSONField(blank=True, default=dict, help_text='The claims for the user as generated by the authenticator maps on their last login.'), + ), + migrations.AlterField( + model_name='authenticatoruser', + name='last_login_map_results', + field=models.JSONField(blank=True, default=list, help_text='A data structure indicating how the authenticator maps were evaluated for the last login attempt.'), + ), + migrations.AlterField( + model_name='authenticatoruser', + name='provider', + field=models.ForeignKey(help_text='The provider this user authenticated from.', on_delete=django.db.models.deletion.PROTECT, related_name='authenticator_providers', to='dab_authentication.authenticator', to_field='slug'), + ), + migrations.AlterField( + model_name='authenticatoruser', + name='user', + field=models.ForeignKey(help_text='The local DB user related to this authenticator user.', on_delete=django.db.models.deletion.CASCADE, related_name='authenticator_users', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/ansible_base/authentication/models/authenticator_user.py b/ansible_base/authentication/models/authenticator_user.py index 4b2be44db..45e657122 100644 --- a/ansible_base/authentication/models/authenticator_user.py +++ b/ansible_base/authentication/models/authenticator_user.py @@ -3,6 +3,7 @@ from django.conf import settings from django.db import models +from django.utils.translation import gettext_lazy as _ from social_django.models import AbstractUserSocialAuth from ansible_base.authentication.models import Authenticator @@ -28,14 +29,31 @@ class AuthenticatorUser(AbstractUserSocialAuth, AbstractCommonModel): the authenticators and links the user to the authenticator that they used to login. """ - provider = models.ForeignKey(Authenticator, to_field='slug', on_delete=models.PROTECT, related_name="authenticator_providers") - user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="authenticator_users", on_delete=models.CASCADE) + provider = models.ForeignKey( + Authenticator, + to_field='slug', + on_delete=models.PROTECT, + related_name="authenticator_providers", + help_text=_("The provider this user authenticated from."), + ) + user = models.ForeignKey( + settings.AUTH_USER_MODEL, + related_name="authenticator_users", + on_delete=models.CASCADE, + help_text=_("The local DB user related to this authenticator user."), + ) # TODO: set self.authenticated based on the provider that is passed to this method. # the provider should be the name of the Authenticator model instance - claims = models.JSONField(default=dict, null=False, blank=True) - last_login_map_results = models.JSONField(default=list, null=False, blank=True) + claims = models.JSONField( + default=dict, null=False, blank=True, help_text=_("The claims for the user as generated by the authenticator maps on their last login.") + ) + last_login_map_results = models.JSONField( + default=list, null=False, blank=True, help_text=_("A data structure indicating how the authenticator maps were evaluated for the last login attempt.") + ) # This field tracks if a user passed or failed an allow map - access_allowed = models.BooleanField(default=None, null=True) + access_allowed = models.BooleanField( + default=None, null=True, help_text=_("Tracks if this user was allowed access to the system from the authenticator maps.") + ) encrypted_fields = ["extra_data"] diff --git a/ansible_base/oauth2_provider/migrations/0007_alter_oauth2accesstoken_application_and_more.py b/ansible_base/oauth2_provider/migrations/0007_alter_oauth2accesstoken_application_and_more.py new file mode 100644 index 000000000..28df58107 --- /dev/null +++ b/ansible_base/oauth2_provider/migrations/0007_alter_oauth2accesstoken_application_and_more.py @@ -0,0 +1,51 @@ +# Generated by Django 4.2.16 on 2024-11-22 21:25 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('dab_oauth2_provider', '0006_alter_oauth2accesstoken_created_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='oauth2accesstoken', + name='application', + field=models.ForeignKey(blank=True, help_text='The related application. If None, this is a user token instead of an application token.', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='access_tokens', to=settings.OAUTH2_PROVIDER_APPLICATION_MODEL), + ), + migrations.AlterField( + model_name='oauth2accesstoken', + name='description', + field=models.TextField(blank=True, default='', help_text='A description for this token.'), + ), + migrations.AlterField( + model_name='oauth2accesstoken', + name='last_used', + field=models.DateTimeField(default=None, editable=False, help_text='A timestamp of when this token was last used.', null=True), + ), + migrations.AlterField( + model_name='oauth2accesstoken', + name='token', + field=models.CharField(help_text='The generated token value.', max_length=255, unique=True), + ), + migrations.AlterField( + model_name='oauth2application', + name='description', + field=models.TextField(blank=True, default='', help_text='A description of this application.'), + ), + migrations.AlterField( + model_name='oauth2application', + name='user', + field=models.ForeignKey(blank=True, help_text='The user who created the application.', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='applications', to=settings.AUTH_USER_MODEL), + ), + migrations.AlterField( + model_name='oauth2refreshtoken', + name='token', + field=models.CharField(help_text='The refresh token value.', max_length=255), + ), + ] diff --git a/ansible_base/oauth2_provider/models/access_token.py b/ansible_base/oauth2_provider/models/access_token.py index be0353df2..1378bd0c6 100644 --- a/ansible_base/oauth2_provider/models/access_token.py +++ b/ansible_base/oauth2_provider/models/access_token.py @@ -58,28 +58,17 @@ class Meta(oauth2_models.AbstractAccessToken.Meta): blank=True, null=True, related_name='access_tokens', + help_text=_('The related application. If None, this is a user token instead of an application token.'), ) - description = models.TextField( - default='', - blank=True, - ) - last_used = models.DateTimeField( - null=True, - default=None, - editable=False, - ) + description = models.TextField(default='', blank=True, help_text=_('A description for this token.')) + last_used = models.DateTimeField(null=True, default=None, editable=False, help_text=_('A timestamp of when this token was last used.')) scope = models.CharField( default='write', max_length=32, help_text=_("Allowed scopes, further restricts user permissions. Must be a simple space-separated string with allowed scopes ['read', 'write']."), validators=[validate_scope], ) - token = prevent_search( - models.CharField( - max_length=255, - unique=True, - ) - ) + token = prevent_search(models.CharField(max_length=255, unique=True, help_text=_("The generated token value."))) updated = None # Tracked in CommonModel with 'modified', no need for this def is_valid(self, scopes=None): diff --git a/ansible_base/oauth2_provider/models/application.py b/ansible_base/oauth2_provider/models/application.py index bbbb20fbf..9899061d2 100644 --- a/ansible_base/oauth2_provider/models/application.py +++ b/ansible_base/oauth2_provider/models/application.py @@ -43,11 +43,13 @@ class Meta(oauth2_models.AbstractAccessToken.Meta): null=True, blank=True, on_delete=models.CASCADE, + help_text=_("The user who created the application."), ) description = models.TextField( default='', blank=True, + help_text=_("A description of this application."), ) organization = models.ForeignKey( getattr(settings, 'ANSIBLE_BASE_ORGANIZATION_MODEL'), diff --git a/ansible_base/oauth2_provider/models/refresh_token.py b/ansible_base/oauth2_provider/models/refresh_token.py index a782b35b2..afd5ef5a7 100644 --- a/ansible_base/oauth2_provider/models/refresh_token.py +++ b/ansible_base/oauth2_provider/models/refresh_token.py @@ -22,7 +22,7 @@ class Meta(oauth2_models.AbstractRefreshToken.Meta): ordering = ('id',) swappable = "OAUTH2_PROVIDER_REFRESH_TOKEN_MODEL" - token = prevent_search(models.CharField(max_length=255)) + token = prevent_search(models.CharField(max_length=255, help_text=_("The refresh token value."))) updated = None # Tracked in CommonModel with 'modified', no need for this def save(self, *args, **kwargs): diff --git a/ansible_base/rbac/migrations/0003_alter_dabpermission_codename_and_more.py b/ansible_base/rbac/migrations/0003_alter_dabpermission_codename_and_more.py new file mode 100644 index 000000000..14a1c2635 --- /dev/null +++ b/ansible_base/rbac/migrations/0003_alter_dabpermission_codename_and_more.py @@ -0,0 +1,107 @@ +# Generated by Django 4.2.16 on 2024-11-26 01:48 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('contenttypes', '0002_remove_content_type_name'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('dab_rbac', '0002_alter_objectrole_provides_teams_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='dabpermission', + name='codename', + field=models.CharField(help_text='A codename for the permission, in the format {action}_{model_name}. Where action is typically the view set action (view/list/etc) from Django rest framework.', max_length=100, verbose_name='codename'), + ), + migrations.AlterField( + model_name='dabpermission', + name='content_type', + field=models.ForeignKey(help_text='The content type this permission will apply to.', on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype', verbose_name='content type'), + ), + migrations.AlterField( + model_name='dabpermission', + name='name', + field=models.CharField(help_text='The name of this permission.', max_length=255, verbose_name='name'), + ), + migrations.AlterField( + model_name='objectrole', + name='content_type', + field=models.ForeignKey(help_text='The content type of the subject of permission assignments. Duplicated from related RoleDefinition.', on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype'), + ), + migrations.AlterField( + model_name='objectrole', + name='object_id', + field=models.TextField(help_text='The database primary key of the subject of permission assignments.'), + ), + migrations.AlterField( + model_name='roledefinition', + name='description', + field=models.TextField(blank=True, help_text='A description of this role.'), + ), + migrations.AlterField( + model_name='roledefinition', + name='managed', + field=models.BooleanField(default=False, editable=False, help_text='Is this role managed by the system (not changeable by the users).'), + ), + migrations.AlterField( + model_name='roledefinition', + name='name', + field=models.TextField(db_index=True, help_text='The name of this role.', unique=True), + ), + migrations.AlterField( + model_name='roleevaluation', + name='content_type_id', + field=models.PositiveIntegerField(help_text='The related content type id.'), + ), + migrations.AlterField( + model_name='roleevaluation', + name='object_id', + field=models.PositiveIntegerField(help_text='The id of the object that the related role gives the related permission to.'), + ), + migrations.AlterField( + model_name='roleevaluationuuid', + name='content_type_id', + field=models.PositiveIntegerField(help_text='The related content type id.'), + ), + migrations.AlterField( + model_name='roleevaluationuuid', + name='object_id', + field=models.UUIDField(help_text='The object UUID this role evaluation will be applied to.'), + ), + migrations.AlterField( + model_name='roleteamassignment', + name='content_type', + field=models.ForeignKey(help_text='The content type this applies to.', null=True, on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype'), + ), + migrations.AlterField( + model_name='roleteamassignment', + name='object_role', + field=models.ForeignKey(editable=False, help_text='A roll-up of the fields (role_definition, content_type).', null=True, on_delete=django.db.models.deletion.CASCADE, to='dab_rbac.objectrole'), + ), + migrations.AlterField( + model_name='roleteamassignment', + name='team', + field=models.ForeignKey(help_text='The team that receives permissions.', on_delete=django.db.models.deletion.CASCADE, related_name='role_assignments', to=settings.ANSIBLE_BASE_TEAM_MODEL), + ), + migrations.AlterField( + model_name='roleuserassignment', + name='content_type', + field=models.ForeignKey(help_text='The content type this applies to.', null=True, on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype'), + ), + migrations.AlterField( + model_name='roleuserassignment', + name='object_role', + field=models.ForeignKey(editable=False, help_text='A roll-up of the fields (role_definition, content_type).', null=True, on_delete=django.db.models.deletion.CASCADE, to='dab_rbac.objectrole'), + ), + migrations.AlterField( + model_name='roleuserassignment', + name='user', + field=models.ForeignKey(help_text='The user this role is assigned to.', on_delete=django.db.models.deletion.CASCADE, related_name='role_assignments', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/ansible_base/rbac/models.py b/ansible_base/rbac/models.py index 430ea32a8..703b8b559 100644 --- a/ansible_base/rbac/models.py +++ b/ansible_base/rbac/models.py @@ -30,9 +30,18 @@ class DABPermission(models.Model): "This is a minimal copy of auth.Permission for internal use" - name = models.CharField("name", max_length=255) - content_type = models.ForeignKey(ContentType, models.CASCADE, verbose_name="content type") - codename = models.CharField("codename", max_length=100) + name = models.CharField("name", max_length=255, help_text=_("The name of this permission.")) + content_type = models.ForeignKey(ContentType, models.CASCADE, verbose_name="content type", help_text=_("The content type this permission will apply to.")) + codename = models.CharField( + "codename", + max_length=100, + help_text=_( + ''' + A codename for the permission, in the format {action}_{model_name}. + Where action is typically the view set action (view/list/etc) from Django rest framework. + ''' + ), + ) class Meta: app_label = 'dab_rbac' @@ -155,9 +164,11 @@ class Meta: ordering = ['id'] verbose_name_plural = _('role_definition') - name = models.TextField(db_index=True, unique=True) - description = models.TextField(blank=True) - managed = models.BooleanField(default=False, editable=False) # pulp definition of Role uses locked + name = models.TextField(db_index=True, unique=True, help_text=_("The name of this role.")) + description = models.TextField(blank=True, help_text=_("A description of this role.")) + managed = models.BooleanField( + default=False, editable=False, help_text=_("Is this role managed by the system (not changeable by the users).") + ) # pulp definition of Role uses locked permissions = models.ManyToManyField('dab_rbac.DABPermission', related_name='role_definitions') content_type = models.ForeignKey( ContentType, @@ -330,8 +341,10 @@ class Meta: abstract = True # role_definition set on child models to set appropriate help_text and related_name - content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) - object_id = models.TextField(null=False) + content_type = models.ForeignKey( + ContentType, on_delete=models.CASCADE, help_text=_("The content type of the subject of permission assignments. Duplicated from related RoleDefinition.") + ) + object_id = models.TextField(null=False, help_text=_("The database primary key of the subject of permission assignments.")) content_object = GenericForeignKey('content_type', 'object_id') @classmethod @@ -375,11 +388,13 @@ class AssignmentBase(ImmutableCommonModel, ObjectRoleFields): both models are immutable, making caching easy. """ - object_role = models.ForeignKey('dab_rbac.ObjectRole', on_delete=models.CASCADE, editable=False, null=True) + object_role = models.ForeignKey( + 'dab_rbac.ObjectRole', on_delete=models.CASCADE, editable=False, null=True, help_text=_("A roll-up of the fields (role_definition, content_type).") + ) object_id = models.TextField( null=True, blank=True, help_text=_('The primary key of the object this assignment applies to; null value indicates system-wide assignment.') ) - content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, null=True) + content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, null=True, help_text=_("The content type this applies to.")) # object_role is internal, and not shown in serializer # content_type does not have a link, and ResourceType will be used in lieu sometime @@ -406,7 +421,9 @@ class RoleUserAssignment(AssignmentBase): help_text=_("The role definition which defines permissions conveyed by this assignment."), related_name='user_assignments', ) - user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='role_assignments') + user = models.ForeignKey( + settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='role_assignments', help_text=_("The user this role is assigned to.") + ) router_basename = 'roleuserassignment' class Meta: @@ -430,7 +447,9 @@ class RoleTeamAssignment(AssignmentBase): help_text=_("The role definition which defines permissions conveyed by this assignment."), related_name='team_assignments', ) - team = models.ForeignKey(settings.ANSIBLE_BASE_TEAM_MODEL, on_delete=models.CASCADE, related_name='role_assignments') + team = models.ForeignKey( + settings.ANSIBLE_BASE_TEAM_MODEL, on_delete=models.CASCADE, related_name='role_assignments', help_text=_("The team that receives permissions.") + ) router_basename = 'roleteamassignment' class Meta: @@ -641,7 +660,7 @@ def save(self, *args, **kwargs): codename = models.TextField(null=False, help_text=_("The name of the permission, giving the action and the model, from the Django Permission model.")) # NOTE: we do not form object_id and content_type into a content_object, following from AWX practice # this can be relaxed as we have comparative performance testing to confirm doing so does not affect permissions - content_type_id = models.PositiveIntegerField(null=False) + content_type_id = models.PositiveIntegerField(null=False, help_text=_("The related content type id.")) def obj_perm_id(self): "Used for in-memory hashing of the type of object permission this represents" @@ -709,7 +728,7 @@ class Meta(RoleEvaluationMeta): related_name='permission_partials', help_text=_("The object role that grants this form of permission."), ) - object_id = models.PositiveIntegerField(null=False) + object_id = models.PositiveIntegerField(null=False, help_text=_("The id of the object that the related role gives the related permission to.")) class RoleEvaluationUUID(RoleEvaluationFields): @@ -727,7 +746,7 @@ class Meta(RoleEvaluationMeta): related_name='permission_partials_uuid', help_text=_("The object role that grants this form of permission."), ) - object_id = models.UUIDField(null=False) + object_id = models.UUIDField(null=False, help_text=_("The object UUID this role evaluation will be applied to.")) def get_evaluation_model(cls): diff --git a/ansible_base/resource_registry/migrations/0007_alter_resource_ansible_id_and_more.py b/ansible_base/resource_registry/migrations/0007_alter_resource_ansible_id_and_more.py new file mode 100644 index 000000000..1caf8074c --- /dev/null +++ b/ansible_base/resource_registry/migrations/0007_alter_resource_ansible_id_and_more.py @@ -0,0 +1,62 @@ +# Generated by Django 4.2.16 on 2024-11-26 01:48 + +import ansible_base.resource_registry.models.service_identifier +from django.db import migrations, models +import django.db.models.deletion +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + ('contenttypes', '0002_remove_content_type_name'), + ('dab_resource_registry', '0006_alter_resource_service_id'), + ] + + operations = [ + migrations.AlterField( + model_name='resource', + name='ansible_id', + field=models.UUIDField(db_index=True, default=uuid.uuid4, help_text='A unique ID identifying this resource by the resource server.', unique=True), + ), + migrations.AlterField( + model_name='resource', + name='content_type', + field=models.ForeignKey(help_text='The content type for this resource.', on_delete=django.db.models.deletion.CASCADE, related_name='resources', to='contenttypes.contenttype'), + ), + migrations.AlterField( + model_name='resource', + name='is_partially_migrated', + field=models.BooleanField(default=False, help_text="A flag indicating that the resource has been copied into the resource server, but the service_id hasn't been updated yet."), + ), + migrations.AlterField( + model_name='resource', + name='name', + field=models.CharField(help_text='The name of this resource.', max_length=512, null=True), + ), + migrations.AlterField( + model_name='resource', + name='object_id', + field=models.TextField(db_index=True, help_text='The object id for this resource.'), + ), + migrations.AlterField( + model_name='resource', + name='service_id', + field=models.UUIDField(default=ansible_base.resource_registry.models.service_identifier.service_id, help_text='ID of the service responsible for managing this resource.'), + ), + migrations.AlterField( + model_name='resourcetype', + name='content_type', + field=models.OneToOneField(help_text='The content type for this resource type.', on_delete=django.db.models.deletion.CASCADE, related_name='resource_type', to='contenttypes.contenttype'), + ), + migrations.AlterField( + model_name='resourcetype', + name='externally_managed', + field=models.BooleanField(help_text='Is this resource type managed externally from this service.'), + ), + migrations.AlterField( + model_name='resourcetype', + name='name', + field=models.CharField(db_index=True, editable=False, help_text='The name of this resource type.', max_length=256, unique=True), + ), + ] diff --git a/ansible_base/resource_registry/models/resource.py b/ansible_base/resource_registry/models/resource.py index d216362e0..2cc399b0c 100644 --- a/ansible_base/resource_registry/models/resource.py +++ b/ansible_base/resource_registry/models/resource.py @@ -23,9 +23,11 @@ def __init__(self, *args, **kwargs): self.resource_registry = get_registry() - content_type = models.OneToOneField(ContentType, on_delete=models.CASCADE, related_name="resource_type", unique=True) - externally_managed = models.BooleanField() - name = models.CharField(max_length=256, unique=True, db_index=True, editable=False, blank=False, null=False) + content_type = models.OneToOneField( + ContentType, on_delete=models.CASCADE, related_name="resource_type", unique=True, help_text=_("The content type for this resource type.") + ) + externally_managed = models.BooleanField(help_text=_("Is this resource type managed externally from this service.")) + name = models.CharField(max_length=256, unique=True, db_index=True, editable=False, blank=False, null=False, help_text=_("The name of this resource type.")) @property def serializer_class(self): @@ -40,28 +42,28 @@ def get_resource_config(self): class Resource(models.Model): - content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, related_name="resources") + content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, related_name="resources", help_text=_("The content type for this resource.")) # this has to accommodate integer and UUID object IDs - object_id = models.TextField(null=False, db_index=True) + object_id = models.TextField(null=False, db_index=True, help_text=_("The object id for this resource.")) content_object = GenericForeignKey('content_type', 'object_id') service_id = models.UUIDField( null=False, default=service_id, - help_text="The ID of the service responsible for managing this resource.", + help_text=_("ID of the service responsible for managing this resource."), ) # we're not using this as the primary key because the ansible_id can change if the object is # externally managed. - ansible_id = models.UUIDField(default=uuid.uuid4, db_index=True, unique=True) + ansible_id = models.UUIDField(default=uuid.uuid4, db_index=True, unique=True, help_text=_("A unique ID identifying this resource by the resource server.")) # human readable name for the resource - name = models.CharField(max_length=512, null=True) + name = models.CharField(max_length=512, null=True, help_text=_("The name of this resource.")) is_partially_migrated = models.BooleanField( default=False, - help_text="This gets set to True when a resource has been copied into the resource server, but the service_id hasn't been updated yet.", + help_text=_("A flag indicating that the resource has been copied into the resource server, but the service_id hasn't been updated yet."), ) def summary_fields(self):