netbox-contract 2.3.2__tar.gz → 2.4.1__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 (95) hide show
  1. {netbox_contract-2.3.2/netbox_contract.egg-info → netbox_contract-2.4.1}/PKG-INFO +22 -4
  2. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/README.md +9 -2
  3. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/netbox_contract/__init__.py +2 -2
  4. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/netbox_contract/api/serializers.py +42 -22
  5. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/netbox_contract/api/urls.py +1 -0
  6. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/netbox_contract/api/views.py +6 -0
  7. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/netbox_contract/constants.py +1 -0
  8. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/netbox_contract/filtersets.py +25 -4
  9. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/netbox_contract/forms.py +113 -37
  10. netbox_contract-2.4.1/netbox_contract/locale/en/LC_MESSAGES/django.mo +0 -0
  11. netbox_contract-2.4.1/netbox_contract/locale/en/LC_MESSAGES/django.po +499 -0
  12. netbox_contract-2.4.1/netbox_contract/locale/fr/LC_MESSAGES/django.mo +0 -0
  13. netbox_contract-2.4.1/netbox_contract/locale/fr/LC_MESSAGES/django.po +503 -0
  14. netbox_contract-2.4.1/netbox_contract/migrations/0041_contracttype_contract_contract_type.py +48 -0
  15. netbox_contract-2.4.1/netbox_contract/migrations/0042_remove_contract_netbox_cont_externa_6343fb_idx_and_more.py +42 -0
  16. netbox_contract-2.4.1/netbox_contract/migrations/0043_invoice_status.py +21 -0
  17. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/netbox_contract/models.py +65 -12
  18. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/netbox_contract/navigation.py +17 -0
  19. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/netbox_contract/search.py +10 -1
  20. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/netbox_contract/tables.py +48 -21
  21. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/netbox_contract/template_content.py +27 -5
  22. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/netbox_contract/templates/netbox_contract/contract.html +13 -7
  23. netbox_contract-2.4.1/netbox_contract/templates/netbox_contract/contracttype.html +33 -0
  24. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/netbox_contract/templates/netbox_contract/invoice.html +4 -0
  25. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/netbox_contract/urls.py +48 -6
  26. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/netbox_contract/views.py +52 -22
  27. {netbox_contract-2.3.2 → netbox_contract-2.4.1/netbox_contract.egg-info}/PKG-INFO +22 -4
  28. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/netbox_contract.egg-info/SOURCES.txt +9 -4
  29. netbox_contract-2.4.1/netbox_contract.egg-info/requires.txt +14 -0
  30. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/pyproject.toml +17 -1
  31. netbox_contract-2.3.2/netbox_contract/tests/__init__.py +0 -1
  32. netbox_contract-2.3.2/netbox_contract/tests/custom.py +0 -18
  33. netbox_contract-2.3.2/netbox_contract/tests/test_views.py +0 -449
  34. netbox_contract-2.3.2/netbox_contract.egg-info/requires.txt +0 -2
  35. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/CONTRIBUTING.md +0 -0
  36. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/LICENSE +0 -0
  37. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/MANIFEST.in +0 -0
  38. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/docs/img/accounting_dimensions.png +0 -0
  39. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/docs/img/contract.png +0 -0
  40. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/docs/img/contract_linked_objects.png +0 -0
  41. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/docs/img/invoice.png +0 -0
  42. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/docs/img/invoice_line.png +0 -0
  43. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/docs/img/invoice_linked_objects.png +0 -0
  44. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/netbox_contract/api/__init__.py +0 -0
  45. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/netbox_contract/migrations/0001_initial.py +0 -0
  46. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/netbox_contract/migrations/0002_alter_contract_circuit_and_more.py +0 -0
  47. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/netbox_contract/migrations/0003_alter_contract_custom_field_data_and_more.py +0 -0
  48. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/netbox_contract/migrations/0004_contract_currency_invoice_currency.py +0 -0
  49. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/netbox_contract/migrations/0005_contract_accounting_dimensions_and_more.py +0 -0
  50. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/netbox_contract/migrations/0006_alter_contract_circuit.py +0 -0
  51. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/netbox_contract/migrations/0007_invoice_date.py +0 -0
  52. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/netbox_contract/migrations/0008_invoice_comments.py +0 -0
  53. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/netbox_contract/migrations/0009_contract_external_reference.py +0 -0
  54. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/netbox_contract/migrations/0010_invoice_contracts.py +0 -0
  55. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/netbox_contract/migrations/0011_auto_20230122_2112.py +0 -0
  56. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/netbox_contract/migrations/0012_remove_invoice_contract.py +0 -0
  57. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/netbox_contract/migrations/0013_contract_documents_invoice_documents.py +0 -0
  58. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/netbox_contract/migrations/0014_contract_end_date.py +0 -0
  59. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/netbox_contract/migrations/0015_contractassignement.py +0 -0
  60. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/netbox_contract/migrations/0016_contract_parent.py +0 -0
  61. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/netbox_contract/migrations/0017_alter_contract_accounting_dimensions.py +0 -0
  62. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/netbox_contract/migrations/0018_contract_external_partie_object_id_and_more.py +0 -0
  63. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/netbox_contract/migrations/0019_auto_20230924_1813.py +0 -0
  64. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/netbox_contract/migrations/0020_alter_contract_external_partie.py +0 -0
  65. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/netbox_contract/migrations/0021_alter_contract_external_partie.py +0 -0
  66. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/netbox_contract/migrations/0022_alter_contract_internal_partie_alter_contract_parent.py +0 -0
  67. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/netbox_contract/migrations/0023_rename_contractassignement_contractassignment_and_more.py +0 -0
  68. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/netbox_contract/migrations/0024_remove_contract_external_partie.py +0 -0
  69. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/netbox_contract/migrations/0025_remove_contract_circuit_and_more.py +0 -0
  70. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/netbox_contract/migrations/0026_auto_20240421_1550.py +0 -0
  71. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/netbox_contract/migrations/0027_invoiceline.py +0 -0
  72. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/netbox_contract/migrations/0028_invoiceline_accounting_dimensions.py +0 -0
  73. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/netbox_contract/migrations/0029_remove_invoiceline_accounting_dimensions_and_more.py +0 -0
  74. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/netbox_contract/migrations/0030_alter_invoiceline_accounting_dimensions.py +0 -0
  75. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/netbox_contract/migrations/0031_contract_invoice_template_invoice_template.py +0 -0
  76. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/netbox_contract/migrations/0032_alter_invoice_period_end_alter_invoice_period_start_and_more.py +0 -0
  77. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/netbox_contract/migrations/0033_remove_contract_invoice_template.py +0 -0
  78. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/netbox_contract/migrations/0034_contract_yrc_alter_contract_mrc.py +0 -0
  79. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/netbox_contract/migrations/0035_accountingdimension_status_and_more.py +0 -0
  80. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/netbox_contract/migrations/0036_alter_invoiceline_accounting_dimensions.py +0 -0
  81. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/netbox_contract/migrations/0037_contract_notice_period_alter_contract_currency_and_more.py +0 -0
  82. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/netbox_contract/migrations/0038_alter_accountingdimension_options_and_more.py +0 -0
  83. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/netbox_contract/migrations/0039_alter_accountingdimension_status_and_more.py +0 -0
  84. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/netbox_contract/migrations/0040_alter_contract_currency_alter_invoice_currency_and_more.py +0 -0
  85. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/netbox_contract/migrations/__init__.py +0 -0
  86. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/netbox_contract/templates/contract_assignments_bottom.html +0 -0
  87. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/netbox_contract/templates/contract_list_bottom.html +0 -0
  88. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/netbox_contract/templates/netbox_contract/accountingdimension.html +0 -0
  89. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/netbox_contract/templates/netbox_contract/contractassignment.html +0 -0
  90. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/netbox_contract/templates/netbox_contract/invoiceline.html +0 -0
  91. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/netbox_contract/templates/netbox_contract/serviceprovider.html +0 -0
  92. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/netbox_contract.egg-info/dependency_links.txt +0 -0
  93. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/netbox_contract.egg-info/top_level.txt +0 -0
  94. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/setup.cfg +0 -0
  95. {netbox_contract-2.3.2 → netbox_contract-2.4.1}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: netbox-contract
