From 9621a13074c6f0057717b15030da4409d9e5d917 Mon Sep 17 00:00:00 2001 From: Graham Knapp <32717635+dancergraham@users.noreply.github.com> Date: Mon, 2 Dec 2024 15:41:51 +0100 Subject: [PATCH 1/4] First draft improved api documentation --- docs/types/flag.rst | 11 +++ docs/types/sample.rst | 9 ++ docs/types/switch.rst | 9 ++ docs/usage/custom_models.rst | 162 +++++++++++++++++++++++++++++++++++ docs/usage/index.rst | 12 +++ 5 files changed, 203 insertions(+) create mode 100644 docs/usage/custom_models.rst diff --git a/docs/types/flag.rst b/docs/types/flag.rst index 5e984736..221d0a0f 100644 --- a/docs/types/flag.rst +++ b/docs/types/flag.rst @@ -243,3 +243,14 @@ Whether or not you enabled :ref:`Auto Create Missing Flags ` to any level known by Python default logger. + + +Flag Methods +============ + +The Flag class has the following public methods: + +:is_active: + Determines if the flag is active for a given request. Returns a boolean value. +:is_active_for_user: + Determines if the flag is active for a given user. Returns a boolean value. diff --git a/docs/types/sample.rst b/docs/types/sample.rst index 651aa5db..7f203ea1 100644 --- a/docs/types/sample.rst +++ b/docs/types/sample.rst @@ -136,3 +136,12 @@ Whether or not you enabled :ref:`Auto Create Missing Sample ` to any level known by Python default logger. + + +Sample Methods +============== + +The Sample class has the following public methods: + +:is_active: + Determines if the sample is active. Returns a boolean value. diff --git a/docs/types/switch.rst b/docs/types/switch.rst index 8b0ee30a..24247026 100644 --- a/docs/types/switch.rst +++ b/docs/types/switch.rst @@ -106,3 +106,12 @@ Whether or not you enabled :ref:`Auto Create Missing Switch ` to any level known by Python default logger. + + +Switch Methods +============== + +The Switch class has the following public methods: + +:is_active: + Determines if the switch is active. Returns a boolean value. diff --git a/docs/usage/custom_models.rst b/docs/usage/custom_models.rst new file mode 100644 index 00000000..c85e207a --- /dev/null +++ b/docs/usage/custom_models.rst @@ -0,0 +1,162 @@ +.. _usage-custom-models: + +================ +Custom Models +================ + +For many cases, the default models for flags, switches, and samples provide all the necessary functionality. However, if you need additional fields or functionality, you can use custom models. + +Custom Flag Model +================= + +To use a custom flag model, define a `WAFFLE_FLAG_MODEL` setting in your `settings.py`. The custom flag model must inherit from `waffle.models.AbstractBaseFlag` or `waffle.models.AbstractUserFlag`. + +Example: + +.. code-block:: python + + # settings.py + WAFFLE_FLAG_MODEL = 'myapp.Flag' + + # models.py + from waffle.models import AbstractUserFlag, CACHE_EMPTY + from waffle.utils import get_setting, keyfmt, get_cache + + class Flag(AbstractUserFlag): + FLAG_COMPANIES_CACHE_KEY = 'FLAG_COMPANIES_CACHE_KEY' + FLAG_COMPANIES_CACHE_KEY_DEFAULT = 'flag:%s:companies' + + companies = models.ManyToManyField( + Company, + blank=True, + help_text=_('Activate this flag for these companies.'), + ) + + def get_flush_keys(self, flush_keys=None): + flush_keys = super(Flag, self).get_flush_keys(flush_keys) + companies_cache_key = get_setting(Flag.FLAG_COMPANIES_CACHE_KEY, Flag.FLAG_COMPANIES_CACHE_KEY_DEFAULT) + flush_keys.append(keyfmt(companies_cache_key, self.name)) + return flush_keys + + def is_active_for_user(self, user): + is_active = super(Flag, self).is_active_for_user(user) + if is_active: + return is_active + + if getattr(user, 'company_id', None): + company_ids = self._get_company_ids() + if user.company_id in company_ids: + return True + + def _get_company_ids(self): + cache = get_cache() + cache_key = keyfmt( + get_setting(Flag.FLAG_COMPANIES_CACHE_KEY, Flag.FLAG_COMPANIES_CACHE_KEY_DEFAULT), + self.name + ) + cached = cache.get(cache_key) + if cached == CACHE_EMPTY: + return set() + if cached: + return cached + + company_ids = set(self.companies.all().values_list('pk', flat=True)) + if not company_ids: + cache.add(cache_key, CACHE_EMPTY) + return set() + + cache.add(cache_key, company_ids) + return company_ids + + # admin.py + from waffle.admin import FlagAdmin as WaffleFlagAdmin + + class FlagAdmin(WaffleFlagAdmin): + raw_id_fields = tuple(list(WaffleFlagAdmin.raw_id_fields) + ['companies']) + admin.site.register(Flag, FlagAdmin) + +To reference the custom flag model in your project, use the `get_waffle_flag_model` method. + +.. code-block:: python + + from waffle import get_waffle_flag_model + + Flag = get_waffle_flag_model() + + +Custom Switch Model +=================== + +To use a custom switch model, define a `WAFFLE_SWITCH_MODEL` setting in your `settings.py`. The custom switch model must inherit from `waffle.models.AbstractBaseSwitch`. + +Example: + +.. code-block:: python + + # settings.py + WAFFLE_SWITCH_MODEL = 'myapp.Switch' + + # models.py + from waffle.models import AbstractBaseSwitch + + class Switch(AbstractBaseSwitch): + + owner = models.CharField( + max_length=100, + blank=True, + help_text=_('The individual/team who owns this switch.'), + ) + + # admin.py + from waffle.admin import SwitchAdmin as WaffleSwitchAdmin + + class SwitchAdmin(WaffleSwitchAdmin): + raw_id_fields = tuple(list(WaffleSwitchAdmin.raw_id_fields) + ['owner']) + admin.site.register(Switch, SwitchAdmin) + +To reference the custom switch model in your project, use the `get_waffle_switch_model` method. + +.. code-block:: python + + from waffle import get_waffle_switch_model + + Switch = get_waffle_switch_model() + + +Custom Sample Model +=================== + +To use a custom sample model, define a `WAFFLE_SAMPLE_MODEL` setting in your `settings.py`. The custom sample model must inherit from `waffle.models.AbstractBaseSample`. + +Example: + +.. code-block:: python + + # settings.py + WAFFLE_SAMPLE_MODEL = 'myapp.Sample' + + # models.py + from waffle.models import AbstractBaseSample + + class Sample(AbstractBaseSample): + + owner = models.CharField( + max_length=100, + blank=True, + help_text=_('The individual/team who owns this sample.'), + ) + + # admin.py + from waffle.admin import SampleAdmin as WaffleSampleAdmin + + class SampleAdmin(WaffleSampleAdmin): + raw_id_fields = tuple(list(WaffleSampleAdmin.raw_id_fields) + ['owner']) + admin.site.register(Sample, SampleAdmin) + +To reference the custom sample model in your project, use the `get_waffle_sample_model` method. + +.. code-block:: python + + from waffle import get_waffle_sample_model + + Sample = get_waffle_sample_model() diff --git a/docs/usage/index.rst b/docs/usage/index.rst index d91d8edb..1f7e6864 100644 --- a/docs/usage/index.rst +++ b/docs/usage/index.rst @@ -19,3 +19,15 @@ JavaScript. javascript json cli + +Public Methods +============== + +The following public methods are available in `waffle/__init__.py`: + +:flag_is_active: + Determines if a flag is active for a given request. Returns a boolean value. +:switch_is_active: + Determines if a switch is active. Returns a boolean value. +:sample_is_active: + Determines if a sample is active. Returns a boolean value. From 6ab12cdb158d89e9964808f4963d4231c942c44c Mon Sep 17 00:00:00 2001 From: dancergraham Date: Tue, 3 Dec 2024 09:58:38 +0100 Subject: [PATCH 2/4] format: typos and restructuring --- docs/types/flag.rst | 45 +++++++++++++++++++++---------------------- docs/types/sample.rst | 27 +++++++++++++------------- docs/types/switch.rst | 26 ++++++++++++------------- docs/usage/index.rst | 12 ------------ 4 files changed, 49 insertions(+), 61 deletions(-) diff --git a/docs/types/flag.rst b/docs/types/flag.rst index 221d0a0f..238993c8 100644 --- a/docs/types/flag.rst +++ b/docs/types/flag.rst @@ -43,33 +43,33 @@ Flag Attributes Flags can be administered through the Django `admin site`_ or the :ref:`command line `. They have the following attributes: -:Name: +:name: The name of the flag. Will be used to identify the flag everywhere. -:Everyone: +:everyone: Globally set the Flag, **overriding all other criteria**. Leave as *Unknown* to use other criteria. -:Testing: +:testing: Can the flag be specified via a querystring parameter? :ref:`See below `. -:Percent: +:percent: A percentage of users for whom the flag will be active, if no other criteria applies to them. -:Superusers: +:superusers: Is this flag always active for superusers? -:Staff: +:staff: Is this flag always active for staff? -:Authenticated: +:authenticated: Is this flag always active for authenticated users? -:Languages: +:languages: Is the ``LANGUAGE_CODE`` of the request in this list? (Comma-separated values.) -:Groups: +:groups: A list of group IDs for which this flag will always be active. -:Users: +:users: A list of user IDs for which this flag will always be active. -:Rollout: +:rollout: Activate Rollout mode? :ref:`See below `. -:Note: +:note: Describe where the flag is used. A Flag will be active if *any* of the criteria are true for the current @@ -85,6 +85,16 @@ are in the group *or* if they are in the 12%. the actual proportion of users for whom the Flag is active will probably differ slightly from the Percentage value. +Flag Methods +============ + +The Flag class has the following public methods: + +:is_active: + Determines if the flag is active for a given request. Returns a boolean value. +:is_active_for_user: + Determines if the flag is active for a given user. Returns a boolean value. + .. _types-flag-custom-model: @@ -243,14 +253,3 @@ Whether or not you enabled :ref:`Auto Create Missing Flags ` to any level known by Python default logger. - - -Flag Methods -============ - -The Flag class has the following public methods: - -:is_active: - Determines if the flag is active for a given request. Returns a boolean value. -:is_active_for_user: - Determines if the flag is active for a given user. Returns a boolean value. diff --git a/docs/types/sample.rst b/docs/types/sample.rst index 7f203ea1..fba72ae8 100644 --- a/docs/types/sample.rst +++ b/docs/types/sample.rst @@ -42,13 +42,23 @@ Sample Attributes Samples can be administered through the Django `admin site`_ or the :ref:`command line `. They have the following attributes: -:Name: +:name: The name of the Sample. -:Percent: +:percent: A number from 0.0 to 100.0 that determines how often the Sample will be active. -:Note: - Describe where the Sample is used. +:note: + Describes where the Sample is used. + + + +Sample Methods +============== + +The Sample class has the following public methods: + +:is_active: + Determines if the sample is active. Returns a boolean value. .. _admin site: https://docs.djangoproject.com/en/dev/ref/contrib/admin/ @@ -136,12 +146,3 @@ Whether or not you enabled :ref:`Auto Create Missing Sample ` to any level known by Python default logger. - - -Sample Methods -============== - -The Sample class has the following public methods: - -:is_active: - Determines if the sample is active. Returns a boolean value. diff --git a/docs/types/switch.rst b/docs/types/switch.rst index 24247026..1e5d654a 100644 --- a/docs/types/switch.rst +++ b/docs/types/switch.rst @@ -15,17 +15,26 @@ Switch Attributes Switches can be administered through the Django `admin site`_ or the :ref:`command line `. They have the following attributes: -:Name: +:name: The name of the Switch. -:Active: +:active: Is the Switch active or inactive. -:Note: - Describe where the Switch is used. +:note: + Describes where the Switch is used. .. _admin site: https://docs.djangoproject.com/en/dev/ref/contrib/admin/ +Switch Methods +============== + +The Switch class has the following public methods: + +:is_active: + Determines if the switch is active. Returns a boolean value. + + .. _types-custom-switch-models: Custom Switch Models @@ -106,12 +115,3 @@ Whether or not you enabled :ref:`Auto Create Missing Switch ` to any level known by Python default logger. - - -Switch Methods -============== - -The Switch class has the following public methods: - -:is_active: - Determines if the switch is active. Returns a boolean value. diff --git a/docs/usage/index.rst b/docs/usage/index.rst index 1f7e6864..d91d8edb 100644 --- a/docs/usage/index.rst +++ b/docs/usage/index.rst @@ -19,15 +19,3 @@ JavaScript. javascript json cli - -Public Methods -============== - -The following public methods are available in `waffle/__init__.py`: - -:flag_is_active: - Determines if a flag is active for a given request. Returns a boolean value. -:switch_is_active: - Determines if a switch is active. Returns a boolean value. -:sample_is_active: - Determines if a sample is active. Returns a boolean value. From e0df96fe52656e813b72abef06576a147a7631d8 Mon Sep 17 00:00:00 2001 From: dancergraham Date: Tue, 3 Dec 2024 10:47:53 +0100 Subject: [PATCH 3/4] fix: clear warning on build --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 06d55864..367ee37e 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -119,7 +119,7 @@ # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +# html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. From 709bda450641e3e7594be33b3d5d93410641707e Mon Sep 17 00:00:00 2001 From: dancergraham Date: Tue, 3 Dec 2024 10:48:19 +0100 Subject: [PATCH 4/4] chore: remove duplicate info --- docs/usage/custom_models.rst | 162 ----------------------------------- 1 file changed, 162 deletions(-) delete mode 100644 docs/usage/custom_models.rst diff --git a/docs/usage/custom_models.rst b/docs/usage/custom_models.rst deleted file mode 100644 index c85e207a..00000000 --- a/docs/usage/custom_models.rst +++ /dev/null @@ -1,162 +0,0 @@ -.. _usage-custom-models: - -================ -Custom Models -================ - -For many cases, the default models for flags, switches, and samples provide all the necessary functionality. However, if you need additional fields or functionality, you can use custom models. - -Custom Flag Model -================= - -To use a custom flag model, define a `WAFFLE_FLAG_MODEL` setting in your `settings.py`. The custom flag model must inherit from `waffle.models.AbstractBaseFlag` or `waffle.models.AbstractUserFlag`. - -Example: - -.. code-block:: python - - # settings.py - WAFFLE_FLAG_MODEL = 'myapp.Flag' - - # models.py - from waffle.models import AbstractUserFlag, CACHE_EMPTY - from waffle.utils import get_setting, keyfmt, get_cache - - class Flag(AbstractUserFlag): - FLAG_COMPANIES_CACHE_KEY = 'FLAG_COMPANIES_CACHE_KEY' - FLAG_COMPANIES_CACHE_KEY_DEFAULT = 'flag:%s:companies' - - companies = models.ManyToManyField( - Company, - blank=True, - help_text=_('Activate this flag for these companies.'), - ) - - def get_flush_keys(self, flush_keys=None): - flush_keys = super(Flag, self).get_flush_keys(flush_keys) - companies_cache_key = get_setting(Flag.FLAG_COMPANIES_CACHE_KEY, Flag.FLAG_COMPANIES_CACHE_KEY_DEFAULT) - flush_keys.append(keyfmt(companies_cache_key, self.name)) - return flush_keys - - def is_active_for_user(self, user): - is_active = super(Flag, self).is_active_for_user(user) - if is_active: - return is_active - - if getattr(user, 'company_id', None): - company_ids = self._get_company_ids() - if user.company_id in company_ids: - return True - - def _get_company_ids(self): - cache = get_cache() - cache_key = keyfmt( - get_setting(Flag.FLAG_COMPANIES_CACHE_KEY, Flag.FLAG_COMPANIES_CACHE_KEY_DEFAULT), - self.name - ) - cached = cache.get(cache_key) - if cached == CACHE_EMPTY: - return set() - if cached: - return cached - - company_ids = set(self.companies.all().values_list('pk', flat=True)) - if not company_ids: - cache.add(cache_key, CACHE_EMPTY) - return set() - - cache.add(cache_key, company_ids) - return company_ids - - # admin.py - from waffle.admin import FlagAdmin as WaffleFlagAdmin - - class FlagAdmin(WaffleFlagAdmin): - raw_id_fields = tuple(list(WaffleFlagAdmin.raw_id_fields) + ['companies']) - admin.site.register(Flag, FlagAdmin) - -To reference the custom flag model in your project, use the `get_waffle_flag_model` method. - -.. code-block:: python - - from waffle import get_waffle_flag_model - - Flag = get_waffle_flag_model() - - -Custom Switch Model -=================== - -To use a custom switch model, define a `WAFFLE_SWITCH_MODEL` setting in your `settings.py`. The custom switch model must inherit from `waffle.models.AbstractBaseSwitch`. - -Example: - -.. code-block:: python - - # settings.py - WAFFLE_SWITCH_MODEL = 'myapp.Switch' - - # models.py - from waffle.models import AbstractBaseSwitch - - class Switch(AbstractBaseSwitch): - - owner = models.CharField( - max_length=100, - blank=True, - help_text=_('The individual/team who owns this switch.'), - ) - - # admin.py - from waffle.admin import SwitchAdmin as WaffleSwitchAdmin - - class SwitchAdmin(WaffleSwitchAdmin): - raw_id_fields = tuple(list(WaffleSwitchAdmin.raw_id_fields) + ['owner']) - admin.site.register(Switch, SwitchAdmin) - -To reference the custom switch model in your project, use the `get_waffle_switch_model` method. - -.. code-block:: python - - from waffle import get_waffle_switch_model - - Switch = get_waffle_switch_model() - - -Custom Sample Model -=================== - -To use a custom sample model, define a `WAFFLE_SAMPLE_MODEL` setting in your `settings.py`. The custom sample model must inherit from `waffle.models.AbstractBaseSample`. - -Example: - -.. code-block:: python - - # settings.py - WAFFLE_SAMPLE_MODEL = 'myapp.Sample' - - # models.py - from waffle.models import AbstractBaseSample - - class Sample(AbstractBaseSample): - - owner = models.CharField( - max_length=100, - blank=True, - help_text=_('The individual/team who owns this sample.'), - ) - - # admin.py - from waffle.admin import SampleAdmin as WaffleSampleAdmin - - class SampleAdmin(WaffleSampleAdmin): - raw_id_fields = tuple(list(WaffleSampleAdmin.raw_id_fields) + ['owner']) - admin.site.register(Sample, SampleAdmin) - -To reference the custom sample model in your project, use the `get_waffle_sample_model` method. - -.. code-block:: python - - from waffle import get_waffle_sample_model - - Sample = get_waffle_sample_model()