Skip to content

Commit 1eb695c

Browse files
committed
Merge branch 'segment' into 'main'
update segment serializer to handle path file upload. Remove unnecesary... See merge request 701/netbox/cesnet_service_path_plugin!28
2 parents 67ca012 + 6a9880a commit 1eb695c

File tree

7 files changed

+179
-62
lines changed

7 files changed

+179
-62
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,9 @@ The plugin provides comprehensive REST API and GraphQL support:
228228
- `/api/plugins/cesnet-service-path-plugin/service-paths/` - Service path management
229229
- `/api/plugins/cesnet-service-path-plugin/segments/{id}/geojson-api/` - Geographic data
230230

231+
#### Example of segment with path file PATCH and POST
232+
See [detailed example in docs](./docs/API_path.md).
233+
231234
### Geographic API Features
232235

233236
- **Lightweight list serializers** for performance

cesnet_service_path_plugin/api/serializers/segment.py

Lines changed: 69 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,12 @@
1010
from cesnet_service_path_plugin.models.segment import Segment
1111
from cesnet_service_path_plugin.utils import export_segment_paths_as_geojson
1212

13+
from cesnet_service_path_plugin.utils import process_path_data, determine_file_format_from_extension
14+
from django.core.exceptions import ValidationError as DjangoValidationError
15+
1316

1417
class SegmentSerializer(NetBoxModelSerializer):
15-
"""Default serializer Segment - excludes heavy geometry fields"""
18+
"""Default serializer Segment - now with file upload support"""
1619

1720
url = serializers.HyperlinkedIdentityField(view_name="plugins-api:cesnet_service_path_plugin-api:segment-detail")
1821
provider = ProviderSerializer(required=True, nested=True)
@@ -22,60 +25,12 @@ class SegmentSerializer(NetBoxModelSerializer):
2225
location_b = LocationSerializer(required=True, nested=True)
2326
circuits = CircuitSerializer(required=False, many=True, nested=True)
2427

25-
# Only include lightweight path info
26-
has_path_data = serializers.SerializerMethodField(read_only=True)
27-
28-
class Meta:
29-
model = Segment
30-
fields = (
31-
"id",
32-
"url",
33-
"display",
34-
"name",
35-
"status",
36-
"network_label",
37-
"install_date",
38-
"termination_date",
39-
"provider",
40-
"provider_segment_id",
41-
"provider_segment_name",
42-
"provider_segment_contract",
43-
"site_a",
44-
"location_a",
45-
"site_b",
46-
"location_b",
47-
"circuits",
48-
# Only basic path info, no heavy geometry
49-
"path_length_km",
50-
"path_source_format",
51-
"path_notes",
52-
"has_path_data",
53-
"tags",
54-
)
55-
brief_fields = (
56-
"id",
57-
"url",
58-
"display",
59-
"name",
60-
"status",
61-
"has_path_data",
62-
"tags",
63-
)
64-
65-
def get_has_path_data(self, obj):
66-
return obj.has_path_data()
67-
68-
69-
class SegmentListSerializer(NetBoxModelSerializer):
70-
"""Lightweight serializer for list views - excludes heavy geometry fields"""
71-
72-
url = serializers.HyperlinkedIdentityField(view_name="plugins-api:cesnet_service_path_plugin-api:segment-detail")
73-
provider = ProviderSerializer(required=True, nested=True)
74-
site_a = SiteSerializer(required=True, nested=True)
75-
location_a = LocationSerializer(required=True, nested=True)
76-
site_b = SiteSerializer(required=True, nested=True)
77-
location_b = LocationSerializer(required=True, nested=True)
78-
circuits = CircuitSerializer(required=False, many=True, nested=True)
28+
# Add file upload field
29+
path_file = serializers.FileField(
30+
required=False,
31+
write_only=True, # Only for input, not included in output
32+
help_text="Upload a file containing the path geometry. Supported formats: GeoJSON (.geojson, .json), KML (.kml), KMZ (.kmz)",
33+
)
7934

