from django.core.exceptions import FieldDoesNotExist from django.apps import apps # import the logging library import warnings import logging import collections import persisting_theory # Get an instance of a logger logger = logging.getLogger(__name__) #: The package where autodiscover will try to find preferences to register from .managers import PreferencesManager from .settings import preferences_settings from .exceptions import NotFoundInRegistry from .types import StringPreference from .preferences import EMPTY_SECTION, Section class MissingPreference(StringPreference): """ Used as a fallback when the preference object is not found in registries This can happen for example when you delete a preference in the code, but don't remove the corresponding entries in database """ pass class PreferenceModelsRegistry(persisting_theory.Registry): """Store relationships beetween preferences model and preferences registry""" look_into = preferences_settings.REGISTRY_MODULE def register(self, preference_model, preference_registry): self[preference_model] = preference_registry preference_registry.preference_model = preference_model if not hasattr(preference_model, "registry"): setattr(preference_model, "registry", preference_registry) self.attach_manager(preference_model, preference_registry) def attach_manager(self, model, registry): if not hasattr(model, "instance"): return def instance_getter(self): return registry.manager(instance=self) getter = property(instance_getter) instance_class = model._meta.get_field("instance").remote_field.model setattr(instance_class, preferences_settings.MANAGER_ATTRIBUTE, getter) def get_by_preference(self, preference): return self[ preference._meta.proxy_for_model if preference._meta.proxy else preference.__class__ ] def get_by_instance(self, instance): """Return a preference registry using a model instance""" # we iterate through registered preference models in order to get the instance class # and check if instance is an instance of this class for model, registry in self.items(): try: instance_class = model._meta.get_field("instance").remote_field.model if isinstance(instance, instance_class): return registry except FieldDoesNotExist: # global preferences pass return None preference_models = PreferenceModelsRegistry() class PreferenceRegistry(persisting_theory.Registry): """ Registries are special dictionaries that are used by dynamic-preferences to register and access your preferences. dynamic-preferences has one registry per Preference type: - :py:const:`user_preferences` - :py:const:`site_preferences` - :py:const:`global_preferences` In order to register preferences automatically, you must call :py:func:`autodiscover` in your URLconf. """ look_into = preferences_settings.REGISTRY_MODULE #: a name to identify the registry name = "preferences_registry" preference_model = None #: used to reverse urls for sections in form views/templates section_url_namespace = None def __init__(self, *args, **kwargs): super(PreferenceRegistry, self).__init__(*args, **kwargs) self.section_objects = collections.OrderedDict() def register(self, preference_class): """ Store the given preference class in the registry. :param preference_class: a :py:class:`prefs.Preference` subclass """ preference = preference_class(registry=self) self.section_objects[preference.section.name] = preference.section try: self[preference.section.name][preference.name] = preference except KeyError: self[preference.section.name] = collections.OrderedDict() self[preference.section.name][preference.name] = preference return preference_class def _fallback(self, section_name, pref_name): """ Create a fallback preference object, This is used when you have model instances that do not match any registered preferences, see #41 """ message = ( "Creating a fallback preference with " + 'section "{}" and name "{}".' + "This means you have preferences in your database that " + "don't match any registered preference. " + "If you want to delete these entries, please refer to the " + "documentation: https://django-dynamic-preferences.readthedocs.io/en/latest/lifecycle.html" ) # NOQA warnings.warn(message.format(section_name, pref_name)) class Fallback(MissingPreference): section = Section(name=section_name) if section_name else None name = pref_name default = "" help_text = "Obsolete: missing in registry" return Fallback() def get(self, name, section=None, fallback=False): """ Returns a previously registered preference :param section: The section name under which the preference is registered :type section: str. :param name: The name of the preference. You can use dotted notation 'section.name' if you want to avoid providing section param :type name: str. :param fallback: Should we return a dummy preference object instead of raising an error if no preference is found? :type name: bool. :return: a :py:class:`prefs.BasePreference` instance """ # try dotted notation try: _section, name = name.split(preferences_settings.SECTION_KEY_SEPARATOR) return self[_section][name] except ValueError: pass # use standard params try: return self[section][name] except KeyError: if fallback: return self._fallback(section_name=section, pref_name=name) raise NotFoundInRegistry( "No such preference in {0} with section={1} and name={2}".format( self.__class__.__name__, section, name ) ) def get_by_name(self, name): """Get a preference by name only (no section)""" for section in self.values(): for preference in section.values(): if preference.name == name: return preference raise NotFoundInRegistry( "No such preference in {0} with name={1}".format( self.__class__.__name__, name ) ) def manager(self, **kwargs): """Return a preference manager that can be used to retrieve preference values""" return PreferencesManager(registry=self, model=self.preference_model, **kwargs) def sections(self): """ :return: a list of apps with registered preferences :rtype: list """ return self.keys() def preferences(self, section=None): """ Return a list of all registered preferences or a list of preferences registered for a given section :param section: The section name under which the preference is registered :type section: str. :return: a list of :py:class:`prefs.BasePreference` instances """ if section is None: return [self[section][name] for section in self for name in self[section]] else: return [self[section][name] for name in self[section]] class PerInstancePreferenceRegistry(PreferenceRegistry): pass class GlobalPreferenceRegistry(PreferenceRegistry): section_url_namespace = "dynamic_preferences:global.section" def populate(self, **kwargs): return self.models(**kwargs) global_preferences_registry = GlobalPreferenceRegistry()