Задать вопрос
@zven_bpe

Отображение GenericRelation в админ панели Django?

Пишу сайт переводов текстов произведений и песен на Python, использую фреймворк Django. Решил добавить, помимо моделей текста, перевода и пользователя, модели для муз. групп и писателей (Band и Author соответственно). Ввиду того, что это в первую очередь сайт переводов, не захотел загружать эти модели полями albums (альбомы для муз. групп) или birth_date (дата рождения для писателей), к тому же данная информация не всегда может быть определена точно и вообще нужна.

Но при этом мне захотелось добавить возможность создания пользовательских полей. Например, создаю поле для конкретной instance модели Band, даю название (например, "Количество альбомов") и значение (например, "10"). Таким образом можно создать абсолютно любое поле с любым названием и значением для любой модели (пусть для модели автора или музыкальной группы), а можно и не создавать, при этом не оставляя поля БД пустыми. Чтобы реализовать такую идею, создал в models.py еще одну модель ExtraField. Код для нее ниже:

class ExtraField(models.Model):
    '''Extra fields for different models.'''
    title          = models.CharField(verbose_name='Field title (displayed on website page)', max_length=50, blank=False, null=False)
    value          = models.CharField(verbose_name='Field value', max_length=100, blank=False, null=False)
    display        = models.BooleanField(verbose_name='Display it on website?', default=True, blank=True, null=False)
    content_type   = models.ForeignKey(ContentType, on_delete=models.CASCADE, verbose_name='Model instance reference', related_name='fields', limit_choices_to=Q(app_label='translations', model='originaltext') | Q(app_label='translations', model='translation') | Q(app_label='translations', model='band') | Q(app_label='translations', model='author'))
    object_id      = models.PositiveIntegerField(verbose_name='Real instance ID')
    content_object = GenericForeignKey('content_type', 'object_id')

Вот код всех остальных моделей:
from django.db import models
from django.db.models import Q
from django.contrib.auth import get_user_model
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation

from .config import LANGUAGES_MODEL_CHOICE, WORK_TYPE_MODEL_CHOICE, \
                    PROFICIENCY_LEVELS_MODEL_CHOICE


user = get_user_model()


class Post(models.Model):
    '''`Post` model. Used as a parent abstract model for models `OriginalText` and `Translation`.'''
    title           = models.CharField(verbose_name='Title', max_length=100, blank=False, null=False)
    slug            = models.SlugField(verbose_name='Slug', max_length=150, unique=True, null=False)
    content         = models.TextField(verbose_name='Content', blank=False, null=False)
    language_code   = models.IntegerField(verbose_name='Language ID', choices=LANGUAGES_MODEL_CHOICE, blank=False, null=False)
    posted_at       = models.DateTimeField(verbose_name='Posted at', auto_now_add=True, editable=False)
    last_upd        = models.DateTimeField(verbose_name='Last updated', auto_now=True, editable=False)
    user            = models.ForeignKey(user, on_delete=models.CASCADE, verbose_name='User instance', related_name='%(class)s_related')
    verified        = models.BooleanField(verbose_name='Is verified', blank=False, null=False, default=False)
    proficiency_lvl = models.IntegerField(verbose_name='Proficiency level ID', choices=PROFICIENCY_LEVELS_MODEL_CHOICE, blank=False, null=False)
    suggested_lvl   = models.IntegerField(verbose_name='AI-suggested proficiency level ID', choices=PROFICIENCY_LEVELS_MODEL_CHOICE, null=True, default=None)
    fields          = GenericRelation('ExtraField')

    class Meta:
        abstract = True

    def __str__(self) -> str:
        return self.title


class OriginalText(Post):
    '''`OriginalText` model.'''
    work_type      = models.IntegerField(verbose_name='Work type (prose/poetry)', choices=WORK_TYPE_MODEL_CHOICE, blank=False, null=False)
    comments       = GenericRelation('PostComment')
    likes          = GenericRelation('PostLike')
    content_type   = models.ForeignKey(ContentType, on_delete=models.CASCADE, verbose_name='Text author', related_name='texts', null=True)
    object_id      = models.PositiveIntegerField(verbose_name='Real instance ID')
    content_object = GenericForeignKey('content_type', 'object_id')


class Translation(Post):
    '''`Translation` model.'''
    original  = models.ForeignKey(OriginalText, on_delete=models.CASCADE, verbose_name='Original text instance')
    is_rhymed = models.BooleanField(verbose_name='Poetic translation (for poems only)', null=True, default=None)
    comments  = GenericRelation('PostComment')
    likes     = GenericRelation('PostLike')


