mod: модели (preliminary 04) + миграции

А) Удалена таблица TbFormat (форматы носителей) и заменена на CharField в TbOffer.

Б) Удалено поле s_img_copyright из модел TbImage (для этого будет использовано поле author из filer).
This commit is contained in:
2026-06-12 13:49:37 +03:00
parent 7f5ea8b567
commit bb915c82e1
3 changed files with 82 additions and 76 deletions

View File

@@ -9,7 +9,7 @@ from django.utils.html import format_html, mark_safe
from easy_thumbnails.files import get_thumbnailer
from .models import (
TbImage, TbArticle, TbArtist, TbItem, TbLabel, TbSeller,
TbOffer, TbSource, TbOfferHistory, TbMusicStyle, TbFormat
TbOffer, TbSource, TbOfferHistory, TbMusicStyle
)
# ============================================================================
@@ -283,12 +283,6 @@ class MusicStyleAdmin(admin.ModelAdmin):
readonly_fields = ('s_style_slug',)
class FormatAdmin(admin.ModelAdmin):
"""Админ для форматов"""
list_display = ('id', 's_format', 's_format_slug')
search_fields = ('s_format',)
readonly_fields = ('s_format_slug',)
class ArtistAdmin(admin.ModelAdmin):
"""Админ для артистов"""
@@ -332,9 +326,9 @@ class SourceAdmin(admin.ModelAdmin):
class OfferAdmin(admin.ModelAdmin):
"""Админ для предложений"""
list_display = ('id', 's_offer', 'k_offer_to_item', 'f_offer_price', 'i_offer_quantity', 'i_offer_views')
list_filter = ('l_offer_condition_media', 'l_offer_condition_sleeve', 't_offer_created')
list_filter = ('l_offer_condition_media', 'l_offer_condition_sleeve', 't_offer_created', 'l_offer_to_format')
search_fields = ('s_offer',)
filter_horizontal = ('k_offer_to_format', 'k_offer_to_image')
filter_horizontal = ('k_offer_to_image',)
readonly_fields = ('s_offer_skip32', 't_offer_created', 't_offer_updated', 'i_offer_views', 'i_offer_favorites')
@@ -351,7 +345,6 @@ class OfferHistoryAdmin(admin.ModelAdmin):
admin.site.register(TbImage, ImageAdmin)
admin.site.register(TbArticle, ArticleAdmin)
admin.site.register(TbMusicStyle, MusicStyleAdmin)
admin.site.register(TbFormat, FormatAdmin)
admin.site.register(TbArtist, ArtistAdmin)
admin.site.register(TbItem, ItemAdmin)
admin.site.register(TbLabel, LabelAdmin)

View File

