""" These functions handle the adding of fields to other models. """ from typing import Optional, Type, Union import collections 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 ugettext_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, collections.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. """ import collections from django.apps import apps app_label = app if isinstance(field_definitions, str): field_definitions = [field_definitions] elif not isinstance(field_definitions, collections.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} )