class TranslationRemark(models.Model):
    '''`TranslationRemark` model.'''
    translation = models.ForeignKey(Translation, on_delete=models.CASCADE, verbose_name='Translation instance')
    remark_text = models.CharField(verbose_name='Remark content', max_length=250, blank=False, null=False)
    digit_start = models.IntegerField(verbose_name='Start digit', blank=False, null=False)
    digit_end   = models.IntegerField(verbose_name='End digit', blank=False, null=False)


class PostComment(models.Model):
    '''`PostComment` model.'''
    content_type   = models.ForeignKey(ContentType, on_delete=models.CASCADE, verbose_name='Model instance reference', related_name='comments', limit_choices_to=Q(app_label='translations', model='originaltext') | Q(app_label='translations', model='translation'))
    object_id      = models.PositiveIntegerField(verbose_name='Real instance ID')
    content_object = GenericForeignKey('content_type', 'object_id')
    comment_text   = models.TextField(verbose_name='Comment content', blank=False, null=False)
    posted_at      = models.DateTimeField(verbose_name='Posted at', auto_now_add=True, editable=False)
    answer_to      = models.ForeignKey('self', on_delete=models.SET_NULL, verbose_name='Answer to comment instance', blank=True, null=True)
    author         = models.ForeignKey(user, on_delete=models.CASCADE, verbose_name='User instance')


class PostLike(models.Model):
    '''`PostLike` model.'''
    content_type   = models.ForeignKey(ContentType, on_delete=models.CASCADE, verbose_name='Model instance reference', related_name='likes')
    object_id      = models.PositiveIntegerField(verbose_name='Real instance ID')
    content_object = GenericForeignKey('content_type', 'object_id')
    author         = models.ForeignKey(user, on_delete=models.CASCADE, verbose_name='User instance')


class Category(models.Model):
    '''`Category` model.'''
    title          = models.CharField(verbose_name='Title', max_length=30, blank=False, null=False, unique=True)
    slug           = models.SlugField(verbose_name='Slug', max_length=40, unique=True, null=False)
    content_type   = models.ForeignKey(ContentType, on_delete=models.CASCADE, verbose_name='Model instance reference', related_name='categories', limit_choices_to=Q(app_label='translations', model='originaltext') | Q(app_label='translations', model='translation'))
    object_id      = models.PositiveIntegerField(verbose_name='Real instance ID')
    content_object = GenericForeignKey('content_type', 'object_id')

    class Meta:
        verbose_name_plural = 'Categories'

    def __str__(self) -> str:
        return f'{self.title} - {self.content_type}'


class Band(models.Model):
    '''`Band` model.'''
    image  = models.ImageField(verbose_name='image', upload_to='translations/static/images/bands', null=True, blank=True)
    name   = models.CharField(verbose_name='Title', max_length=100, blank=False, null=False, unique=True)
    slug   = models.SlugField(verbose_name='Slug', max_length=100, unique=True, null=False)
    desc   = models.CharField(verbose_name='Description', max_length=300, blank=False, null=False)
    texts  = GenericRelation('OriginalText')
    fields = GenericRelation('ExtraField')

    def __str__(self) -> str:
        return self.name


class Author(models.Model):
    '''`Author` model.'''
    image     = models.ImageField(verbose_name='image', upload_to='translations/static/images/authors', null=True, blank=True)
    full_name = models.CharField(verbose_name='Title', max_length=100, blank=False, null=False)
    slug      = models.SlugField(verbose_name='Slug', max_length=100, unique=True, null=False)
    desc      = models.CharField(verbose_name='Description', max_length=300, blank=False, null=False)
    texts     = GenericRelation('OriginalText')
    fields    = GenericRelation('ExtraField')

    def __str__(self) -> str:
        return self.full_name


Проблема в том, что модель в админке отображается не так, как я хочу. Т.е. добавлять инстанции ExtraField надо на отдельной странице, а это неудобно. Хочется, чтобы их можно было создавать непосредственно на странице создания писателей или муз. групп, чтобы там было что-то вроде "Добавить поле". Но я понятия не имею, как редактировать код админки.

Вопрос в том, хорошая ли идея добавлять такие пользовательские поля через отдельную модель и как отображать ее на странице другой модели, чтобы в форме создания писателей, муз. групп (возможно, еще добавить такие поля и для модели текста, и для перевода) была также какая-нибудь форма "Добавить доп. поле"? Ибо создавать отдельно само поле и уже в нем через content_type и object_id задавать определенного автора / муз. группу / перевод вообще неудобно. Если моделей станет больше, то найти нужную будет нереально.
Надеюсь, я понятно объяснил. Заранее спасибо
  • Вопрос задан
  • 26 просмотров
Подписаться 1 Простой Комментировать
Пригласить эксперта
Ответы на вопрос 1
@Everything_is_bad
Ты зачем-то сделал какую-то дичь, GenericRelation тут не нужен, сядь и больше продумай про связи.
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы