From 6b582be3d909b494a28c21f9c0a793f692f5f8bd Mon Sep 17 00:00:00 2001 From: Daniel Ursache Dogariu Date: Fri, 5 Jul 2024 15:38:40 +0300 Subject: [PATCH] Reduce the number of db queries for feature flags --- backend/hub/admin.py | 75 ++++++++++++++++-------- backend/hub/forms.py | 8 +-- backend/hub/models.py | 12 +++- backend/hub/social_adapters.py | 2 +- backend/hub/views.py | 35 +++++------ backend/locale/ro/LC_MESSAGES/django.mo | Bin 21395 -> 21610 bytes 6 files changed, 82 insertions(+), 50 deletions(-) diff --git a/backend/hub/admin.py b/backend/hub/admin.py index b4c7f91e..49038c29 100644 --- a/backend/hub/admin.py +++ b/backend/hub/admin.py @@ -457,50 +457,79 @@ def import_cities(self, request): def flags_phase_1(modeladmin, request, queryset): - FeatureFlag.objects.filter(flag="enable_org_registration").update(is_enabled=True) - FeatureFlag.objects.filter(flag="enable_org_approval").update(is_enabled=True) - FeatureFlag.objects.filter(flag="enable_org_voting").update(is_enabled=True) - FeatureFlag.objects.filter(flag="enable_candidate_registration").update(is_enabled=True) + enabled = [ + "enable_org_registration", + "enable_org_approval", + "enable_org_voting", + "enable_candidate_registration", + ] + FeatureFlag.objects.filter(flag__in=enabled).update(is_enabled=True) + + disabled = [ + "enable_candidate_voting", + ] + FeatureFlag.objects.filter(flag__in=disabled).update(is_enabled=False) + FeatureFlag.objects.filter(flag="enable_candidate_supporting").update( is_enabled=get_feature_flag(FLAG_CHOICES.global_support_round) ) - FeatureFlag.objects.filter(flag="enable_candidate_voting").update(is_enabled=False) flags_phase_1.short_description = _("Set flags for PHASE 1 - organization & candidate registrations") def flags_phase_2(modeladmin, request, queryset): - FeatureFlag.objects.filter(flag="enable_org_registration").update(is_enabled=True) - FeatureFlag.objects.filter(flag="enable_org_approval").update(is_enabled=True) - FeatureFlag.objects.filter(flag="enable_org_voting").update(is_enabled=True) - FeatureFlag.objects.filter(flag="enable_candidate_registration").update(is_enabled=False) - FeatureFlag.objects.filter(flag="enable_candidate_supporting").update(is_enabled=False) - FeatureFlag.objects.filter(flag="enable_candidate_voting").update(is_enabled=False) + enabled = [ + "enable_org_registration", + "enable_org_approval", + "enable_org_voting", + ] + FeatureFlag.objects.filter(flag__in=enabled).update(is_enabled=True) + + disabled = [ + "enable_candidate_registration", + "enable_candidate_supporting", + "enable_candidate_voting", + ] + FeatureFlag.objects.filter(flag__in=disabled).update(is_enabled=False) flags_phase_2.short_description = _("Set flags for PHASE 2 - candidate validation") def flags_phase_3(modeladmin, request, queryset): - FeatureFlag.objects.filter(flag="enable_org_registration").update(is_enabled=True) - FeatureFlag.objects.filter(flag="enable_org_approval").update(is_enabled=True) - FeatureFlag.objects.filter(flag="enable_org_voting").update(is_enabled=True) - FeatureFlag.objects.filter(flag="enable_candidate_registration").update(is_enabled=False) - FeatureFlag.objects.filter(flag="enable_candidate_supporting").update(is_enabled=False) - FeatureFlag.objects.filter(flag="enable_candidate_voting").update(is_enabled=True) + enabled = [ + "enable_org_registration", + "enable_org_approval", + "enable_org_voting", + "enable_candidate_voting", + ] + FeatureFlag.objects.filter(flag__in=enabled).update(is_enabled=True) + + disabled = [ + "enable_candidate_registration", + "enable_candidate_supporting", + ] + FeatureFlag.objects.filter(flag__in=disabled).update(is_enabled=False) flags_phase_3.short_description = _("Set flags for PHASE 3 - voting") def flags_final_phase(modeladmin, request, queryset): - FeatureFlag.objects.filter(flag="enable_org_registration").update(is_enabled=True) - FeatureFlag.objects.filter(flag="enable_org_approval").update(is_enabled=True) - FeatureFlag.objects.filter(flag="enable_org_voting").update(is_enabled=True) - FeatureFlag.objects.filter(flag="enable_candidate_registration").update(is_enabled=False) - FeatureFlag.objects.filter(flag="enable_candidate_supporting").update(is_enabled=False) - FeatureFlag.objects.filter(flag="enable_candidate_voting").update(is_enabled=False) + enabled = [ + "enable_org_registration", + "enable_org_approval", + "enable_org_voting", + ] + FeatureFlag.objects.filter(flag__in=enabled).update(is_enabled=True) + + disabled = [ + "enable_candidate_registration", + "enable_candidate_supporting", + "enable_candidate_voting", + ] + FeatureFlag.objects.filter(flag__in=disabled).update(is_enabled=False) flags_final_phase.short_description = _("Set flags for FINAL PHASE - results") diff --git a/backend/hub/forms.py b/backend/hub/forms.py index 015b4037..2fa553fe 100644 --- a/backend/hub/forms.py +++ b/backend/hub/forms.py @@ -186,7 +186,7 @@ def __init__(self, *args, **kwargs): self.initial["org"] = self.user.orgs.first().id - if FeatureFlag.objects.filter(flag="single_domain_round", is_enabled=True).exists(): + if FeatureFlag.is_enabled("single_domain_round"): self.fields["domain"].widget.attrs["disabled"] = True self.initial["domain"] = Domain.objects.first().id @@ -194,7 +194,7 @@ def clean_org(self): return self.user.orgs.first().id def clean_domain(self): - if FeatureFlag.objects.filter(flag="single_domain_round", is_enabled=True).exists(): + if FeatureFlag.is_enabled("single_domain_round"): return Domain.objects.first() return self.cleaned_data.get("domain") @@ -227,7 +227,7 @@ class Meta: def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - if not FeatureFlag.objects.filter(flag="enable_candidate_registration", is_enabled=True).exists(): + if not FeatureFlag.is_enabled("enable_candidate_registration"): for key in self.fields.keys(): self.fields[key].widget.attrs["disabled"] = True @@ -239,7 +239,7 @@ def __init__(self, *args, **kwargs): self.fields[key].widget.attrs["required"] = True def save(self, commit=True): - if not FeatureFlag.objects.filter(flag="enable_candidate_registration", is_enabled=True).exists(): + if not FeatureFlag.is_enabled("enable_candidate_registration"): # This should not happen unless someone messes with the form code raise PermissionDenied diff --git a/backend/hub/models.py b/backend/hub/models.py index c8cb2884..6a441615 100644 --- a/backend/hub/models.py +++ b/backend/hub/models.py @@ -131,6 +131,16 @@ class Meta: def __str__(self): return f"{FLAG_CHOICES[self.flag]}" + @staticmethod + def is_enabled(flag: str) -> bool: + """ + Check if the requested feature flag is enabled + """ + if not flag: + return False + + return FeatureFlag.objects.filter(flag=flag, is_enabled=True).exists() + class BlogPost(TimeStampedModel): title = models.CharField(_("Title"), max_length=254) @@ -528,7 +538,7 @@ def save(self, *args, **kwargs): if self.id and CandidateVote.objects.filter(candidate=self).exists(): raise ValidationError(_("Cannot update candidate after votes have been cast.")) - if FeatureFlag.objects.filter(flag="single_domain_round", is_enabled=True).exists(): + if FeatureFlag.is_enabled("single_domain_round"): self.domain = Domain.objects.first() # This covers the flow when a candidate is withdrawn as the official proposal or the organization, while diff --git a/backend/hub/social_adapters.py b/backend/hub/social_adapters.py index 6fb1c751..f2801e5f 100644 --- a/backend/hub/social_adapters.py +++ b/backend/hub/social_adapters.py @@ -65,7 +65,7 @@ def update_user_org(org: Organization, token: str, *, in_auth_flow: bool = False redirect the user to a relevant error page. """ - if not FeatureFlag.objects.filter(flag="enable_org_registration", is_enabled=True).exists(): + if not FeatureFlag.is_enabled("enable_org_registration"): if in_auth_flow: raise ImmediateHttpResponse(redirect(reverse("error-org-registration-closed"))) else: diff --git a/backend/hub/views.py b/backend/hub/views.py index 4fb4c12b..a830e905 100644 --- a/backend/hub/views.py +++ b/backend/hub/views.py @@ -255,18 +255,12 @@ class OrganizationRegisterRequestCreateView(HubCreateView): ) def get(self, request, *args, **kwargs): - if ( - not settings.ENABLE_ORG_REGISTRATION_FORM - or not FeatureFlag.objects.filter(flag="enable_org_registration", is_enabled=True).exists() - ): + if not settings.ENABLE_ORG_REGISTRATION_FORM or not FeatureFlag.is_enabled("enable_org_registration"): raise PermissionDenied return super().get(request, *args, **kwargs) def post(self, request, *args, **kwargs): - if ( - not settings.ENABLE_ORG_REGISTRATION_FORM - or not FeatureFlag.objects.filter(flag="enable_org_registration", is_enabled=True).exists() - ): + if not settings.ENABLE_ORG_REGISTRATION_FORM or not FeatureFlag.is_enabled("enable_org_registration"): raise PermissionDenied return super().post(request, *args, **kwargs) @@ -294,7 +288,7 @@ def post(self, request, *args, **kwargs): @permission_required_or_403("hub.approve_organization", (Organization, "pk", "pk")) def organization_vote(request, pk, action): - if not FeatureFlag.objects.filter(flag="enable_org_approval", is_enabled=True).exists(): + if not FeatureFlag.is_enabled("enable_org_approval"): raise PermissionDenied try: @@ -354,7 +348,7 @@ class CandidateListView(HubListView): template_name = "candidate/list.html" def get_qs(self): - if FeatureFlag.objects.filter(flag="enable_candidate_voting", is_enabled=True).exists(): + if FeatureFlag.is_enabled("enable_candidate_voting"): return Candidate.objects_with_org.filter( org__status=Organization.STATUS.accepted, status=Candidate.STATUS.accepted, is_proposed=True ) @@ -432,12 +426,12 @@ class CandidateRegisterRequestCreateView(LoginRequiredMixin, HubCreateView): form_class = CandidateRegisterForm def get(self, request, *args, **kwargs): - if not FeatureFlag.objects.filter(flag="enable_candidate_registration", is_enabled=True).exists(): + if not FeatureFlag.is_enabled("enable_candidate_registration"): raise PermissionDenied return super().get(request, *args, **kwargs) def post(self, request, *args, **kwargs): - if not FeatureFlag.objects.filter(flag="enable_candidate_registration", is_enabled=True).exists(): + if not FeatureFlag.is_enabled("enable_candidate_registration"): raise PermissionDenied return super().post(request, *args, **kwargs) @@ -461,9 +455,8 @@ def get_success_url(self): return reverse("candidate-update", args=(self.object.id,)) def post(self, request, *args, **kwargs): - if ( - FeatureFlag.objects.filter(flag="enable_candidate_registration", is_enabled=True).exists() - or FeatureFlag.objects.filter(flag="enable_candidate_supporting", is_enabled=True).exists() + if FeatureFlag.is_enabled("enable_candidate_registration") or FeatureFlag.is_enabled( + "enable_candidate_supporting" ): return super().post(request, *args, **kwargs) raise PermissionDenied @@ -471,7 +464,7 @@ def post(self, request, *args, **kwargs): @permission_required_or_403("hub.vote_candidate", (Candidate, "pk", "pk")) def candidate_vote(request, pk): - if not FeatureFlag.objects.filter(flag="enable_candidate_voting", is_enabled=True).exists(): + if not FeatureFlag.is_enabled("enable_candidate_voting"): raise PermissionDenied try: @@ -505,7 +498,7 @@ def candidate_vote(request, pk): @permission_required_or_403("hub.delete_candidate", (Candidate, "pk", "pk")) def candidate_revoke(request, pk): - if not FeatureFlag.objects.filter(flag="enable_candidate_supporting", is_enabled=True).exists(): + if not FeatureFlag.is_enabled("enable_candidate_supporting"): raise PermissionDenied candidate = get_object_or_404(Candidate, pk=pk) @@ -523,7 +516,7 @@ def candidate_revoke(request, pk): @permission_required_or_403("hub.support_candidate", (Candidate, "pk", "pk")) def candidate_support(request, pk): - if not FeatureFlag.objects.filter(flag="enable_candidate_supporting", is_enabled=True).exists(): + if not FeatureFlag.is_enabled("enable_candidate_supporting"): raise PermissionDenied candidate = get_object_or_404(Candidate, pk=pk, is_proposed=True) @@ -544,9 +537,9 @@ def candidate_support(request, pk): @permission_required_or_403("hub.approve_candidate", (Candidate, "pk", "pk")) def candidate_status_confirm(request, pk): if ( - FeatureFlag.objects.filter(flag="enable_candidate_registration", is_enabled=True).exists() - or FeatureFlag.objects.filter(flag="enable_candidate_supporting", is_enabled=True).exists() - or FeatureFlag.objects.filter(flag="enable_candidate_voting", is_enabled=True).exists() + FeatureFlag.is_enabled("enable_candidate_registration") + or FeatureFlag.is_enabled("enable_candidate_supporting") + or FeatureFlag.is_enabled("enable_candidate_voting") ): raise PermissionDenied diff --git a/backend/locale/ro/LC_MESSAGES/django.mo b/backend/locale/ro/LC_MESSAGES/django.mo index 809a81e4124981ed6eb3edb00a1f5842ac23cd73..caa2ae6e44c7f356966759a206bd6e7b9e9a1bac 100644 GIT binary patch delta 5797 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 Hx+?C!@ifr9 delta 5598 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 NTJ}p7yR+8>{|~Ujeb)d0