8035
# Only include lightweight path info
8136
has_path_data = serializers.SerializerMethodField(read_only=True)
@@ -105,6 +60,7 @@ class Meta:
10560
"path_source_format",
10661
"path_notes",
10762
"has_path_data",
63+
"path_file", # Add the file field
10864
"tags",
10965
)
11066
brief_fields = (
@@ -120,6 +76,64 @@ class Meta:
12076
def get_has_path_data(self, obj):
12177
return obj.has_path_data()
12278

79+
def update(self, instance, validated_data):
80+
"""Handle file upload during update"""
81+
path_file = validated_data.pop("path_file", None)
82+
83+
# Update other fields first
84+
instance = super().update(instance, validated_data)
85+
86+
# Process uploaded file if provided
87+
if path_file:
88+
try:
89+
# Process the uploaded file using existing utility functions
90+
file_format = determine_file_format_from_extension(path_file.name)
91+
path_geometry = process_path_data(path_file, file_format)
92+
93+
# Update instance with processed geometry
94+
instance.path_geometry = path_geometry
95+
instance.path_source_format = file_format
96+
# path_length_km will be auto-calculated in the model's save method
97+
instance.save()
98+
99+
except DjangoValidationError as e:
100+
raise serializers.ValidationError(f"Path file error: {str(e)}")
101+
except Exception as e:
102+
raise serializers.ValidationError(f"Error processing file '{path_file.name}': {str(e)}")
103+
104+
return instance
105+
106+
def create(self, validated_data):
107+
"""Handle file upload during creation"""
108+
path_file = validated_data.pop("path_file", None)
109+
110+
# Create instance without path data first
111+
instance = super().create(validated_data)
112+
113+
# Process uploaded file if provided
114+
if path_file:
115+
try:
116+
# Process the uploaded file using existing utility functions
117+
file_format = determine_file_format_from_extension(path_file.name)
118+
path_geometry = process_path_data(path_file, file_format)
119+
120+
# Update instance with processed geometry
121+
instance.path_geometry = path_geometry
122+
instance.path_source_format = file_format
123+
# path_length_km will be auto-calculated in the model's save method
124+
instance.save()
125+
126+
except DjangoValidationError as e:
127+
# Clean up created instance if path processing fails
128+
instance.delete()
129+
raise serializers.ValidationError(f"Path file error: {str(e)}")
130+
except Exception as e:
131+
# Clean up created instance if path processing fails
132+
instance.delete()
133+
raise serializers.ValidationError(f"Error processing file '{path_file.name}': {str(e)}")
134+
135+
return instance
136+
123137

124138
class SegmentDetailSerializer(NetBoxModelSerializer):
125139
"""Full serializer with all geometry data for detail views"""

cesnet_service_path_plugin/api/serializers/segment_circuit_mapping.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from rest_framework import serializers
22
from netbox.api.serializers import NetBoxModelSerializer
3-
from cesnet_service_path_plugin.api.serializers.segment import SegmentListSerializer
4-
from cesnet_service_path_plugin.api.serializers.service_path import ServicePathSerializer
3+
from cesnet_service_path_plugin.api.serializers.segment import SegmentSerializer
54
from cesnet_service_path_plugin.models import SegmentCircuitMapping
65
from circuits.api.serializers import CircuitSerializer
76

@@ -11,7 +10,7 @@ class SegmentCircuitMappingSerializer(NetBoxModelSerializer):
1110
view_name="plugins-api:cesnet_service_path_plugin-api:segmentcircuitmapping-detail"
1211
)
1312
circuit = CircuitSerializer(nested=True)
14-
segment = SegmentListSerializer(nested=True)
13+
segment = SegmentSerializer(nested=True)
1514

1615
class Meta:
1716
model = SegmentCircuitMapping

cesnet_service_path_plugin/api/serializers/service_path.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@
22
from netbox.api.serializers import NetBoxModelSerializer
33
from rest_framework import serializers
44

5-
from cesnet_service_path_plugin.api.serializers.segment import SegmentListSerializer
5+
from cesnet_service_path_plugin.api.serializers.segment import SegmentSerializer
66
from cesnet_service_path_plugin.models import ServicePath
77

88

99
class ServicePathSerializer(NetBoxModelSerializer):
1010
url = serializers.HyperlinkedIdentityField(
1111
view_name="plugins-api:cesnet_service_path_plugin-api:servicepath-detail"
1212
)
13-
segments = SegmentListSerializer(many=True, read_only=True, nested=True)
13+
segments = SegmentSerializer(many=True, read_only=True, nested=True)
1414
circuits = CircuitSerializer(required=False, many=True, nested=True)
1515

1616
class Meta:

cesnet_service_path_plugin/api/serializers/service_path_segment_mapping.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from rest_framework import serializers
22
from netbox.api.serializers import NetBoxModelSerializer
3-
from cesnet_service_path_plugin.api.serializers.segment import SegmentListSerializer
3+
from cesnet_service_path_plugin.api.serializers.segment import SegmentSerializer
44
from cesnet_service_path_plugin.api.serializers.service_path import (
55
ServicePathSerializer,
66
)
@@ -16,7 +16,7 @@ class ServicePathSegmentMappingSerializer(NetBoxModelSerializer):
1616
# required=True
1717
# )
1818
service_path = ServicePathSerializer(nested=True)
19-
segment = SegmentListSerializer(nested=True)
19+
segment = SegmentSerializer(nested=True)
2020

