-
Notifications
You must be signed in to change notification settings - Fork 429
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Add metadata fields to core entities (API) #3315
Changes from 7 commits
ed6ffda
8e4afcb
e970e93
f851601
9095696
4c3ab51
a9d7ef9
b05cab3
e182abc
a1c0ea1
11468a5
bb3cbcd
cf9c494
2cfe423
d8f24fe
fb04bbc
de463be
75719cb
20477cc
7076117
bab2c9f
f98f8c3
50b43bc
c5c1044
d7dca4a
0d24c4c
a0cdc13
309e3fe
0b9a5df
10bc36b
9bbdfe9
100f8eb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -481,23 +481,23 @@ def task_processor_synchronously(settings): | |
|
||
|
||
@pytest.fixture() | ||
def a_metadata_field(organisation): | ||
def a_metadata_field(organisation: Organisation) -> MetadataField: | ||
return MetadataField.objects.create(name="a", type="int", organisation=organisation) | ||
|
||
|
||
@pytest.fixture() | ||
def b_metadata_field(organisation): | ||
def b_metadata_field(organisation: Organisation) -> MetadataField: | ||
return MetadataField.objects.create(name="b", type="str", organisation=organisation) | ||
|
||
|
||
@pytest.fixture() | ||
def required_a_environment_metadata_field( | ||
organisation, | ||
a_metadata_field, | ||
environment, | ||
project, | ||
project_content_type, | ||
): | ||
organisation: Organisation, | ||
a_metadata_field: MetadataField, | ||
environment: Environment, | ||
project: Project, | ||
project_content_type: ContentType, | ||
) -> MetadataModelField: | ||
environment_type = ContentType.objects.get_for_model(environment) | ||
model_field = MetadataModelField.objects.create( | ||
field=a_metadata_field, | ||
|
@@ -511,7 +511,75 @@ def required_a_environment_metadata_field( | |
|
||
|
||
@pytest.fixture() | ||
def optional_b_environment_metadata_field(organisation, b_metadata_field, environment): | ||
def required_a_feature_metadata_field( | ||
organisation: Organisation, | ||
a_metadata_field: MetadataField, | ||
feature_content_type: ContentType, | ||
project: Project, | ||
project_content_type: ContentType, | ||
) -> MetadataModelField: | ||
model_field = MetadataModelField.objects.create( | ||
field=a_metadata_field, | ||
content_type=feature_content_type, | ||
) | ||
|
||
MetadataModelFieldRequirement.objects.create( | ||
content_type=project_content_type, object_id=project.id, model_field=model_field | ||
) | ||
|
||
return model_field | ||
|
||
|
||
@pytest.fixture() | ||
def required_a_segment_metadata_field( | ||
organisation: Organisation, | ||
a_metadata_field: MetadataField, | ||
segment_content_type: ContentType, | ||
project: Project, | ||
project_content_type: ContentType, | ||
) -> MetadataModelField: | ||
model_field = MetadataModelField.objects.create( | ||
field=a_metadata_field, | ||
content_type=segment_content_type, | ||
) | ||
|
||
MetadataModelFieldRequirement.objects.create( | ||
content_type=project_content_type, object_id=project.id, model_field=model_field | ||
) | ||
|
||
return model_field | ||
|
||
|
||
@pytest.fixture() | ||
def optional_b_feature_metadata_field( | ||
organisation: Organisation, b_metadata_field: MetadataField, feature: Feature | ||
) -> MetadataModelField: | ||
feature_type = ContentType.objects.get_for_model(feature) | ||
|
||
return MetadataModelField.objects.create( | ||
field=b_metadata_field, | ||
content_type=feature_type, | ||
) | ||
|
||
|
||
@pytest.fixture() | ||
def optional_b_segment_metadata_field( | ||
organisation: Organisation, b_metadata_field: MetadataField, segment: Segment | ||
) -> MetadataModelField: | ||
segment_type = ContentType.objects.get_for_model(segment) | ||
|
||
return MetadataModelField.objects.create( | ||
field=b_metadata_field, | ||
content_type=segment_type, | ||
) | ||
|
||
|
||
@pytest.fixture() | ||
def optional_b_environment_metadata_field( | ||
organisation: Organisation, | ||
b_metadata_field: MetadataField, | ||
environment: Environment, | ||
) -> MetadataModelField: | ||
environment_type = ContentType.objects.get_for_model(environment) | ||
|
||
return MetadataModelField.objects.create( | ||
|
@@ -521,7 +589,10 @@ def optional_b_environment_metadata_field(organisation, b_metadata_field, enviro | |
|
||
|
||
@pytest.fixture() | ||
def environment_metadata_a(environment, required_a_environment_metadata_field): | ||
def environment_metadata_a( | ||
environment: Environment, | ||
required_a_environment_metadata_field: MetadataModelFieldRequirement, | ||
) -> Metadata: | ||
environment_type = ContentType.objects.get_for_model(environment) | ||
return Metadata.objects.create( | ||
object_id=environment.id, | ||
|
@@ -532,7 +603,10 @@ def environment_metadata_a(environment, required_a_environment_metadata_field): | |
|
||
|
||
@pytest.fixture() | ||
def environment_metadata_b(environment, optional_b_environment_metadata_field): | ||
def environment_metadata_b( | ||
environment: Environment, | ||
optional_b_environment_metadata_field: MetadataModelFieldRequirement, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Type hint in wrong here, right? It should be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Corrected. |
||
) -> Metadata: | ||
environment_type = ContentType.objects.get_for_model(environment) | ||
return Metadata.objects.create( | ||
object_id=environment.id, | ||
|
@@ -543,12 +617,22 @@ def environment_metadata_b(environment, optional_b_environment_metadata_field): | |
|
||
|
||
@pytest.fixture() | ||
def environment_content_type(): | ||
def environment_content_type() -> ContentType: | ||
return ContentType.objects.get_for_model(Environment) | ||
|
||
|
||
@pytest.fixture() | ||
def project_content_type(): | ||
def feature_content_type() -> ContentType: | ||
return ContentType.objects.get_for_model(Feature) | ||
|
||
|
||
@pytest.fixture() | ||
def segment_content_type() -> ContentType: | ||
return ContentType.objects.get_for_model(Segment) | ||
|
||
|
||
@pytest.fixture() | ||
def project_content_type() -> ContentType: | ||
return ContentType.objects.get_for_model(Project) | ||
|
||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,6 +11,7 @@ | |
from environments.sdk.serializers_mixins import ( | ||
HideSensitiveFieldsSerializerMixin, | ||
) | ||
from metadata.serializers import MetadataSerializer, SerializerWithMetadata | ||
from projects.models import Project | ||
from users.serializers import ( | ||
UserIdsSerializer, | ||
|
@@ -270,11 +271,33 @@ def get_last_modified_in_current_environment( | |
return getattr(instance, "last_modified_in_current_environment", None) | ||
|
||
|
||
class UpdateFeatureSerializer(ListCreateFeatureSerializer): | ||
"""prevent users from changing certain values after creation""" | ||
class FeatureSerializerWithMetadata( | ||
SerializerWithMetadata, ListCreateFeatureSerializer | ||
): | ||
metadata = MetadataSerializer(required=False, many=True) | ||
|
||
class Meta(ListCreateFeatureSerializer.Meta): | ||
read_only_fields = ListCreateFeatureSerializer.Meta.read_only_fields + ( | ||
fields = ListCreateFeatureSerializer.Meta.fields + ("metadata",) | ||
|
||
def get_project(self, validated_data: dict = None) -> Project: | ||
view = self.context.get("view") | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can't we get project from There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure, Changed. |
||
if view and (project_pk := view.kwargs.get("project_pk")): | ||
try: | ||
return Project.objects.get(pk=project_pk) | ||
except Project.DoesNotExist: | ||
raise serializers.ValidationError("Project not found.") | ||
|
||
raise serializers.ValidationError( | ||
"Unable to retrieve project for metadata validation." | ||
) | ||
|
||
|
||
class UpdateFeatureSerializerWithMetadata(FeatureSerializerWithMetadata): | ||
"""prevent users from changing certain values after creation""" | ||
|
||
class Meta(FeatureSerializerWithMetadata.Meta): | ||
read_only_fields = FeatureSerializerWithMetadata.Meta.read_only_fields + ( | ||
"default_enabled", | ||
"initial_value", | ||
"name", | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,15 @@ | ||
import typing | ||
|
||
from django.conf import settings | ||
from django.contrib.contenttypes.models import ContentType | ||
from flag_engine.segments.constants import PERCENTAGE_SPLIT | ||
from rest_framework import serializers | ||
from rest_framework.exceptions import ValidationError | ||
from rest_framework.serializers import ListSerializer | ||
from rest_framework_recursive.fields import RecursiveField | ||
|
||
from metadata.models import Metadata | ||
from metadata.serializers import MetadataSerializer | ||
from projects.models import Project | ||
from segments.models import Condition, Segment, SegmentRule | ||
|
||
|
@@ -42,6 +45,7 @@ class Meta: | |
|
||
class SegmentSerializer(serializers.ModelSerializer): | ||
rules = RuleSerializer(many=True) | ||
metadata = MetadataSerializer(required=False, many=True) | ||
|
||
class Meta: | ||
model = Segment | ||
|
@@ -59,13 +63,15 @@ def create(self, validated_data): | |
self.validate_project_segment_limit(project) | ||
|
||
rules_data = validated_data.pop("rules", []) | ||
metadata_data = validated_data.pop("metadata", []) | ||
self.validate_segment_rules_conditions_limit(rules_data) | ||
|
||
# create segment with nested rules and conditions | ||
segment = Segment.objects.create(**validated_data) | ||
self._update_or_create_segment_rules( | ||
rules_data, segment=segment, is_create=True | ||
) | ||
self._update_or_create_metadata(metadata_data, segment=segment) | ||
return segment | ||
|
||
def update(self, instance, validated_data): | ||
|
@@ -156,6 +162,24 @@ def _update_or_create_segment_rules( | |
child_rules, rule=child_rule, is_create=is_create | ||
) | ||
|
||
def _update_or_create_metadata( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we have to do this because we are not using NestedModelSerializer Here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I understand that it is not related to your code, but do we know why are we not using NestedModelSerializer here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the decision to not employ NestedModelSerializer here is based on the simplicity and structure of the models and data relationships we are dealing with. |
||
self, metadata_data: typing.Dict, segment: typing.Optional[Segment] = None | ||
) -> None: | ||
for metadata_item in metadata_data: | ||
metadata_id = metadata_item.pop("id", None) | ||
if metadata_item.get("delete"): | ||
Metadata.objects.filter(id=metadata_id).delete() | ||
continue | ||
gagantrivedi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
Metadata.objects.update_or_create( | ||
id=metadata_id, | ||
defaults={ | ||
**metadata_item, | ||
"content_type": ContentType.objects.get_for_model(Segment), | ||
"object_id": segment.id, | ||
}, | ||
) | ||
|
||
@staticmethod | ||
def _update_or_create_segment_rule( | ||
rule_data: dict, segment: Segment = None, rule: SegmentRule = None | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Type hint in wrong here, right? It should be MetadataModelField
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Corrected.