3
- Version: 2.3.2
3
+ Version: 2.4.1
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
@@ -13,6 +13,17 @@ Description-Content-Type: text/markdown
13
13
  License-File: LICENSE
14
14
  Requires-Dist: python-dateutil
15
15
  Requires-Dist: drf_yasg
16
+ Requires-Dist: Django
17
+ Provides-Extra: dev
18
+ Requires-Dist: check-manifest; extra == "dev"
19
+ Requires-Dist: mkdocs; extra == "dev"
20
+ Requires-Dist: mkdocs-material; extra == "dev"
21
+ Requires-Dist: ruff; extra == "dev"
22
+ Provides-Extra: test
23
+ Requires-Dist: coverage; extra == "test"
24
+ Requires-Dist: pytest; extra == "test"
25
+ Requires-Dist: pytest-cov; extra == "test"
26
+ Dynamic: license-file
16
27
 
17
28
  # Contract plugin
18
29
 
@@ -36,6 +47,7 @@ Accounting dimensions can be linked with invoice lines.
36
47
  |----------------|----------------|
37
48
  | 4.1 | 2.2 |
38
49
  | 4.2 | 2.3 |
50
+ | 4.3 | 2.4 |
39
51
 
40
52
  ## Installing
41
53
 
@@ -89,13 +101,14 @@ PLUGINS_CONFIG = {
89
101
 
90
102
  ### Customize the plugin fields choices
91
103
 
92
- Internal partie reference the legal entity of your organization that is a partie to the contract.
104
+ Internal party reference the legal entity of your organization that is a party to the contract.
93
105
  The first currency will also be the default currency for contracts.
94
106
 
95
107
  ```python
96
108
  # configuration.py
97
109
  FIELD_CHOICES = {
98
- 'netbox_contract.Contract.internal_partie': (
110
+ 'netbox_contract.Contract.internal_party': (
111
+ ('default', 'Default entity', 'green'),
99
112
  ('entity1', 'Entity 1', 'green'),
100
113
  ('entity2', 'Entity 2', 'yellow'),
101
114
  ),
@@ -109,6 +122,11 @@ FIELD_CHOICES = {
109
122
  ('active', 'Active', 'green'),
110
123
  ('canceled', 'Canceled', 'red'),
111
124
  )
125
+ 'netbox_contract.Invoice.status': (
126
+ ('draft', 'Draft', 'yellow'),
127
+ ('posted', 'Posted', 'green'),
128
+ ('canceled', 'Canceled', 'red'),
129
+ )
112
130
  }
113
131
 
114
132
  ```
@@ -20,6 +20,7 @@ Accounting dimensions can be linked with invoice lines.
20
20
  |----------------|----------------|
21
21
  | 4.1 | 2.2 |
22
22
  | 4.2 | 2.3 |
23
+ | 4.3 | 2.4 |
23
24
 
24
25
  ## Installing
25
26
 
@@ -73,13 +74,14 @@ PLUGINS_CONFIG = {
73
74
 
74
75
  ### Customize the plugin fields choices
75
76
 
76
- Internal partie reference the legal entity of your organization that is a partie to the contract.
77
+ Internal party reference the legal entity of your organization that is a party to the contract.
77
78
  The first currency will also be the default currency for contracts.
78
79
 
79
80
  ```python
80
81
  # configuration.py
81
82
  FIELD_CHOICES = {
82
- 'netbox_contract.Contract.internal_partie': (
83
+ 'netbox_contract.Contract.internal_party': (
84
+ ('default', 'Default entity', 'green'),
83
85
  ('entity1', 'Entity 1', 'green'),
84
86
  ('entity2', 'Entity 2', 'yellow'),
85
87
  ),
@@ -93,6 +95,11 @@ FIELD_CHOICES = {
93
95
  ('active', 'Active', 'green'),
94
96
  ('canceled', 'Canceled', 'red'),
95
97
  )
98
+ 'netbox_contract.Invoice.status': (
99
+ ('draft', 'Draft', 'yellow'),
100
+ ('posted', 'Posted', 'green'),
101
+ ('canceled', 'Canceled', 'red'),
102
+ )
96
103
  }
97
104
 
98
105
  ```
@@ -5,11 +5,11 @@ 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.3.2'
8
+ version = '2.4.1'
9
9
  author = 'Marc Lebreuil'
10
10
  author_email = 'marc@famillelebreuil.net'
11
11
  base_url = 'contracts'
12
- min_version = '4.2.0'
12
+ min_version = '4.3.0'
13
13
  required_settings = []
14
14
  default_settings = {
15
15
  'top_level_menu': False,
@@ -11,6 +11,7 @@ from ..models import (
11
11
  AccountingDimension,
12
12
  Contract,
13
13
  ContractAssignment,
14
+ ContractType,
14
15
  Invoice,
15
16
  InvoiceLine,
16
17
  ServiceProvider,
@@ -23,8 +24,8 @@ class NestedContractSerializer(WritableNestedSerializer):
23
24
  )
24
25
  yrc = serializers.DecimalField(max_digits=10, decimal_places=2, read_only=True)
25
26
  tenant = TenantSerializer(nested=True, required=False, allow_null=True)
26
- external_partie_object_type = ContentTypeField(queryset=ContentType.objects.all())
27
- external_partie_object = serializers.SerializerMethodField(read_only=True)
27
+ external_party_object_type = ContentTypeField(queryset=ContentType.objects.all())
28
+ external_party_object = serializers.SerializerMethodField(read_only=True)
28
29
 
29
30
  class Meta:
30
31
  model = Contract
@@ -33,11 +34,11 @@ class NestedContractSerializer(WritableNestedSerializer):
33
34
  'url',
34
35
  'display',
35
36
  'name',
36
- 'external_partie_object_type',
37
- 'external_partie_object_id',
38
- 'external_partie_object',
37
+ 'external_party_object_type',
38
+ 'external_party_object_id',
39
+ 'external_party_object',
39
40
  'external_reference',
40
- 'internal_partie',
41
+ 'internal_party',
41
42
  'tenant',
42
43
  'status',
43
44
  'start_date',
@@ -53,13 +54,13 @@ class NestedContractSerializer(WritableNestedSerializer):
53
54
  )
54
55
 
55
56
  @swagger_serializer_method(serializer_or_field=serializers.JSONField)
56
- def get_external_partie_object(self, instance):
57
+ def get_external_party_object(self, instance):
57
58
  serializer = get_serializer_for_model(
58
- instance.external_partie_object_type.model_class()
59
+ instance.external_party_object_type.model_class()
59
60
  )
60
61
  context = {'request': self.context['request']}
61
62
  return serializer(
62
- instance.external_partie_object, nested=True, context=context
63
+ instance.external_party_object, nested=True, context=context
63
64
  ).data
64
65
 
65
66
 
@@ -92,8 +93,8 @@ class ContractSerializer(NetBoxModelSerializer):
92
93
  yrc = serializers.DecimalField(max_digits=10, decimal_places=2, read_only=True)
93
94
  parent = NestedContractSerializer(many=False, required=False)
94
95
  tenant = TenantSerializer(nested=True, required=False, allow_null=True)
95
- external_partie_object_type = ContentTypeField(queryset=ContentType.objects.all())
96
- external_partie_object = serializers.SerializerMethodField(read_only=True)
96
+ external_party_object_type = ContentTypeField(queryset=ContentType.objects.all())
97
+ external_party_object = serializers.SerializerMethodField(read_only=True)
97
98
 
98
99
  class Meta:
99
100
  model = Contract
@@ -102,11 +103,11 @@ class ContractSerializer(NetBoxModelSerializer):
102
103
  'url',
103
104
  'display',
104
105
  'name',
105
- 'external_partie_object_type',
106
- 'external_partie_object_id',
107
- 'external_partie_object',
106
+ 'external_party_object_type',
107
+ 'external_party_object_id',
108
+ 'external_party_object',
108
109
  'external_reference',
109
- 'internal_partie',
110
+ 'internal_party',
110
111
  'tenant',
111
112
  'status',
112
113
  'start_date',
@@ -130,11 +131,11 @@ class ContractSerializer(NetBoxModelSerializer):
130
131
  'url',
131
132
  'display',
132
133
  'name',
133
- 'external_partie_object_type',
134
- 'external_partie_object_id',
135
- 'external_partie_object',
134
+ 'external_party_object_type',
135
+ 'external_party_object_id',
136
+ 'external_party_object',
136
137
  'external_reference',
137
- 'internal_partie',
138
+ 'internal_party',
138
139
  'tenant',
139
140
  'status',
140
141
  'start_date',
@@ -151,13 +152,13 @@ class ContractSerializer(NetBoxModelSerializer):
151
152
  )
152
153
 
153
154
  @swagger_serializer_method(serializer_or_field=serializers.JSONField)
154
- def get_external_partie_object(self, instance):
155
+ def get_external_party_object(self, instance):
155
156
  serializer = get_serializer_for_model(
156
- instance.external_partie_object_type.model_class()
157
+ instance.external_party_object_type.model_class()
157
158
  )
158
159
  context = {'request': self.context['request']}
159
160
  return serializer(
160
- instance.external_partie_object, nested=True, context=context
161
+ instance.external_party_object, nested=True, context=context
161
162
  ).data
162
163
 
163
164
 
@@ -395,3 +396,22 @@ class AccountingDimensionSerializer(NetBoxModelSerializer):
395
396
  'last_updated',
396
397
  )
