""" These functions handle the adding of fields to other models. """ from typing import Optional, Type, Union from collections.abc import Iterable from django.core.exceptions import FieldDoesNotExist, ImproperlyConfigured from django.db.models import CASCADE, ForeignKey, ManyToManyField # from settings import self._field_registry, self._model_registry from django.utils.translation import gettext_lazy as _ from . import fields FIELD_TYPES = { "ForeignKey": ForeignKey, "ManyToManyField": ManyToManyField, } class Registry(object): """Keeps track of fields and models registered.""" def __init__(self): self._field_registry = {} self._model_registry = {} def register_model(self, app: str, model_name, field_type: str, field_definitions: Union[str, Iterable]): """ Registration process for Django 1.7+. Args: app: app name/label model_name: name of the model field_definitions: a string, tuple or list of field configurations field_type: either 'ForeignKey' or 'ManyToManyField' Raises: ImproperlyConfigured: For incorrect parameter types or missing model. """ from django.apps import apps app_label = app if isinstance(field_definitions, str): field_definitions = [field_definitions] elif not isinstance(field_definitions, Iterable): raise ImproperlyConfigured( _("Field configuration for %(app)s should be a string or iterable") % {"app": app} ) if field_type not in ("ForeignKey", "ManyToManyField"): raise ImproperlyConfigured(_('`field_type` must be either `"ForeignKey"` or `"ManyToManyField"`.')) try: if not hasattr(model_name, "_meta"): app_config = apps.get_app_config(app) app_label = app_config.label model = app_config.get_model(model_name) else: model = model_name model_name = model._meta.model_name opts = model._meta if app_label not in self._model_registry: self._model_registry[app_label] = [] if model not in self._model_registry[app_label]: self._model_registry[app_label].append(model) except LookupError: raise ImproperlyConfigured( 'Model "%(model)s" doesn\'t exist in app "%(app)s".' % {"model": model_name, "app": app} ) if not isinstance(field_definitions, (tuple, list)): field_definitions = [field_definitions] for fld in field_definitions: extra_params = {"to": "categories.Category", "blank": True} if field_type != "ManyToManyField": extra_params["on_delete"] = CASCADE extra_params["null"] = True if isinstance(fld, str): field_name = fld elif isinstance(fld, dict): if "name" in fld: field_name = fld.pop("name") else: continue extra_params.update(fld) else: raise ImproperlyConfigured( _("%(settings)s doesn't recognize the value of %(app)s.%(model)s") % {"settings": "CATEGORY_SETTINGS", "app": app, "model": model_name} ) registry_name = ".".join([app_label, model_name.lower(), field_name]) if registry_name in self._field_registry: continue try: opts.get_field(field_name) except FieldDoesNotExist: self._field_registry[registry_name] = FIELD_TYPES[field_type](**extra_params) self._field_registry[registry_name].contribute_to_class(model, field_name) def register_m2m(self, model, field_name: str = "categories", extra_params: Optional[dict] = None): """Register a field name to the model as a many to many field.""" extra_params = extra_params or {} return self._register(model, field_name, extra_params, fields.CategoryM2MField) def register_fk(self, model, field_name: str = "category", extra_params: Optional[dict] = None): """Register a field name to the model as a foreign key.""" extra_params = extra_params or {} return self._register(model, field_name, extra_params, fields.CategoryFKField) def _register( self, model, field_name: str, extra_params: Optional[dict] = None, field: Type = fields.CategoryFKField, ): """Does the heavy lifting for registering a field to a model.""" app_label = model._meta.app_label extra_params = extra_params or {} registry_name = ".".join((app_label, model.__name__, field_name)).lower() if registry_name in self._field_registry: return # raise AlreadyRegistered opts = model._meta try: opts.get_field(field_name) except FieldDoesNotExist: if app_label not in self._model_registry: self._model_registry[app_label] = [] if model not in self._model_registry[app_label]: self._model_registry[app_label].append(model) self._field_registry[registry_name] = field(**extra_params) self._field_registry[registry_name].contribute_to_class(model, field_name) registry = Registry() def _process_registry(registry, call_func): """ Given a dictionary, and a registration function, process the registry. """ from django.apps import apps from django.core.exceptions import ImproperlyConfigured for key, value in list(registry.items()): model = apps.get_model(*key.split(".")) if model is None: raise ImproperlyConfigured(_("%(key)s is not a model") % {"key": key}) if isinstance(value, (tuple, list)): for item in value: if isinstance(item, str): call_func(model, item) elif isinstance(item, dict): field_name = item.pop("name") call_func(model, field_name, extra_params=item) else: raise ImproperlyConfigured( _("%(settings)s doesn't recognize the value of %(key)s") % {"settings": "CATEGORY_SETTINGS", "key": key} ) elif isinstance(value, str): call_func(model, value) elif isinstance(value, dict): field_name = value.pop("name") call_func(model, field_name, extra_params=value) else: raise ImproperlyConfigured( _("%(settings)s doesn't recognize the value of %(key)s") % {"settings": "CATEGORY_SETTINGS", "key": key} )