netbox-contract 2.4.2__tar.gz → 2.4.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. {netbox_contract-2.4.2/netbox_contract.egg-info → netbox_contract-2.4.4}/PKG-INFO +14 -2
  2. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/README.md +12 -0
  3. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/__init__.py +10 -1
  4. netbox_contract-2.4.4/netbox_contract/constants.py +36 -0
  5. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/filtersets.py +34 -3
  6. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/forms.py +64 -4
  7. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/tables.py +30 -0
  8. netbox_contract-2.4.4/netbox_contract/template_content.py +32 -0
  9. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/templates/netbox_contract/serviceprovider.html +10 -0
  10. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/views.py +13 -0
  11. {netbox_contract-2.4.2 → netbox_contract-2.4.4/netbox_contract.egg-info}/PKG-INFO +14 -2
  12. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/pyproject.toml +2 -2
  13. netbox_contract-2.4.2/netbox_contract/constants.py +0 -14
  14. netbox_contract-2.4.2/netbox_contract/template_content.py +0 -118
  15. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/CONTRIBUTING.md +0 -0
  16. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/LICENSE +0 -0
  17. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/MANIFEST.in +0 -0
  18. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/docs/img/accounting_dimensions.png +0 -0
  19. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/docs/img/contract.png +0 -0
  20. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/docs/img/contract_linked_objects.png +0 -0
  21. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/docs/img/invoice.png +0 -0
  22. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/docs/img/invoice_line.png +0 -0
  23. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/docs/img/invoice_linked_objects.png +0 -0
  24. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/api/__init__.py +0 -0
  25. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/api/serializers.py +0 -0
  26. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/api/urls.py +0 -0
  27. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/api/views.py +0 -0
  28. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/locale/en/LC_MESSAGES/django.mo +0 -0
  29. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/locale/en/LC_MESSAGES/django.po +0 -0
  30. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/locale/fr/LC_MESSAGES/django.mo +0 -0
  31. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/locale/fr/LC_MESSAGES/django.po +0 -0
  32. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/migrations/0001_initial.py +0 -0
  33. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/migrations/0002_alter_contract_circuit_and_more.py +0 -0
  34. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/migrations/0003_alter_contract_custom_field_data_and_more.py +0 -0
  35. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/migrations/0004_contract_currency_invoice_currency.py +0 -0
  36. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/migrations/0005_contract_accounting_dimensions_and_more.py +0 -0
  37. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/migrations/0006_alter_contract_circuit.py +0 -0
  38. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/migrations/0007_invoice_date.py +0 -0
  39. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/migrations/0008_invoice_comments.py +0 -0
  40. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/migrations/0009_contract_external_reference.py +0 -0
  41. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/migrations/0010_invoice_contracts.py +0 -0
  42. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/migrations/0011_auto_20230122_2112.py +0 -0
  43. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/migrations/0012_remove_invoice_contract.py +0 -0
  44. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/migrations/0013_contract_documents_invoice_documents.py +0 -0
  45. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/migrations/0014_contract_end_date.py +0 -0
  46. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/migrations/0015_contractassignement.py +0 -0
  47. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/migrations/0016_contract_parent.py +0 -0
  48. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/migrations/0017_alter_contract_accounting_dimensions.py +0 -0
  49. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/migrations/0018_contract_external_partie_object_id_and_more.py +0 -0
  50. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/migrations/0019_auto_20230924_1813.py +0 -0
  51. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/migrations/0020_alter_contract_external_partie.py +0 -0
  52. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/migrations/0021_alter_contract_external_partie.py +0 -0
  53. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/migrations/0022_alter_contract_internal_partie_alter_contract_parent.py +0 -0
  54. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/migrations/0023_rename_contractassignement_contractassignment_and_more.py +0 -0
  55. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/migrations/0024_remove_contract_external_partie.py +0 -0
  56. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/migrations/0025_remove_contract_circuit_and_more.py +0 -0
  57. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/migrations/0026_auto_20240421_1550.py +0 -0
  58. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/migrations/0027_invoiceline.py +0 -0
  59. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/migrations/0028_invoiceline_accounting_dimensions.py +0 -0
  60. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/migrations/0029_remove_invoiceline_accounting_dimensions_and_more.py +0 -0
  61. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/migrations/0030_alter_invoiceline_accounting_dimensions.py +0 -0
  62. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/migrations/0031_contract_invoice_template_invoice_template.py +0 -0
  63. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/migrations/0032_alter_invoice_period_end_alter_invoice_period_start_and_more.py +0 -0
  64. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/migrations/0033_remove_contract_invoice_template.py +0 -0
  65. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/migrations/0034_contract_yrc_alter_contract_mrc.py +0 -0
  66. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/migrations/0035_accountingdimension_status_and_more.py +0 -0
  67. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/migrations/0036_alter_invoiceline_accounting_dimensions.py +0 -0
  68. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/migrations/0037_contract_notice_period_alter_contract_currency_and_more.py +0 -0
  69. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/migrations/0038_alter_accountingdimension_options_and_more.py +0 -0
  70. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/migrations/0039_alter_accountingdimension_status_and_more.py +0 -0
  71. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/migrations/0040_alter_contract_currency_alter_invoice_currency_and_more.py +0 -0
  72. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/migrations/0041_contracttype_contract_contract_type.py +0 -0
  73. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/migrations/0042_remove_contract_netbox_cont_externa_6343fb_idx_and_more.py +0 -0
  74. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/migrations/0043_invoice_status.py +0 -0
  75. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/migrations/__init__.py +0 -0
  76. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/models.py +0 -0
  77. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/navigation.py +0 -0
  78. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/search.py +0 -0
  79. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/templates/contract_assignments_bottom.html +0 -0
  80. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/templates/contract_list_bottom.html +0 -0
  81. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/templates/netbox_contract/accountingdimension.html +0 -0
  82. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/templates/netbox_contract/contract.html +0 -0
  83. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/templates/netbox_contract/contractassignment.html +0 -0
  84. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/templates/netbox_contract/contracttype.html +0 -0
  85. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/templates/netbox_contract/invoice.html +0 -0
  86. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/templates/netbox_contract/invoiceline.html +0 -0
  87. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract/urls.py +0 -0
  88. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract.egg-info/SOURCES.txt +0 -0
  89. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract.egg-info/dependency_links.txt +0 -0
  90. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract.egg-info/requires.txt +0 -0
  91. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/netbox_contract.egg-info/top_level.txt +0 -0
  92. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/setup.cfg +0 -0
  93. {netbox_contract-2.4.2 → netbox_contract-2.4.4}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: netbox-contract