397
398
  brief_fields = ('id', 'name', 'value', 'url', 'display')
399
+
400
+
401
+ class ContractTypeSerializer(NetBoxModelSerializer):
402
+ url = serializers.HyperlinkedIdentityField(view_name='plugins-api:netbox_contract-api:contracttype-detail')
403
+
404
+ class Meta:
405
+ model = ContractType
406
+ fields = (
407
+ 'id',
408
+ 'url',
409
+ 'display',
410
+ 'name',
411
+ 'description',
412
+ 'tags',
413
+ 'custom_fields',
414
+ 'created',
415
+ 'last_updated',
416
+ )
417
+ brief_fields = ('id', 'name', 'description', 'url', 'display')
@@ -6,6 +6,7 @@ app_name = 'netbox_contract'
6
6
 
7
7
  router = NetBoxRouter()
8
8
  router.register('contracts', views.ContractViewSet)
9
+ router.register('contracttype', views.ContractTypeViewSet)
9
10
  router.register('invoices', views.InvoiceViewSet)
10
11
  router.register('serviceproviders', views.ServiceProviderViewSet)
11
12
  router.register('contractassignment', views.ContractAssignmentViewSet)
@@ -7,6 +7,7 @@ from .serializers import (
7
7
  AccountingDimensionSerializer,
8
8
  ContractAssignmentSerializer,
9
9
  ContractSerializer,
10
+ ContractTypeSerializer,
10
11
  InvoiceLineSerializer,
11
12
  InvoiceSerializer,
12
13
  ServiceProviderSerializer,
@@ -51,3 +52,8 @@ class InvoiceLineViewSet(NetBoxModelViewSet):
51
52
  class AccountingDimensionViewSet(NetBoxModelViewSet):
52
53
  queryset = models.AccountingDimension.objects.prefetch_related('tags')
53
54
  serializer_class = AccountingDimensionSerializer
55
+
56
+
57
+ class ContractTypeViewSet(NetBoxModelViewSet):
58
+ queryset = models.ContractType.objects.prefetch_related('tags')
59
+ serializer_class = ContractTypeSerializer
@@ -7,6 +7,7 @@ SERVICE_PROVIDER_MODELS = Q(
7
7
 
8
8
  ASSIGNEMENT_MODELS = Q(
9
9
  Q(app_label='circuits', model__in=('circuit',))
10
+ | Q(app_label='circuits', model__in=('virtualcircuit',))
10
11
  | Q(app_label='dcim', model__in=('site',))
11
12
  | Q(app_label='dcim', model__in=('device',))
12
13
  | Q(app_label='virtualization', model__in=('virtualmachine',))
@@ -1,36 +1,42 @@
1
1
  import django_filters
2
2
  from django.db.models import Q
3
3
  from netbox.filtersets import NetBoxModelFilterSet
4
- from tenancy.filtersets import TenancyFilterSet
4
+ from tenancy.filtersets import ContactModelFilterSet, TenancyFilterSet
5
5
 
6
6
  from .models import (
7
7
  AccountingDimension,
8
8
  AccountingDimensionStatusChoices,
9
9
  Contract,
10
10
  ContractAssignment,
11
+ ContractType,
11
12
  CurrencyChoices,
12
13
  InternalEntityChoices,
13
14
  Invoice,
14
15
  InvoiceLine,
15
16
  ServiceProvider,
16
17
  StatusChoices,
18
+ InvoiceStatusChoices,
17
19
  )
18
20
 
19
21
 
20
- class ContractFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
22
+ class ContractFilterSet(ContactModelFilterSet, NetBoxModelFilterSet, TenancyFilterSet):
21
23
  status = django_filters.MultipleChoiceFilter(choices=StatusChoices, null_value=None)
22
- internal_partie = django_filters.MultipleChoiceFilter(
24
+ internal_party = django_filters.MultipleChoiceFilter(
23
25
  choices=InternalEntityChoices, null_value=None
24
26
  )
25
27
  currency = django_filters.MultipleChoiceFilter(
26
28
  choices=CurrencyChoices, null_value=None
27
29
  )
30
+ contract_type = django_filters.ModelMultipleChoiceFilter(
31
+ field_name='contract_type__name', to_field_name='name', queryset=ContractType.objects.all()
32
+ )
28
33
 
29
34
  class Meta:
30
35
  model = Contract
31
36
  fields = (
32
37
  'id',
33
38
  'name',
39
+ 'contract_type',
34
40
  'external_reference',
35
41
  'start_date',
36
42
  'end_date',
@@ -48,9 +54,15 @@ class ContractFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
48
54
 
49
55
 
50
56
  class InvoiceFilterSet(NetBoxModelFilterSet):
57
+ status = django_filters.MultipleChoiceFilter(choices=InvoiceStatusChoices, null_value=None)
51
58
  currency = django_filters.MultipleChoiceFilter(
52
59
  choices=CurrencyChoices, null_value=None
53
60
  )
61
+ accounting_dimensions = django_filters.ModelChoiceFilter(
62
+ field_name='invoicelines__accounting_dimensions',
63
+ queryset=AccountingDimension.objects.all(),
64
+ label='Accounting Dimension'
65
+ )
54
66
 
55
67
  class Meta:
56
68
  model = Invoice
@@ -71,7 +83,7 @@ class InvoiceFilterSet(NetBoxModelFilterSet):
71
83
  )
72
84
 
73
85
 
74
- class ServiceProviderFilterSet(NetBoxModelFilterSet):
86
+ class ServiceProviderFilterSet(ContactModelFilterSet, NetBoxModelFilterSet):
75
87
  class Meta:
76
88
  model = ServiceProvider
77
89
  fields = ('id', 'name')
@@ -80,6 +92,15 @@ class ServiceProviderFilterSet(NetBoxModelFilterSet):
80
92
  return queryset.filter(name__icontains=value)
81
93
 
82
94
 
95
+ class ContractTypeFilterSet(NetBoxModelFilterSet):
96
+ class Meta:
97
+ model = ContractType
98
+ fields = ('name', 'description', 'color')
99
+
100
+ def search(self, queryset, name, value):
101
+ return queryset.filter(name__icontains=value)
102
+
103
+
83
104
  class ContractAssignmentFilterSet(NetBoxModelFilterSet):
84
105
  class Meta:
85
106
  model = ContractAssignment
@@ -9,10 +9,11 @@ from netbox.forms import (
9
9
  NetBoxModelForm,
10
10
  NetBoxModelImportForm,
11
11
  )
12
+ from tenancy.forms import ContactModelFilterForm, TenancyFilterForm
12
13
  from tenancy.models import Tenant
13
- from tenancy.forms import TenancyFilterForm
14
14
  from utilities.forms import BOOLEAN_WITH_BLANK_CHOICES
15
15
  from utilities.forms.fields import (
16
+ ColorField,
16
17
  CommentField,
17
18
  ContentTypeChoiceField,
18
19
  CSVChoiceField,
@@ -26,18 +27,20 @@ from utilities.forms.fields import (
26
27
  )
27
28
  from utilities.forms.widgets import DatePicker, HTMXSelect
28
29
 
29
- from .constants import SERVICE_PROVIDER_MODELS, ASSIGNEMENT_MODELS
30
+ from .constants import ASSIGNEMENT_MODELS, SERVICE_PROVIDER_MODELS
30
31
  from .models import (
31
32
  AccountingDimension,
32
33
  AccountingDimensionStatusChoices,
33
34
  Contract,
34
35
  ContractAssignment,
36
+ ContractType,
35
37
  CurrencyChoices,
36
38
  InternalEntityChoices,
37
39
  Invoice,
38
40
  InvoiceLine,
39
41
  ServiceProvider,
40
42
  StatusChoices,
43
+ InvoiceStatusChoices,
41
44
  )
42
45
 
43
46
  plugin_settings = settings.PLUGINS_CONFIG['netbox_contract']
@@ -49,13 +52,13 @@ plugin_settings = settings.PLUGINS_CONFIG['netbox_contract']
49
52
  class ContractForm(NetBoxModelForm):
50
53
  comments = CommentField(label=_('Comments'))
51
54
 
52
- external_partie_object_type = ContentTypeChoiceField(
55
+ external_party_object_type = ContentTypeChoiceField(
53
56
  queryset=ContentType.objects.all(),
54
57
  limit_choices_to=SERVICE_PROVIDER_MODELS,
55
58
  widget=HTMXSelect(),
56
- label=_('External partie object type'),
59
+ label=_('External party object type'),
57
60
  )
58
- external_partie_object = forms.ModelChoiceField(queryset=None, label=_('External partie object'))
61
+ external_party_object = forms.ModelChoiceField(queryset=None, label=_('External party object'))
59
62
  tenant = DynamicModelChoiceField(queryset=Tenant.objects.all(), required=False, selector=True, label=_('Tenant'))
60
63
  parent = DynamicModelChoiceField(
61
64
  queryset=Contract.objects.all(),
@@ -63,30 +66,33 @@ class ContractForm(NetBoxModelForm):
63
66
  selector=True,
64
67
  label=_('Parent'),
65
68
  )
69
+ contract_type = DynamicModelChoiceField(
70
+ queryset=ContractType.objects.all(), required=False, selector=True, label=_('Contract type')
71
+ )
66
72
 
67
73
  def __init__(self, *args, **kwargs):
68
74
  initial = kwargs.get('initial', None)
69
75
  super().__init__(*args, **kwargs)
70
76
 
71
77
  # Initialize the external party object gfk
72
- if initial and 'external_partie_object_type' in initial:
73
- external_partie_object_type = ContentType.objects.get_for_id(initial['external_partie_object_type'])
74
- external_partie_class = external_partie_object_type.model_class()
75
- self.fields['external_partie_object'].queryset = external_partie_class.objects.all()
78
+ if initial and 'external_party_object_type' in initial:
79
+ external_party_object_type = ContentType.objects.get_for_id(initial['external_party_object_type'])
80
+ external_party_class = external_party_object_type.model_class()
81
+ self.fields['external_party_object'].queryset = external_party_class.objects.all()
76
82
  if (
77
- self.instance.external_partie_object_type
78
- and self.instance.external_partie_object_type.id == external_partie_object_type.id
83
+ self.instance.external_party_object_type
84
+ and self.instance.external_party_object_type.id == external_party_object_type.id
79
85
  ):
80
- self.fields['external_partie_object'].initial = self.instance.external_partie_object
86
+ self.fields['external_party_object'].initial = self.instance.external_party_object
81
87
  else:
82
- self.fields['external_partie_object'].initial = None
83
- elif self.instance.external_partie_object_type:
84
- external_partie_class = self.instance.external_partie_object_type.model_class()
85
- self.fields['external_partie_object'].queryset = external_partie_class.objects.all()
86
- self.fields['external_partie_object'].initial = self.instance.external_partie_object
88
+ self.fields['external_party_object'].initial = None
89
+ elif self.instance.external_party_object_type:
90
+ external_party_class = self.instance.external_party_object_type.model_class()
91
+ self.fields['external_party_object'].queryset = external_party_class.objects.all()
92
+ self.fields['external_party_object'].initial = self.instance.external_party_object
87
93
  else:
88
- self.fields['external_partie_object'].queryset = ServiceProvider.objects.all()
89
- self.fields['external_partie_object'].initial = None
94
+ self.fields['external_party_object'].queryset = ServiceProvider.objects.all()
95
+ self.fields['external_party_object'].initial = None
90
96
 
91
97
  # Initialise fields settings
92
98
  mandatory_fields = plugin_settings.get('mandatory_contract_fields')
@@ -101,10 +107,11 @@ class ContractForm(NetBoxModelForm):
101
107
  model = Contract
102
108
  fields = (
103
109
  'name',
104
- 'external_partie_object_type',
105
- 'external_partie_object',
110
+ 'contract_type',
111
+ 'external_party_object_type',
112
+ 'external_party_object',
106
113
  'external_reference',
107
- 'internal_partie',
114
+ 'internal_party',
108
115
  'tenant',
109
116
  'status',
110
117
  'start_date',
@@ -135,11 +142,17 @@ class ContractForm(NetBoxModelForm):
135
142
  raise ValidationError('you should set monthly OR yearly recuring costs not both')
136
143
 
137
144
 
138
- class ContractFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
145
+ class ContractFilterForm(ContactModelFilterForm, TenancyFilterForm, NetBoxModelFilterSetForm):
139
146
  model = Contract
140
147
 
148
+ contract_type = DynamicModelChoiceField(
149
+ queryset=ContractType.objects.all(),
150
+ required=False,
151
+ selector=True,
152
+ label=_('Contract type'),
153
+ )
141
154
  external_reference = forms.CharField(required=False, label=_('External reference'))
142
- internal_partie = forms.ChoiceField(choices=InternalEntityChoices, required=False, label=_('Internal partie'))
155
+ internal_party = forms.ChoiceField(choices=InternalEntityChoices, required=False, label=_('Internal party'))
143
156
  status = forms.ChoiceField(choices=StatusChoices, required=False, label=_('Status'))
144
157
  currency = forms.ChoiceField(choices=CurrencyChoices, required=False, label=_('Currency'))
145
158
  parent = DynamicModelChoiceField(
@@ -152,13 +165,13 @@ class ContractFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
152
165
 
153
166
 
154
167
  class ContractCSVForm(NetBoxModelImportForm):
155
- external_partie_object_type = CSVContentTypeField(
168
+ external_party_object_type = CSVContentTypeField(
156
169
  queryset=ContentType.objects.all(),
157
170
  limit_choices_to=SERVICE_PROVIDER_MODELS,
158
171
  help_text='service provider object type in the form <app>.<model>',
159
172
  )
160
- external_partie_object_id = forms.CharField(
161
- help_text='service provider object name', label=_('External partie name')
173
+ external_party_object_id = forms.CharField(
174
+ help_text='service provider object name', label=_('External party name')
162
175
  )
163
176
  tenant = CSVModelChoiceField(
164
177
  queryset=Tenant.objects.all(),
@@ -175,15 +188,23 @@ class ContractCSVForm(NetBoxModelImportForm):
175
188
  required=False,
176
189
  label=_('Parent'),
177
190
  )
191
+ contract_type = CSVModelChoiceField(
192
+ queryset=ContractType.objects.all(),
193
+ to_field_name='name',
194
+ help_text='Contract type name',
195
+ required=False,
196
+ label=_('Contract type'),
197
+ )
178
198
 
179
199
  class Meta:
180
200
  model = Contract
181
201
  fields = [
182
202
  'name',
183
- 'external_partie_object_type',
184
- 'external_partie_object_id',
203
+ 'contract_type',
204
+ 'external_party_object_type',
205
+ 'external_party_object_id',
185
206
  'external_reference',
186
- 'internal_partie',
207
+ 'internal_party',
187
208
  'tenant',
188
209
  'status',
189
210
  'start_date',
@@ -200,18 +221,24 @@ class ContractCSVForm(NetBoxModelImportForm):
200
221
  'parent',
201
222
  ]
202
223
 
203
- def clean_external_partie_object_id(self):
204
- name = self.cleaned_data.get('external_partie_object_id')
205
- external_partie_object_type = self.cleaned_data.get('external_partie_object_type')
206
- external_partie_object = external_partie_object_type.get_object_for_this_type(name=name)
224
+ def clean_external_party_object_id(self):
225
+ name = self.cleaned_data.get('external_party_object_id')
226
+ external_party_object_type = self.cleaned_data.get('external_party_object_type')
227
+ external_party_object = external_party_object_type.get_object_for_this_type(name=name)
207
228
 
208
- return external_partie_object.id
229
+ return external_party_object.id
209
230
 
210
231
 
211
232
  class ContractBulkEditForm(NetBoxModelBulkEditForm):
212
233
  name = forms.CharField(max_length=100, required=False, label=_('Name'))
234
+ contract_type = DynamicModelChoiceField(
235
+ queryset=ContractType.objects.all(),
236
+ required=False,
237
+ selector=True,
238
+ label=_('Contract Type')
239
+ )
213
240
  external_reference = forms.CharField(max_length=100, required=False, label=_('External reference'))
214
- internal_partie = forms.ChoiceField(choices=InternalEntityChoices, required=False, label=_('Internal partie'))
241
+ internal_party = forms.ChoiceField(choices=InternalEntityChoices, required=False, label=_('Internal party'))
215
242
  tenant = DynamicModelChoiceField(queryset=Tenant.objects.all(), required=False, selector=True, label=_('Tenant'))
216
243
  comments = CommentField(required=False, label=_('Comments'))
217
244
  parent = DynamicModelChoiceField(
@@ -225,6 +252,44 @@ class ContractBulkEditForm(NetBoxModelBulkEditForm):
225
252
  model = Contract
226
253
 
227
254
 
255
+ # ContractType
256
+
257
+
258
+ class ContractTypeForm(NetBoxModelForm):
259
+ color = ColorField(label=_('Color'))
260
+
261
+ class Meta:
262
+ model = ContractType
263
+ fields = (
264
+ 'name',
265
+ 'description',
266
+ 'color',
267
+ 'tags',
268
+ )
269
+
270
+
271
+ class ContractTypeCSVForm(NetBoxModelImportForm):
272
+ name = forms.CharField(max_length=100, label=_('Name'))
273
+ description = CommentField(label=_('Description'))
274
+ color = ColorField(label=_('Color'))
275
+
276
+ class Meta:
277
+ model = ContractType
278
+ fields = ['name', 'description', 'color']
279
+
280
+
281
+ class ContractTypeBulkEditForm(NetBoxModelBulkEditForm):
282
+ description = CommentField(label=_('Description'))
283
+ nullable_fields = ('comments',)
284
+ color = ColorField(label=_('Color'), required=False,)
285
+ model = ContractType
286
+
287
+
288
+ class ContractTypeFilterForm(NetBoxModelFilterSetForm):
289
+ model = ContractType
290
+ name = forms.CharField(required=False, label=_('Name'))
291
+ description = CommentField(label=_('Description'))
292
+
228
293
  # Invoice
229
294
 
230
295
 
@@ -316,6 +381,7 @@ class InvoiceForm(NetBoxModelForm):
316
381
  'date',
317
382
  'contracts',
318
383
  'template',
384
+ 'status',
319
385
  'period_start',
320
386
  'period_end',
321
387
  'currency',
@@ -342,6 +408,7 @@ class InvoiceFilterForm(NetBoxModelFilterSetForm):
342
408
  widget=forms.Select(choices=BOOLEAN_WITH_BLANK_CHOICES),
343
409
  label=_('Template'),
344
410
  )
411
+ status = forms.ChoiceField(choices=InvoiceStatusChoices, required=False, label=_('Status'))
345
412
  currency = forms.ChoiceField(choices=CurrencyChoices, required=False, label=_('Currency'))
346
413
  contracts = DynamicModelMultipleChoiceField(
347
414
  queryset=Contract.objects.all(),
@@ -349,6 +416,13 @@ class InvoiceFilterForm(NetBoxModelFilterSetForm):
349
416
  selector=True,
350
417
  label=_('Contracts'),
351
418
  )
419
+ accounting_dimensions = DynamicModelMultipleChoiceField(
420
+ queryset=AccountingDimension.objects.all(),
421
+ required=False,
422
+ selector=True,
423
+ label=_('Accounting Dimensions'),
424
+ )
425
+
352
426
  tag = TagFilterField(model)
353
427
 
354
428
 
@@ -359,6 +433,7 @@ class InvoiceCSVForm(NetBoxModelImportForm):
359
433
  help_text='Related Contracts',
360
434
  label=_('Contracts'),
361
435
  )
436
+ status = CSVChoiceField(choices=InvoiceStatusChoices, help_text='Invoice status', label=_('Status'))
362
437
 
363
438
  class Meta:
364
439
  model = Invoice
@@ -367,6 +442,7 @@ class InvoiceCSVForm(NetBoxModelImportForm):
367
442
  'date',
368
443
  'contracts',
369
444
  'template',
445
+ 'status',
370
446
  'period_start',
371
447
  'period_end',
372
448
  'currency',
@@ -437,7 +513,7 @@ class ServiceProviderForm(NetBoxModelForm):
437
513
  fields = ('name', 'slug', 'portal_url', 'comments', 'tags')
438
514
 
439
515
 
440
- class ServiceProviderFilterForm(NetBoxModelFilterSetForm):
516
+ class ServiceProviderFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
441
517
  model = ServiceProvider
442
518
  name = forms.CharField(required=False, label=_('Name'))
443
519
  tag = TagFilterField(model)