@@ -0,0 +1,54 @@
# Generated by Django 6.0.5 on 2026-06-12 10:39
import django.db.models.deletion
import filer.fields.image
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('frontend', '0001_initial'),
migrations.swappable_dependency(settings.FILER_IMAGE_MODEL),
]
operations = [
migrations.RemoveField(
model_name='tboffer',
name='k_offer_to_format',
),
migrations.RemoveField(
model_name='tbimage',
name='s_img_copyright',
),
migrations.AddField(
model_name='tboffer',
name='l_offer_to_format',
field=models.CharField(choices=[('lp', 'Vinyl Long-Play (12")'), ('ep', 'Vinyl Extended-Play (12", 10", 7")'), ('45', 'Vinyl 7" (45 rpm)'), ('cd', 'Compact Disc'), ('ld', 'LaserDisc'), ('md', 'MiniDisc Record'), ('ms', 'Used MiniDisc (для записи)'), ('cs', 'Cassette Record'), ('uc', 'Used Cassette (для записи)'), ('tp', 'Tape Reel Record'), ('ur', 'Used Tape Reel (для записи)'), ('??', 'Other')], db_index=True, default='??', help_text='Форматы основного носителей (пластинка, CD, кассета и т.п.). Если несколько носителей (и разных), то это указывать в "Дополнительных данных" в JSON-формате. Например: <tt>{"formats": {"lp": 2, "cd": 1},}</tt>', max_length=2, verbose_name='Формат'),
),
migrations.AlterField(
model_name='tbimage',
name='f_img_confidence_score',
field=models.FloatField(blank=True, default=10.0, help_text='Уверенность (для автоматических данных) 0.0 - 10.0, насколько уверены, что это правильное изображение', null=True, verbose_name='Достоверность'),
),
migrations.AlterField(
model_name='tbimage',
name='i_img_sort',
field=models.IntegerField(db_index=True, default=0, help_text='Порядок отображения изображений. Чем меньше число, тем выше в списке. Можно использовать для указания обложки (0), задника (1) и т.д.', verbose_name='Сортировка'),
),
migrations.AlterField(
model_name='tbimage',
name='image',
field=filer.fields.image.FilerImageField(default=1, help_text='Файл изображения, загруженный через django_filer.', on_delete=django.db.models.deletion.DO_NOTHING, to=settings.FILER_IMAGE_MODEL, verbose_name='Файл изображения'),
preserve_default=False,
),
migrations.AlterField(
model_name='tbimage',
name='s_img_src_url',
field=models.URLField(blank=True, help_text='URL источника, если изображение взято (в том числе и парсером) из внешнего источника (например, Discogs)', null=True, verbose_name='URL'),
),
migrations.DeleteModel(
name='TbFormat',
),
]

View File

