Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Attributes in service + service details page + display org when filtering by team #740

Merged
merged 3 commits into from
Nov 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/manual/resource_tracking/attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ For example, to manage Kubernetes namespace (as Resource Group) you could create
- limit.cpu
- limit.memory

Attributes can be linked to services. Then can be used by operation survey fields.

## Attributes and quota

Attributes are defined in the resource tracking and then
Expand Down
6 changes: 5 additions & 1 deletion profiles/filters/quota.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,8 @@
class QuotaFilter(SquestFilter):
class Meta:
model = Quota
fields = ['scope', 'attribute_definition']
fields = ['scope', 'attribute_definition', 'attribute_definition__services']

def __init__(self, *args, **kwargs):
super(QuotaFilter, self).__init__(*args, **kwargs)
self.filters['attribute_definition__services'].field.label = 'Services'
2 changes: 1 addition & 1 deletion resource_tracker_v2/filters/attribute_definition_filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@
class AttributeDefinitionFilter(SquestFilter):
class Meta:
model = AttributeDefinition
fields = ['name']
fields = ['name', 'services']
16 changes: 13 additions & 3 deletions resource_tracker_v2/forms/attribute_definition_form.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
from django.forms import CharField, TextInput
from django.forms import ModelMultipleChoiceField

from Squest.utils.squest_model_form import SquestModelForm
from resource_tracker_v2.models import AttributeDefinition
from service_catalog.models import Service


class AttributeDefinitionForm(SquestModelForm):
class Meta:
model = AttributeDefinition
fields = ["name", "description"]

name = CharField(label="Name", widget=TextInput())
description = CharField(label="Description", widget=TextInput(), required=False)
services = ModelMultipleChoiceField(queryset=Service.objects.all(), required=False)

def __init__(self, *args, **kwargs):
super(AttributeDefinitionForm, self).__init__(*args, **kwargs)
if self.instance.id:
self.fields['services'].initial = [service for service in self.instance.services.all()]

def save(self, commit=True):
attribute_definition = super().save(commit)
attribute_definition.services.set(self.cleaned_data['services'])
return attribute_definition
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,11 @@ class Meta:
def validate(self, data):
super().validate(data)
field_type = self.instance.type if self.instance is not None else None
service_attribute_definitions = self.instance.operation.service.attribute_definitions.all() if self.instance is not None else []
attribute_definition = self.instance.attribute_definition if self.instance is not None else None
attribute_definition = data.get("attribute_definition") if 'attribute_definition' in data.keys() else attribute_definition
if attribute_definition and field_type != 'integer':
raise ValidationError({"attribute_definition": f"Attribute definition must be linked to an integer field"})
if attribute_definition and attribute_definition not in service_attribute_definitions:
raise ValidationError({"attribute_definition": f"Attribute definition must be linked to an Service's attribute definitions"})
return data
6 changes: 2 additions & 4 deletions service_catalog/filters/instance_filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from django.db.models import Q
from django.forms import CheckboxInput, SelectMultiple
from django.utils.translation import gettext_lazy as _
from django_filters import MultipleChoiceFilter, BooleanFilter, BaseInFilter, CharFilter
from django_filters import MultipleChoiceFilter, BooleanFilter, BaseInFilter, CharFilter, ModelMultipleChoiceFilter

from Squest.utils.squest_filter import SquestFilter
from profiles.models import Scope, AbstractScope
Expand All @@ -30,9 +30,7 @@ class Meta:
fields = ['id', 'name', 'requester', 'service', 'state', 'quota_scope']

state = MultipleChoiceFilter(choices=InstanceState.choices)
quota_scope = MultipleChoiceFilter(
choices=AbstractScope.objects.filter(id__in=Scope.objects.values_list("id", flat=True)).values_list("id",
"name"))
quota_scope = ModelMultipleChoiceFilter(queryset=Scope.objects.all())
service = MultipleChoiceFilter(choices=Service.objects.values_list("id", "name"))
requester = MultipleChoiceFilter(choices=User.objects.values_list("id", "username"))
no_requesters = BooleanFilter(method='no_requester', label="No requester", widget=CheckboxInput())
Expand Down
7 changes: 3 additions & 4 deletions service_catalog/filters/request_filter.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from django.contrib.auth.models import User
from django.forms import SelectMultiple, HiddenInput
from django_filters import MultipleChoiceFilter
from django_filters import MultipleChoiceFilter, ModelMultipleChoiceFilter

from Squest.utils.squest_filter import SquestFilter
from profiles.models import Scope, AbstractScope
Expand All @@ -20,10 +20,9 @@ class Meta:
choices=[],
widget=SelectMultiple(attrs={'data-live-search': "true"}))

