Django autogenerated models and relationships doesn't show up in model meta

I have the below code to create a Vote model for any given other model that I want to attach this relation to.

from functools import lru_cache

from django.contrib.auth import get_user_model
from django.db import models
from django.utils.translation import gettext_lazy as _

from apps.common.models.base import BaseModel
from apps.common.utils import camel_to_snake


@lru_cache(maxsize=None)
def create_vote_class(klass):
    class Vote(BaseModel):
        class VoteType(models.IntegerChoices):
            UPVOTE = 1, _("Upvote")
            DOWNVOTE = -1, _("Downvote")

        user = models.ForeignKey(
            get_user_model(),
            on_delete=models.CASCADE,
            related_name=f"{camel_to_snake(klass.__name__)}_votes",
            verbose_name=_("User"),
            help_text=_("The voter."),
        )
        post = models.ForeignKey(
            klass,
            on_delete=models.CASCADE,
            related_name=f"{camel_to_snake(klass.__name__)}_votes",
            verbose_name=_("Post"),
            help_text=_("The post this vote is for."),
        )
        body = models.IntegerField(
            choices=VoteType.choices,
            verbose_name=_("Body"),
            help_text=_("The actual vote. -1 or +1"),
        )

        def type(self):
            return self.get_body_display()

        class Meta:
            abstract = True
            app_label = klass._meta.app_label  # NOQA

    klass_name = f"{klass.__name__}Vote"
    bases = (Vote,)

    class Meta:
        app_label = klass._meta.app_label  # NOQA
        verbose_name = _(klass_name)
        verbose_name_plural = _(f"{klass_name}s")

    klass_dict = {"__module__": klass.__module__, "Meta": Meta}
    vote_class = (type(klass_name, bases, klass_dict)  # NOQA
    return vote_class


class VoteMaker:
    is_relation = True
    many_to_many = True

    def __init__(self, **kwargs):
        self.related_name = kwargs.get("related_name")
        self.vote_class_attribute_name = kwargs.get("vote_class_attribute_name")

    def contribute_to_class(self, cls, name):
        setattr(cls, self.vote_class_attribute_name, create_vote_class(cls))
        setattr(cls, name, self.get_votes_field(cls))

    def get_votes_field(self, cls):
        field = models.ManyToManyField(
            get_user_model(),
            through=create_vote_class(cls),
            related_name=self.related_name or f"voted_{camel_to_snake(cls.__name__)}s",
            verbose_name=_("Votes"),
            help_text=_("Votes for this post."),
        )
        return field


def votes(related_name=None, vote_class_attribute_name="vote_class"):
    vote_maker = VoteMaker(
        related_name=related_name,
        vote_class_attribute_name=vote_class_attribute_name,
    )
    return vote_maker

I'm using it like this:

class Post(BaseModel):
    votes = votes()

The Vote model is being generated, the related names and attribute names on the models are correct, I can query the Vote model, everything good there.

But now when I try to create an admin page for this Post model:

@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
    fields = ["votes"]

I'm getting an error:

Unknown field(s) (votes) specified for Post. Check fields/fieldsets/exclude attributes of class PostAdmin.

I've tried to dig deep into the source code but so far I only got to the

model._meta._get_fields(reverse=False) 

being called, which should have my m2m field, votes, but doesn't.

Anyone can point me to the right direction on how to solve this issue?

Edit 1:

Extra:

In [1]: from apps.core.models import *

In [2]: Post.votes
Out[2]: <django.db.models.fields.related.ManyToManyField>

In [3]: 

As you can see, Post.votes is indeed a ManyToMany field

Edit 2:

Moving forward I discovered that it shouldn't look like in Edit 1 above.

For example, in user model:

ipdb> User.groups
<django.db.models.fields.related_descriptors.ManyToManyDescriptor object at 0x1334e5a90>

So I have changed my original code slightly and now it looks like this:

def contribute_to_class(self, cls, name):
    setattr(cls, self.vote_class_attribute_name, create_vote_class(cls))
    self.get_votes_field(cls).contribute_to_class(cls, name)

which helped with some of the problem.

ipdb> Post._meta.many_to_many
(<django.db.models.fields.related.ManyToManyField: comments>, <django.db.models.fields.related.ManyToManyField: votes>)

Now I can see the many to many fields I have, but now I am still having some issues in other parts of django, still related to the admin.

The full traceback:

Internal Server Error: /core/post/add/
Traceback (most recent call last):
  File "/Users/isikkaplan/Desktop/code/personal/wsd/wsd/venv/lib/python3.8/site-packages/django/forms/forms.py", line 183, in __getitem__
    field = self.fields[name]
KeyError: 'votes'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/isikkaplan/Desktop/code/personal/wsd/wsd/venv/lib/python3.8/site-packages/django/core/handlers/exception.py", line 55, in inner
    response = get_response(request)
  File "/Users/isikkaplan/Desktop/code/personal/wsd/wsd/venv/lib/python3.8/site-packages/django/core/handlers/base.py", line 220, in _get_response
    response = response.render()
  File "/Users/isikkaplan/Desktop/code/personal/wsd/wsd/venv/lib/python3.8/site-packages/django/template/response.py", line 114, in render
    self.content = self.rendered_content
  File "/Users/isikkaplan/Desktop/code/personal/wsd/wsd/venv/lib/python3.8/site-packages/django/template/response.py", line 92, in rendered_content
    return template.render(context, self._request)
  File "/Users/isikkaplan/Desktop/code/personal/wsd/wsd/venv/lib/python3.8/site-packages/django/template/backends/django.py", line 62, in render
    return self.template.render(context)
  File "/Users/isikkaplan/Desktop/code/personal/wsd/wsd/venv/lib/python3.8/site-packages/django/template/base.py", line 175, in render
    return self._render(context)
  File "/Users/isikkaplan/Desktop/code/personal/wsd/wsd/venv/lib/python3.8/site-packages/django/template/base.py", line 167, in _render
    return self.nodelist.render(context)
  File "/Users/isikkaplan/Desktop/code/personal/wsd/wsd/venv/lib/python3.8/site-packages/django/template/base.py", line 1005, in render
    return SafeString("".join([node.render_annotated(context) for node in self]))
  File "/Users/isikkaplan/Desktop/code/personal/wsd/wsd/venv/lib/python3.8/site-packages/django/template/base.py", line 1005, in <listcomp>
    return SafeString("".join([node.render_annotated(context) for node in self]))
  File "/Users/isikkaplan/Desktop/code/personal/wsd/wsd/venv/lib/python3.8/site-packages/django/template/base.py", line 966, in render_annotated
    return self.render(context)
  File "/Users/isikkaplan/Desktop/code/personal/wsd/wsd/venv/lib/python3.8/site-packages/django/template/loader_tags.py", line 157, in render
    return compiled_parent._render(context)
  File "/Users/isikkaplan/Desktop/code/personal/wsd/wsd/venv/lib/python3.8/site-packages/django/template/base.py", line 167, in _render
    return self.nodelist.render(context)
  File "/Users/isikkaplan/Desktop/code/personal/wsd/wsd/venv/lib/python3.8/site-packages/django/template/base.py", line 1005, in render
    return SafeString("".join([node.render_annotated(context) for node in self]))
  File "/Users/isikkaplan/Desktop/code/personal/wsd/wsd/venv/lib/python3.8/site-packages/django/template/base.py", line 1005, in <listcomp>
    return SafeString("".join([node.render_annotated(context) for node in self]))
  File "/Users/isikkaplan/Desktop/code/personal/wsd/wsd/venv/lib/python3.8/site-packages/django/template/base.py", line 966, in render_annotated
    return self.render(context)
  File "/Users/isikkaplan/Desktop/code/personal/wsd/wsd/venv/lib/python3.8/site-packages/django/template/loader_tags.py", line 157, in render
    return compiled_parent._render(context)
  File "/Users/isikkaplan/Desktop/code/personal/wsd/wsd/venv/lib/python3.8/site-packages/django/template/base.py", line 167, in _render
    return self.nodelist.render(context)
  File "/Users/isikkaplan/Desktop/code/personal/wsd/wsd/venv/lib/python3.8/site-packages/django/template/base.py", line 1005, in render
    return SafeString("".join([node.render_annotated(context) for node in self]))
  File "/Users/isikkaplan/Desktop/code/personal/wsd/wsd/venv/lib/python3.8/site-packages/django/template/base.py", line 1005, in <listcomp>
    return SafeString("".join([node.render_annotated(context) for node in self]))
  File "/Users/isikkaplan/Desktop/code/personal/wsd/wsd/venv/lib/python3.8/site-packages/django/template/base.py", line 966, in render_annotated
    return self.render(context)
  File "/Users/isikkaplan/Desktop/code/personal/wsd/wsd/venv/lib/python3.8/site-packages/django/template/loader_tags.py", line 63, in render
    result = block.nodelist.render(context)
  File "/Users/isikkaplan/Desktop/code/personal/wsd/wsd/venv/lib/python3.8/site-packages/django/template/base.py", line 1005, in render
    return SafeString("".join([node.render_annotated(context) for node in self]))
  File "/Users/isikkaplan/Desktop/code/personal/wsd/wsd/venv/lib/python3.8/site-packages/django/template/base.py", line 1005, in <listcomp>
    return SafeString("".join([node.render_annotated(context) for node in self]))
  File "/Users/isikkaplan/Desktop/code/personal/wsd/wsd/venv/lib/python3.8/site-packages/django/template/base.py", line 966, in render_annotated
    return self.render(context)
  File "/Users/isikkaplan/Desktop/code/personal/wsd/wsd/venv/lib/python3.8/site-packages/django/template/loader_tags.py", line 63, in render
    result = block.nodelist.render(context)
  File "/Users/isikkaplan/Desktop/code/personal/wsd/wsd/venv/lib/python3.8/site-packages/django/template/base.py", line 1005, in render
    return SafeString("".join([node.render_annotated(context) for node in self]))
  File "/Users/isikkaplan/Desktop/code/personal/wsd/wsd/venv/lib/python3.8/site-packages/django/template/base.py", line 1005, in <listcomp>
    return SafeString("".join([node.render_annotated(context) for node in self]))
  File "/Users/isikkaplan/Desktop/code/personal/wsd/wsd/venv/lib/python3.8/site-packages/django/template/base.py", line 966, in render_annotated
    return self.render(context)
  File "/Users/isikkaplan/Desktop/code/personal/wsd/wsd/venv/lib/python3.8/site-packages/django/template/defaulttags.py", line 238, in render
    nodelist.append(node.render_annotated(context))
  File "/Users/isikkaplan/Desktop/code/personal/wsd/wsd/venv/lib/python3.8/site-packages/django/template/base.py", line 966, in render_annotated
    return self.render(context)
  File "/Users/isikkaplan/Desktop/code/personal/wsd/wsd/venv/lib/python3.8/site-packages/django/template/loader_tags.py", line 208, in render
    return template.render(context)
  File "/Users/isikkaplan/Desktop/code/personal/wsd/wsd/venv/lib/python3.8/site-packages/django/template/base.py", line 177, in render
    return self._render(context)
  File "/Users/isikkaplan/Desktop/code/personal/wsd/wsd/venv/lib/python3.8/site-packages/django/template/base.py", line 167, in _render
    return self.nodelist.render(context)
  File "/Users/isikkaplan/Desktop/code/personal/wsd/wsd/venv/lib/python3.8/site-packages/django/template/base.py", line 1005, in render
    return SafeString("".join([node.render_annotated(context) for node in self]))
  File "/Users/isikkaplan/Desktop/code/personal/wsd/wsd/venv/lib/python3.8/site-packages/django/template/base.py", line 1005, in <listcomp>
    return SafeString("".join([node.render_annotated(context) for node in self]))
  File "/Users/isikkaplan/Desktop/code/personal/wsd/wsd/venv/lib/python3.8/site-packages/django/template/base.py", line 966, in render_annotated
    return self.render(context)
  File "/Users/isikkaplan/Desktop/code/personal/wsd/wsd/venv/lib/python3.8/site-packages/django/template/defaulttags.py", line 238, in render
    nodelist.append(node.render_annotated(context))
  File "/Users/isikkaplan/Desktop/code/personal/wsd/wsd/venv/lib/python3.8/site-packages/django/template/base.py", line 966, in render_annotated
    return self.render(context)
  File "/Users/isikkaplan/Desktop/code/personal/wsd/wsd/venv/lib/python3.8/site-packages/django/template/defaulttags.py", line 193, in render
    values = list(values)
  File "/Users/isikkaplan/Desktop/code/personal/wsd/wsd/venv/lib/python3.8/site-packages/django/contrib/admin/helpers.py", line 156, in __iter__
    yield AdminField(self.form, field, is_first=(i == 0))
  File "/Users/isikkaplan/Desktop/code/personal/wsd/wsd/venv/lib/python3.8/site-packages/django/contrib/admin/helpers.py", line 170, in __init__
    self.field = form[field]  # A django.forms.BoundField instance
  File "/Users/isikkaplan/Desktop/code/personal/wsd/wsd/venv/lib/python3.8/site-packages/django/forms/forms.py", line 185, in __getitem__
    raise KeyError(
KeyError: "Key 'votes' not found in 'PostForm'. Choices are: image, title, user."
Back to Top