From 57d0ba7ae20eaec7e3613a7d8d10c1cb21f86a67 Mon Sep 17 00:00:00 2001 From: Marc Lebreuil Date: Sun, 24 Mar 2024 12:00:46 +0000 Subject: [PATCH 1/8] Initialize v2.0.11 Changes to be committed: modified: pyproject.toml modified: src/netbox_contract/__init__.py --- pyproject.toml | 2 +- src/netbox_contract/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index ac7d2bf..e52173d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "netbox-contract" -version = "2.0.10" +version = "2.0.11" authors = [ { name="Marc Lebreuil", email="marc@famillelebreuil.net" }, ] diff --git a/src/netbox_contract/__init__.py b/src/netbox_contract/__init__.py index 51d6e7f..802c7a7 100644 --- a/src/netbox_contract/__init__.py +++ b/src/netbox_contract/__init__.py @@ -5,7 +5,7 @@ class ContractsConfig(PluginConfig): name = 'netbox_contract' verbose_name = 'Netbox contract' description = 'Contract management plugin for Netbox' - version = '2.0.10' + version = '2.0.11' author = 'Marc Lebreuil' author_email = 'marc@famillelebreuil.net' base_url = 'contracts' From ca9c7edc5ec4ce91ecb8f413ae68e80415b114ed Mon Sep 17 00:00:00 2001 From: Marc Lebreuil Date: Fri, 29 Mar 2024 10:31:50 +0000 Subject: [PATCH 2/8] contract - api - external partie change New object choice were not taken into account Changes to be committed: modified: src/netbox_contract/api/serializers.py new file: src/netbox_contract/migrations/0024_remove_contract_external_partie.py modified: src/netbox_contract/models.py deleted: src/netbox_contract/templates/contact_assignments_bottom.html --- src/netbox_contract/api/serializers.py | 17 ++++- .../0024_remove_contract_external_partie.py | 19 ++++++ src/netbox_contract/models.py | 7 --- .../templates/contact_assignments_bottom.html | 63 ------------------- 4 files changed, 33 insertions(+), 73 deletions(-) create mode 100644 src/netbox_contract/migrations/0024_remove_contract_external_partie.py delete mode 100644 src/netbox_contract/templates/contact_assignments_bottom.html diff --git a/src/netbox_contract/api/serializers.py b/src/netbox_contract/api/serializers.py index 2cfbff9..9a2b3a3 100644 --- a/src/netbox_contract/api/serializers.py +++ b/src/netbox_contract/api/serializers.py @@ -54,10 +54,10 @@ class ContractSerializer(NetBoxModelSerializer): url = serializers.HyperlinkedIdentityField( view_name='plugins-api:netbox_contract-api:contract-detail' ) - # circuit= NestedCircuitSerializer(many=True, required=False) - external_partie = NestedServiceProviderSerializer(many=False) parent = NestedContractSerializer(many=False, required=False) tenant = NestedTenantSerializer(many=False, required=False) + external_partie_object_type = ContentTypeField(queryset=ContentType.objects.all()) + external_partie_object = serializers.SerializerMethodField(read_only=True) class Meta: model = Contract @@ -66,7 +66,9 @@ class Meta: 'url', 'display', 'name', - 'external_partie', + 'external_partie_object_type', + 'external_partie_object_id', + 'external_partie_object', 'external_reference', 'internal_partie', 'tenant', @@ -88,6 +90,15 @@ class Meta: 'last_updated', ) + @swagger_serializer_method(serializer_or_field=serializers.JSONField) + def get_external_partie_object(self, instance): + serializer = get_serializer_for_model( + instance.external_partie_object_type.model_class(), + prefix=NESTED_SERIALIZER_PREFIX, + ) + context = {'request': self.context['request']} + return serializer(instance.external_partie_object, context=context).data + class InvoiceSerializer(NetBoxModelSerializer): url = serializers.HyperlinkedIdentityField( diff --git a/src/netbox_contract/migrations/0024_remove_contract_external_partie.py b/src/netbox_contract/migrations/0024_remove_contract_external_partie.py new file mode 100644 index 0000000..20b20b1 --- /dev/null +++ b/src/netbox_contract/migrations/0024_remove_contract_external_partie.py @@ -0,0 +1,19 @@ +# Generated by Django 4.2.10 on 2024-03-29 09:49 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ( + 'netbox_contract', + '0023_rename_contractassignement_contractassignment_and_more', + ), + ] + + operations = [ + migrations.RemoveField( + model_name='contract', + name='external_partie', + ), + ] diff --git a/src/netbox_contract/models.py b/src/netbox_contract/models.py index 956912a..10710c8 100644 --- a/src/netbox_contract/models.py +++ b/src/netbox_contract/models.py @@ -78,13 +78,6 @@ def get_absolute_url(self): class Contract(NetBoxModel): name = models.CharField(max_length=100) - external_partie = models.ForeignKey( - to=ServiceProvider, - on_delete=models.CASCADE, - related_name='contracts', - blank=True, - null=True, - ) external_partie_object_type = models.ForeignKey( to=ContentType, on_delete=models.CASCADE, blank=True, null=True diff --git a/src/netbox_contract/templates/contact_assignments_bottom.html b/src/netbox_contract/templates/contact_assignments_bottom.html deleted file mode 100644 index 6c3047b..0000000 --- a/src/netbox_contract/templates/contact_assignments_bottom.html +++ /dev/null @@ -1,63 +0,0 @@ -{% load helpers %} - -
-
Contacts
-
- {% with contacts=object.contacts.all %} - {% if contacts.exists %} - - - - - - - - - - {% for contact in contacts %} - - - - - - - - - {% endfor %} -
NameRolePriorityPhoneEmail
{{ contact.contact|linkify }}{{ contact.role|placeholder }}{{ contact.get_priority_display|placeholder }} - {% if contact.contact.phone %} - {{ contact.contact.phone }} - {% else %} - {{ ''|placeholder }} - {% endif %} - - {% if contact.contact.email %} - {{ contact.contact.email }} - {% else %} - {{ ''|placeholder }} - {% endif %} - - {% if perms.tenancy.change_contactassignment %} - - - - {% endif %} - {% if perms.tenancy.delete_contactassignment %} - - - - {% endif %} -
- {% else %} -
None
- {% endif %} - {% endwith %} -
- {% if perms.tenancy.add_contactassignment %} - - {% endif %} -
\ No newline at end of file From 5ca0a695a9d88f8d46f48872706664f676b6d7e3 Mon Sep 17 00:00:00 2001 From: Marc Lebreuil Date: Fri, 29 Mar 2024 10:34:47 +0000 Subject: [PATCH 3/8] Readme update issue 115 Changes to be committed: modified: README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index b23c39e..898230f 100644 --- a/README.md +++ b/README.md @@ -135,4 +135,6 @@ Add support for Netbox 3.5 which become the minimum version supported to accomod #### version 2.0.10 * [107](https://github.com/mlebreuil/netbox-contract/issues/107) Add the contacts tab to the service provider detail view. * [111](https://github.com/mlebreuil/netbox-contract/issues/111) Correct assignment spelling. +#### version 2.0.10 +* [115](https://github.com/mlebreuil/netbox-contract/issues/115) API correction for contract external partie From 74cc1708a185cd7f871110310224b7af3a3cf00f Mon Sep 17 00:00:00 2001 From: Marc Lebreuil Date: Fri, 29 Mar 2024 10:55:17 +0000 Subject: [PATCH 4/8] Tenant and accounting dimensions fields optional Changes to be committed: modified: README.md modified: src/netbox_contract/forms.py --- README.md | 1 + src/netbox_contract/forms.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 898230f..c148945 100644 --- a/README.md +++ b/README.md @@ -137,4 +137,5 @@ Add support for Netbox 3.5 which become the minimum version supported to accomod * [111](https://github.com/mlebreuil/netbox-contract/issues/111) Correct assignment spelling. #### version 2.0.10 * [115](https://github.com/mlebreuil/netbox-contract/issues/115) API correction for contract external partie +* [117](https://github.com/mlebreuil/netbox-contract/issues/117) Tenant and accounting dimensions optional diff --git a/src/netbox_contract/forms.py b/src/netbox_contract/forms.py index 53bcdc4..432dbe9 100644 --- a/src/netbox_contract/forms.py +++ b/src/netbox_contract/forms.py @@ -55,9 +55,9 @@ class ContractForm(NetBoxModelForm): widget=HTMXSelect(), ) external_partie_object = forms.ModelChoiceField(queryset=None) - tenant = DynamicModelChoiceField(queryset=Tenant.objects.all()) + tenant = DynamicModelChoiceField(queryset=Tenant.objects.all(), required=False) parent = DynamicModelChoiceField(queryset=Contract.objects.all(), required=False) - accounting_dimensions = Dimensions() + accounting_dimensions = Dimensions(required=False) def __init__(self, *args, **kwargs): initial = kwargs.get('initial', None) From 17007e8c9cb7e169e61bdaeafa8e2154ee7953f0 Mon Sep 17 00:00:00 2001 From: Marc Lebreuil Date: Fri, 29 Mar 2024 14:20:52 +0000 Subject: [PATCH 5/8] Add settings to toggle visibility and required For selected invoices and contract fields Changes to be committed: modified: README.md modified: src/netbox_contract/__init__.py modified: src/netbox_contract/forms.py modified: src/netbox_contract/templates/netbox_contract/contract.html modified: src/netbox_contract/templates/netbox_contract/invoice.html modified: src/netbox_contract/views.py --- README.md | 17 +++++++++++--- src/netbox_contract/__init__.py | 4 ++++ src/netbox_contract/forms.py | 22 +++++++++++++++++++ .../templates/netbox_contract/contract.html | 14 ++++++++++++ .../templates/netbox_contract/invoice.html | 2 ++ src/netbox_contract/views.py | 8 +++++++ 6 files changed, 64 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c148945..34bb890 100644 --- a/README.md +++ b/README.md @@ -30,8 +30,9 @@ PLUGINS = [ ] ``` -Customize the location of the plugin's menu: +### Customize the plugin +The following configurationitems can be set: ```python # configuration.py @@ -42,13 +43,23 @@ PLUGINS_CONFIG = { "account": "", "project": "", "cost center": "" - } + }, + 'mandatory_contract_fields': ['accounting_dimensions'], + 'hidden_contract_fields': [], + 'mandatory_invoice_fields': ['accounting_dimensions'], + 'hidden_invoice_fields': [], } } ``` -Customize fields choices. +* top_level_menu : If "Contracts" appears under the "Plugins" menu item or on its own +* default_accounting_dimensions: The accounting dimensions which will appear in the field' background when empty. +* mandatory_contract_fields, mandatory_invoice_fields: Fields which are not required by default and can be set as such. The list of fields is at the bottom of the contract import form. +* hidden_contract_fields, hidden_invoice_fields: List of fields to be hidden. Fields should not be required to be hidden. + +### Customize the plugin fields choices + Internal partie reference the legal entity of your organization that is a partie to the contract. ```python diff --git a/src/netbox_contract/__init__.py b/src/netbox_contract/__init__.py index 802c7a7..ce676e6 100644 --- a/src/netbox_contract/__init__.py +++ b/src/netbox_contract/__init__.py @@ -18,6 +18,10 @@ class ContractsConfig(PluginConfig): 'project': '', 'cost center': '', }, + 'mandatory_contract_fields': [], + 'hidden_contract_fields': [], + 'mandatory_invoice_fields': [], + 'hidden_invoice_fields': [], } diff --git a/src/netbox_contract/forms.py b/src/netbox_contract/forms.py index 432dbe9..6799b6a 100644 --- a/src/netbox_contract/forms.py +++ b/src/netbox_contract/forms.py @@ -103,6 +103,15 @@ def __init__(self, *args, **kwargs): # 'accounting_dimensions' # ].widget.attrs['placeholder'] = '{"key": "value"}' + # Initialise fields settings + mandatory_fields = plugin_settings.get('mandatory_contract_fields') + for field in mandatory_fields: + self.fields[field].required = True + hidden_fields = plugin_settings.get('hidden_contract_fields') + for field in hidden_fields: + if not self.fields[field].required: + self.fields[field].widget = forms.HiddenInput() + class Meta: model = Contract fields = ( @@ -138,6 +147,19 @@ class InvoiceForm(NetBoxModelForm): contracts = DynamicModelMultipleChoiceField( queryset=Contract.objects.all(), required=False ) + accounting_dimensions = Dimensions(required=False) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + # Initialise fields settings + mandatory_fields = plugin_settings.get('mandatory_invoice_fields') + for field in mandatory_fields: + self.fields[field].required = True + hidden_fields = plugin_settings.get('hidden_invoice_fields') + for field in hidden_fields: + if not self.fields[field].required: + self.fields[field].widget = forms.HiddenInput() class Meta: model = Invoice diff --git a/src/netbox_contract/templates/netbox_contract/contract.html b/src/netbox_contract/templates/netbox_contract/contract.html index e05f663..7a7cdad 100644 --- a/src/netbox_contract/templates/netbox_contract/contract.html +++ b/src/netbox_contract/templates/netbox_contract/contract.html @@ -38,34 +38,46 @@
Contract
Internal partie {{ object.internal_partie }} + {% if not 'tenant' in hidden_fields %} Tenant {{ object.tenant }} + {% endif %} + {% if not 'start_date' in hidden_fields %} Start date {{ object.start_date }} + {% endif %} + {% if not 'end_date' in hidden_fields %} End date {{ object.end_date }} + {% endif %} + {% if not 'initial_term' in hidden_fields %} Initial term {{ object.initial_term }} + {% endif %} + {% if not 'renewal_term' in hidden_fields %} Renewal term {{ object.renewal_term }} + {% endif %} Currency {{ object.currency }} + {% if not 'accounting_dimensions' in hidden_fields %} Accounting dimensions {{ object.accounting_dimensions }} + {% endif %} Monthly recuring costs {{ object.mrc }} @@ -78,12 +90,14 @@
Contract
Invoice frequency {{ object.invoice_frequency }} + {% if not 'parent' in hidden_fields %} Parent {{ object.parent.name }} + {% endif %} {% if object.documents %} Documents diff --git a/src/netbox_contract/templates/netbox_contract/invoice.html b/src/netbox_contract/templates/netbox_contract/invoice.html index ddd5ea6..1043c8a 100644 --- a/src/netbox_contract/templates/netbox_contract/invoice.html +++ b/src/netbox_contract/templates/netbox_contract/invoice.html @@ -36,10 +36,12 @@
Invoices
Currency {{ object.currency }} + {% if not 'accounting_dimensions' in hidden_fields %} Accounting dimensions {{ object.accounting_dimensions }} + {% endif %} Amount {{ object.amount }} diff --git a/src/netbox_contract/views.py b/src/netbox_contract/views.py index b9fad4b..4b3dc09 100644 --- a/src/netbox_contract/views.py +++ b/src/netbox_contract/views.py @@ -2,6 +2,7 @@ from circuits.models import Circuit from dateutil.relativedelta import relativedelta +from django.conf import settings from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ObjectDoesNotExist from django.shortcuts import get_object_or_404, render @@ -15,6 +16,8 @@ from . import filtersets, forms, tables from .models import Contract, ContractAssignment, Invoice, ServiceProvider +plugin_settings = settings.PLUGINS_CONFIG['netbox_contract'] + # ServiceProvider views @@ -129,7 +132,10 @@ def get_extra_context(self, request, instance): childs_table = tables.ContractListBottomTable(instance.childs.all()) childs_table.configure(request) + hidden_fields = plugin_settings.get('hidden_contract_fields') + return { + 'hidden_fields': hidden_fields, 'invoices_table': invoices_table, 'assignments_table': assignments_table, 'childs_table': childs_table, @@ -212,8 +218,10 @@ class InvoiceView(generic.ObjectView): def get_extra_context(self, request, instance): contracts_table = tables.ContractListTable(instance.contracts.all()) contracts_table.configure(request) + hidden_fields = plugin_settings.get('hidden_invoice_fields') return { + 'hidden_fields': hidden_fields, 'contracts_table': contracts_table, } From 74fc161a5073e60efb3873725ce4b0c89f5b88f8 Mon Sep 17 00:00:00 2001 From: Marc Lebreuil Date: Fri, 29 Mar 2024 18:08:01 +0000 Subject: [PATCH 6/8] Add read only caluclated yrc to contract Changes to be committed: modified: README.md modified: src/netbox_contract/api/serializers.py modified: src/netbox_contract/api/views.py modified: src/netbox_contract/tables.py modified: src/netbox_contract/templates/netbox_contract/contract.html modified: src/netbox_contract/views.py --- README.md | 1 + src/netbox_contract/api/serializers.py | 2 ++ src/netbox_contract/api/views.py | 5 ++++- src/netbox_contract/tables.py | 4 +++- src/netbox_contract/templates/netbox_contract/contract.html | 4 ++++ src/netbox_contract/views.py | 5 +++-- 6 files changed, 17 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 34bb890..b247b7a 100644 --- a/README.md +++ b/README.md @@ -149,4 +149,5 @@ Add support for Netbox 3.5 which become the minimum version supported to accomod #### version 2.0.10 * [115](https://github.com/mlebreuil/netbox-contract/issues/115) API correction for contract external partie * [117](https://github.com/mlebreuil/netbox-contract/issues/117) Tenant and accounting dimensions optional +* [119](https://github.com/mlebreuil/netbox-contract/issues/119) Add a Yearly recuring cost, read only, calculated field for contract diff --git a/src/netbox_contract/api/serializers.py b/src/netbox_contract/api/serializers.py index 9a2b3a3..cd127c1 100644 --- a/src/netbox_contract/api/serializers.py +++ b/src/netbox_contract/api/serializers.py @@ -54,6 +54,7 @@ class ContractSerializer(NetBoxModelSerializer): url = serializers.HyperlinkedIdentityField( view_name='plugins-api:netbox_contract-api:contract-detail' ) + yrc = serializers.DecimalField(max_digits=10, decimal_places=2, read_only=True) parent = NestedContractSerializer(many=False, required=False) tenant = NestedTenantSerializer(many=False, required=False) external_partie_object_type = ContentTypeField(queryset=ContentType.objects.all()) @@ -80,6 +81,7 @@ class Meta: 'currency', 'accounting_dimensions', 'mrc', + 'yrc', 'nrc', 'invoice_frequency', 'comments', diff --git a/src/netbox_contract/api/views.py b/src/netbox_contract/api/views.py index 8d8a520..35c06fb 100644 --- a/src/netbox_contract/api/views.py +++ b/src/netbox_contract/api/views.py @@ -1,3 +1,4 @@ +from django.db.models import F from netbox.api.viewsets import NetBoxModelViewSet from .. import filtersets, models @@ -10,7 +11,9 @@ class ContractViewSet(NetBoxModelViewSet): - queryset = models.Contract.objects.prefetch_related('parent', 'circuit', 'tags') + queryset = models.Contract.objects.prefetch_related( + 'parent', 'circuit', 'tags' + ).annotate(yrc=F('mrc') * 12) serializer_class = ContractSerializer diff --git a/src/netbox_contract/tables.py b/src/netbox_contract/tables.py index 2c9ee27..e73adf3 100644 --- a/src/netbox_contract/tables.py +++ b/src/netbox_contract/tables.py @@ -89,8 +89,9 @@ class Meta(NetBoxTable.Meta): class ContractListTable(NetBoxTable): name = tables.Column(linkify=True) - external_partie_object = tables.Column(linkify=True) + external_partie_object = tables.Column(verbose_name='External partie', linkify=True) parent = tables.Column(linkify=True) + yrc = tables.Column(verbose_name='Yerly recuring costs') class Meta(NetBoxTable.Meta): model = Contract @@ -111,6 +112,7 @@ class Meta(NetBoxTable.Meta): 'currency', 'accounting_dimensions', 'mrc', + 'yrc', 'nrc', 'invoice_frequency', 'documents', diff --git a/src/netbox_contract/templates/netbox_contract/contract.html b/src/netbox_contract/templates/netbox_contract/contract.html index 7a7cdad..99eee5b 100644 --- a/src/netbox_contract/templates/netbox_contract/contract.html +++ b/src/netbox_contract/templates/netbox_contract/contract.html @@ -82,6 +82,10 @@
Contract
Monthly recuring costs {{ object.mrc }} + + Yearly recuring costs + {{ object.yrc }} + Non recuring costs {{ object.nrc }} diff --git a/src/netbox_contract/views.py b/src/netbox_contract/views.py index 4b3dc09..c805e65 100644 --- a/src/netbox_contract/views.py +++ b/src/netbox_contract/views.py @@ -5,6 +5,7 @@ from django.conf import settings from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ObjectDoesNotExist +from django.db.models import F from django.shortcuts import get_object_or_404, render from netbox.views import generic from netbox.views.generic.utils import get_prerequisite_model @@ -120,7 +121,7 @@ class ContractAssignmentBulkImportView(generic.BulkImportView): @register_model_view(Contract) class ContractView(generic.ObjectView): - queryset = Contract.objects.all() + queryset = Contract.objects.annotate(yrc=F('mrc') * 12) def get_extra_context(self, request, instance): invoices_table = tables.InvoiceListTable(instance.invoices.all()) @@ -143,7 +144,7 @@ def get_extra_context(self, request, instance): class ContractListView(generic.ObjectListView): - queryset = Contract.objects.all() + queryset = Contract.objects.annotate(yrc=F('mrc') * 12) table = tables.ContractListTable filterset = filtersets.ContractFilterSet filterset_form = forms.ContractFilterSetForm From 00f4844b9a78a5b02032b2659150efdb4c4f7d15 Mon Sep 17 00:00:00 2001 From: Marc Lebreuil Date: Fri, 29 Mar 2024 18:19:20 +0000 Subject: [PATCH 7/8] Quick search only filters active contracts On branch 105-quick-search-only-active-contracts Changes to be committed: modified: src/netbox_contract/filtersets.py --- src/netbox_contract/filtersets.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/netbox_contract/filtersets.py b/src/netbox_contract/filtersets.py index cc62ac2..1e6c1bf 100644 --- a/src/netbox_contract/filtersets.py +++ b/src/netbox_contract/filtersets.py @@ -14,6 +14,7 @@ def search(self, queryset, name, value): Q(name__icontains=value) | Q(external_reference__icontains=value) | Q(comments__icontains=value) + | Q(status__iexact='Active') ) From 8bb83f4d32242612239f8ea63b0e1a32cad3e7f3 Mon Sep 17 00:00:00 2001 From: Marc Lebreuil Date: Fri, 29 Mar 2024 18:22:11 +0000 Subject: [PATCH 8/8] Update README Changes to be committed: modified: README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b247b7a..a245f25 100644 --- a/README.md +++ b/README.md @@ -146,8 +146,9 @@ Add support for Netbox 3.5 which become the minimum version supported to accomod #### version 2.0.10 * [107](https://github.com/mlebreuil/netbox-contract/issues/107) Add the contacts tab to the service provider detail view. * [111](https://github.com/mlebreuil/netbox-contract/issues/111) Correct assignment spelling. -#### version 2.0.10 +#### version 2.0.11 * [115](https://github.com/mlebreuil/netbox-contract/issues/115) API correction for contract external partie * [117](https://github.com/mlebreuil/netbox-contract/issues/117) Tenant and accounting dimensions optional * [119](https://github.com/mlebreuil/netbox-contract/issues/119) Add a Yearly recuring cost, read only, calculated field for contract +* [15](https://github.com/mlebreuil/netbox-contract/issues/105) AQuick serach limited to active contracts