Flagsmith / Flagsmith/flagsmith
PATCHing a versioned segment override without feature_segment.id returns a 500
- Dominant language
- Python
- Stars
- 6.6k
- Forks
- 567
- Avg merge
- 1d 13h
- Merged PRs (30d)
- 121
Description
Sentry Issue: [FLAGSMITH-API-6DZ](https://flagsmith.sentry.io/issues/7695361412/?referrer=github_integration)
```
UniqueViolation: duplicate key value violates unique constraint "features_featuresegment_feature_id_environment_i_f1fde686_uniq"
DETAIL: Key (feature_id, environment_id, segment_id, environment_feature_version_id)=(244763, 75194, 1157509, 374d8530-15d7-427b-97e8-8b41ad053f6b) already exists.
File "django/db/backends/utils.py", line 105, in _execute
return self.cursor.execute(sql, params)
File "/build/.venv/lib/python3.13/site-packages/opentelemetry/instrumentation/psycopg2/__init__.py", line 351, in execute
return _cursor_tracer.traced_execution(
File "/build/.venv/lib/python3.13/site-packages/opentelemetry/instrumentation/dbapi/__init__.py", line 761, in traced_execution
return query_method(*args, **kwargs)
IntegrityError: duplicate key value violates unique constraint "features_featuresegment_feature_id_environment_i_f1fde686_uniq"
DETAIL: Key (feature_id, environment_id, segment_id, environment_feature_version_id)=(244763, 75194, 1157509, 374d8530-15d7-427b-97e8-8b41ad053f6b) already exists.
(35 additional frame(s) were not displayed)
...
File "features/versioning/views.py", line 295, in perform_create
serializer.save(
File "features/versioning/serializers.py", line 41, in save
response = super().save(**kwargs) # type: ignore[no-untyped-call]
File "features/serializers.py", line 871, in create
return super().create(validated_data) # type: ignore[no-any-return,no-untyped-call]
File "features/feature_segments/serializers.py", line 80, in save
return super().save(**kwargs) # type: ignore[return-value]
```
Updating a segment override on a v2 feature version via `PATCH .../featurestates/{id}/` returns an unhandled 500 when the nested `feature_segment` payload omits `id`.
The cause is in how the nested serializer resolves the related object. `drf_writable_nested`'s `update_or_create_direct_relations` only looks the relation up by primary key taken from the request payload:
```python
pk = self._get_related_pk(data, model_class)
if pk:
obj = model_class.objects.filter(pk=pk).first()
```
With no `id` in the payload, `obj` is `None`, so `CustomCreateSegmentOverrideFeatureSegmentSerializer.save()` runs in create mode and inserts a second `FeatureSegment` for a `(feature, environment, segment, environment_feature_version)` combination that already exists.
### Steps To Reproduce
1. Enable v2 feature versioning on an environment and create a segment override for a feature.
2. Create a new feature version and note the feature state ID of the existing segment override.
3. `PATCH /api/v1/environments/{env}/features/{feature}/versions/{version_uuid}/featurestates/{fs_id}/` with body `{"feature_segment": {"priority": 0, "segment": }}` — i.e. without `feature_segment.id`.
4. Observe a 500 and the `IntegrityError` above.
### Expected behavior
The request should update the existing `FeatureSegment` in place (the combination of feature, environment, segment and version already identifies it uniquely), making the endpoint idempotent.
Failing that, it should at least be handled and returned as a 400 validation error rather than an unhandled 500.
Contributor guide
Research direction
Start at features/versioning/views.py:295 and trace the PATCH request through features/versioning/serializers.py and features/feature_segments/serializers.py, focusing on how the nested feature_segment is resolved when id is absent. Reproduce the documented request against a versioned feature, then verify that the existing override is updated idempotently or that the conflict is returned as a 400 validation error instead of a 500.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- django, python
- Domain
- api, backend
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 74/100