Skip to content

Commit

Permalink
Add support for global defaults
Browse files Browse the repository at this point in the history
resolves #108
  • Loading branch information
radiac committed Aug 27, 2024
1 parent 256bbe1 commit fc41447
Show file tree
Hide file tree
Showing 7 changed files with 92 additions and 6 deletions.
6 changes: 6 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ are available by installing the develop branch from github.
2.1.0, TBC
----------

Feature:

* Global defaults can be set with ``settings.TAGULOUS_DEFAULT_TAG_OPTIONS`` (#108)

Bugfix:

* Form field ``has_changed`` now correctly detects if a TagField has changed (#185)
Expand All @@ -38,6 +42,8 @@ Features:
Changes:

* Drop Django 2.2 support
* Drop Python 3.7 support - from now on Tagulous will only guarantee compatibility with
the latest versions of Python for supported Django versions.
* Documentation fixes (#154)
* Simplify steps for contributors (#166)
* Remove ``AppConfig.default_app_config``, deprecated in Django 3.2 (#169)
Expand Down
13 changes: 11 additions & 2 deletions docs/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,17 @@ Settings
Most projects won't need these settings, but they allow for finer control of how
Tagulous behaves.

.. note::
Model and form field options are managed separately by :doc:`tag_options`.
``TAGULOUS_DEFAULT_TAG_OPTIONS``
Global defaults for tag field options.

This will override the default tag options for every tag field on models and forms.

See :doc:`tag_options` for the list of tag field options and their defaults, and how
to override them on individual tag models and tag fields.

Default:

TAGULOUS_DEFAULT_TAG_OPTIONS = {}


``TAGULOUS_NAME_MAX_LENGTH``, ``TAGULOUS_SLUG_MAX_LENGTH``, ``TAGULOUS_LABEL_MAX_LENGTH``
Expand Down
3 changes: 2 additions & 1 deletion tagulous/models/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from django.utils.text import capfirst

from .. import constants
from .. import settings as tag_settings
from .descriptors import SingleTagDescriptor, TagDescriptor
from .models import BaseTagModel, TagModel, TagTreeModel
from .options import TagOptions
Expand Down Expand Up @@ -48,7 +49,7 @@ def __init__(self, to=None, to_base=None, **kwargs):

# Extract options from kwargs
options = {}
for key, default in constants.OPTION_DEFAULTS.items():
for key, default in tag_settings.DEFAULT_TAG_OPTIONS.items():
# Look in kwargs, then in tag_meta
if key in kwargs:
options[key] = kwargs.pop(key)
Expand Down
8 changes: 6 additions & 2 deletions tagulous/models/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"""

from .. import constants
from .. import settings as tag_settings
from ..utils import parse_tags, render_tags


Expand Down Expand Up @@ -90,7 +91,7 @@ def __getattr__(self, name):
return ""
if name not in constants.OPTION_DEFAULTS:
raise AttributeError(name)
return self.__dict__.get(name, constants.OPTION_DEFAULTS[name])
return self.__dict__.get(name, tag_settings.DEFAULT_TAG_OPTIONS[name])

def _get_items(self, with_defaults, keys):
"""
Expand All @@ -99,7 +100,10 @@ def _get_items(self, with_defaults, keys):
if with_defaults:
return dict(
[
(name, self.__dict__.get(name, constants.OPTION_DEFAULTS[name]))
(
name,
self.__dict__.get(name, tag_settings.DEFAULT_TAG_OPTIONS[name]),
)
for name in keys
]
)
Expand Down
16 changes: 16 additions & 0 deletions tagulous/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

from django.conf import settings

from .constants import OPTION_DEFAULTS

#
# Database control settings
#
Expand All @@ -22,6 +24,20 @@
SLUG_ALLOW_UNICODE = getattr(settings, "TAGULOUS_SLUG_ALLOW_UNICODE", False)


#
# Field settings
#

# Collect dict of default values, and use them to override the internal defaults
# Validate them against the internal defaults, as TagOptions would
DEFAULT_TAG_OPTIONS = {
**OPTION_DEFAULTS,
**getattr(settings, "TAGULOUS_DEFAULT_TAG_OPTIONS", {}),
}
if _unknown := set(DEFAULT_TAG_OPTIONS) - set(OPTION_DEFAULTS):
raise ValueError(f"Unexpected TAGULOUS_DEFAULT_TAG_OPTIONS: {', '.join(_unknown)}")


#
# Autocomplete settings
#
Expand Down
21 changes: 20 additions & 1 deletion tests/lib.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import importlib
import os
import re
import sys
import unittest
from contextlib import contextmanager
from io import StringIO

import django
from django.db import connection, models
from django.test import testcases
from django.test import override_settings, testcases

from tagulous import models as tag_models
from tagulous import settings as tag_settings

# Detect test environment
# This is used when creating files (migrations and fixtures) to ensure that
Expand Down Expand Up @@ -275,3 +278,19 @@ def tagfield_html(html: str) -> str:
f' aria-describedby="{match.group(1)}_helptext">',
)
return html


@contextmanager
def override_tag_settings(**options):
# Reload settings
context = override_settings(TAGULOUS_DEFAULT_TAG_OPTIONS=options)
context.__enter__()
importlib.reload(tag_settings)

try:
yield
finally:
context.__exit__(None, None, None)

# Reload settings
importlib.reload(tag_settings)
31 changes: 31 additions & 0 deletions tests/test_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
from tagulous import constants as tag_constants
from tagulous import models as tag_models

from .lib import override_tag_settings


class TagOptionsTest(TestCase):
"""
Expand All @@ -33,6 +35,35 @@ def test_defaults(self):
),
)

def test_global_defaults(self):
# Set some overrides
overrides = {
"initial": "override",
"protect_initial": False,
"force_lowercase": True,
"autocomplete_view": "/override/",
}
for key, value in overrides.items():
self.assertNotEqual(value, tag_constants.OPTION_DEFAULTS[key])

expected = {**tag_constants.OPTION_DEFAULTS, **overrides}

with override_tag_settings(**overrides):
opt = tag_models.TagOptions()
self.assertEqual(opt.items(with_defaults=False), {})
self.assertEqual(opt.items(), expected)
self.assertEqual(opt.form_items(with_defaults=False), {})
self.assertEqual(
opt.form_items(),
dict(
[
(k, v)
for k, v in expected.items()
if k in tag_constants.FORM_OPTIONS
]
),
)

def test_override_defaults(self):
opt = tag_models.TagOptions(force_lowercase=True, case_sensitive=True)
local_args = {"force_lowercase": True, "case_sensitive": True}
Expand Down

0 comments on commit fc41447

Please sign in to comment.