3
- Version: 2.4.2
3
+ Version: 2.4.4
4
4
  Summary: Contract management plugin for Netbox
5
5
  Author-email: Marc Lebreuil <marc@famillelebreuil.net>
6
6
  Project-URL: Homepage, https://github.com/mlebreuil/netbox-contract
@@ -8,7 +8,7 @@ Project-URL: Bug Tracker, https://github.com/mlebreuil/netbox-contract/issues
8
8
  Classifier: Programming Language :: Python :: 3
9
9
  Classifier: License :: OSI Approved :: MIT License
10
10
  Classifier: Operating System :: OS Independent
11
- Requires-Python: >=3.10
11
+ Requires-Python: >=3.12
12
12
  Description-Content-Type: text/markdown
13
13
  License-File: LICENSE
14
14
  Requires-Dist: python-dateutil
@@ -48,6 +48,7 @@ Accounting dimensions can be linked with invoice lines.
48
48
  | 4.1 | 2.2 |
49
49
  | 4.2 | 2.3 |
50
50
  | 4.3 | 2.4 |
51
+ | 4.4 | 2.4 |
51
52
 
52
53
  ## Installing
53
54
 
@@ -89,6 +90,17 @@ PLUGINS_CONFIG = {
89
90
  'mandatory_invoice_fields': [],
90
91
  'hidden_invoice_fields': [],
91
92
  'mandatory_dimensions': [],
93
+ 'supported_models': [
94
+ 'circuits.circuit',
95
+ 'circuits.virtualcircuit',
96
+ 'dcim.site',
97
+ 'dcim.device',
98
+ 'dcim.rack',
99
+ 'virtualization.virtualmachine',
100
+ 'virtualization.cluster',
101
+ 'ipam.ipaddress',
102
+ 'ipam.prefix',
103
+ ],
92
104
  }