@@ -28,7 +28,6 @@
# │ l_img_reality │ Тип (real, abstract)
# │ i_img_sort │ Порядок отображения
# │ f_img_confidence_ │ Доверие данным (0-1)
# │ s_img_copyright │ Авторские права
# │ t_img_created │ Timestamp
# │ t_img_updated │ Timestamp
# │ │ ⬆ Индекс на: id (+), i_img_sort
@@ -95,16 +94,6 @@
# │ [промежуточная таблица: article_id, musicstyle_id]
#
#
# ┌──────────────────┐
# │ TbFormat │ Форматы носителей (LP, CD, Cassette...)
# ├──────────────────┼────────────────────────────────────────────────────────
# │ PK: id │ AutoField
# │ s_format │ Название (LP, CD, Blu-ray, Cassette...)
# │ s_format_slug │ SlugField(16) — уникальный
# └──────────────────┘
# △
# │ M2M TbOffer.k_offer_to_format
# │ [промежуточная таблица: offer_id, format_id]
#
#
# ##════════════════════════════════════════════════════════════════════════════##
@@ -148,11 +137,11 @@
# ├────────────────────────────────┼───────────────────────────────
# │ PK: id │ AutoField
# │ s_offer │ Название (indexed)
# │ l_offer_to_format │ Формат (CD, Vinyl, Digital...)
# │ k_offer_to_item │ FK → TbItem [indexed]
# │ k_offer_to_label │ FK → TbLabel [indexed]
# │ k_offer_to_source │ FK → TbSource [indexed]
# │ k_offer_to_article │ FK → TbArticle (опционально)
# │ k_offer_to_format │ M2M → TbFormat (может быть несколько)
# │ k_offer_to_image │ M2M → TbImage (несколько фото)
# │ l_offer_condition_media │ Состояние (s, m, nm, vg, g, f, p)
# │ l_offer_condition_sleeve │ Состояние (s, m, nm, vg, g, f, p)
@@ -318,15 +307,6 @@ class TbImage(models.Model):
verbose_name='Достоверность',
help_text='Уверенность (для автоматических данных) 0.0 - 10.0, насколько уверены, что это правильное изображение',
)
s_img_copyright = models.CharField(
# Авторские права и лицензия (по идее -- ненужное поле. Можно в filer использовать `obj.image.author`.
max_length=255,
blank=True,
default='',
editable=False, # Поле не редактируется. Кандидат на удаление.
verbose_name='Авторские права / Лицензия',
help_text='Например: "© 2024 User" или "CC-BY"',
)
t_img_created = models.DateTimeField(auto_now_add=True, verbose_name='Дата добавления',)
t_img_updated = models.DateTimeField(auto_now=True, verbose_name='Дата обновления',)
@@ -824,43 +804,6 @@ class TbSeller(models.Model):
ordering = ('s_seller',)
# ============================================================================
# ФОРМАТЫ НОСИТЕЛЕЙ
# ============================================================================
class TbFormat(models.Model):
"""
Формат носителя (LP, CD, Cassette и т.д.).
Используем SmallAutoField для оптимизации (макс ~32k).
Форматов обычно 50-100, поэтому 2 байта более чем достаточно.
"""
# Используем SmallAutoField для оптимизации
id = models.SmallAutoField(primary_key=True)
s_format = models.CharField(
max_length=16,
blank=False,
unique=True,
db_index=True,
verbose_name='Формат носителя',
help_text='Название формата носителя, например: "LP", "CD", "Blu-ray", "Compact Cassette", "MiniDisc",'
' "Hi-Fi", "Accessory" и т.д.',
)
s_format_slug = models.SlugField(
max_length=16,
blank=False,
unique=True,
verbose_name='Слаг',
)
def __str__(self):
return f"format: {self.s_format}"
class Meta:
verbose_name = 'Формат носителя'
verbose_name_plural = 'Форматы носителей'
ordering = ('s_format',)
# ============================================================================
# ПРЕДЛОЖЕНИЯ / ОФФЕРЫ
# ============================================================================
@@ -879,6 +822,20 @@ class TbOffer(models.Model):
P = 'p', 'Poor (плохое)'
OTHER = '??', 'Other'
class Format(models.TextChoices):
LP = 'lp', 'Vinyl Long-Play (12")'
EP = 'ep', 'Vinyl Extended-Play (12", 10", 7")'
V45 = '45', 'Vinyl 7" (45 rpm)'
CD = 'cd', 'Compact Disc'
LD = 'ld', 'LaserDisc'
REC_MD = 'md', 'MiniDisc Record'
USE_MD = 'ms', 'Used MiniDisc (для записи)'
REC_CS = 'cs', 'Cassette Record'
USE_CS = 'uc', 'Used Cassette (для записи)'
REC_RR = 'tp', 'Tape Reel Record'
USE_RR = 'ur', 'Used Tape Reel (для записи)'
OTHER = '??', 'Other'
s_offer = models.CharField(
max_length=128,
blank=False,
@@ -887,6 +844,16 @@ class TbOffer(models.Model):
help_text='Техническое название оффера для внутреннего использования, например:'
' "Abbey Road (LP) AnTrop NM/NM (МЗГ)" или "TDK CDing I 60 (б/у) VG/VG (Janan, 198x синяя-градиент)"'
)
l_offer_to_format = models.CharField(
max_length=2,
choices=Format.choices,
default=Format.OTHER,
db_index=True,
verbose_name='Формат',
help_text='Форматы основного носителей (пластинка, CD, кассета и т.п.). Если несколько носителей (и разных),'
' то это указывать в "Дополнительных данных" в JSON-формате. Например:'
' <tt>{"formats": {"lp": 2, "cd": 1},}</tt>',
)
# Связи
k_offer_to_article = models.ForeignKey(
TbArticle,
@@ -950,14 +917,6 @@ class TbOffer(models.Model):
verbose_name='Каталожный номер / Barcode',
help_text='Например: "SD 16023" или "5099923452355"',
)
k_offer_to_format = models.ManyToManyField(
TbFormat,
blank=True,
related_name='format_to_offer', # ← format.format_to_offers.all()
db_index=True, # Принудительно создаем индекс, т.к. SQLite их сам не создаст.
verbose_name='Форматы',
help_text='Форматы носителей (пластинка, CD, кассета и т.п.). Можно выбрать несколько.',
)
d_offer_date_release = models.DateField(
blank=True,
null=True,