This class holds the *value* for a given multivariate feature option. This value is the same for every environment, but the percent allocation is set in MultivariateFeatureStateValue which varies per-environment.
| 31 | |
| 32 | |
| 33 | class MultivariateFeatureOption( |
| 34 | LifecycleModelMixin, # type: ignore[misc] |
| 35 | AbstractBaseFeatureValueModel, |
| 36 | AbstractBaseExportableModel, |
| 37 | abstract_base_auditable_model_factory(["uuid"]), # type: ignore[misc] |
| 38 | ): |
| 39 | """ |
| 40 | This class holds the *value* for a given multivariate feature |
| 41 | option. This value is the same for every environment, but the |
| 42 | percent allocation is set in MultivariateFeatureStateValue |
| 43 | which varies per-environment. |
| 44 | """ |
| 45 | |
| 46 | history_record_class_path = ( |
| 47 | "features.multivariate.models.HistoricalMultivariateFeatureOption" |
| 48 | ) |
| 49 | related_object_type = RelatedObjectType.FEATURE |
| 50 | |
| 51 | feature = models.ForeignKey( |
| 52 | "features.Feature", |
| 53 | on_delete=models.CASCADE, |
| 54 | related_name="multivariate_options", |
| 55 | ) |
| 56 | |
| 57 | key = models.CharField( |
| 58 | max_length=255, |
| 59 | null=True, |
| 60 | validators=[validate_slug], |
| 61 | help_text="A stable, human-readable identifier for the variant.", |
| 62 | ) |
| 63 | |
| 64 | # This field is stored at the feature level but not used here - it is transferred |
| 65 | # to the MultivariateFeatureStateValue on creation of a new option or when creating |
| 66 | # a new environment. |
| 67 | default_percentage_allocation = models.FloatField( |
| 68 | default=100, |
| 69 | validators=[MinValueValidator(0), MaxValueValidator(100)], |
| 70 | ) |
| 71 | |
| 72 | class Meta: |
| 73 | unique_together = ("feature", "key") |
| 74 | |
| 75 | @hook(AFTER_CREATE) |
| 76 | def create_multivariate_feature_state_values(self): # type: ignore[no-untyped-def] |
| 77 | for feature_state in self.feature.feature_states.filter( |
| 78 | identity=None, feature_segment=None |
| 79 | ): |
| 80 | MultivariateFeatureStateValue.objects.create( |
| 81 | feature_state=feature_state, |
| 82 | multivariate_feature_option=self, |
| 83 | percentage_allocation=self.default_percentage_allocation, |
| 84 | ) |
| 85 | |
| 86 | @hook(AFTER_CREATE) |
| 87 | def make_feature_multivariate(self): # type: ignore[no-untyped-def] |
| 88 | # Handle the check on feature.type in the method itself to ensure this is |
| 89 | # only performed after create. Using `when` and `is_not` means |
| 90 | # LifecycleModel.__init__() queries for feature on every object init. |
no test coverage detected
searching dependent graphs…