93
105
  }
94
106
 
@@ -21,6 +21,7 @@ Accounting dimensions can be linked with invoice lines.
21
21
  | 4.1 | 2.2 |
22
22
  | 4.2 | 2.3 |
23
23
  | 4.3 | 2.4 |
24
+ | 4.4 | 2.4 |
24
25
 
25
26
  ## Installing
26
27
 
@@ -62,6 +63,17 @@ PLUGINS_CONFIG = {
62
63
  'mandatory_invoice_fields': [],
63
64
  'hidden_invoice_fields': [],
64
65
  'mandatory_dimensions': [],
66
+ 'supported_models': [
67
+ 'circuits.circuit',
68
+ 'circuits.virtualcircuit',
69
+ 'dcim.site',
70
+ 'dcim.device',
71
+ 'dcim.rack',
72
+ 'virtualization.virtualmachine',
73
+ 'virtualization.cluster',
74
+ 'ipam.ipaddress',
75
+ 'ipam.prefix',
76
+ ],
65
77
  }
66
78
  }
67
79
 
@@ -5,7 +5,7 @@ class ContractsConfig(PluginConfig):
5
5
  name = 'netbox_contract'
6
6
  verbose_name = 'Netbox contract'
7
7
  description = 'Contract management plugin for Netbox'
8
- version = '2.4.2'
8
+ version = '2.4.4'
9
9
  author = 'Marc Lebreuil'
10
10
  author_email = 'marc@famillelebreuil.net'
11
11
  base_url = 'contracts'
@@ -18,6 +18,15 @@ class ContractsConfig(PluginConfig):
18
18
  'mandatory_invoice_fields': [],
19
19
  'hidden_invoice_fields': [],
20
20
  'mandatory_dimensions': [],
21
+ 'supported_models': [
22
+ 'circuits.circuit',
23
+ 'circuits.virtualcircuit',
24
+ 'dcim.site',
25
+ 'dcim.device',
26
+ 'dcim.rack',
27
+ 'virtualization.virtualmachine',
28
+ 'virtualization.cluster',
29
+ ],
21
30
  }
22
31
 
23
32
 
@@ -0,0 +1,36 @@
1
+ from django.db.models import Q
2
+ from django.conf import settings
3
+
4
+ plugin_settings = settings.PLUGINS_CONFIG['netbox_contract']
5
+
6
+ ASSIGNEMENT_TYPES = plugin_settings.get('supported_models')
7
+
8
+ SERVICE_PROVIDER_TYPES = (
9
+ 'circuits.provider',
10
+ 'netbox_contract.serviceprovider',
11
+ )
12
+
13
+ # ASSIGNEMENT_TYPES = (
14
+ # 'circuits.circuit',
15
+ # 'circuits.virtualcircuit',
16
+ # 'dcim.site',
17
+ # 'dcim.device',
18
+ # 'dcim.rack',
19
+ # 'virtualization.virtualmachine',
20
+ # 'virtualization.cluster',
21
+ # 'ipam.ipaddress',
22
+ # 'ipam.prefix',
23
+ # )
24
+
25
+
26
+ def build_models_q(model_strings):
27
+ """Build a Q object from a list of 'app_label.model' strings."""
28
+ q = Q()
29
+ for model_string in model_strings:
30
+ app_label, model = model_string.split('.')
31
+ q |= Q(app_label=app_label, model=model)
32
+ return q
33
+
34
+
35
+ ASSIGNEMENT_MODELS = build_models_q(ASSIGNEMENT_TYPES)
36
+ SERVICE_PROVIDER_MODELS = build_models_q(SERVICE_PROVIDER_TYPES)
@@ -1,7 +1,9 @@
1
1
  import django_filters