2121
class Meta:
2222
model = ServicePathSegmentMapping

cesnet_service_path_plugin/api/views/segment.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,5 @@ def get_serializer_class(self):
2020
if pathdata:
2121
return SegmentDetailSerializer
2222

23+
# Use the updated SegmentSerializer for all other actions (including create/update)
2324
return SegmentSerializer

docs/API_path.md

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# API Usage Examples
2+
3+
## Creating a New Segment with Path Data (POST)
4+
5+
Create a new segment with all required fields and upload path geometry from a file:
6+
7+
```bash
8+
curl -X POST "http://localhost:8000/api/plugins/cesnet_service_path_plugin/segments/" \
9+
-H "Authorization: Token YOUR_API_TOKEN" \
10+
-F "name=Test Segment API" \
11+
-F "status=active" \
12+
-F "provider=32" \
13+
-F "site_a=314" \
14+
-F "location_a=387" \
15+
-F "site_b=36" \
16+
-F "location_b=24" \
17+
-F "network_label=API-TEST-01" \
18+
-F "provider_segment_id=API-TEST-SEGMENT-001" \
19+
-F "provider_segment_name=Test Provider Segment" \
20+
-F "provider_segment_contract=CONTRACT-2024-001" \
21+
-F "path_file=@/path/to/your/segment_path.kmz" \
22+
-F "path_notes=Path data uploaded via API" \
23+
-F "install_date=2024-01-15" \
24+
-F "termination_date=2024-12-31"
25+
```
26+
27+
**Supported file formats:**
28+
- GeoJSON files: `.geojson`, `.json`
29+
- KML files: `.kml`
30+
- KMZ files: `.kmz`
31+
32+
## Updating an Existing Segment (PATCH)
33+
34+
Update segment with ID 10 - you can update any combination of fields:
35+
36+
### Update Path Data Only
37+
```bash
38+
curl -X PATCH "http://localhost:8000/api/plugins/cesnet_service_path_plugin/segments/10/" \
39+
-H "Authorization: Token YOUR_API_TOKEN" \
40+
-F "path_file=@/home/albert/cesnet/netbox/data/prubehy/segment_10_Mapa-Trasa.kmz" \
41+
-F "path_notes=Updated path data via API"
42+
```
43+
44+
### Update Multiple Fields Including Path Data
45+
```bash
46+
curl -X PATCH "http://localhost:8000/api/plugins/cesnet_service_path_plugin/segments/10/" \
47+
-H "Authorization: Token YOUR_API_TOKEN" \
48+
-F "network_label=UPDATED-LABEL" \
49+
-F "provider_segment_name=Updated Provider Name" \
50+
-F "status=planned" \
51+
-F "path_file=@/path/to/updated_path.geojson" \
52+
-F "path_notes=Updated both metadata and path geometry" \
53+
-F "termination_date=2025-06-30"
54+
```
55+
56+
## Important Notes
57+
58+
1. **Authentication**: Replace `YOUR_API_TOKEN` with your actual NetBox API token
59+
2. **Content-Type**: Use `multipart/form-data` (automatic with `-F` flag) when uploading files
60+
3. **File Path**: Use absolute paths for the `path_file` parameter
61+
4. **Required Fields**: For POST requests, ensure all required fields are included:
62+
- `name`, `status`, `provider`, `site_a`, `location_a`, `site_b`, `location_b`
63+
5. **Field IDs**: Use numeric IDs for foreign key fields (provider, sites, locations)
64+
6. **Path Processing**: Files are automatically processed and geometry is calculated
65+
7. **Error Handling**: Invalid files will return detailed error messages
66+
67+
## Response Example
68+
69+
Successful upload will return the segment data with path information:
70+
71+
```json
72+
{
73+
"id": 10,
74+
"name": "Test Segment API",
75+
"status": "active",
76+
"path_length_km": "125.456",
77+
"path_source_format": "kmz",
78+
"path_notes": "Path data uploaded via API",
79+
"has_path_data": true,
80+
...
81+
}
82+
```
83+
84+
## Getting Field IDs
85+
86+
To find the correct IDs for providers, sites, and locations:
87+
88+
```bash
89+
# List providers
90+
curl -H "Authorization: Token YOUR_API_TOKEN" \
91+
"http://localhost:8000/api/circuits/providers/"
92+
93+
# List sites
94+
curl -H "Authorization: Token YOUR_API_TOKEN" \
95+
"http://localhost:8000/api/dcim/sites/"
96+
97+
# List locations for a specific site
98+
curl -H "Authorization: Token YOUR_API_TOKEN" \
99+
"http://localhost:8000/api/dcim/locations/?site_id=314"
100+
```

0 commit comments

Comments
 (0)