from django.core.exceptions import ImproperlyConfigured
from django.db import models


class ModelFieldRegistry:
    """
    Handles the recurring pattern where we need to register different values for different
    model field types, and retrieve the one that most closely matches a given model field,
    according to its type (taking inheritance into account), and in the case of foreign keys,
    the type of the related model (again, taking inheritance into account).

    For example, this is used by wagtail.admin.forms.models when constructing model forms:
    we use such a registry to retrieve the appropriate dict of arguments to pass to the
    form field constructor. A lookup for a models.TextField will return a dict specifying a
    text area widget, and a lookup for a foreign key to Image will return a dict specifying
    an image chooser widget.
    """

    def __init__(self):
        # values in this dict will be returned if the field type exactly matches an item here
        self.values_by_exact_class = {}

        # values in this dict will be returned if any class in the field's inheritance chain
        # matches, preferring more specific subclasses
        self.values_by_class = {
            models.ForeignKey: self.foreign_key_lookup,
        }

        # values in this dict will be returned if the field is a foreign key to a related
        # model in here, matching most specific subclass first
        self.values_by_fk_related_model = {}

    def register(self, field_class, to=None, value=None, exact_class=False):
        if to:
            if field_class == models.ForeignKey:
                self.values_by_fk_related_model[to] = value
            else:
                raise ImproperlyConfigured(
                    "The 'to' argument on ModelFieldRegistry.register is only valid for ForeignKey fields"
                )
        elif exact_class:
            self.values_by_exact_class[field_class] = value
        else:
            self.values_by_class[field_class] = value

    def get(self, field):
        value = None
        if field.__class__ in self.values_by_exact_class:
            value = self.values_by_exact_class[field.__class__]
        else:
            for klass in field.__class__.mro():
                if klass in self.values_by_class:
                    value = self.values_by_class[klass]
                    break

        if callable(value) and not isinstance(value, type):
            value = value(field)

        return value

    def foreign_key_lookup(self, field):
        value = None
        target_model = field.remote_field.model

        for model in target_model.mro():
            if model in self.values_by_fk_related_model:
                value = self.values_by_fk_related_model[model]
                break

        if callable(value) and not isinstance(value, type):
            value = value(field)

        return value

@@ -86,34 +86,1281 @@
 %0A%0Aclass 
-ModelFieldRegistry
+ObjectTypeRegistry:%0A    %22%22%22%0A    Implements a lookup table for mapping objects to values according to the object type.%0A    The most specific type according to the object's inheritance chain is selected.%0A    %22%22%22%0A%0A    def __init__(self):%0A        # values in this dict will be returned if the field type exactly matches an item here%0A        self.values_by_exact_class = %7B%7D%0A%0A        # values in this dict will be returned if any class in the field's inheritance chain%0A        # matches, preferring more specific subclasses%0A        self.values_by_class = %7B%7D%0A%0A    def register(self, cls, value=None, exact_class=False):%0A        if exact_class:%0A            self.values_by_exact_class%5Bcls%5D = value%0A        else:%0A            self.values_by_class%5Bcls%5D = value%0A%0A    def get(self, obj):%0A        value = None%0A        if obj.__class__ in self.values_by_exact_class:%0A            value = self.values_by_exact_class%5Bobj.__class__%5D%0A        else:%0A            for klass in obj.__class__.mro():%0A                if klass in self.values_by_class:%0A                    value = self.values_by_class%5Bklass%5D%0A                    break%0A%0A        if callable(value) and not isinstance(value, type):%0A            value = value(obj)%0A%0A        return value%0A%0A%0Aclass ModelFieldRegistry(ObjectTypeRegistry)
 :%0A    %22%22
@@ -2151,282 +2151,26 @@
     
-# values in this dict will be returned if the field type exactly matches an item here%0A        self.values_by_exact_class = %7B%7D%0A%0A        # values in this dict will be returned if any class in the field's inheritance chain%0A        # matches, preferring more specific subclasses
+super().__init__()
 %0A   
@@ -2198,25 +2198,9 @@
 lass
- = %7B%0A            
+%5B
 mode
@@ -2212,17 +2212,19 @@
 reignKey
-:
+%5D =
  self.fo
@@ -2243,19 +2243,8 @@
 okup
-,%0A        %7D
 %0A%0A  
@@ -2838,30 +2838,18 @@
       el
-if exact_clas
 s
+e
 :%0A      
@@ -2859,602 +2859,74 @@
    s
-elf.values_by_exact_class%5Bfield_class%5D = value%0A        else:%0A            self.values_by_class%5Bfield_class%5D = value%0A%0A    def get(self, field):%0A        value = None%0A        if field.__class__ in self.values_by_exact_class:%0A            value = self.values_by_exact_class%5Bfield.__class__%5D%0A        else:%0A            for klass in field.__class__.mro():%0A                if klass in self.values_by_class:%0A                    value = self.values_by_class%5Bklass%5D%0A                    break%0A%0A        if callable(value) and not isinstance(value, type):%0A            value = value(field)%0A%0A        return value
+uper().register(field_class, value=value, exact_class=exact_class)
 %0A%0A  

Refactor out a general ObjectTypeRegistry from ModelFieldRegistry