diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9191aa9..e00c602 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,621 +1,47 @@
# Changelog
-All notable changes to this project will be documented in this file.
+## [6.1.0] - 2026-03-09
-The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
-and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+> ⚠️ Requires NetBox >= 4.5.4. NetBox 4.5.0–4.5.3 is not supported by this release — use v6.0.x for those versions.
-## [6.0.0] - 2025-01-12
+### Breaking Changes
-### BREAKING CHANGE
+- **Minimum NetBox version raised to 4.5.4.**
+ This release uses `StrFilterLookup` from strawberry-graphql-django >= 0.79.0, which ships with
+ NetBox 4.5.4. Starting the plugin on NetBox 4.5.0–4.5.3 will raise an `ImportError`.
-- **NetBox 4.5.0 Required**: This version is ONLY compatible with NetBox 4.5.0 and later
- - Filter system updated to use NetBox 4.5.0's new filter architecture
- - NOT backward compatible with NetBox 4.4.x
- - Users on NetBox 4.4.x must use plugin version 5.4.x or earlier
-
-### Removed (Phase 3 - Type-Specific Data Cleanup)
-
-- **Database**: Removed `type_specific_data` JSONField column from Segment table (migration 0035)
-- **Models**: Removed legacy JSON validation functions from `segment_types.py`:
- - `SEGMENT_TYPE_SCHEMAS` dictionary (174 lines of JSON schema definitions)
- - `validate_segment_type_data()` - Runtime JSON validation
- - `get_segment_type_schema()` - Schema retrieval
- - `get_all_segment_types()` - Segment type listing
-- **Segment Model**: Removed JSONField-related helper methods:
- - `validate_type_specific_data()` - JSON validation method
- - `get_type_specific_display()` - JSON formatting for templates
- - `has_type_specific_data()` - JSON data check (model method; GraphQL method remains)
-- **API**: `type_specific_data` field is now a computed field (no longer a JSONField)
- - Returns structured data from relational models (DarkFiberSegmentData, OpticalSpectrumSegmentData, EthernetServiceSegmentData)
- - Same field name as before, but underlying implementation is completely different
- - Data comes from OneToOne related models instead of JSON blob
-- **Filters**: Removed `has_type_specific_data` filter from segment filtersets
-- **Forms**: Removed `has_type_specific_data` form field from segment filter forms
-- **Total code reduction**: ~405 lines of legacy code removed
-
-### Changed
-
-- **API**: `type_specific_data` field completely reimplemented as computed field
- - Now returns data from relational models instead of JSON blob
- - Field name intentionally kept the same for API familiarity
- - GraphQL field also renamed from `type_specific_technicals` to `type_specific_data`
-- **Architecture**: Fully migrated to relational database schema for type-specific data
- - All type-specific data stored in dedicated models (Phase 2)
- - JSONField completely removed from database (Phase 3)
- - Clean, normalized schema with proper database constraints
-
-### Technical Details
-
-- **Migration 0035**: One-way migration removes `type_specific_data` column
-- **Data Safety**: All existing data preserved in relational models (migrated in Phase 2)
-- **Performance**: Improved query performance with indexed relational fields
-- **Validation**: Database-level constraints replace runtime JSON validation
-- **Maintainability**: Django model fields replace 272 lines of JSON schema code
-
-### Non-Breaking Change Note
-
-**API field name remains the same**: `type_specific_data`
-- Field name is unchanged from previous versions
-- However, the underlying implementation is completely different:
- - **Before 6.0.0**: Direct JSONField column in database
- - **After 6.0.0**: Computed field reading from relational models
-- **No API client changes required** - the field name is identical
-- Data structure is also similar, making the transition seamless
-
-### Documentation
-
-- Added `PHASE_3_PLAN.md` - Detailed implementation plan
-- Added `PHASE_3_COMPLETE.md` - Completion summary with metrics
-- Added `RUN_MIGRATION_0035.md` - Migration instructions
-- Added `PHASE_3_QUICK_VERIFICATION.md` - Post-migration verification
-
-## [5.4.0] - 2025-12-08
-
-### Added
-
-- **Contract Information Management System**: Complete replacement of SegmentFinancialInfo with ContractInfo
- - Versioned contract system with linear version chains (similar to Git commits)
- - Support for contract amendments and renewals through NetBox clone functionality
- - Many-to-many relationship between contracts and segments (one contract can cover multiple segments)
- - Contract metadata tracking: contract number, type (new/amendment/renewal), effective dates
- - Enhanced recurring charge tracking with configurable periods (monthly, quarterly, annually, etc.)
- - Commitment end date calculation and tracking with visual indicators
- - Contract version history visualization in UI
-
-- **Contract Versioning Features**:
- - Linear version chain using linked list pattern (previous_version/superseded_by)
- - Automatic version numbering (v1, v2, v3...)
- - Version navigation: get first version, latest version, full version history
- - Active/superseded contract status tracking
- - Clone functionality for creating amendments and renewals
-
-- **Contract Financial Tracking**:
- - Recurring charges with customizable periods (monthly, quarterly, semi-annually, annually, bi-annually)
- - Number of recurring charge periods tracking
- - Non-recurring charges for setup/installation fees
- - Multi-currency support with immutable currency (set at contract creation)
- - Automatic financial calculations:
- - Total recurring cost (recurring charge × number of periods)
- - Total contract value (recurring + non-recurring charges)
- - Commitment end date (start date + recurring periods)
-
-- **Contract UI Components**:
- - ContractInfo list view with advanced filtering
- - Contract detail view with version history timeline
- - Color-coded date badges for contract status (green/orange/red/gray)
- - Interactive tooltips showing days remaining and contract status
- - Version chain visualization showing contract evolution
- - Financial summary panel with all calculations
- - Navigation menu integration
-
-- **Contract API Enhancements**:
- - New `/api/plugins/cesnet-service-path-plugin/contract-info/` endpoint
- - Support for versioning fields in API:
- - `previous_version`: Link to previous contract version
- - `superseded_by`: Link to superseding contract version
- - `is_active`: Boolean indicating if contract is current version
- - `version`: Calculated version number
- - Computed financial fields in API responses
- - Advanced filtering: by active status, version status, contract type, currency, dates
-
-- **Segment View Enhancements**:
- - M:N contract relationship support in segment detail view
- - Display all contracts associated with a segment
- - Color-coded contract status indicators
- - Contract end date and commitment end date visualization
-
-- **GraphQL Support**:
- - Updated GraphQL schema for ContractInfo model
- - Support for querying contract versions and relationships
- - Financial calculations available in GraphQL queries
-
-### Changed
-
-- **Breaking**: Replaced SegmentFinancialInfo model with ContractInfo model
- - Changed from 1:1 segment-financial relationship to M:N segment-contract relationship
- - Financial information now managed through contracts rather than directly on segments
- - API endpoint changed from `/segment-financial-info/` to `/contract-info/`
-
-- **Database Schema**:
- - Removed SegmentFinancialInfo table
- - Added ContractInfo table with versioning support
- - Added ContractSegmentMapping join table for M:N relationships
- - Migration automatically converts existing financial data to contracts
-
-- **Financial Field Changes**:
- - Renamed `monthly_charge` to `recurring_charge` with configurable period
- - Added `recurring_charge_period` field (monthly, quarterly, annually, etc.)
- - Renamed `commitment_period_months` to `number_of_recurring_charges`
- - Renamed `recurring_charge_end_date` to `commitment_end_date` for clarity
- - Made recurring charge fields nullable to support amendments without recurring charges
-
-- **Model Improvements**:
- - Currency is now immutable after contract creation (cannot be changed in amendments)
- - All contract attributes can be updated through versioning (except currency)
- - Enhanced clone functionality for proper M2M relationship handling
- - Improved date calculations and validations
-
-- **Color-Coded Date Visualization**:
- - Contract end dates now show color-coded status badges
- - Commitment end dates display with visual indicators:
- - Green: Date has passed
- - Orange: Within 30 days of expiration
- - Red: More than 30 days remaining
- - Gray: Date not set
- - Interactive tooltips showing exact dates and days remaining
-
-### Removed
-
-- **Breaking**: SegmentFinancialInfo model and related components
- - Removed `/api/plugins/cesnet-service-path-plugin/segment-financial-info/` endpoint
- - Removed SegmentFinancialInfo views, forms, tables, and serializers
- - Removed direct financial relationship from segments
+- **GraphQL CharField filter types changed.**
+ All string filter fields migrated from `FilterLookup[str]` → `StrFilterLookup[str]` to eliminate
+ `DuplicatedTypeName` schema errors introduced in strawberry-graphql-django 0.79.0.
+ GraphQL clients relying on type introspection may need updating.
### Fixed
-- Improved decimal handling in financial calculations
-- Enhanced date validation for contract periods
-- Better error handling for version chain operations
-- Fixed M2M relationship serialization in API responses
-
-### Migration Notes
+- GraphQL `@field` resolver methods now correctly declare `Info` type annotations, resolving
+ startup errors on NetBox 4.5.4+ with stricter strawberry-django introspection.
-- **Database Migration Required**: Migration 0033 automatically converts SegmentFinancialInfo to ContractInfo
-- **Data Preservation**: All existing financial data is preserved during migration:
- - Monthly charges → recurring charges (monthly period)
- - Commitment period months → number of recurring charges
- - Segment install/termination dates → contract start/end dates
- - Notes and tags are fully preserved
- - Created/updated timestamps are maintained
-- **API Breaking Change**: Update API clients to use `/contract-info/` endpoint instead of `/segment-financial-info/`
-- **Permission Updates**: New permissions for ContractInfo (view, add, change, delete)
-- **M:N Relationships**: Segments can now be associated with multiple contracts
-- **Versioning Workflow**: Use NetBox clone functionality to create contract amendments
+### Compatibility
-### Upgrade Instructions
-
-1. **Backup your database** before upgrading (important for any major version)
-2. Update the plugin: `pip install --upgrade cesnet_service_path_plugin`
-3. Run migrations: `python manage.py migrate cesnet_service_path_plugin`
-4. Update API integrations to use new `/contract-info/` endpoint
-5. Review and update user permissions for ContractInfo model
-6. Test contract creation and amendment workflow in UI
-
-## [5.3.0] - 2025-11-19
+| cesnet_service_path_plugin | NetBox |
+|---|---|
+| 6.1.0+ | 4.5.4+ |
+| 6.0.x | 4.5.0 – 4.5.3 |
+## [6.0.0] - 2026-01-21
### Added
-
- - **Introduced ownership type support attribute**
- - New **database migration** adding ownership_type to segments.
- - New constants in custom_choices and model methods for ownership type labels and colors.
- - Added backend and frontend color mappings for ownership type (badges + map line colors).
- - Added new "Ownership Type" color scheme to the Segments Map, including:
- - Optimized Segments map color scheme change for faster rendering
+- New features and improvements.
### Changed
-
- - Improved segment map UI:
- - Popups and detail panels now show both status badge and ownership type badge.
- - Map legend updated to support ownership types.
- - Optimized color scheme switching with a new updateSegmentColors() function to avoid full redraw.
- - Status color mapping corrected (duplicate “Planned” entry resolved).
- - Enhanced fall-back line logic to correctly show straight-line path only when path data is missing.
- - Adjusted styling of badges and map-line colors for better consistency with Bootstrap and existing segment status colors.
+- Enhancements to existing functionalities.
### Fixed
+- Bugs addressed in this release.
- - Several UI inconsistencies in map popups where status badges were duplicated or missing label formatting.
- - Missing ownership fields in multiple API outputs and templates.
+### Deprecated
+- Features that will be removed in future releases.
### Removed
+- Obsolete features from the previous versions.
- - Deprecated static color entries in map_status_colors.html.
-
-## [5.2.1] - 2025-11-07
-
-### Added
-- **Topology Visualization**: Interactive network topology visualization using Cytoscape.js
- - Visual representation of segment connections and circuit terminations
- - Multi-topology support for service paths with multiple segments
- - Automatic topology generation for both segments and service paths
- - Clean NetBox Blue styled visualization with gradients and shadows
- - Interactive topology viewer with hover tooltips showing node details
- - Topology visualization integrated into segment and service path detail views
- - Topology visualization added to circuit detail pages showing related segments/service paths
- - Toggle between multiple topologies when segment belongs to multiple service paths
-
-- **Commitment End Date Tracking**: Enhanced financial commitment monitoring
- - Automatic calculation of commitment end date based on install date and commitment period
- - Color-coded commitment status indicators:
- - Red: More than 30 days until end
- - Orange: Within 30 days of end
- - Green: Commitment period has ended
- - Gray: No commitment period set
- - Interactive tooltips showing days remaining until commitment end
- - Visual feedback for commitment periods that have ended
- - Commitment end date displayed in segment detail view with badge styling
- - GraphQL API support for commitment end dates with ISO format
-
-### Changed
-- **Circuit Extensions Refactoring**: Improved code organization
- - Renamed `CircuitKomoraSegmentExtension` to `CircuitSegmentExtension` for better naming consistency
- - Enhanced circuit detail view with topology visualization support
- - Better separation of concerns in template content extensions
- - Circuit pages now show topology visualizations for associated segments
-
-- **Currency Field Enhancement**: Made charge_currency field required
- - Removed default currency value to ensure explicit currency selection
- - Migration `0031` updates currency field constraints
- - Currency must now be explicitly set when creating financial information
- - Prevents accidental use of default currency when not intended
-
-- **Table Improvements**: Enhanced data presentation
- - Circuit column in SegmentCircuitMappingTable now orders by CID instead of name
- - Improved ordering logic for better data organization and searchability
-
-- **Version Update**: Updated to version 5.2.1b5 in pyproject.toml
-
-### Fixed
-- Added missing `python-dateutil` dependency to pyproject.toml for date calculations
-- Improved commitment end date calculation with proper timezone handling using `django.utils.timezone`
-- Enhanced tooltip rendering with proper Bootstrap integration
-- Fixed tooltip data attributes for proper display of commitment information
-
-### Technical Details
-- New utility module `utils_topology.py` with `TopologyBuilder` class for generating network graphs
-- Cytoscape.js (v3.28.1) integration for advanced graph visualization
-- Reusable topology visualization templates:
- - `topology_visualization.html` - Core Cytoscape includes and initialization
- - `topology_segment_card.html` - Topology display card with multi-topology support
- - `topology_styles.html` - Styling for topology containers and tooltips
-- Support for multiple topologies on single page with tab switching functionality
-- Topology data stored as JSON and rendered client-side for performance
-- Color-coding system for commitment status based on time remaining (30-day threshold)
-- New GraphQL field resolver for `commitment_end_date` with ISO format output
-- Template extensions now check for service path membership to generate appropriate topologies
-
-### Migration Notes
-- **Migration 0031**: Updates `charge_currency` field to remove default value - requires explicit currency selection
-- **New Dependencies**: Added `python-dateutil` for relativedelta calculations in commitment period tracking
-- **Template Updates**: New topology visualization templates require Cytoscape.js CDN (included automatically)
-- **API Changes**: GraphQL API now includes `commitment_end_date` field in SegmentFinancialInfoType
-
-### Upgrade Instructions
-1. Run migrations: `python manage.py migrate cesnet_service_path_plugin`
-2. Install new dependency: `pip install python-dateutil` (or upgrade plugin package)
-3. Update existing financial records to set currency explicitly if using default
-4. Refresh browser cache to load new topology visualization assets
-
-## [5.2.0] - 2025-10-29
-
-### Added
-- **Financial Information Management**: New segment financial tracking system
- - `SegmentFinancialInfo` model for tracking segment costs and commitments
- - Monthly charge and non-recurring charge fields
- - Multi-currency support with configurable currency list
- - Commitment period tracking (months)
- - Automatic cost calculations (total commitment cost, total with setup)
- - Permission-based access control for financial data
- - Integration with segment detail view
- - REST API support with nested serialization in segment endpoints
- - Financial info displayed only to users with view permissions
-
-- **Plugin Configuration**: Enhanced configuration options
- - Configurable currency list in plugin settings
- - Default currency selection
- - Example configuration in README
-
-- **Plugin Metadata**: Added `netbox-plugin.yaml`
- - Official plugin metadata file for NetBox plugin registry
- - Compatibility matrix with NetBox versions
- - Package information and versioning
-
-### Changed
-- **API Enhancements**:
- - Improved error handling in segment serializer with detailed logging
- - Financial info included in segment API responses (permission-based)
- - Cleaner error messages for path file upload failures
- - Better separation of validation and processing errors
-
-- **Permission System**:
- - Financial data visibility controlled by Django permissions
- - View, add, change, and delete permissions for financial info
- - Automatic permission checks in views and API
-
-- **Documentation Updates**:
- - Updated plugin configuration examples with currency settings
- - Corrected file path references (configuration.py → configuration/plugins.py)
- - Updated compatibility badge to reflect NetBox 4.4 support
-
-- **Development Dependencies**:
- - Unpinned development dependency versions for flexibility
- - Updated Python version requirement to >= 3.10
- - Corrected license classifier to Apache 2.0
-
-### Technical Details
-- Financial info uses one-to-one relationship with Segment model
-- Currency choices are dynamically loaded from plugin configuration
-- Financial data is optional - segments can exist without financial info
-- API serializer uses method field for conditional financial data inclusion
-- Redirect-based views for better UX (financial detail redirects to segment detail)
-- Custom return URL handling for create/edit/delete operations
-
-### Migration Notes
-- **New Model**: `SegmentFinancialInfo` table will be created
-- **Permissions**: Four new permissions added for financial info management
-- **Configuration**: Optional currency configuration can be added to plugin settings
-- **API Change**: Segment API responses now include `financial_info` field (null if no data or no permission)
-
-## [5.1.0] - 2025-09-23
-
-### Added
-- **Segment Type System**: Complete implementation of segment type classification
- - `segment_type` field with Dark Fiber, Optical Spectrum, and Ethernet Service types
- - Type-specific data fields stored as JSON with dynamic schemas
- - Smart numeric filtering for type-specific fields with operators (>, <, >=, <=, ranges)
- - Dynamic form generation based on selected segment type
- - Type-specific field validation and conversion (Decimal, Integer)
- - Enhanced GraphQL API with type-specific data filtering (`has_type_specific_data`)
-
-- **Enhanced Map Visualization**: Advanced mapping features
- - Segment type-based coloring and legend in map views
- - Color schemes: by status and by provider
- - Improved overlapping segment detection and selection
- - Multiple background map layers (OpenStreetMap, satellite, topographic, CartoDB)
-
-- **Smart Filtering System**: Advanced filtering capabilities
- - Smart numeric filters for JSON fields with operator support
- - Type-specific field filters (fiber_type, connector_type, modulation_format, etc.)
- - Range filters for numeric fields (fiber_attenuation_max, wavelength, port_speed, etc.)
- - Boolean value parsing improvements
- - Enhanced search functionality including segment_type
-
-### Changed
-- Updated segment form to preserve type-specific field values during type changes
-- Enhanced JavaScript form handling to hide fields without clearing values
-- Improved field initialization and population logic
-- Updated segment table to include segment_type column
-- Modified API serializers to handle path file uploads
-- Removed unnecessary SegmentListSerializer, unified with SegmentSerializer
-
-### Fixed
-- Fixed form rendering issues with type-specific fields
-- Improved value preservation when switching segment types
-- Enhanced JSON field conversion for Decimal types
-- Fixed smart numeric filtering edge cases
-- Resolved issues with dynamic field visibility
-
-
-## [5.0.3] - 2025-08-29
-
-### Fixed
-- **Critical**: Added save_m2m() call to SegmentForm to properly save tags
- - Fixed missing many-to-many relationship saving (tags were being lost)
- - Ensured proper persistence of all many-to-many fields
-
-### Changed
-- Updated documentation with sample map in README
-- Added Apache 2.0 License (same as NetBox)
-- Updated pyproject.toml with repository information
-
-## [5.0.2] - 2025-08-21
-
-### Added
-- **Documentation Improvements**: Enhanced README and licensing
- - Apache 2.0 License badge and full license file
- - Sample map visualization in README
- - Updated repository information in pyproject.toml
-
-### Changed
-- Repository metadata and documentation updates
-- Warning about work-in-progress status
-
-## [5.0.1] - 2025-08-04
-
-### Added
-- **Comprehensive Segment Map**: Interactive map view for all segments
- - Map utilizes list view filtering capabilities
- - Multiple background layer options
- - Improved navigation and user experience
-
-### Fixed
-- Fixed button types to prevent form submission when changing map layers
-- Enhanced map layer switching controls
-
-## [5.0.0] - 2025-08-01
-
-### Added
-- **Geographic Path Visualization**: Complete interactive map system with Leaflet
- - Multiple tile layer support (OpenStreetMap, satellite, topographic, CartoDB variants)
- - Individual segment map views with path geometry display
- - Comprehensive segments map view with filtering support
- - Overlapping segment detection and selection interface
- - Status-based color coding for visual segment identification
-
-- **Path Data Management**: Full support for geographic path data
- - KML, KMZ, and GeoJSON file format support
- - Enhanced KMZ processing with multi-layer extraction
- - Automatic 3D to 2D coordinate conversion
- - Path geometry validation and error reporting
- - Automatic path length calculation using projected coordinates
- - Path data export as GeoJSON files
-
-- **Advanced Map Features**:
- - Interactive controls (pan, zoom, fit-to-bounds)
- - Fallback visualization with straight lines when path data unavailable
- - Site markers for segment endpoints
- - Detailed segment information panels
- - Path data availability indicators
- - Responsive map controls and layer switching
-
-- **Enhanced Data Model**:
- - `path_geometry` field for storing MultiLineString geometries
- - `path_length_km` field with automatic calculation
- - `path_source_format` field tracking data origin
- - `path_notes` field for additional metadata
- - Geographic helper methods for coordinate handling
-
-- **UI/UX Improvements**:
- - Template extensions for Circuits, Providers, Sites, Locations, and Tenants
- - Custom table columns showing path data availability
- - Date status indicators with visual progress bars
- - Enhanced filtering including geographic data availability
- - Improved navigation with map view integration
-
-- **API Enhancements**:
- - Separate serializers for list and detail views (performance optimization)
- - Geographic data endpoints for map visualization
- - GeoJSON export capabilities
- - Path bounds and coordinate data in API responses
- - Enhanced filtering on geographic fields
-
-- **GraphQL Support**:
- - Complete GraphQL schema with geographic field support
- - Custom scalar types for path bounds and coordinates
- - Lazy-loaded relationship fields for performance
- - Geographic data queries and filtering
-
-### Changed
-- **Breaking**: Upgraded to Django 5.2.3 with GeoDjango support
-- **Breaking**: Added PostGIS dependency for geographic features
-- **Breaking**: Modified database schema to include geographic fields
-- Improved segment form with path data upload capability
-- Enhanced segment detail view with geographic information
-- Updated table layouts with new path-related columns
-- Refactored status choices to use configurable ChoiceSet system
-- Improved error handling for geographic data processing
-
-### Fixed
-- Resolved migration conflicts during table renaming process
-- Fixed segment validation to properly handle location-site relationships
-- Improved date validation with better error messaging
-- Enhanced KMZ file processing for complex archive structures
-- Fixed coordinate system handling for accurate length calculations
-- Fixed segment detail view table rendering and typos
-
-### Technical Details
-- Added `geopandas`, `fiona`, and `shapely` as core dependencies
-- Implemented comprehensive GIS utility functions
-- Added extensive JavaScript map handling with modular design
-- Created reusable template components for map functionality
-- Enhanced error handling and logging for geographic operations
-- Implemented proper geometric validation and sanitization
-- Replaced setup.py with pyproject.toml for modern Python packaging
-
-### Migration Notes
-- **Database Migration Required**: New geographic fields require PostGIS
-- **Dependency Installation**: Geographic libraries (GDAL, GEOS, PROJ) required
-- **Configuration Updates**: May need GeoDjango configuration updates
-- **Data Migration**: Existing installations will have empty path geometry fields
-
-## [4.3.0] - 2025-05-16
-
-### Added
-- **NetBox 4.3 Compatibility**: Updated for NetBox 4.3 support
- - New URL patterns for bulk operations on service paths, segment mappings, and circuit mappings
- - Enhanced Meta classes for filter consistency
- - Improved import structure for better maintainability
-
-### Changed
-- Updated imports in filters.py for better readability
-- Changed `model` to `models` in template_content.py for multi-model compatibility
-- Removed unused imports and decorators for cleaner code
-- Updated plugin version to 4.3.0
-
-## [4.0.1] - 2025-02-24
-
-### Fixed
-- **Bookmark and Subscription Issues**: Resolved non-functional bookmark and subscription features
-- Updated plugin configuration and version management
-- Removed unused imports for cleaner codebase
-
-## [4.0.0] - 2025-02-19
-
-### Added
-- **Enhanced Service Management**: Comprehensive service path and segment management
- - Ability to assign segments to circuits or service paths from segment detail view
- - Improved ServicePath kind field with ChoiceSet system
- - Enhanced date validation logic in SegmentForm
- - Date status display in table and detail views with color-coded progress bars
-
-### Changed
-- **Breaking**: Refactored from "Komora" to "CESNET" branding throughout
- - Plugin renamed from `komora_service_path_plugin` to `cesnet_service_path_plugin`
- - Database table names changed from `komora_*` to `cesnet_*`
- - URL patterns and configuration updated
- - All references and documentation updated
-
-- **Model Improvements**:
- - Replaced `state` field with `status` field in ServicePath and Segment models
- - Added StatusChoices for consistent status options
- - Made provider field required in SegmentForm
- - Enhanced date validation with install_date/termination_date constraints
-
-- **Data Cleanup**:
- - Removed sync_status from all models
- - Removed device_ and port_ fields from Segment model
- - Merged segment notes (note_a, note_b) into unified comments field
- - Removed imported_data and komora_id fields
- - Removed unnecessary db_table options from models
-
-### Fixed
-- Fixed link for adding segment to a circuit
-- Fixed EditForm for SegmentCircuitMapping model
-- Enabled ID linkify for mapping tables
-- Fixed date status logic and display
-- Enhanced form validation and error handling
-
-### Migration Notes
-- **Breaking**: Database table renaming requires careful migration
-- **Data Migration**: Existing installations need to migrate from old table names
-- **Configuration**: Update plugin configuration from komora to cesnet references
-
-## [0.1.0] - 2024-04-23
-
-### Added
-- **Initial Release**: First version published on PyPI
-- Basic segment and service path management
-- Provider and circuit relationship tracking
-- Simple filtering and table views
-- REST API endpoints
-- NetBox 3.7 compatibility
-
-### Features
-- Segment model with provider, site, and date tracking
-- Service path model with status and kind classification
-- Mapping models for segment-circuit and service path-segment relationships
-- Basic NetBox integration with standard views and forms
-- Template extensions for related model pages
-
----
-
-## Development Guidelines
-
-When updating this changelog:
-1. Add new entries at the top under "Unreleased" section
-2. Move completed features to version sections when releasing
-3. Follow [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) format
-4. Include breaking changes with **Breaking** prefix
-5. Group changes by type: Added, Changed, Deprecated, Removed, Fixed, Security
-6. Include migration notes for database or configuration changes
\ No newline at end of file
+### Security
+- Security improvements implemented.
\ No newline at end of file
diff --git a/CLAUDE.md b/CLAUDE.md
deleted file mode 100644
index f9f878d..0000000
--- a/CLAUDE.md
+++ /dev/null
@@ -1,519 +0,0 @@
-# CLAUDE.md - AI Assistant Context
-
-## Project Overview
-
-**cesnet_service_path_plugin** is a NetBox plugin for managing network service paths and segments with advanced geographic visualization, interactive topology visualization, and financial tracking capabilities.
-
-- **Type**: NetBox Plugin (Django application)
-- **Language**: Python 3.10+
-- **Framework**: Django with NetBox Plugin Framework
-- **Current Version**: 6.0.0
-- **License**: Apache-2.0
-
-## Core Purpose
-
-This plugin extends NetBox to provide comprehensive network service path management with:
-- Network segment tracking between locations
-- Service path definitions connecting multiple segments
-- Geographic path visualization using actual route data (KML, KMZ, GeoJSON)
-- Interactive topology visualization using Cytoscape.js
-- Financial information tracking with multi-currency support
-- Contract management with versioning
-
-## Architecture
-
-### Directory Structure
-
-```
-cesnet_service_path_plugin/
-├── cesnet_service_path_plugin/ # Main plugin package
-│ ├── api/ # REST API (views, serializers, URLs)
-│ │ ├── views/ # API ViewSets
-│ │ └── serializers/ # DRF serializers
-│ ├── filtersets/ # Django filters for list views
-│ ├── forms/ # Django forms for CRUD operations
-│ ├── graphql/ # GraphQL schema and types
-│ ├── migrations/ # Django database migrations
-│ ├── models/ # Core data models
-│ │ ├── segment.py # Network segment model
-│ │ ├── service_path.py # Service path model
-│ │ ├── contract_info.py # Contract tracking with versioning
-│ │ ├── segment_types.py # Segment type definitions
-│ │ └── custom_choices.py # Status, currency, etc. choices
-│ ├── tables/ # django-tables2 definitions
-│ ├── templates/ # Django HTML templates
-│ ├── templatetags/ # Custom template filters/tags
-│ ├── utils/ # Utility functions
-│ │ ├── utils_gis.py # Geographic data processing
-│ │ └── utils_topology.py # Topology generation
-│ └── views/ # Django views
-├── tests/ # Test suite
-└── docs/ # Documentation
-
-```
-
-### Key Technologies
-
-- **Django**: Web framework (NetBox is built on Django)
-- **Django GIS (GeoDjango)**: Geographic database features
-- **PostGIS**: PostgreSQL extension for geographic data
-- **GDAL/GEOS/PROJ**: Geographic data processing libraries
-- **GeoPandas/Fiona/Shapely**: Python geographic libraries
-- **Leaflet.js**: Interactive map visualization
-- **Cytoscape.js**: Network topology graphs
-- **Django REST Framework**: API endpoints
-- **Strawberry GraphQL**: GraphQL API (via NetBox)
-
-### Database Requirements
-
-- PostgreSQL with PostGIS extension enabled
-- Must use `django.contrib.gis.db.backends.postgis` engine
-- Stores geographic data using MultiLineString geometries (SRID 4326 - WGS84)
-
-## Core Models
-
-### 1. Segment (`models/segment.py`)
-
-Represents a physical network segment between two locations.
-
-**Key Fields:**
-- `name`: Segment identifier
-- `network_label`: Optional network label
-- `provider`: Foreign key to NetBox Provider
-- `site_a`, `site_b`: Start and end sites (required)
-- `location_a`, `location_b`: Specific locations within sites (optional)
-- `install_date`, `termination_date`: Lifecycle dates
-- `status`: Active, Planned, Offline, etc. (customizable)
-- `ownership_type`: Leased, Owned, etc.
-- `segment_type`: Dark fiber, optical spectrum, ethernet service
-- `path_geometry`: PostGIS MultiLineString (geographic route)
-- `path_length_km`: Calculated path length
-- `circuits`: Many-to-Many through SegmentCircuitMapping
-
-**Type-Specific Data Models (OneToOne relationships):**
-- `DarkFiberSegmentData`: Fiber mode, attenuation, connectors, etc.
-- `OpticalSpectrumSegmentData`: Wavelength, dispersion, modulation, etc.
-- `EthernetServiceSegmentData`: Port speed, VLAN, encapsulation, MTU, etc.
-
-**Geographic Features:**
-- Stores actual geographic paths (not just A-to-B lines)
-- Supports KML, KMZ, and GeoJSON upload
-- Automatic path length calculation
-- Fallback to straight lines if no path data
-
-### 2. ServicePath (`models/service_path.py`)
-
-Represents a logical service path composed of multiple segments.
-
-**Key Fields:**
-- `name`: Service path identifier
-- `status`: Active, Planned, Offline
-- `kind`: Experimental, Core, Customer (customizable)
-- `segments`: Many-to-Many through ServicePathSegmentMapping
-- `comments`: Additional notes
-
-**Purpose:**
-- Group segments into logical end-to-end paths
-- Visualize complete service topology
-- Track service-level status and classification
-
-### 3. ContractInfo (`models/contract_info.py`)
-
-Tracks legal agreements and financial information for segments.
-
-**Version Chain:**
-- Contracts support version history using linked list pattern
-- `previous_version` → points to older version
-- `superseded_by` → points to newer version
-- Contract types: New, Amendment, Renewal
-
-**Key Fields:**
-- `contract_number`: Provider's reference
-- `contract_type`: New, Amendment, Renewal (auto-set)
-- `effective_date`: When contract version takes effect
-- `charge_currency`: Currency (fixed, cannot change in amendments)
-- `non_recurring_charge`: One-time setup fees (cumulative)
-- `recurring_charge`: Regular periodic charge
-- `recurring_period`: Monthly, Quarterly, Annual
-- `commitment_months`: Contract commitment period
-- `change_reason`: Required for amendments/renewals
-- `segments`: Many-to-Many through ContractSegmentMapping
-
-**Financial Calculations:**
-- Commitment end date based on effective date + commitment months
-- Visual status badges (red >30 days, orange <30 days, green expired)
-- Total costs calculated automatically
-
-### 4. Mapping Models
-
-**ServicePathSegmentMapping:**
-- Links segments to service paths
-- Tracks position/order in the path
-
-**SegmentCircuitMapping:**
-- Links segments to NetBox circuits
-- Many-to-many relationship
-
-**ContractSegmentMapping:**
-- Links contract versions to segments
-- Supports multiple segments per contract
-
-### 5. Type-Specific Data Models
-
-Three specialized models store technical parameters for different segment types using OneToOne relationships with Segment:
-
-**DarkFiberSegmentData** (`models/dark_fiber_data.py`):
-- `fiber_mode`: Single-mode or Multimode
-- `single_mode_subtype`: G.652D, G.655, G.657A1, etc.
-- `multimode_subtype`: OM1, OM2, OM3, OM4, OM5
-- `jacket_type`: Indoor, Outdoor, Armored, etc.
-- `fiber_attenuation_max`: Maximum attenuation (dB/km)
-- `total_loss`: End-to-end optical loss (dB)
-- `total_length`: Physical cable length (km)
-- `number_of_fibers`: Fiber strand count
-- `connector_type_side_a`, `connector_type_side_b`: LC/APC, SC/UPC, etc.
-
-**OpticalSpectrumSegmentData** (`models/optical_spectrum_data.py`):
-- `wavelength`: Center wavelength (nm) - C-band/L-band
-- `spectral_slot_width`: Optical channel bandwidth (GHz)
-- `itu_grid_position`: ITU-T G.694.1 channel number
-- `chromatic_dispersion`: Dispersion at wavelength (ps/nm)
-- `pmd_tolerance`: Polarization mode dispersion (ps)
-- `modulation_format`: NRZ, PAM4, QPSK, 16QAM, etc.
-
-**EthernetServiceSegmentData** (`models/ethernet_service_data.py`):
-- `port_speed`: Bandwidth (Mbps)
-- `vlan_id`: Primary VLAN tag (1-4094)
-- `vlan_tags`: Additional VLANs for QinQ
-- `encapsulation_type`: 802.1Q, 802.1ad, MPLS, MEF E-Line, etc.
-- `interface_type`: RJ45, SFP, SFP+, QSFP+, QSFP28, etc.
-- `mtu_size`: Maximum transmission unit (bytes)
-
-**Architecture Benefits:**
-- **Database constraints**: Field validation at DB level (NOT NULL, CHECK, ranges)
-- **Indexed fields**: Fast queries on individual parameters
-- **Type safety**: Django ORM prevents invalid data types
-- **Maintainability**: Add fields via migrations, not JSON schema updates
-- **API clarity**: Structured objects, not JSON blobs
-
-**API Access:**
-```python
-# Computed field on Segment API
-segment['type_specific_technicals'] # Returns appropriate model data
-```
-
-## Key Features
-
-### 1. Geographic Visualization
-
-**Map Features:**
-- Interactive Leaflet maps with multiple tile layers
-- OpenStreetMap, satellite, topographic views
-- Status-based, provider-based, or segment-type color coding
-- Click segments for detailed information
-- Export GeoJSON data via API
-
-**Path Data Processing:**
-- Upload KML, KMZ, or GeoJSON files
-- Automatic 3D to 2D conversion
-- Multi-segment path support
-- Length calculation using projected coordinates
-- Path validation with error reporting
-
-**Implementation:** `utils/utils_gis.py`
-
-### 2. Topology Visualization
-
-**Features:**
-- Interactive network graphs using Cytoscape.js
-- Automatic topology generation for segments and service paths
-- Multi-topology support (toggle between different views)
-- Node types: locations, circuits, circuit terminations
-- Hover tooltips with detailed information
-- NetBox Blue themed styling
-
-**Implementation:** `utils/utils_topology.py`
-
-### 3. Contract Management
-
-**Versioning System:**
-- Linear version chain (linked list)
-- Types: New contract, Amendment, Renewal
-- Immutable currency across versions
-- Change tracking with reason field
-
-**Financial Tracking:**
-- Multi-currency support (configurable)
-- Recurring and non-recurring charges
-- Commitment period tracking
-- Automatic end date calculation
-- Visual status indicators
-
-### 4. API Support
-
-**REST API:**
-- Full CRUD operations for all models
-- Geographic data in GeoJSON format
-- Financial data with permission checks
-- Endpoints: `/api/plugins/cesnet-service-path-plugin/`
-
-**GraphQL API:**
-- Query segments, service paths, contracts
-- Geographic fields (geometry, bounds, coordinates)
-- Advanced filtering
-- Nested relationships
-- Access: `/graphql/`
-
-## Configuration
-
-### Plugin Configuration (`configuration/plugins.py`)
-
-```python
-PLUGINS = [
- 'cesnet_service_path_plugin',
-]
-
-PLUGINS_CONFIG = {
- "cesnet_service_path_plugin": {
- # Currency configuration
- 'currencies': [
- ('CZK', 'Czech Koruna'),
- ('EUR', 'Euro'),
- ('USD', 'US Dollar'),
- ],
- 'default_currency': 'EUR',
- },
-}
-```
-
-### Custom Choices
-
-Extend status, kind, or other choices in `configuration.py`:
-
-```python
-FIELD_CHOICES = {
- 'cesnet_service_path_plugin.choices.status': (
- ('custom_status', 'Custom Status', 'blue'),
- ),
- 'cesnet_service_path_plugin.choices.kind': (
- ('custom_kind', 'Custom Kind', 'purple'),
- )
-}
-```
-
-## Development Guidelines
-
-### Database Engine Requirement
-
-Must configure NetBox to use PostGIS engine:
-
-```python
-DATABASE_ENGINE = "django.contrib.gis.db.backends.postgis"
-```
-
-### System Dependencies
-
-Required for geographic features:
-- PostgreSQL with PostGIS extension
-- GDAL runtime libraries (`gdal-bin`, `libgdal34`)
-- GEOS libraries (`libgeos-c1t64`)
-- PROJ libraries (`libproj25`)
-
-### Testing Geographic Features
-
-```python
-from cesnet_service_path_plugin.utils import check_gis_environment
-check_gis_environment()
-```
-
-### Code Style
-
-- Uses Black and autopep8 for formatting
-- Ruff for linting (`.ruff.toml`)
-- Django best practices
-- NetBox plugin patterns
-
-## Important Patterns
-
-### 1. Model Registration
-
-Models inherit from `NetBoxModel` which provides:
-- Automatic primary key
-- Created/last_updated timestamps
-- Custom fields support
-- Tags support
-- Change logging
-
-### 2. Geographic Data Handling
-
-**Upload Path Data:**
-1. User uploads KML/KMZ/GeoJSON file via form
-2. `utils_gis.py` processes file (validation, conversion)
-3. Stored as PostGIS MultiLineString
-4. Length calculated and stored
-
-**Display Path Data:**
-1. API endpoint returns GeoJSON
-2. Leaflet map renders paths
-3. Click handlers show segment details
-
-### 3. Permission-Based Visibility
-
-Financial information respects Django permissions:
-- View: `cesnet_service_path_plugin.view_contractinfo`
-- Add: `cesnet_service_path_plugin.add_contractinfo`
-- Change: `cesnet_service_path_plugin.change_contractinfo`
-- Delete: `cesnet_service_path_plugin.delete_contractinfo`
-
-API includes financial data only if user has view permission.
-
-### 4. Template Extensions
-
-Plugin extends NetBox core pages:
-- Circuit pages: Show related segments
-- Provider pages: List provider segments
-- Site/Location pages: Display connected segments
-- Uses NetBox template extension points
-
-## Common Tasks
-
-### Adding a New Model
-
-1. Create model in `models/`
-2. Add to `models/__init__.py`
-3. Create migration: `python manage.py makemigrations cesnet_service_path_plugin`
-4. Create serializer in `api/serializers/`
-5. Create viewset in `api/views/`
-6. Add URL route in `api/urls.py`
-7. Create form in `forms/`
-8. Create table in `tables/`
-9. Create filterset in `filtersets/`
-10. Create views in `views/`
-11. Add URL routes in `urls.py`
-12. Add to navigation in `navigation.py`
-13. Update GraphQL schema if needed
-
-### Adding a Geographic Feature
-
-1. Use GeoDjango fields (`gis_models.MultiLineStringField`)
-2. Process with GeoPandas/Fiona in `utils_gis.py`
-3. Serialize as GeoJSON in API
-4. Render with Leaflet in templates
-
-### Working with Contracts
-
-**Creating New Contract:**
-- Use `ContractTypeChoices.NEW`
-- No previous_version
-
-**Creating Amendment:**
-- Clone existing contract data
-- Set `previous_version` to current active
-- Set `superseded_by` on old contract
-- Use `ContractTypeChoices.AMENDMENT`
-- Currency must match original
-
-**Creating Renewal:**
-- Similar to amendment
-- Use `ContractTypeChoices.RENEWAL`
-- Typically extends commitment period
-
-## Testing
-
-Run tests:
-```bash
-pytest
-```
-
-Key test files:
-- `tests/test_komora_service_path_plugin.py`: Main integration tests
-- `tests/test_integration_segment_type_specific_data.py`: Type-specific data tests
-- `tests/test_integration_segment_financial_info_api.py`: Financial API tests
-
-## Troubleshooting
-
-### PostGIS Not Available
-
-**Symptoms:** GeoDjango errors, cannot create geographic fields
-
-**Solution:**
-1. Enable PostGIS: `CREATE EXTENSION IF NOT EXISTS postgis;`
-2. Configure engine: `DATABASE_ENGINE = "django.contrib.gis.db.backends.postgis"`
-3. Install system libraries: `gdal-bin`, `libgdal34`, `libgeos-c1t64`, `libproj25`
-
-### Path Upload Fails
-
-**Symptoms:** File upload rejected, validation errors
-
-**Solution:**
-- Check file format (KML, KMZ, GeoJSON)
-- Verify file contains LineString/MultiLineString geometries
-- Check for 3D coordinates (will auto-convert to 2D)
-- Review error messages in UI
-
-### Financial Data Not Visible
-
-**Symptoms:** Contract info not showing in UI or API
-
-**Solution:**
-- Check user has `view_contractinfo` permission
-- Verify contract exists and is linked to segment
-- Check API response for null financial_info field
-
-### Map Not Loading
-
-**Symptoms:** Blank map, JavaScript errors
-
-**Solution:**
-- Check browser console for tile layer errors
-- Verify internet connectivity (tile layers from CDN)
-- Check segment has valid path_geometry data
-
-## URLs and Endpoints
-
-### Web UI
-- Segments List: `/plugins/cesnet-service-path-plugin/segments/`
-- Segments Map: `/plugins/cesnet-service-path-plugin/segments-map/`
-- Service Paths: `/plugins/cesnet-service-path-plugin/service-paths/`
-- Contracts: `/plugins/cesnet-service-path-plugin/contracts/`
-
-### API
-- Base: `/api/plugins/cesnet-service-path-plugin/`
-- Segments: `/api/plugins/cesnet-service-path-plugin/segments/`
-- Service Paths: `/api/plugins/cesnet-service-path-plugin/service-paths/`
-- Contracts: `/api/plugins/cesnet-service-path-plugin/contracts/`
-- GeoJSON Export: `/api/plugins/cesnet-service-path-plugin/segments/{id}/geojson-api/`
-
-### GraphQL
-- Endpoint: `/graphql/`
-- Queries: `segment_list`, `service_path_list`, `contract_info_list`
-
-## Resources
-
-- **Repository**: https://github.com/CESNET/cesnet_service_path_plugin
-- **Issues**: https://github.com/CESNET/cesnet_service_path_plugin/issues
-- **NetBox Docs**: https://docs.netbox.dev/
-- **NetBox Plugin Dev**: https://github.com/netbox-community/netbox-plugin-tutorial
-- **GeoDjango**: https://docs.djangoproject.com/en/stable/ref/contrib/gis/
-- **PostGIS**: https://postgis.net/documentation/
-- **Leaflet**: https://leafletjs.com/reference.html
-- **Cytoscape.js**: https://js.cytoscape.org/
-
-## Recent Changes (v5.3.0)
-
-- Implemented versioned contract system with M:N segment relationships
-- Contract versioning using linear chain (linked list) pattern
-- Support for contract amendments and renewals
-- Immutable attributes and cumulative charges across versions
-- Removed direct provider relationship from contract model
-- Enhanced contract filtering and display
-
-## Authors
-
-- Jan Krupa (jan.krupa@cesnet.cz)
-- Jiri Vrany (jiri.vrany@cesnet.cz)
-
----
-
-**Last Updated**: Based on version 5.3.0 analysis
-**For AI Assistants**: This document provides context for understanding and working with the cesnet_service_path_plugin codebase. Use it to answer questions, make changes, or debug issues.
diff --git a/cesnet_service_path_plugin/__init__.py b/cesnet_service_path_plugin/__init__.py
index eb30013..91a326b 100644
--- a/cesnet_service_path_plugin/__init__.py
+++ b/cesnet_service_path_plugin/__init__.py
@@ -21,7 +21,7 @@ class CesnetServicePathPluginConfig(PluginConfig):
base_url = "cesnet-service-path-plugin"
author = __email__
graphql_schema = "graphql.schema"
- min_version = "4.5.0"
+ min_version = "4.5.4"
max_version = "4.5.99"
diff --git a/cesnet_service_path_plugin/api/serializers/contract_info.py b/cesnet_service_path_plugin/api/serializers/contract_info.py
index ea1e827..0e5843b 100644
--- a/cesnet_service_path_plugin/api/serializers/contract_info.py
+++ b/cesnet_service_path_plugin/api/serializers/contract_info.py
@@ -28,11 +28,11 @@ class ContractInfoSerializer(NetBoxModelSerializer):
queryset=ContractInfo.objects.all(),
required=False,
allow_null=True,
- help_text="Link to previous version for amendments/renewals"
+ help_text="Link to previous version for amendments/renewals",
)
contract_type = serializers.CharField(
required=False,
- help_text="Type of contract (auto-set to 'amendment' if previous_version exists, can be set to 'renewal')"
+ help_text="Type of contract (auto-set to 'amendment' if previous_version exists, can be set to 'renewal')",
)
# Read-only versioning fields
superseded_by = serializers.PrimaryKeyRelatedField(read_only=True)
@@ -122,11 +122,13 @@ def get_segments_detail(self, obj):
"plugins-api:cesnet_service_path_plugin-api:segment-detail", kwargs={"pk": segment.id}
)
- segments_list.append({
- "id": segment.id,
- "url": segment_url,
- "display": str(segment),
- "name": segment.name,
- })
+ segments_list.append(
+ {
+ "id": segment.id,
+ "url": segment_url,
+ "display": str(segment),
+ "name": segment.name,
+ }
+ )
return segments_list
diff --git a/cesnet_service_path_plugin/api/serializers/dark_fiber_data_serializer.py b/cesnet_service_path_plugin/api/serializers/dark_fiber_data_serializer.py
index 0ec5d8f..ccc0506 100644
--- a/cesnet_service_path_plugin/api/serializers/dark_fiber_data_serializer.py
+++ b/cesnet_service_path_plugin/api/serializers/dark_fiber_data_serializer.py
@@ -18,46 +18,44 @@ class DarkFiberSegmentDataSerializer(NetBoxModelSerializer):
# Write-only segment ID for POST/PUT/PATCH operations
segment_id = serializers.PrimaryKeyRelatedField(
queryset=Segment.objects.all(),
- source='segment',
+ source="segment",
write_only=True,
required=True,
- help_text="ID of the segment this technical data belongs to"
+ help_text="ID of the segment this technical data belongs to",
)
class Meta:
model = DarkFiberSegmentData
fields = [
- 'segment',
- 'segment_id',
- 'fiber_mode',
- 'single_mode_subtype',
- 'multimode_subtype',
- 'jacket_type',
- 'fiber_attenuation_max',
- 'total_loss',
- 'total_length',
- 'number_of_fibers',
- 'connector_type_side_a',
- 'connector_type_side_b',
- 'created',
- 'last_updated',
+ "segment",
+ "segment_id",
+ "fiber_mode",
+ "single_mode_subtype",
+ "multimode_subtype",
+ "jacket_type",
+ "fiber_attenuation_max",
+ "total_loss",
+ "total_length",
+ "number_of_fibers",
+ "connector_type_side_a",
+ "connector_type_side_b",
+ "created",
+ "last_updated",
]
def get_segment(self, obj):
"""Return basic segment info for GET operations"""
- request = self.context.get('request')
+ request = self.context.get("request")
if not request:
return {
- 'id': obj.segment.id,
- 'name': obj.segment.name,
+ "id": obj.segment.id,
+ "name": obj.segment.name,
}
return {
- 'id': obj.segment.id,
- 'name': obj.segment.name,
- 'url': request.build_absolute_uri(
- f'/api/plugins/cesnet-service-path-plugin/segments/{obj.segment.id}/'
- )
+ "id": obj.segment.id,
+ "name": obj.segment.name,
+ "url": request.build_absolute_uri(f"/api/plugins/cesnet-service-path-plugin/segments/{obj.segment.id}/"),
}
def validate(self, data):
@@ -68,15 +66,15 @@ def validate(self, data):
"""
from django.core.exceptions import ValidationError as DjangoValidationError
- segment = data.get('segment')
+ segment = data.get("segment")
# Check if this is an update (instance exists) or create (new instance)
if not self.instance and segment:
# Creating new instance - check if segment already has data
- if hasattr(segment, 'dark_fiber_data'):
- raise serializers.ValidationError({
- 'segment': f'Segment "{segment.name}" already has dark fiber technical data.'
- })
+ if hasattr(segment, "dark_fiber_data"):
+ raise serializers.ValidationError(
+ {"segment": f'Segment "{segment.name}" already has dark fiber technical data.'}
+ )
# Trigger model-level validation by creating a temporary instance
# This ensures model's clean() method is called
@@ -93,6 +91,6 @@ def validate(self, data):
instance.clean()
except DjangoValidationError as e:
# Convert Django ValidationError to DRF ValidationError
- raise serializers.ValidationError(e.message_dict if hasattr(e, 'message_dict') else str(e))
+ raise serializers.ValidationError(e.message_dict if hasattr(e, "message_dict") else str(e))
return data
diff --git a/cesnet_service_path_plugin/api/serializers/ethernet_service_data_serializer.py b/cesnet_service_path_plugin/api/serializers/ethernet_service_data_serializer.py
index b9b71be..680d19c 100644
--- a/cesnet_service_path_plugin/api/serializers/ethernet_service_data_serializer.py
+++ b/cesnet_service_path_plugin/api/serializers/ethernet_service_data_serializer.py
@@ -18,42 +18,40 @@ class EthernetServiceSegmentDataSerializer(NetBoxModelSerializer):
# Write-only segment ID for POST/PUT/PATCH operations
segment_id = serializers.PrimaryKeyRelatedField(
queryset=Segment.objects.all(),
- source='segment',
+ source="segment",
write_only=True,
required=True,
- help_text="ID of the segment this technical data belongs to"
+ help_text="ID of the segment this technical data belongs to",
)
class Meta:
model = EthernetServiceSegmentData
fields = [
- 'segment',
- 'segment_id',
- 'port_speed',
- 'vlan_id',
- 'vlan_tags',
- 'encapsulation_type',
- 'interface_type',
- 'mtu_size',
- 'created',
- 'last_updated',
+ "segment",
+ "segment_id",
+ "port_speed",
+ "vlan_id",
+ "vlan_tags",
+ "encapsulation_type",
+ "interface_type",
+ "mtu_size",
+ "created",
+ "last_updated",
]
def get_segment(self, obj):
"""Return basic segment info for GET operations"""
- request = self.context.get('request')
+ request = self.context.get("request")
if not request:
return {
- 'id': obj.segment.id,
- 'name': obj.segment.name,
+ "id": obj.segment.id,
+ "name": obj.segment.name,
}
return {
- 'id': obj.segment.id,
- 'name': obj.segment.name,
- 'url': request.build_absolute_uri(
- f'/api/plugins/cesnet-service-path-plugin/segments/{obj.segment.id}/'
- )
+ "id": obj.segment.id,
+ "name": obj.segment.name,
+ "url": request.build_absolute_uri(f"/api/plugins/cesnet-service-path-plugin/segments/{obj.segment.id}/"),
}
def validate(self, data):
@@ -64,15 +62,15 @@ def validate(self, data):
"""
from django.core.exceptions import ValidationError as DjangoValidationError
- segment = data.get('segment')
+ segment = data.get("segment")
# Check if this is an update (instance exists) or create (new instance)
if not self.instance and segment:
# Creating new instance - check if segment already has data
- if hasattr(segment, 'ethernet_service_data'):
- raise serializers.ValidationError({
- 'segment': f'Segment "{segment.name}" already has ethernet service technical data.'
- })
+ if hasattr(segment, "ethernet_service_data"):
+ raise serializers.ValidationError(
+ {"segment": f'Segment "{segment.name}" already has ethernet service technical data.'}
+ )
# Trigger model-level validation by creating a temporary instance
# This ensures model's clean() method is called
@@ -89,6 +87,6 @@ def validate(self, data):
instance.clean()
except DjangoValidationError as e:
# Convert Django ValidationError to DRF ValidationError
- raise serializers.ValidationError(e.message_dict if hasattr(e, 'message_dict') else str(e))
+ raise serializers.ValidationError(e.message_dict if hasattr(e, "message_dict") else str(e))
return data
diff --git a/cesnet_service_path_plugin/api/serializers/optical_spectrum_data_serializer.py b/cesnet_service_path_plugin/api/serializers/optical_spectrum_data_serializer.py
index 2fcd18a..90dd358 100644
--- a/cesnet_service_path_plugin/api/serializers/optical_spectrum_data_serializer.py
+++ b/cesnet_service_path_plugin/api/serializers/optical_spectrum_data_serializer.py
@@ -18,42 +18,40 @@ class OpticalSpectrumSegmentDataSerializer(NetBoxModelSerializer):
# Write-only segment ID for POST/PUT/PATCH operations
segment_id = serializers.PrimaryKeyRelatedField(
queryset=Segment.objects.all(),
- source='segment',
+ source="segment",
write_only=True,
required=True,
- help_text="ID of the segment this technical data belongs to"
+ help_text="ID of the segment this technical data belongs to",
)
class Meta:
model = OpticalSpectrumSegmentData
fields = [
- 'segment',
- 'segment_id',
- 'wavelength',
- 'spectral_slot_width',
- 'itu_grid_position',
- 'chromatic_dispersion',
- 'pmd_tolerance',
- 'modulation_format',
- 'created',
- 'last_updated',
+ "segment",
+ "segment_id",
+ "wavelength",
+ "spectral_slot_width",
+ "itu_grid_position",
+ "chromatic_dispersion",
+ "pmd_tolerance",
+ "modulation_format",
+ "created",
+ "last_updated",
]
def get_segment(self, obj):
"""Return basic segment info for GET operations"""
- request = self.context.get('request')
+ request = self.context.get("request")
if not request:
return {
- 'id': obj.segment.id,
- 'name': obj.segment.name,
+ "id": obj.segment.id,
+ "name": obj.segment.name,
}
return {
- 'id': obj.segment.id,
- 'name': obj.segment.name,
- 'url': request.build_absolute_uri(
- f'/api/plugins/cesnet-service-path-plugin/segments/{obj.segment.id}/'
- )
+ "id": obj.segment.id,
+ "name": obj.segment.name,
+ "url": request.build_absolute_uri(f"/api/plugins/cesnet-service-path-plugin/segments/{obj.segment.id}/"),
}
def validate(self, data):
@@ -64,15 +62,15 @@ def validate(self, data):
"""
from django.core.exceptions import ValidationError as DjangoValidationError
- segment = data.get('segment')
+ segment = data.get("segment")
# Check if this is an update (instance exists) or create (new instance)
if not self.instance and segment:
# Creating new instance - check if segment already has data
- if hasattr(segment, 'optical_spectrum_data'):
- raise serializers.ValidationError({
- 'segment': f'Segment "{segment.name}" already has optical spectrum technical data.'
- })
+ if hasattr(segment, "optical_spectrum_data"):
+ raise serializers.ValidationError(
+ {"segment": f'Segment "{segment.name}" already has optical spectrum technical data.'}
+ )
# Trigger model-level validation by creating a temporary instance
# This ensures model's clean() method is called
@@ -89,6 +87,6 @@ def validate(self, data):
instance.clean()
except DjangoValidationError as e:
# Convert Django ValidationError to DRF ValidationError
- raise serializers.ValidationError(e.message_dict if hasattr(e, 'message_dict') else str(e))
+ raise serializers.ValidationError(e.message_dict if hasattr(e, "message_dict") else str(e))
return data
diff --git a/cesnet_service_path_plugin/api/serializers/segment.py b/cesnet_service_path_plugin/api/serializers/segment.py
index 316ec4e..9b8b825 100644
--- a/cesnet_service_path_plugin/api/serializers/segment.py
+++ b/cesnet_service_path_plugin/api/serializers/segment.py
@@ -72,17 +72,17 @@ def get_type_specific_data(self, obj):
or EthernetServiceSegmentData depending on the segment's type.
"""
try:
- if obj.segment_type == 'dark_fiber':
+ if obj.segment_type == "dark_fiber":
try:
return DarkFiberSegmentDataSerializer(obj.dark_fiber_data, context=self.context).data
except obj.dark_fiber_data.RelatedObjectDoesNotExist:
return None
- elif obj.segment_type == 'optical_spectrum':
+ elif obj.segment_type == "optical_spectrum":
try:
return OpticalSpectrumSegmentDataSerializer(obj.optical_spectrum_data, context=self.context).data
except obj.optical_spectrum_data.RelatedObjectDoesNotExist:
return None
- elif obj.segment_type == 'ethernet_service':
+ elif obj.segment_type == "ethernet_service":
try:
return EthernetServiceSegmentDataSerializer(obj.ethernet_service_data, context=self.context).data
except obj.ethernet_service_data.RelatedObjectDoesNotExist:
diff --git a/cesnet_service_path_plugin/api/views/dark_fiber_data_view.py b/cesnet_service_path_plugin/api/views/dark_fiber_data_view.py
index 116999c..b061a57 100644
--- a/cesnet_service_path_plugin/api/views/dark_fiber_data_view.py
+++ b/cesnet_service_path_plugin/api/views/dark_fiber_data_view.py
@@ -13,5 +13,5 @@ class DarkFiberSegmentDataViewSet(NetBoxModelViewSet):
Provides CRUD operations for dark fiber segment technical specifications.
"""
- queryset = DarkFiberSegmentData.objects.select_related('segment').all()
+ queryset = DarkFiberSegmentData.objects.select_related("segment").all()
serializer_class = DarkFiberSegmentDataSerializer
diff --git a/cesnet_service_path_plugin/api/views/ethernet_service_data_view.py b/cesnet_service_path_plugin/api/views/ethernet_service_data_view.py
index f5c1efb..f32f55a 100644
--- a/cesnet_service_path_plugin/api/views/ethernet_service_data_view.py
+++ b/cesnet_service_path_plugin/api/views/ethernet_service_data_view.py
@@ -13,5 +13,5 @@ class EthernetServiceSegmentDataViewSet(NetBoxModelViewSet):
Provides CRUD operations for ethernet service segment technical specifications.
"""
- queryset = EthernetServiceSegmentData.objects.select_related('segment').all()
+ queryset = EthernetServiceSegmentData.objects.select_related("segment").all()
serializer_class = EthernetServiceSegmentDataSerializer
diff --git a/cesnet_service_path_plugin/api/views/optical_spectrum_data_view.py b/cesnet_service_path_plugin/api/views/optical_spectrum_data_view.py
index 1ae0093..a278a69 100644
--- a/cesnet_service_path_plugin/api/views/optical_spectrum_data_view.py
+++ b/cesnet_service_path_plugin/api/views/optical_spectrum_data_view.py
@@ -13,5 +13,5 @@ class OpticalSpectrumSegmentDataViewSet(NetBoxModelViewSet):
Provides CRUD operations for optical spectrum segment technical specifications.
"""
- queryset = OpticalSpectrumSegmentData.objects.select_related('segment').all()
+ queryset = OpticalSpectrumSegmentData.objects.select_related("segment").all()
serializer_class = OpticalSpectrumSegmentDataSerializer
diff --git a/cesnet_service_path_plugin/filtersets/contract_info.py b/cesnet_service_path_plugin/filtersets/contract_info.py
index a07eaa7..46c5632 100644
--- a/cesnet_service_path_plugin/filtersets/contract_info.py
+++ b/cesnet_service_path_plugin/filtersets/contract_info.py
@@ -84,6 +84,4 @@ def search(self, queryset, name, value):
contract_number = Q(contract_number__icontains=value)
notes = Q(notes__icontains=value)
- return queryset.filter(
- contract_number | notes
- )
+ return queryset.filter(contract_number | notes)
diff --git a/cesnet_service_path_plugin/forms/contract_info.py b/cesnet_service_path_plugin/forms/contract_info.py
index de74180..ae94ca8 100644
--- a/cesnet_service_path_plugin/forms/contract_info.py
+++ b/cesnet_service_path_plugin/forms/contract_info.py
@@ -44,7 +44,6 @@ class ContractInfoForm(NetBoxModelForm):
required=False, min_value=0, help_text="Number of recurring charge periods (0 for no recurring charges)"
)
-
notes = forms.CharField(
required=False, widget=forms.Textarea(attrs={"rows": 3}), help_text="Notes specific to this version"
)
@@ -89,9 +88,9 @@ def __init__(self, *args, **kwargs):
# For amendments, make currency field disabled in the UI
# Note: disabled fields don't submit values, but we handle this in clean_charge_currency()
self.fields["charge_currency"].disabled = True
- self.fields["charge_currency"].help_text = (
- "Currency cannot be changed in amendments (inherited from original contract)"
- )
+ self.fields[
+ "charge_currency"
+ ].help_text = "Currency cannot be changed in amendments (inherited from original contract)"
def clean_charge_currency(self):
"""
diff --git a/cesnet_service_path_plugin/graphql/filters.py b/cesnet_service_path_plugin/graphql/filters.py
index c1bc6e6..e7361ca 100644
--- a/cesnet_service_path_plugin/graphql/filters.py
+++ b/cesnet_service_path_plugin/graphql/filters.py
@@ -8,7 +8,7 @@
from netbox.graphql.filters import NetBoxModelFilter
-from strawberry_django import FilterLookup
+from strawberry_django import FilterLookup, StrFilterLookup
if TYPE_CHECKING:
from circuits.graphql.filters import CircuitFilter, ProviderFilter
@@ -36,19 +36,19 @@ class ContractInfoFilter(NetBoxModelFilter):
"""GraphQL filter for ContractInfo model"""
# Basic fields
- contract_number: FilterLookup[str] | None = strawberry_django.filter_field()
- contract_type: FilterLookup[str] | None = strawberry_django.filter_field()
+ contract_number: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ contract_type: StrFilterLookup[str] | None = strawberry_django.filter_field()
# Financial fields
- charge_currency: FilterLookup[str] | None = strawberry_django.filter_field()
- recurring_charge_period: FilterLookup[str] | None = strawberry_django.filter_field()
+ charge_currency: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ recurring_charge_period: StrFilterLookup[str] | None = strawberry_django.filter_field()
# Date fields
- start_date: FilterLookup[str] | None = strawberry_django.filter_field()
- end_date: FilterLookup[str] | None = strawberry_django.filter_field()
+ start_date: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ end_date: StrFilterLookup[str] | None = strawberry_django.filter_field()
# Notes
- notes: FilterLookup[str] | None = strawberry_django.filter_field()
+ notes: StrFilterLookup[str] | None = strawberry_django.filter_field()
# Related segments
segments: Annotated["SegmentFilter", strawberry.lazy(".filters")] | None = strawberry_django.filter_field()
@@ -79,22 +79,22 @@ class SegmentFilter(NetBoxModelFilter):
"""GraphQL filter for Segment model"""
# Basic fields
- name: FilterLookup[str] | None = strawberry_django.filter_field()
- network_label: FilterLookup[str] | None = strawberry_django.filter_field()
- install_date: FilterLookup[str] | None = strawberry_django.filter_field() # Date fields as string
- termination_date: FilterLookup[str] | None = strawberry_django.filter_field()
- status: FilterLookup[str] | None = strawberry_django.filter_field()
- ownership_type: FilterLookup[str] | None = strawberry_django.filter_field()
- provider_segment_id: FilterLookup[str] | None = strawberry_django.filter_field()
- comments: FilterLookup[str] | None = strawberry_django.filter_field()
+ name: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ network_label: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ install_date: StrFilterLookup[str] | None = strawberry_django.filter_field() # Date fields as string
+ termination_date: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ status: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ ownership_type: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ provider_segment_id: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ comments: StrFilterLookup[str] | None = strawberry_django.filter_field()
# Segment type field
- segment_type: FilterLookup[str] | None = strawberry_django.filter_field()
+ segment_type: StrFilterLookup[str] | None = strawberry_django.filter_field()
# Path geometry fields
path_length_km: FilterLookup[float] | None = strawberry_django.filter_field()
- path_source_format: FilterLookup[str] | None = strawberry_django.filter_field()
- path_notes: FilterLookup[str] | None = strawberry_django.filter_field()
+ path_source_format: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ path_notes: StrFilterLookup[str] | None = strawberry_django.filter_field()
# Related fields - using lazy imports to avoid circular dependencies
provider: Annotated["ProviderFilter", strawberry.lazy("circuits.graphql.filters")] | None = (
@@ -184,10 +184,10 @@ def has_type_specific_data(self, value: bool, prefix: str) -> Q:
class ServicePathFilter(NetBoxModelFilter):
"""GraphQL filter for ServicePath model"""
- name: FilterLookup[str] | None = strawberry_django.filter_field()
- status: FilterLookup[str] | None = strawberry_django.filter_field()
- kind: FilterLookup[str] | None = strawberry_django.filter_field()
- comments: FilterLookup[str] | None = strawberry_django.filter_field()
+ name: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ status: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ kind: StrFilterLookup[str] | None = strawberry_django.filter_field()
+ comments: StrFilterLookup[str] | None = strawberry_django.filter_field()
# Related segments
segments: Annotated["SegmentFilter", strawberry.lazy(".filters")] | None = strawberry_django.filter_field()
diff --git a/cesnet_service_path_plugin/graphql/types.py b/cesnet_service_path_plugin/graphql/types.py
index 6d76ae3..bedb73a 100644
--- a/cesnet_service_path_plugin/graphql/types.py
+++ b/cesnet_service_path_plugin/graphql/types.py
@@ -5,6 +5,7 @@
from dcim.graphql.types import LocationType, SiteType
from netbox.graphql.types import NetBoxObjectType
from strawberry import auto, field, lazy
+from strawberry.types import Info
from strawberry_django import type as strawberry_django_type
from decimal import Decimal
@@ -70,28 +71,28 @@ class ContractInfoType(NetBoxObjectType):
superseded_by: Optional[Annotated["ContractInfoType", lazy(".types")]]
@field
- def version(self, info) -> int:
+ def version(self, info: Info) -> int:
"""Calculate version number by counting predecessors"""
return self.version
@field
- def is_active(self, info) -> bool:
+ def is_active(self, info: Info) -> bool:
"""Check if this is the active (not superseded) version"""
return self.is_active
@field
- def total_recurring_cost(self, info) -> Optional[Decimal]:
+ def total_recurring_cost(self, info: Info) -> Optional[Decimal]:
"""Calculate total recurring cost - only if user has permission"""
# Permission check happens at the query level, so if we're here, user has access
return self.total_recurring_cost
@field
- def total_contract_value(self, info) -> Optional[Decimal]:
+ def total_contract_value(self, info: Info) -> Optional[Decimal]:
"""Total contract value including non-recurring charge - only if user has permission"""
return self.total_contract_value
@field
- def commitment_end_date(self, info) -> Optional[str]:
+ def commitment_end_date(self, info: Info) -> Optional[str]:
"""Calculate commitment end date based on start date and recurring periods"""
if hasattr(self, "commitment_end_date") and self.commitment_end_date:
return self.commitment_end_date.isoformat()
@@ -128,7 +129,7 @@ class SegmentType(NetBoxObjectType):
circuits: List[Annotated["CircuitType", lazy("circuits.graphql.types")]]
@field
- def contracts(self, info) -> List[Annotated["ContractInfoType", lazy(".types")]]:
+ def contracts(self, info: Info) -> List[Annotated["ContractInfoType", lazy(".types")]]:
"""
Return contracts only if user has permission to view them.
This mimics the REST API behavior for M:N relationships.
diff --git a/cesnet_service_path_plugin/migrations/0031_alter_segmentfinancialinfo_charge_currency.py b/cesnet_service_path_plugin/migrations/0031_alter_segmentfinancialinfo_charge_currency.py
index adaf7a2..6e95689 100644
--- a/cesnet_service_path_plugin/migrations/0031_alter_segmentfinancialinfo_charge_currency.py
+++ b/cesnet_service_path_plugin/migrations/0031_alter_segmentfinancialinfo_charge_currency.py
@@ -4,15 +4,14 @@
class Migration(migrations.Migration):
-
dependencies = [
- ('cesnet_service_path_plugin', '0030_alter_segment_location_a_alter_segment_location_b'),
+ ("cesnet_service_path_plugin", "0030_alter_segment_location_a_alter_segment_location_b"),
]
operations = [
migrations.AlterField(
- model_name='segmentfinancialinfo',
- name='charge_currency',
+ model_name="segmentfinancialinfo",
+ name="charge_currency",
field=models.CharField(max_length=3),
),
]
diff --git a/cesnet_service_path_plugin/migrations/0032_segment_ownership_type.py b/cesnet_service_path_plugin/migrations/0032_segment_ownership_type.py
index ccf6aa7..ea53977 100644
--- a/cesnet_service_path_plugin/migrations/0032_segment_ownership_type.py
+++ b/cesnet_service_path_plugin/migrations/0032_segment_ownership_type.py
@@ -4,15 +4,14 @@
class Migration(migrations.Migration):
-
dependencies = [
- ('cesnet_service_path_plugin', '0031_alter_segmentfinancialinfo_charge_currency'),
+ ("cesnet_service_path_plugin", "0031_alter_segmentfinancialinfo_charge_currency"),
]
operations = [
migrations.AddField(
- model_name='segment',
- name='ownership_type',
- field=models.CharField(default='leased', max_length=30),
+ model_name="segment",
+ name="ownership_type",
+ field=models.CharField(default="leased", max_length=30),
),
]
diff --git a/cesnet_service_path_plugin/migrations/0033_contractinfo_contractsegmentmapping_and_more.py b/cesnet_service_path_plugin/migrations/0033_contractinfo_contractsegmentmapping_and_more.py
index 714ff8d..65d7e9c 100644
--- a/cesnet_service_path_plugin/migrations/0033_contractinfo_contractsegmentmapping_and_more.py
+++ b/cesnet_service_path_plugin/migrations/0033_contractinfo_contractsegmentmapping_and_more.py
@@ -16,7 +16,7 @@ def migrate_financial_info_to_contract(apps, schema_editor):
SegmentFinancialInfo = apps.get_model("cesnet_service_path_plugin", "SegmentFinancialInfo")
ContractInfo = apps.get_model("cesnet_service_path_plugin", "ContractInfo")
ContractSegmentMapping = apps.get_model("cesnet_service_path_plugin", "ContractSegmentMapping")
- Segment = apps.get_model("cesnet_service_path_plugin", "Segment")
+ Segment = apps.get_model("cesnet_service_path_plugin", "Segment") # noqa: F841
TaggedItem = apps.get_model("extras", "TaggedItem")
ContentType = apps.get_model("contenttypes", "ContentType")
@@ -36,7 +36,9 @@ def migrate_financial_info_to_contract(apps, schema_editor):
end_date = segment.termination_date if segment.termination_date else None
# Determine number_of_recurring_charges (convert commitment_period_months to number of charges)
- number_of_recurring_charges = old_financial_info.commitment_period_months if old_financial_info.commitment_period_months else None
+ number_of_recurring_charges = (
+ old_financial_info.commitment_period_months if old_financial_info.commitment_period_months else None
+ )
# Create new ContractInfo
contract = ContractInfo.objects.create(
@@ -95,7 +97,9 @@ def reverse_migrate_contract_to_financial_info(apps, schema_editor):
segment=segment,
monthly_charge=contract.recurring_charge or Decimal("0"),
charge_currency=contract.charge_currency,
- non_recurring_charge=contract.non_recurring_charge if contract.non_recurring_charge and contract.non_recurring_charge > 0 else None,
+ non_recurring_charge=contract.non_recurring_charge
+ if contract.non_recurring_charge and contract.non_recurring_charge > 0
+ else None,
commitment_period_months=commitment_period_months,
notes=contract.notes,
created=contract.created,
@@ -110,7 +114,6 @@ def reverse_migrate_contract_to_financial_info(apps, schema_editor):
class Migration(migrations.Migration):
-
dependencies = [
("cesnet_service_path_plugin", "0032_segment_ownership_type"),
("circuits", "0052_extend_circuit_abs_distance_upper_limit"),
@@ -129,18 +132,71 @@ class Migration(migrations.Migration):
"custom_field_data",
models.JSONField(blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder),
),
- ("contract_number", models.CharField(blank=True, help_text="Provider's contract reference number", max_length=100)),
- ("contract_type", models.CharField(default="new", editable=False, help_text="Type of contract (set automatically)", max_length=20)),
- ("charge_currency", models.CharField(blank=True, default="CZK", help_text="Currency for all charges (defaults to CZK if not specified, cannot be changed in amendments)", max_length=3)),
+ (
+ "contract_number",
+ models.CharField(blank=True, help_text="Provider's contract reference number", max_length=100),
+ ),
+ (
+ "contract_type",
+ models.CharField(
+ default="new", editable=False, help_text="Type of contract (set automatically)", max_length=20
+ ),
+ ),
+ (
+ "charge_currency",
+ models.CharField(
+ blank=True,
+ default="CZK",
+ help_text="Currency for all charges (defaults to CZK if not specified, cannot be changed in amendments)",
+ max_length=3,
+ ),
+ ),
(
"non_recurring_charge",
- models.DecimalField(blank=True, decimal_places=2, default=Decimal("0"), help_text="One-time fees for this version (setup, installation, etc.)", max_digits=10, null=True),
+ models.DecimalField(
+ blank=True,
+ decimal_places=2,
+ default=Decimal("0"),
+ help_text="One-time fees for this version (setup, installation, etc.)",
+ max_digits=10,
+ null=True,
+ ),
+ ),
+ (
+ "recurring_charge",
+ models.DecimalField(
+ blank=True,
+ decimal_places=2,
+ help_text="Recurring fee amount (optional for amendments)",
+ max_digits=10,
+ null=True,
+ ),
+ ),
+ (
+ "recurring_charge_period",
+ models.CharField(
+ blank=True,
+ help_text="Frequency of recurring charges (optional for amendments)",
+ max_length=20,
+ null=True,
+ ),
+ ),
+ (
+ "number_of_recurring_charges",
+ models.PositiveIntegerField(
+ blank=True,
+ help_text="Number of recurring charge periods in this contract (optional for amendments)",
+ null=True,
+ ),
+ ),
+ (
+ "start_date",
+ models.DateField(blank=True, help_text="When this contract version starts (optional)", null=True),
+ ),
+ (
+ "end_date",
+ models.DateField(blank=True, help_text="When this contract version ends (optional)", null=True),
),
- ("recurring_charge", models.DecimalField(blank=True, decimal_places=2, help_text="Recurring fee amount (optional for amendments)", max_digits=10, null=True)),
- ("recurring_charge_period", models.CharField(blank=True, help_text="Frequency of recurring charges (optional for amendments)", max_length=20, null=True)),
- ("number_of_recurring_charges", models.PositiveIntegerField(blank=True, help_text="Number of recurring charge periods in this contract (optional for amendments)", null=True)),
- ("start_date", models.DateField(blank=True, help_text="When this contract version starts (optional)", null=True)),
- ("end_date", models.DateField(blank=True, help_text="When this contract version ends (optional)", null=True)),
("notes", models.TextField(blank=True, help_text="Notes specific to this version")),
(
"previous_version",
@@ -179,7 +235,10 @@ class Migration(migrations.Migration):
name="ContractSegmentMapping",
fields=[
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
- ("added_date", models.DateField(auto_now_add=True, help_text="When this segment was added to the contract")),
+ (
+ "added_date",
+ models.DateField(auto_now_add=True, help_text="When this segment was added to the contract"),
+ ),
("notes", models.TextField(blank=True, help_text="Notes about this segment-contract relationship")),
(
"contract",
diff --git a/cesnet_service_path_plugin/migrations/0034_add_type_specific_models.py b/cesnet_service_path_plugin/migrations/0034_add_type_specific_models.py
index e39881c..9c80234 100644
--- a/cesnet_service_path_plugin/migrations/0034_add_type_specific_models.py
+++ b/cesnet_service_path_plugin/migrations/0034_add_type_specific_models.py
@@ -13,7 +13,6 @@
import netbox.models.deletion
import taggit.managers
import utilities.json
-from decimal import Decimal
from django.db import migrations, models
@@ -46,7 +45,7 @@ def migrate_json_to_models_forward(apps, schema_editor):
json_data = segment.type_specific_data
# Handle fiber_type - was multichoice (list) in JSON
- fiber_type_raw = json_data.get("fiber_type")
+ fiber_type_raw = json_data.get("fiber_type") # noqa: F841
# Note: The new model doesn't have a direct fiber_type list field
# Instead we have fiber_mode and subtypes
# For now, we'll skip fiber_type as it's being replaced by the new structure
@@ -104,7 +103,7 @@ def migrate_json_to_models_forward(apps, schema_editor):
print(f" ⚠️ {error_msg}")
# Print migration summary
- print(f"\n✅ Migration completed:")
+ print("\n✅ Migration completed:")
print(f" - Dark Fiber segments: {stats['dark_fiber']}")
print(f" - Optical Spectrum segments: {stats['optical_spectrum']}")
print(f" - Ethernet Service segments: {stats['ethernet_service']}")
@@ -194,7 +193,7 @@ def migrate_json_to_models_reverse(apps, schema_editor):
Reverse migration: Copy data from models back to JSON field.
This allows rollback if needed.
"""
- Segment = apps.get_model("cesnet_service_path_plugin", "Segment")
+ Segment = apps.get_model("cesnet_service_path_plugin", "Segment") # noqa: F841
DarkFiberSegmentData = apps.get_model("cesnet_service_path_plugin", "DarkFiberSegmentData")
OpticalSpectrumSegmentData = apps.get_model("cesnet_service_path_plugin", "OpticalSpectrumSegmentData")
EthernetServiceSegmentData = apps.get_model("cesnet_service_path_plugin", "EthernetServiceSegmentData")
@@ -203,9 +202,7 @@ def migrate_json_to_models_reverse(apps, schema_editor):
for df_data in DarkFiberSegmentData.objects.all():
segment = df_data.segment
segment.type_specific_data = {
- "fiber_attenuation_max": float(df_data.fiber_attenuation_max)
- if df_data.fiber_attenuation_max
- else None,
+ "fiber_attenuation_max": float(df_data.fiber_attenuation_max) if df_data.fiber_attenuation_max else None,
"total_loss": float(df_data.total_loss) if df_data.total_loss else None,
"total_length": float(df_data.total_length) if df_data.total_length else None,
"number_of_fibers": df_data.number_of_fibers,
@@ -315,7 +312,6 @@ def _reverse_map_interface_type(new_value):
class Migration(migrations.Migration):
-
dependencies = [
("cesnet_service_path_plugin", "0033_contractinfo_contractsegmentmapping_and_more"),
("extras", "0133_make_cf_minmax_decimal"),
diff --git a/cesnet_service_path_plugin/migrations/0035_remove_type_specific_data.py b/cesnet_service_path_plugin/migrations/0035_remove_type_specific_data.py
index ca6140d..6a171c0 100644
--- a/cesnet_service_path_plugin/migrations/0035_remove_type_specific_data.py
+++ b/cesnet_service_path_plugin/migrations/0035_remove_type_specific_data.py
@@ -4,14 +4,13 @@
class Migration(migrations.Migration):
-
dependencies = [
- ('cesnet_service_path_plugin', '0034_add_type_specific_models'),
+ ("cesnet_service_path_plugin", "0034_add_type_specific_models"),
]
operations = [
migrations.RemoveField(
- model_name='segment',
- name='type_specific_data',
+ model_name="segment",
+ name="type_specific_data",
),
]
diff --git a/cesnet_service_path_plugin/migrations/0036_alter_opticalspectrumsegmentdata_chromatic_dispersion.py b/cesnet_service_path_plugin/migrations/0036_alter_opticalspectrumsegmentdata_chromatic_dispersion.py
index 6fa733e..aaa2de8 100644
--- a/cesnet_service_path_plugin/migrations/0036_alter_opticalspectrumsegmentdata_chromatic_dispersion.py
+++ b/cesnet_service_path_plugin/migrations/0036_alter_opticalspectrumsegmentdata_chromatic_dispersion.py
@@ -5,15 +5,23 @@
class Migration(migrations.Migration):
-
dependencies = [
- ('cesnet_service_path_plugin', '0035_remove_type_specific_data'),
+ ("cesnet_service_path_plugin", "0035_remove_type_specific_data"),
]
operations = [
migrations.AlterField(
- model_name='opticalspectrumsegmentdata',
- name='chromatic_dispersion',
- field=models.DecimalField(blank=True, decimal_places=3, max_digits=8, null=True, validators=[django.core.validators.MinValueValidator(-100000), django.core.validators.MaxValueValidator(100000)]),
+ model_name="opticalspectrumsegmentdata",
+ name="chromatic_dispersion",
+ field=models.DecimalField(
+ blank=True,
+ decimal_places=3,
+ max_digits=8,
+ null=True,
+ validators=[
+ django.core.validators.MinValueValidator(-100000),
+ django.core.validators.MaxValueValidator(100000),
+ ],
+ ),
),
]
diff --git a/cesnet_service_path_plugin/migrations/0037_darkfibersegmentdata_id_and_more.py b/cesnet_service_path_plugin/migrations/0037_darkfibersegmentdata_id_and_more.py
new file mode 100644
index 0000000..312b2c8
--- /dev/null
+++ b/cesnet_service_path_plugin/migrations/0037_darkfibersegmentdata_id_and_more.py
@@ -0,0 +1,113 @@
+# Custom migration: switch primary key from segment_id to auto-generated id
+# for DarkFiberSegmentData, EthernetServiceSegmentData, OpticalSpectrumSegmentData.
+#
+# Django's auto-generated migration fails because it tries to add a new PK
+# column while the old PK constraint still exists. This migration uses raw SQL
+# to do the steps in the correct order:
+# 1. Drop the existing PK constraint on segment_id
+# 2. Add a new "id" identity column as the primary key
+# 3. Add a UNIQUE constraint on segment_id (required by OneToOneField)
+# 4. Register the state changes so Django's ORM stays in sync
+
+import django.db.models.deletion
+from django.db import migrations, models
+
+
+# (table_name, old_pkey_constraint_name)
+# Constraint names come from the \d output in Postgres.
+TABLES = [
+ (
+ "cesnet_service_path_plugin_darkfibersegmentdata",
+ "cesnet_service_path_plugin_darkfibersegmentdata_pkey",
+ ),
+ (
+ "cesnet_service_path_plugin_ethernetservicesegmentdata",
+ "cesnet_service_path_plugin_ethernetservicesegmentdata_pkey",
+ ),
+ (
+ "cesnet_service_path_plugin_opticalspectrumsegmentdata",
+ "cesnet_service_path_plugin_opticalspectrumsegmentdata_pkey",
+ ),
+]
+
+
+def forwards(apps, schema_editor):
+ for table, pkey_name in TABLES:
+ # 1. Drop the existing primary key (segment_id)
+ schema_editor.execute(f'ALTER TABLE "{table}" DROP CONSTRAINT "{pkey_name}";')
+ # 2. Add new "id" column as identity primary key
+ schema_editor.execute(
+ f'ALTER TABLE "{table}" ADD COLUMN "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY;'
+ )
+ # 3. Add unique constraint on segment_id (OneToOneField needs it)
+ schema_editor.execute(f'ALTER TABLE "{table}" ADD CONSTRAINT "{table}_segment_id_key" UNIQUE ("segment_id");')
+
+
+def backwards(apps, schema_editor):
+ for table, pkey_name in TABLES:
+ # Reverse: drop the unique constraint, drop id column, re-add old PK
+ schema_editor.execute(f'ALTER TABLE "{table}" DROP CONSTRAINT "{table}_segment_id_key";')
+ schema_editor.execute(f'ALTER TABLE "{table}" DROP COLUMN "id";')
+ schema_editor.execute(f'ALTER TABLE "{table}" ADD CONSTRAINT "{pkey_name}" PRIMARY KEY ("segment_id");')
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ (
+ "cesnet_service_path_plugin",
+ "0036_alter_opticalspectrumsegmentdata_chromatic_dispersion",
+ ),
+ ]
+
+ operations = [
+ # Raw SQL handles the actual schema change
+ migrations.RunPython(forwards, backwards),
+ # State-only operations so Django's ORM knows about the new fields
+ migrations.SeparateDatabaseAndState(
+ state_operations=[
+ migrations.AddField(
+ model_name="darkfibersegmentdata",
+ name="id",
+ field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False),
+ ),
+ migrations.AlterField(
+ model_name="darkfibersegmentdata",
+ name="segment",
+ field=models.OneToOneField(
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name="dark_fiber_data",
+ to="cesnet_service_path_plugin.segment",
+ ),
+ ),
+ migrations.AddField(
+ model_name="ethernetservicesegmentdata",
+ name="id",
+ field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False),
+ ),
+ migrations.AlterField(
+ model_name="ethernetservicesegmentdata",
+ name="segment",
+ field=models.OneToOneField(
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name="ethernet_service_data",
+ to="cesnet_service_path_plugin.segment",
+ ),
+ ),
+ migrations.AddField(
+ model_name="opticalspectrumsegmentdata",
+ name="id",
+ field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False),
+ ),
+ migrations.AlterField(
+ model_name="opticalspectrumsegmentdata",
+ name="segment",
+ field=models.OneToOneField(
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name="optical_spectrum_data",
+ to="cesnet_service_path_plugin.segment",
+ ),
+ ),
+ ],
+ database_operations=[], # Already handled by RunPython above
+ ),
+ ]
diff --git a/cesnet_service_path_plugin/models/dark_fiber_data.py b/cesnet_service_path_plugin/models/dark_fiber_data.py
index 0f9414e..f86267f 100644
--- a/cesnet_service_path_plugin/models/dark_fiber_data.py
+++ b/cesnet_service_path_plugin/models/dark_fiber_data.py
@@ -24,7 +24,6 @@ class DarkFiberSegmentData(NetBoxModel):
"Segment",
on_delete=models.CASCADE,
related_name="dark_fiber_data",
- primary_key=True,
help_text="Associated segment (1:1 relationship)",
)
@@ -136,9 +135,7 @@ def clean(self):
# Validate that single-mode subtype is only set for single-mode fibers
if self.single_mode_subtype and self.fiber_mode != FiberModeChoices.SINGLE_MODE:
- errors["single_mode_subtype"] = (
- "Single-mode subtype can only be set when fiber mode is Single-mode"
- )
+ errors["single_mode_subtype"] = "Single-mode subtype can only be set when fiber mode is Single-mode"
# Validate that multimode subtype is only set for multimode fibers
if self.multimode_subtype and self.fiber_mode != FiberModeChoices.MULTIMODE:
diff --git a/cesnet_service_path_plugin/models/ethernet_service_data.py b/cesnet_service_path_plugin/models/ethernet_service_data.py
index 49097bd..9efc0b8 100644
--- a/cesnet_service_path_plugin/models/ethernet_service_data.py
+++ b/cesnet_service_path_plugin/models/ethernet_service_data.py
@@ -19,7 +19,6 @@ class EthernetServiceSegmentData(NetBoxModel):
"Segment",
on_delete=models.CASCADE,
related_name="ethernet_service_data",
- primary_key=True,
help_text="Associated segment (1:1 relationship)",
)
diff --git a/cesnet_service_path_plugin/models/optical_spectrum_data.py b/cesnet_service_path_plugin/models/optical_spectrum_data.py
index fa9c394..f34eb15 100644
--- a/cesnet_service_path_plugin/models/optical_spectrum_data.py
+++ b/cesnet_service_path_plugin/models/optical_spectrum_data.py
@@ -18,7 +18,6 @@ class OpticalSpectrumSegmentData(NetBoxModel):
"Segment",
on_delete=models.CASCADE,
related_name="optical_spectrum_data",
- primary_key=True,
help_text="Associated segment (1:1 relationship)",
)
diff --git a/cesnet_service_path_plugin/templates/buttons/clone_custom.html b/cesnet_service_path_plugin/templates/buttons/clone_custom.html
index a4e2a81..0809230 100644
--- a/cesnet_service_path_plugin/templates/buttons/clone_custom.html
+++ b/cesnet_service_path_plugin/templates/buttons/clone_custom.html
@@ -1,6 +1,6 @@
{% load i18n %}
{% if url %}
-
- {{ label }}
-
+
+ {{ label }}
+
{% endif %}
diff --git a/cesnet_service_path_plugin/templates/cesnet_service_path_plugin/circuit_segments_extension.html b/cesnet_service_path_plugin/templates/cesnet_service_path_plugin/circuit_segments_extension.html
index 5d3070a..22bf05d 100644
--- a/cesnet_service_path_plugin/templates/cesnet_service_path_plugin/circuit_segments_extension.html
+++ b/cesnet_service_path_plugin/templates/cesnet_service_path_plugin/circuit_segments_extension.html
@@ -1,5 +1,4 @@
{% load i18n %}
-
-
{% if topologies %}
{% include 'cesnet_service_path_plugin/inc/topology_visualization.html' %}
{% include 'cesnet_service_path_plugin/inc/topology_segment_card.html' %}
-
-{% endif %}
\ No newline at end of file
+{% endif %}
diff --git a/cesnet_service_path_plugin/templates/cesnet_service_path_plugin/contractinfo.html b/cesnet_service_path_plugin/templates/cesnet_service_path_plugin/contractinfo.html
index 3e18a34..862e756 100644
--- a/cesnet_service_path_plugin/templates/cesnet_service_path_plugin/contractinfo.html
+++ b/cesnet_service_path_plugin/templates/cesnet_service_path_plugin/contractinfo.html
@@ -3,9 +3,7 @@
{% load plugins %}
{% load custom_filters %}
{% load render_table from django_tables2 %}
-
{% block content %}
-
@@ -20,9 +18,7 @@
Contract Type
-
- {{ object.get_contract_type_display }}
-
+ {{ object.get_contract_type_display }}
@@ -39,7 +35,6 @@
-
@@ -48,9 +43,7 @@
Currency
-
- {{ object.get_charge_currency_display }}
-
+ {{ object.get_charge_currency_display }}
@@ -71,28 +64,33 @@
Total Recurring Cost
- {{ object.charge_currency }} {{ object.total_recurring_cost|floatformat:2|spacecomma }}
+
+ {{ object.charge_currency }} {{ object.total_recurring_cost|floatformat:2|spacecomma }}
+
{% if object.non_recurring_charge %}
-
- Non-Recurring Charge
- {{ object.charge_currency }} {{ object.non_recurring_charge|floatformat:2|spacecomma }}
-
+
+ Non-Recurring Charge
+ {{ object.charge_currency }} {{ object.non_recurring_charge|floatformat:2|spacecomma }}
+
{% endif %}
{% if object.total_cumulative_non_recurring %}
-
- Cumulative Non-Recurring
- {{ object.charge_currency }} {{ object.total_cumulative_non_recurring|floatformat:2|spacecomma }}
-
+
+ Cumulative Non-Recurring
+
+ {{ object.charge_currency }} {{ object.total_cumulative_non_recurring|floatformat:2|spacecomma }}
+
+
{% endif %}
Total Contract Value
- {{ object.charge_currency }} {{ object.total_contract_value|floatformat:2|spacecomma }}
+
+ {{ object.charge_currency }} {{ object.total_contract_value|floatformat:2|spacecomma }}
+
-
@@ -107,142 +105,130 @@
{% if object.end_date %}
- {{ object.end_date }}
-
+ title="{{ object.get_end_date_tooltip }}">{{ object.end_date }}
{% else %}
{{ ''|placeholder }}
{% endif %}
{% if object.commitment_end_date %}
-
- Commitment End Date
-
-
- {{ object.commitment_end_date }}
-
-
-
+
+ Commitment End Date
+
+
+ {{ object.commitment_end_date }}
+
+
+
{% endif %}
{% plugin_left_page object %}
-
{% include 'inc/panels/custom_fields.html' %}
{% include 'inc/panels/tags.html' %}
{% include 'inc/panels/comments.html' %}
-
{% if version_history %}
-
-
-
-
-
-
- Version
- Type
- Status
- Actions
-
-
-
- {% for version in version_history %}
-
-
- v{{ version.version }}
-
-
-
- {{ version.get_contract_type_display }}
-
-
-
- {% if version.is_active %}
- Active
- {% else %}
- Superseded
- {% endif %}
-
-
- {% if version.pk != object.pk %}
-
- View
-
- {% else %}
- Current
- {% endif %}
-
-
- {% endfor %}
-
-
+
+
+
+
+
+
+ Version
+ Type
+ Status
+ Actions
+
+
+
+ {% for version in version_history %}
+
+
+ v{{ version.version }}
+
+
+ {{ version.get_contract_type_display }}
+
+
+ {% if version.is_active %}
+ Active
+ {% else %}
+ Superseded
+ {% endif %}
+
+
+ {% if version.pk != object.pk %}
+
+ View
+
+ {% else %}
+ Current
+ {% endif %}
+
+
+ {% endfor %}
+
+
+
-
{% endif %}
-
{% if segments %}
-
-
-
-
-
-
- Name
- Segment Type
- Provider
- Status
-
-
-
- {% for segment in segments %}
-
-
- {{ segment.name }}
-
-
-
- {{ segment.get_segment_type_display }}
-
-
-
- {% if segment.provider %}
- {{ segment.provider }}
- {% else %}
- {{ ''|placeholder }}
- {% endif %}
-
-
-
- {{ segment.get_status_display }}
-
-
-
- {% endfor %}
-
-
+
+
+
+
+
+
+ Name
+ Segment Type
+ Provider
+ Status
+
+
+
+ {% for segment in segments %}
+
+
+ {{ segment.name }}
+
+
+ {{ segment.get_segment_type_display }}
+
+
+ {% if segment.provider %}
+ {{ segment.provider }}
+ {% else %}
+ {{ ''|placeholder }}
+ {% endif %}
+
+
+ {{ segment.get_status_display }}
+
+
+ {% endfor %}
+
+
+
-
{% endif %}
-
{% if object.notes %}
-
-
-
{% endif %}
{% plugin_right_page object %}
-
{% endblock content %}
diff --git a/cesnet_service_path_plugin/templates/cesnet_service_path_plugin/inc/cytoscape_includes.html b/cesnet_service_path_plugin/templates/cesnet_service_path_plugin/inc/cytoscape_includes.html
index 80421aa..ae4e3be 100644
--- a/cesnet_service_path_plugin/templates/cesnet_service_path_plugin/inc/cytoscape_includes.html
+++ b/cesnet_service_path_plugin/templates/cesnet_service_path_plugin/inc/cytoscape_includes.html
@@ -1,2 +1,2 @@
-
\ No newline at end of file
+
diff --git a/cesnet_service_path_plugin/templates/cesnet_service_path_plugin/inc/leaflet_includes.html b/cesnet_service_path_plugin/templates/cesnet_service_path_plugin/inc/leaflet_includes.html
index 4b1846d..658e518 100644
--- a/cesnet_service_path_plugin/templates/cesnet_service_path_plugin/inc/leaflet_includes.html
+++ b/cesnet_service_path_plugin/templates/cesnet_service_path_plugin/inc/leaflet_includes.html
@@ -1,3 +1,4 @@
-
-
\ No newline at end of file
+
+
diff --git a/cesnet_service_path_plugin/templates/cesnet_service_path_plugin/inc/map_layer_dropdown.html b/cesnet_service_path_plugin/templates/cesnet_service_path_plugin/inc/map_layer_dropdown.html
index 27e340f..4ce8fa6 100644
--- a/cesnet_service_path_plugin/templates/cesnet_service_path_plugin/inc/map_layer_dropdown.html
+++ b/cesnet_service_path_plugin/templates/cesnet_service_path_plugin/inc/map_layer_dropdown.html
@@ -1,35 +1,62 @@
-
-
+
+
OpenStreetMap
-
\ No newline at end of file
+
diff --git a/cesnet_service_path_plugin/templates/cesnet_service_path_plugin/inc/map_layers_config.html b/cesnet_service_path_plugin/templates/cesnet_service_path_plugin/inc/map_layers_config.html
index 2884bba..af7d1d2 100644
--- a/cesnet_service_path_plugin/templates/cesnet_service_path_plugin/inc/map_layers_config.html
+++ b/cesnet_service_path_plugin/templates/cesnet_service_path_plugin/inc/map_layers_config.html
@@ -126,4 +126,4 @@
}
});
}
-
\ No newline at end of file
+
diff --git a/cesnet_service_path_plugin/templates/cesnet_service_path_plugin/inc/map_layers_styles.html b/cesnet_service_path_plugin/templates/cesnet_service_path_plugin/inc/map_layers_styles.html
index 3fa78e9..a5de8e0 100644
--- a/cesnet_service_path_plugin/templates/cesnet_service_path_plugin/inc/map_layers_styles.html
+++ b/cesnet_service_path_plugin/templates/cesnet_service_path_plugin/inc/map_layers_styles.html
@@ -52,4 +52,4 @@
.segment-highlighted {
filter: drop-shadow(0 0 3px rgba(0, 123, 255, 0.8));
}
-
\ No newline at end of file
+
diff --git a/cesnet_service_path_plugin/templates/cesnet_service_path_plugin/inc/path_data_badge.html b/cesnet_service_path_plugin/templates/cesnet_service_path_plugin/inc/path_data_badge.html
index 4bbc593..3e8b28c 100644
--- a/cesnet_service_path_plugin/templates/cesnet_service_path_plugin/inc/path_data_badge.html
+++ b/cesnet_service_path_plugin/templates/cesnet_service_path_plugin/inc/path_data_badge.html
@@ -1,9 +1,10 @@
{% if record.has_path_data %}
-
+
Yes
{% else %}
No
-{% endif %}
\ No newline at end of file
+{% endif %}
diff --git a/cesnet_service_path_plugin/templates/cesnet_service_path_plugin/inc/path_length.html b/cesnet_service_path_plugin/templates/cesnet_service_path_plugin/inc/path_length.html
index 3889bb3..605b923 100644
--- a/cesnet_service_path_plugin/templates/cesnet_service_path_plugin/inc/path_length.html
+++ b/cesnet_service_path_plugin/templates/cesnet_service_path_plugin/inc/path_length.html
@@ -2,4 +2,4 @@
{{ record.path_length_km }} km
{% else %}
{{ ''|placeholder }}
-{% endif %}
\ No newline at end of file
+{% endif %}
diff --git a/cesnet_service_path_plugin/templates/cesnet_service_path_plugin/inc/topology_segment_card.html b/cesnet_service_path_plugin/templates/cesnet_service_path_plugin/inc/topology_segment_card.html
index aa7e01f..e0fffa5 100644
--- a/cesnet_service_path_plugin/templates/cesnet_service_path_plugin/inc/topology_segment_card.html
+++ b/cesnet_service_path_plugin/templates/cesnet_service_path_plugin/inc/topology_segment_card.html
@@ -1,46 +1,42 @@
{% load i18n %}
{% if not topologies %}
-
- {% trans "No topologies available." %}
-
+ {% trans "No topologies available." %}
{% else %}
-
-