instance__quota_scope = MultipleChoiceFilter(
instance__quota_scope = ModelMultipleChoiceFilter(
label="Quota scope",
choices=AbstractScope.objects.filter(id__in=Scope.objects.values_list("id", flat=True)).values_list("id",
"name"),
queryset=Scope.objects.all(),
widget=SelectMultiple(attrs={'data-live-search': "true"}))

operation__type = MultipleChoiceFilter(
Expand Down
19 changes: 2 additions & 17 deletions service_catalog/forms/operation_forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,10 @@
from service_catalog.models import Operation


class ServiceOperationForm(SquestModelForm):
def __init__(self, *args, **kwargs):
self.service = kwargs.pop("service")
super(ServiceOperationForm, self).__init__(*args, **kwargs)
choice_type = [('CREATE', 'Create'), ('UPDATE', 'Update'), ('DELETE', 'Delete')]
# Default behavior
self.fields['type'].choices = choice_type
self.fields['type'].initial = self.fields['type'].choices[0]

def save(self, commit=True):
new_operation = super(ServiceOperationForm, self).save(commit=False)
new_operation.service = self.service
new_operation.save()
return new_operation


class OperationForm(SquestModelForm):
class Meta:
model = Operation
fields = ["name", "description", "job_template", "type", "process_timeout_second",
fields = ["service", "name", "description", "job_template", "type", "process_timeout_second",
"auto_accept", "auto_process", "enabled", "is_admin_operation", "extra_vars", "default_inventory_id",
"default_limits", "default_tags", "default_skip_tags", "default_credentials_ids", "default_verbosity",
"default_diff_mode", "default_job_type"]
2 changes: 1 addition & 1 deletion service_catalog/forms/service_forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,4 @@ def __init__(self, *args, **kwargs):
class Meta:
model = Service
fields = ["name", "description", "image", "enabled",
"parent_portfolio", "external_support_url", "extra_vars", "description_doc"]
"parent_portfolio", "external_support_url", "extra_vars", "description_doc", "attribute_definitions"]
14 changes: 9 additions & 5 deletions service_catalog/forms/tower_survey_field_form.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,15 @@ def __init__(self, *args, **kwargs):
validator_files = [(file_name, file_name) for file_name in PluginController.get_user_provisioned_validators()]
validator_choices.extend(validator_files)
self.fields['validators'].choices = validator_choices
if self.instance is not None and self.instance.validators is not None:
# Converting comma separated string to python list
instance_validator_as_list = self.instance.validators.split(",")
# set the current value
self.initial["validators"] = instance_validator_as_list
self.fields['attribute_definition'].choices = [(None, "---------")]
if self.instance is not None:
if self.instance.type == 'integer':
self.fields['attribute_definition'].choices += list(self.instance.operation.service.attribute_definitions.values_list('id', 'name'))
if self.instance.validators is not None:
# Converting comma separated string to python list
instance_validator_as_list = self.instance.validators.split(",")
# set the current value
self.initial["validators"] = instance_validator_as_list

def clean_validators(self):
if not self.cleaned_data['validators']:
Expand Down
19 changes: 19 additions & 0 deletions service_catalog/migrations/0031_service_attribute_definitions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Generated by Django 4.2.6 on 2023-11-15 15:58

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('resource_tracker_v2', '0005_auto_20230803_1126'),
('service_catalog', '0030_emailtemplate'),
]

operations = [
migrations.AddField(
model_name='service',
name='attribute_definitions',
field=models.ManyToManyField(blank=True, help_text='List of attributes linked to the service, they could be used on operation fields.', related_name='services', to='resource_tracker_v2.attributedefinition'),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Generated by Django 4.2.6 on 2023-11-20 11:01

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('service_catalog', '0031_service_attribute_definitions'),
]

operations = [
migrations.AlterField(
model_name='operation',
name='name',
field=models.CharField(max_length=100),
),
migrations.AlterField(
model_name='operation',
name='type',
field=models.CharField(choices=[('CREATE', 'Create'), ('UPDATE', 'Update'), ('DELETE', 'Delete')], default='CREATE', max_length=10),
),
]
5 changes: 2 additions & 3 deletions service_catalog/models/operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,12 @@


class Operation(SquestModel):
name = CharField(max_length=100, verbose_name="Operation name")
name = CharField(max_length=100)
description = CharField(max_length=500, blank=True, null=True)
type = CharField(
max_length=10,
choices=OperationType.choices,
default=OperationType.CREATE,
verbose_name="Operation type"
)
service = ForeignKey(Service, on_delete=CASCADE, related_name="operations",
related_query_name="operation")
Expand Down Expand Up @@ -55,7 +54,7 @@ def __str__(self):
return f"{self.name} ({self.service})"

def get_absolute_url(self):
return reverse(f"service_catalog:operation_details", args=[self.service.id, self.pk])
return reverse(f"service_catalog:operation_details", args=[self.pk])

def clean(self):
if self.extra_vars is None or not isinstance(self.extra_vars, dict):
Expand Down
12 changes: 7 additions & 5 deletions service_catalog/models/services.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
from django.core.exceptions import ValidationError
from django.db.models import CharField, ImageField, BooleanField, ForeignKey, SET_NULL, JSONField
from django.db.models import CharField, ImageField, BooleanField, ForeignKey, SET_NULL, JSONField, ManyToManyField
from django.db.models.signals import pre_save
from django.dispatch import receiver
from django.urls import reverse_lazy
from django.utils.translation import gettext_lazy as _

from Squest.utils.squest_model import SquestModel
Expand Down Expand Up @@ -32,9 +31,12 @@ class Meta:
)
extra_vars = JSONField(default=dict, blank=True)
description_doc = ForeignKey('service_catalog.Doc', blank=True, null=True, on_delete=SET_NULL, verbose_name='Description documentation')

def get_absolute_url(self):
return reverse_lazy('service_catalog:operation_list', kwargs={"service_id": self.id})
attribute_definitions = ManyToManyField(
'resource_tracker_v2.AttributeDefinition',
related_name="services",
blank=True,
help_text="List of attributes linked to the service, they could be used on operation fields.",
)

def can_be_enabled(self):
operation_create_list = self.operations.filter(type=OperationType.CREATE, enabled=True)
Expand Down
2 changes: 1 addition & 1 deletion service_catalog/tables/operation_tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class Meta:
auto_accept = TemplateColumn(template_name='generics/custom_columns/generic_boolean.html')
auto_process = TemplateColumn(template_name='generics/custom_columns/generic_boolean.html')
is_admin_operation = TemplateColumn(template_name='generics/custom_columns/generic_boolean.html')
actions = TemplateColumn(template_name='service_catalog/custom_columns/operation_actions.html', orderable=False)
actions = TemplateColumn(template_name='generics/custom_columns/generic_actions.html', orderable=False)


class OperationTableFromInstanceDetails(SquestTable):
Expand Down
3 changes: 2 additions & 1 deletion service_catalog/tables/service_tables.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from django_tables2 import TemplateColumn
from django_tables2 import TemplateColumn, LinkColumn

from Squest.utils.squest_table import SquestTable
from service_catalog.models import Service
Expand All @@ -9,6 +9,7 @@ class ServiceTable(SquestTable):
enabled = TemplateColumn(template_name='generics/custom_columns/generic_boolean_check.html')
operations = TemplateColumn(template_name='service_catalog/custom_columns/service_operations.html',
verbose_name="Operations", orderable=False)
name = LinkColumn()

class Meta:
model = Service
Expand Down
13 changes: 7 additions & 6 deletions service_catalog/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,14 @@
path('service/create/', views.ServiceCreateView.as_view(), name='service_create'),
path('service/<int:pk>/edit/', views.ServiceEditView.as_view(), name='service_edit'),
path('service/<int:pk>/delete/', views.ServiceDeleteView.as_view(), name='service_delete'),
path('service/<int:pk>/', views.ServiceDetailView.as_view(), name='service_details'),

# Operation CRUD
path('service/<int:service_id>/operation/', views.OperationListView.as_view(), name='operation_list'),
path('service/<int:service_id>/operation/create/', views.OperationCreateView.as_view(), name='operation_create'),
path('service/<int:service_id>/operation/<int:pk>/delete/', views.OperationDeleteView.as_view(), name='operation_delete'),
path('service/<int:service_id>/operation/<int:pk>/edit/', views.OperationEditView.as_view(), name='operation_edit'),
path('service/<int:service_id>/operation/<int:pk>/', views.OperationDetailView.as_view(), name='operation_details'),
path('operation/', views.OperationListView.as_view(), name='operation_list'),
path('operation/create/', views.OperationCreateView.as_view(), name='operation_create'),
path('operation/<int:pk>/delete/', views.OperationDeleteView.as_view(), name='operation_delete'),
path('operation/<int:pk>/edit/', views.OperationEditView.as_view(), name='operation_edit'),
path('operation/<int:pk>/', views.OperationDetailView.as_view(), name='operation_details'),

# Request operation endpoints
path('service/<int:service_id>/operation/<int:operation_id>/request/',
Expand All @@ -63,7 +64,7 @@
name='create_operation_list'),

# Edit operation survey endpoint
path('service/<int:service_id>/operation/<int:pk>/survey/', views.operation_edit_survey, name='operation_edit_survey'),
path('operation/<int:pk>/survey/', views.operation_edit_survey, name='operation_edit_survey'),

# Instance CRUD
path('instance/', views.InstanceListView.as_view(), name='instance_list'),
Expand Down
Loading
Loading