Отображение 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
Проблема в том, что модель в админке отображается не так, как я хочу. Т.е. добавлять инстанции ExtraFields
надо на отдельной странице, а это неудобно. Хочется, чтобы их можно было создавать непосредственно на странице создания писателей или муз. групп, чтобы там было что-то вроде "Добавить поле". Но я понятия не имею, как редактировать код админки. Сейчас он такой:
from django.contrib import admin
from .menu_models import Menu, MenuCategory
from .models import OriginalText, Translation, TranslationRemark, \
PostComment, PostLike, Category, Band, Author, ExtraField
@admin.register(OriginalText)
class AdminOriginalText(admin.ModelAdmin):
prepopulated_fields = {'slug' : ('title', )}
@admin.register(Translation)
class AdminTranslation(admin.ModelAdmin):
prepopulated_fields = {'slug' : ('title', )}
@admin.register(Category)
class AdminCategory(admin.ModelAdmin):
prepopulated_fields = {'slug' : ('title', )}
@admin.register(Band)
class AdminBand(admin.ModelAdmin):
prepopulated_fields = {'slug' : ('name', )}
@admin.register(Author)
class AdminAuthor(admin.ModelAdmin):
prepopulated_fields = {'slug' : ('full_name', )}
admin.site.register(TranslationRemark)
admin.site.register(PostComment)
admin.site.register(PostLike)
admin.site.register(Menu)
admin.site.register(MenuCategory)
admin.site.register(ExtraField)
Вопрос в том, хорошая ли идея добавлять такие пользовательские поля через отдельную модель и как отображать ее на странице другой модели, чтобы в форме создания писателей, муз. групп (возможно, еще добавить такие поля и для модели текста, и для перевода) была также какая-нибудь форма "Добавить доп. поле"? Ибо создавать отдельно само поле и уже в нем через content_type
и object_id
задавать определенного автора / муз. группу / перевод вообще неудобно. Если моделей станет больше, то найти нужную будет нереально.
Надеюсь, я понятно объяснил. Заранее спасибо 🤝🏻