2
2
  from django.db.models import Q
3
+ from django.contrib.contenttypes.models import ContentType
3
4
  from netbox.filtersets import NetBoxModelFilterSet
4
5
  from tenancy.filtersets import ContactModelFilterSet, TenancyFilterSet
6
+ from circuits.models import Provider
5
7
 
6
8
  from .models import (
7
9
  AccountingDimension,
@@ -31,16 +33,29 @@ class ContractFilterSet(ContactModelFilterSet, NetBoxModelFilterSet, TenancyFilt
31
33
  field_name='contract_type__name', to_field_name='name', queryset=ContractType.objects.all()
32
34
  )
33
35
 
36
+ service_provider_id = django_filters.NumberFilter(
37
+ field_name='external_party_object_id',
38
+ method='filter_by_service_provider',
39
+ label='Service provider'
40
+ )
41
+
42
+ provider_id = django_filters.NumberFilter(
43
+ field_name='external_party_object_id',
44
+ method='filter_by_circuit_provider',
45
+ label='Circuit provider'
46
+ )
47
+
34
48
  class Meta:
35
49
  model = Contract
36
50
  fields = (
37
51
  'id',
38
52
  'name',
53
+ 'status',
54
+ 'internal_party',
55
+ 'currency',
39
56
  'contract_type',
57
+ 'external_party_object_id',
40
58
  'external_reference',
41
- 'start_date',
42
- 'end_date',
43
- 'initial_term',
44
59
  'parent',
45
60
  )
46
61
 
@@ -52,6 +67,22 @@ class ContractFilterSet(ContactModelFilterSet, NetBoxModelFilterSet, TenancyFilt
52
67
  Q(status__iexact='Active'),
53
68
  )
54
69
 
70
+ def filter_by_service_provider(self, queryset, name, value):
71
+ if not value:
72
+ return queryset
73
+ return queryset.filter(
74
+ external_party_object_id=value,
75
+ external_party_object_type=ContentType.objects.get_for_model(ServiceProvider)
76
+ )
77
+
78
+ def filter_by_circuit_provider(self, queryset, name, value):
79
+ if not value:
80
+ return queryset
81
+ return queryset.filter(
82
+ external_party_object_id=value,
83
+ external_party_object_type=ContentType.objects.get_for_model(Provider)
84
+ )
85
+
55
86
 
56
87
  class InvoiceFilterSet(NetBoxModelFilterSet):
57
88
  status = django_filters.MultipleChoiceFilter(choices=InvoiceStatusChoices, null_value=None)
@@ -11,7 +11,8 @@ from netbox.forms import (
11
11
  )
12
12
  from tenancy.forms import ContactModelFilterForm, TenancyFilterForm
13
13
  from tenancy.models import Tenant
14
- from utilities.forms import BOOLEAN_WITH_BLANK_CHOICES
14
+ from circuits.models import Provider
15
+ from utilities.forms import BOOLEAN_WITH_BLANK_CHOICES, get_field_value
15
16
  from utilities.forms.fields import (
16
17
  ColorField,
17
18
  CommentField,
@@ -26,8 +27,9 @@ from utilities.forms.fields import (
26
27
  TagFilterField,
27
28
  )
28
29
  from utilities.forms.widgets import DatePicker, HTMXSelect
30
+ from utilities.templatetags.builtins.filters import bettertitle
29
31
 
30
- from .constants import ASSIGNEMENT_MODELS, SERVICE_PROVIDER_MODELS
32
+ from .constants import ASSIGNEMENT_MODELS, SERVICE_PROVIDER_MODELS, SERVICE_PROVIDER_TYPES
31
33
  from .models import (
32
34
  AccountingDimension,
33
35
  AccountingDimensionStatusChoices,
@@ -151,16 +153,46 @@ class ContractFilterForm(ContactModelFilterForm, TenancyFilterForm, NetBoxModelF
151
153
  selector=True,
152
154
  label=_('Contract type'),
153
155
  )
156
+
157
+ service_provider_id = DynamicModelChoiceField(
158
+ queryset=ServiceProvider.objects.all(),
159
+ required=False,
160
+ selector=True,
161
+ label=_('Service provider'),
162
+ help_text=_('Filter by Service Provider'),
163
+ )
164
+
165
+ provider_id = DynamicModelChoiceField(
166
+ queryset=Provider.objects.all(),
167
+ required=False,
168
+ selector=True,
169
+ label=_('Circuit Provider'),
170
+ help_text=_('Filter by Circuit Provider'),
171
+ )
172
+
154
173
  external_reference = forms.CharField(required=False, label=_('External reference'))
155
- internal_party = forms.ChoiceField(choices=InternalEntityChoices, required=False, label=_('Internal party'))
174
+
175
+ internal_party = forms.ChoiceField(
176
+ choices=[('', '-----')] + list(InternalEntityChoices),
177
+ required=False,
178
+ label=_('Internal party')
179
+ )
180
+
156
181
  status = forms.ChoiceField(choices=StatusChoices, required=False, label=_('Status'))
157
- currency = forms.ChoiceField(choices=CurrencyChoices, required=False, label=_('Currency'))
182
+
183
+ currency = forms.ChoiceField(
184
+ choices=[('', '-----')] + list(CurrencyChoices),
185
+ required=False,
186
+ label=_('Currency')
187
+ )
188
+
158
189
  parent = DynamicModelChoiceField(
159
190
  queryset=Contract.objects.all(),
160
191
  required=False,
161
192
  selector=True,
162
193
  label=_('Parent'),
163
194
  )
195
+
164
196
  tag = TagFilterField(model)
165
197
 
166
198
 
@@ -237,6 +269,21 @@ class ContractBulkEditForm(NetBoxModelBulkEditForm):
237
269
  selector=True,
238
270
  label=_('Contract Type')
239
271
  )
272
+
273
+ external_party_object_type = ContentTypeChoiceField(
274
+ queryset=ContentType.objects.filter(model__in=SERVICE_PROVIDER_TYPES),
275
+ widget=HTMXSelect(method='post', attrs={'hx-select': '#form_fields'}),
276
+ required=False,
277
+ label=_('External party type')
278
+ )
279
+ external_party_object = DynamicModelChoiceField(
280
+ label=_('External party'),
281
+ queryset=ServiceProvider.objects.none(), # Initial queryset
282
+ required=False,
283
+ disabled=True,
284
+ selector=True
285
+ )
286
+
240
287
  external_reference = forms.CharField(max_length=100, required=False, label=_('External reference'))
241
288
  internal_party = forms.ChoiceField(choices=InternalEntityChoices, required=False, label=_('Internal party'))
242
289
  tenant = DynamicModelChoiceField(queryset=Tenant.objects.all(), required=False, selector=True, label=_('Tenant'))
@@ -251,6 +298,19 @@ class ContractBulkEditForm(NetBoxModelBulkEditForm):
251
298
  nullable_fields = ('comments',)
252
299
  model = Contract
253
300
 
301
+ def __init__(self, *args, **kwargs):
302
+ super().__init__(*args, **kwargs)
303
+
304
+ if external_party_object_type_id := get_field_value(self, 'external_party_object_type'):
305
+ try:
306
+ external_party_object_type = ContentType.objects.get(pk=external_party_object_type_id)
307
+ model = external_party_object_type.model_class()
308
+ self.fields['external_party_object'].queryset = model.objects.all()
309
+ self.fields['external_party_object'].widget.attrs['selector'] = model._meta.label_lower
310
+ self.fields['external_party_object'].disabled = False
311
+ self.fields['external_party_object'].label = _(bettertitle(model._meta.verbose_name))
312
+ except ObjectDoesNotExist:
313
+ pass
254
314
 
255
315
  # ContractType
256
316
 
@@ -193,6 +193,36 @@ class ContractListBottomTable(NetBoxTable):
193
193
  )
194
194
 
195
195
 
196
+ class ContractProviderBottomTable(NetBoxTable):
197
+ name = tables.Column(linkify=True)
198
+ external_party_object = tables.Column(linkify=True)
199
+ status = columns.ChoiceFieldColumn(
200
+ verbose_name=('Status'),
201
+ )
202
+
203
+ class Meta(NetBoxTable.Meta):
204
+ model = Contract
205
+ fields = (
206
+ 'pk',
207
+ 'id',
208
+ 'name',
209
+ 'start_date',
210
+ 'end_date',
211
+ 'external_reference',
212
+ 'status',
213
+ 'mrc',
214
+ 'comments',
215
+ 'actions',
216
+ )
217
+ default_columns = (
218
+ 'name',
219
+ 'status',
220
+ 'external_reference',
221
+ 'start_date',
222
+ 'end_date',
223
+ )
224
+
225
+
196
226
  class InvoiceListTable(NetBoxTable):
197
227
  contracts = tables.ManyToManyColumn(linkify=True)
198
228
  number = tables.Column(linkify=True)
@@ -0,0 +1,32 @@
1
+ from django.contrib.contenttypes.models import ContentType
2
+ from netbox.plugins import PluginTemplateExtension
3
+
4
+ from . import tables
5
+ from .models import ContractAssignment
6
+ from .constants import ASSIGNEMENT_TYPES
7
+
8
+
9
+ class ObjectContractAssignments(PluginTemplateExtension):
10
+ models = ASSIGNEMENT_TYPES
11
+
12
+ def full_width_page(self):
13
+ object = self.context['object']
14
+ object_type = ContentType.objects.get_for_model(object)
15
+
16
+ contract_assignments = ContractAssignment.objects.filter(
17
+ content_type__pk=object_type.id, object_id=object.id
18
+ )
19
+ assignments_table = tables.ContractAssignmentObjectTable(contract_assignments)
20
+ assignments_table.configure(self.context['request'])
21
+
22
+ return self.render(
23
+ 'contract_assignments_bottom.html',
24
+ extra_context={
25
+ 'assignments_table': assignments_table,
26
+ },
27
+ )
28
+
29
+
30
+ template_extensions = [
31
+ ObjectContractAssignments,
32
+ ]
@@ -1,4 +1,6 @@
1
1
  {% extends 'generic/object.html' %}
2
+ {% load plugins %}
3
+ {% load render_table from django_tables2 %}
2
4
  {% load static %}
3
5
  {% load helpers %}
4
6
  {% load i18n %}
@@ -31,6 +33,14 @@
31
33
  {% include 'inc/panels/custom_fields.html' %}
32
34
  {% include 'inc/panels/comments.html' %}
33
35
  {% include 'inc/panels/tags.html' %}
36
+ <div class="row">
37
+ <div class="col col-md-12">
38
+ <div class="card">
39
+ <h5 class="card-header">{% trans "Contracts" %}</h5>
40
+ {% render_table contracts_table %}
41
+ </div>
42
+ </div>
43
+ </div>
34
44
  </div>
35
45
  </div>
36
46
  {% endblock content %}
@@ -75,6 +75,19 @@ class ContractTypeBulkDeleteView(generic.BulkDeleteView):
75
75
  class ServiceProviderView(generic.ObjectView):
76
76
  queryset = ServiceProvider.objects.all()
77
77
 
78
+ def get_extra_context(self, request, instance):
79
+ provider_type = ContentType.objects.get_for_model(ServiceProvider)
80
+ contracts = Contract.objects.filter(
81
+ external_party_object_type__pk=provider_type.id,
82
+ external_party_object_id=instance.id
83
+ )
84
+
85
+ contracts_table = tables.ContractProviderBottomTable(contracts)
86
+ contracts_table.configure(request)
87
+ return {
88
+ 'contracts_table': contracts_table,
89
+ }
90
+
78
91
 
79
92
  class ServiceProviderListView(generic.ObjectListView):
80
93
  queryset = ServiceProvider.objects.all()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: netbox-contract
3
- Version: 2.4.2
3
+ Version: 2.4.4
4
4
  Summary: Contract management plugin for Netbox
5
5
  Author-email: Marc Lebreuil <marc@famillelebreuil.net>
6
6
  Project-URL: Homepage, https://github.com/mlebreuil/netbox-contract
@@ -8,7 +8,7 @@ Project-URL: Bug Tracker, https://github.com/mlebreuil/netbox-contract/issues
8
8
  Classifier: Programming Language :: Python :: 3
9
9
  Classifier: License :: OSI Approved :: MIT License
10
10
  Classifier: Operating System :: OS Independent
11
- Requires-Python: >=3.10
11
+ Requires-Python: >=3.12
12
12
  Description-Content-Type: text/markdown
13
13
  License-File: LICENSE
14
14
  Requires-Dist: python-dateutil
@@ -48,6 +48,7 @@ Accounting dimensions can be linked with invoice lines.
48
48
  | 4.1 | 2.2 |
49
49
  | 4.2 | 2.3 |
50
50
  | 4.3 | 2.4 |
51
+ | 4.4 | 2.4 |
51
52
 
52
53
  ## Installing
53
54
 
@@ -89,6 +90,17 @@ PLUGINS_CONFIG = {
89
90
  'mandatory_invoice_fields': [],
90
91
  'hidden_invoice_fields': [],
91
92
  'mandatory_dimensions': [],
93
+ 'supported_models': [
94
+ 'circuits.circuit',
95
+ 'circuits.virtualcircuit',
96
+ 'dcim.site',
97
+ 'dcim.device',
98
+ 'dcim.rack',
99
+ 'virtualization.virtualmachine',
100
+ 'virtualization.cluster',
101
+ 'ipam.ipaddress',
102
+ 'ipam.prefix',
103
+ ],
92
104
  }
93
105
  }
