-
-
Notifications
You must be signed in to change notification settings - Fork 3.6k
Unify feature check for organization/project #8920
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
Changes from 4 commits
f29401f
8e339c0
b58e282
274a377
277a513
5ad812e
60f0016
7dae9b4
248b567
8911ded
330e1f6
cfa0c66
bf4199b
33c4a30
d011326
6d9f144
9d23f79
444c65e
ff12df7
bff4ab4
652c62c
39581af
21473bc
65b33df
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 |
---|---|---|
|
@@ -97,6 +97,7 @@ def max_concurrent_builds(self, project): | |
|
||
- project | ||
- organization | ||
- plan | ||
- default setting | ||
|
||
:param project: project to be checked | ||
|
@@ -105,6 +106,8 @@ def max_concurrent_builds(self, project): | |
:returns: number of max concurrent builds for the project | ||
:rtype: int | ||
""" | ||
from readthedocs.subscriptions.models import PlanFeature | ||
|
||
max_concurrent_organization = None | ||
organization = project.organizations.first() | ||
if organization: | ||
|
@@ -113,7 +116,11 @@ def max_concurrent_builds(self, project): | |
return ( | ||
project.max_concurrent_builds or | ||
max_concurrent_organization or | ||
settings.RTD_MAX_CONCURRENT_BUILDS | ||
PlanFeature.objects.get_feature_value( | ||
project, | ||
type=PlanFeature.TYPE_CONCURRENT_BUILDS, | ||
default=settings.RTD_MAX_CONCURRENT_BUILDS, | ||
) | ||
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. we had an override just to check for the value from the plan on .com. 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. Shouldn't this method contain all the logic? I mean, also check for Otherwise, it's only being called for the default value, but it won't affect the project's value. 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. It seems my suggestion is already applied, right? |
||
) | ||
|
||
def prefetch_latest_build(self): | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,6 @@ | ||
"""Project views for authenticated users.""" | ||
|
||
import structlog | ||
|
||
from allauth.socialaccount.models import SocialAccount | ||
from django.conf import settings | ||
from django.contrib import messages | ||
|
@@ -40,7 +39,6 @@ | |
) | ||
from readthedocs.core.history import UpdateChangeReasonPostView | ||
from readthedocs.core.mixins import ListViewWithForm, PrivateViewMixin | ||
from readthedocs.core.utils.extend import SettingsOverrideObject | ||
from readthedocs.integrations.models import HttpExchange, Integration | ||
from readthedocs.oauth.services import registry | ||
from readthedocs.oauth.tasks import attach_webhook | ||
|
@@ -79,6 +77,8 @@ | |
ProjectRelationListMixin, | ||
) | ||
from readthedocs.search.models import SearchQuery | ||
from readthedocs.subscriptions.models import PlanFeature | ||
|
||
|
||
log = structlog.get_logger(__name__) | ||
|
||
|
@@ -747,6 +747,7 @@ class DomainMixin(ProjectAdminMixin, PrivateViewMixin): | |
model = Domain | ||
form_class = DomainForm | ||
lookup_url_kwarg = 'domain_pk' | ||
feature_type = PlanFeature.TYPE_CNAME | ||
|
||
def get_success_url(self): | ||
return reverse('projects_domains', args=[self.get_project().slug]) | ||
|
@@ -758,11 +759,13 @@ def get_context_data(self, **kwargs): | |
return context | ||
|
||
def _is_enabled(self, project): | ||
"""Should we allow custom domains for this project?""" | ||
return True | ||
return PlanFeature.objects.has_feature( | ||
project, | ||
type=self.feature_type, | ||
) | ||
|
||
|
||
class DomainListBase(DomainMixin, ListViewWithForm): | ||
class DomainList(DomainMixin, ListViewWithForm): | ||
|
||
def get_context_data(self, **kwargs): | ||
ctx = super().get_context_data(**kwargs) | ||
|
@@ -777,12 +780,7 @@ def get_context_data(self, **kwargs): | |
return ctx | ||
|
||
|
||
class DomainList(SettingsOverrideObject): | ||
|
||
_default_class = DomainListBase | ||
|
||
|
||
class DomainCreateBase(DomainMixin, CreateView): | ||
class DomainCreate(DomainMixin, CreateView): | ||
|
||
def post(self, request, *args, **kwargs): | ||
project = self.get_project() | ||
|
@@ -801,12 +799,7 @@ def get_success_url(self): | |
) | ||
|
||
|
||
class DomainCreate(SettingsOverrideObject): | ||
|
||
_default_class = DomainCreateBase | ||
|
||
|
||
class DomainUpdateBase(DomainMixin, UpdateView): | ||
class DomainUpdate(DomainMixin, UpdateView): | ||
|
||
def post(self, request, *args, **kwargs): | ||
project = self.get_project() | ||
|
@@ -815,11 +808,6 @@ def post(self, request, *args, **kwargs): | |
return HttpResponse('Action not allowed', status=401) | ||
|
||
|
||
class DomainUpdate(SettingsOverrideObject): | ||
|
||
_default_class = DomainUpdateBase | ||
|
||
|
||
class DomainDelete(DomainMixin, DeleteView): | ||
|
||
pass | ||
|
@@ -1062,10 +1050,11 @@ class RegexAutomationRuleUpdate(RegexAutomationRuleMixin, UpdateView): | |
pass | ||
|
||
|
||
class SearchAnalyticsBase(ProjectAdminMixin, PrivateViewMixin, TemplateView): | ||
class SearchAnalytics(ProjectAdminMixin, PrivateViewMixin, TemplateView): | ||
|
||
template_name = 'projects/projects_search_analytics.html' | ||
http_method_names = ['get'] | ||
feature_type = PlanFeature.TYPE_SEARCH_ANALYTICS | ||
|
||
def get(self, request, *args, **kwargs): | ||
download_data = request.GET.get('download', False) | ||
|
@@ -1149,21 +1138,25 @@ def _get_csv_data(self): | |
|
||
def _get_retention_days_limit(self, project): | ||
"""From how many days we need to show data for this project?""" | ||
return settings.RTD_ANALYTICS_DEFAULT_RETENTION_DAYS | ||
return PlanFeature.objects.get_feature_value( | ||
project, | ||
type=self.feature_type, | ||
default=settings.RTD_ANALYTICS_DEFAULT_RETENTION_DAYS, | ||
) | ||
|
||
def _is_enabled(self, project): | ||
"""Should we show search analytics for this project?""" | ||
return True | ||
|
||
|
||
class SearchAnalytics(SettingsOverrideObject): | ||
_default_class = SearchAnalyticsBase | ||
return PlanFeature.objects.has_feature( | ||
project, | ||
type=self.feature_type, | ||
) | ||
Comment on lines
1161
to
+1166
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. This method should go in a mixin since it's shared among multiple classes. |
||
|
||
|
||
class TrafficAnalyticsViewBase(ProjectAdminMixin, PrivateViewMixin, TemplateView): | ||
class TrafficAnalyticsView(ProjectAdminMixin, PrivateViewMixin, TemplateView): | ||
|
||
template_name = 'projects/project_traffic_analytics.html' | ||
http_method_names = ['get'] | ||
feature_type = PlanFeature.TYPE_PAGEVIEW_ANALYTICS | ||
|
||
def get(self, request, *args, **kwargs): | ||
download_data = request.GET.get('download', False) | ||
|
@@ -1239,12 +1232,15 @@ def _get_csv_data(self): | |
|
||
def _get_retention_days_limit(self, project): | ||
"""From how many days we need to show data for this project?""" | ||
return settings.RTD_ANALYTICS_DEFAULT_RETENTION_DAYS | ||
return PlanFeature.objects.get_feature_value( | ||
project, | ||
type=self.feature_type, | ||
default=settings.RTD_ANALYTICS_DEFAULT_RETENTION_DAYS, | ||
stsewd marked this conversation as resolved.
Show resolved
Hide resolved
|
||
) | ||
|
||
def _is_enabled(self, project): | ||
"""Should we show traffic analytics for this project?""" | ||
return True | ||
|
||
|
||
class TrafficAnalyticsView(SettingsOverrideObject): | ||
_default_class = TrafficAnalyticsViewBase | ||
return PlanFeature.objects.has_feature( | ||
project, | ||
type=self.feature_type, | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -218,3 +218,29 @@ def get_feature(self, obj, type): | |
plan__subscriptions__organization=organization, | ||
) | ||
return feature.first() | ||
|
||
# pylint: disable=redefined-builtin | ||
def get_feature_value(self, obj, type, default=None): | ||
""" | ||
Get the value of the given feature. | ||
|
||
Use this function instead of ``get_feature().value`` | ||
when you need to respect the ``RTD_ALL_FEATURES_ENABLED`` setting. | ||
""" | ||
if not settings.RTD_ALL_FEATURES_ENABLED: | ||
feature = self.get_feature(obj, type) | ||
if feature: | ||
return feature.value | ||
return default | ||
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. Here is where I think it needs to encapsulate more logic, so we can rely 100% on it without mixing the |
||
|
||
# pylint: disable=redefined-builtin | ||
def has_feature(self, obj, type): | ||
""" | ||
Get the value of the given feature. | ||
|
||
Use this function instead of ``bool(get_feature())`` | ||
when you need to respect the ``RTD_ALL_FEATURES_ENABLED`` setting. | ||
""" | ||
if settings.RTD_ALL_FEATURES_ENABLED: | ||
return True | ||
return self.get_feature(obj, type) is not None |
Uh oh!
There was an error while loading. Please reload this page.