94
106
 
@@ -7,14 +7,14 @@ build-backend = "setuptools.build_meta"
7
7
 
8
8
  [project]
9
9
  name = "netbox-contract"
10
- version = "2.4.2"
10
+ version = "2.4.4"
11
11
  authors = [
12
12
  { name="Marc Lebreuil", email="marc@famillelebreuil.net" },
13
13
  ]
14
14
 
15
15
  description = "Contract management plugin for Netbox"
16
16
  readme = "README.md"
17
- requires-python = ">=3.10"
17
+ requires-python = ">=3.12"
18
18
 
19
19
  dependencies = [
20
20
  'python-dateutil',
@@ -1,14 +0,0 @@
1
- from django.db.models import Q
2
-
3
- SERVICE_PROVIDER_MODELS = Q(
4
- Q(app_label='circuits', model__in=('provider',))
5
- | Q(app_label='netbox_contract', model__in=('serviceprovider',))
6
- )
7
-
8
- ASSIGNEMENT_MODELS = Q(
9
- Q(app_label='circuits', model__in=('circuit',))
10
- | Q(app_label='circuits', model__in=('virtualcircuit',))
11
- | Q(app_label='dcim', model__in=('site',))
12
- | Q(app_label='dcim', model__in=('device',))
13
- | Q(app_label='virtualization', model__in=('virtualmachine',))
14
- )
@@ -1,118 +0,0 @@
1
- from circuits.models import Circuit, VirtualCircuit
2
- from dcim.models import Device, Site
3
- from django.contrib.contenttypes.models import ContentType
4
- from netbox.plugins import PluginTemplateExtension
5
- from virtualization.models import VirtualMachine
6
-
7
- from . import tables
8
- from .models import ContractAssignment
9
-
10
-
11
- class CircuitContractAssignments(PluginTemplateExtension):
12
- models = ['circuits.circuit']
13
-
14
- def full_width_page(self):
15
- circuit = self.context['object']
16
- circuit_type = ContentType.objects.get_for_model(Circuit)
17
- contract_assignments = ContractAssignment.objects.filter(
18
- content_type__pk=circuit_type.id, object_id=circuit.id
19
- )
20
- assignments_table = tables.ContractAssignmentObjectTable(contract_assignments)
21
- assignments_table.configure(self.context['request'])
22
-
23
- return self.render(
24
- 'contract_assignments_bottom.html',
25
- extra_context={
26
- 'assignments_table': assignments_table,
27
- },
28
- )
29
-
30
-
31
- class DeviceContractAssignments(PluginTemplateExtension):
32
- models = ['dcim.device']
33
-
34
- def full_width_page(self):
35
- device = self.context['object']
36
- device_type = ContentType.objects.get_for_model(Device)
37
- contract_assignments = ContractAssignment.objects.filter(
38
- content_type__pk=device_type.id, object_id=device.id
39
- )
40
- assignments_table = tables.ContractAssignmentObjectTable(contract_assignments)
41
- assignments_table.configure(self.context['request'])
42
-
43
- return self.render(
44
- 'contract_assignments_bottom.html',
45
- extra_context={
46
- 'assignments_table': assignments_table,
47
- },
48
- )
49
-
50
-
51
- class VMContractAssignments(PluginTemplateExtension):
52
- models = ['virtualization.virtualmachine']
53
-
54
- def full_width_page(self):
55
- vm = self.context['object']
56
- vm_type = ContentType.objects.get_for_model(VirtualMachine)
57
- contract_assignments = ContractAssignment.objects.filter(
58
- content_type__pk=vm_type.id, object_id=vm.id
59
- )
60
- assignments_table = tables.ContractAssignmentObjectTable(contract_assignments)
61
- assignments_table.configure(self.context['request'])
62
-
63
- return self.render(
64
- 'contract_assignments_bottom.html',
65
- extra_context={
66
- 'assignments_table': assignments_table,
67
- },
68
- )
69
-
70
-
71
- class SiteContractAssignments(PluginTemplateExtension):
72
- models = ['dcim.site']
73
-
74
- def full_width_page(self):
75
- site = self.context['object']
76
- site_type = ContentType.objects.get_for_model(Site)
77
- contract_assignments = ContractAssignment.objects.filter(
78
- content_type__pk=site_type.id, object_id=site.id
79
- )
80
- assignments_table = tables.ContractAssignmentObjectTable(contract_assignments)
81
- assignments_table.configure(self.context['request'])
82
-
83
- return self.render(
84
- 'contract_assignments_bottom.html',
85
- extra_context={
86
- 'assignments_table': assignments_table,
87
- },
88
- )
89
-
90
-
91
- class VirtualCircuitContractAssignments(PluginTemplateExtension):
92
- models = ['circuits.virtualcircuit']
93
-
94
- def full_width_page(self):
95
- virtualcircuit = self.context['object']
96
- virtualcircuit_type = ContentType.objects.get_for_model(VirtualCircuit)
97
- contract_assignments = ContractAssignment.objects.filter(
98
- content_type__pk=virtualcircuit_type.id, object_id=virtualcircuit.id
99
- )
100
-
101
- assignments_table = tables.ContractAssignmentObjectTable(contract_assignments)
102
- assignments_table.configure(self.context['request'])
103
-
104
- return self.render(
105
- 'contract_assignments_bottom.html',
106
- extra_context={
107
- 'assignments_table': assignments_table,
108
- },
109
- )
110
-
111
-
112
- template_extensions = [
113
- CircuitContractAssignments,
114
- DeviceContractAssignments,
115
- VMContractAssignments,
116
- SiteContractAssignments,
117
- VirtualCircuitContractAssignments,
118
- ]
File without changes