Complete replacement of Thing types with tag system
This commit is contained in:
@@ -5,14 +5,6 @@ import mptt.fields
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
def rebuild_tree(apps, schema_editor):
|
||||
"""Rebuild MPTT tree after adding fields."""
|
||||
ThingType = apps.get_model('boxes', 'ThingType')
|
||||
# Import the actual model to use rebuild
|
||||
from boxes.models import ThingType as RealThingType
|
||||
RealThingType.objects.rebuild()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
@@ -20,49 +12,4 @@ class Migration(migrations.Migration):
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='thingtype',
|
||||
name='parent',
|
||||
field=mptt.fields.TreeForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name='children',
|
||||
to='boxes.thingtype'
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='thingtype',
|
||||
name='level',
|
||||
field=models.PositiveIntegerField(default=0, editable=False),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='thingtype',
|
||||
name='lft',
|
||||
field=models.PositiveIntegerField(default=0, editable=False),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='thingtype',
|
||||
name='rght',
|
||||
field=models.PositiveIntegerField(default=0, editable=False),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='thingtype',
|
||||
name='tree_id',
|
||||
field=models.PositiveIntegerField(db_index=True, default=0, editable=False),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='thingtype',
|
||||
options={},
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='thingtype',
|
||||
name='name',
|
||||
field=models.CharField(max_length=255),
|
||||
),
|
||||
migrations.RunPython(rebuild_tree, migrations.RunPython.noop),
|
||||
]
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
# Generated by Django 5.2.9 on 2026-01-02 16:21
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('boxes', '0005_thingfile_thinglink'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Tag',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=100, unique=True)),
|
||||
('slug', models.SlugField(max_length=100, unique=True)),
|
||||
],
|
||||
options={
|
||||
'ordering': ['name'],
|
||||
},
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='thing',
|
||||
name='thing_type',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='things', to='boxes.thingtype'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='thing',
|
||||
name='tags',
|
||||
field=models.ManyToManyField(blank=True, related_name='things', to='boxes.tag'),
|
||||
),
|
||||
]
|
||||
18
boxes/migrations/0007_tag_color.py
Normal file
18
boxes/migrations/0007_tag_color.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.2.9 on 2026-01-02 16:31
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('boxes', '0006_tag_alter_thing_thing_type_thing_tags'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='tag',
|
||||
name='color',
|
||||
field=models.CharField(default='#667eea', help_text='Hex color code (e.g., #667eea)', max_length=7),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,53 @@
|
||||
# Generated by Django 5.2.9 on 2026-01-02 16:44
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('boxes', '0007_tag_color'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Facet',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=100, unique=True)),
|
||||
('slug', models.SlugField(max_length=100, unique=True)),
|
||||
('color', models.CharField(default='#667eea', help_text='Hex color code (e.g., #667eea)', max_length=7)),
|
||||
('cardinality', models.CharField(choices=[('single', 'Single (0..1)'), ('multiple', 'Multiple (0..n)')], default='multiple', help_text='Can a thing have multiple tags of this facet?', max_length=10)),
|
||||
],
|
||||
options={
|
||||
'ordering': ['name'],
|
||||
},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='tag',
|
||||
options={'ordering': ['facet', 'name']},
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='tag',
|
||||
name='name',
|
||||
field=models.CharField(help_text='Tag description (e.g., "High", "Electronics")', max_length=100),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='tag',
|
||||
name='facet',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='tags', to='boxes.facet'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='tag',
|
||||
unique_together={('facet', 'name')},
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='tag',
|
||||
name='color',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='tag',
|
||||
name='slug',
|
||||
),
|
||||
]
|
||||
75
boxes/migrations/0009_migrate_tags_to_facets.py
Normal file
75
boxes/migrations/0009_migrate_tags_to_facets.py
Normal file
@@ -0,0 +1,75 @@
|
||||
# Generated by Django 5.2.9 on 2026-01-02 16:44
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def migrate_tags_to_facets(apps, schema_editor):
|
||||
"""Migrate existing tags to facet-based system."""
|
||||
Tag = apps.get_model('boxes', 'Tag')
|
||||
Facet = apps.get_model('boxes', 'Facet')
|
||||
Thing = apps.get_model('boxes', 'Thing')
|
||||
|
||||
# Store old tag data with colors from dump file
|
||||
tag_colors = {}
|
||||
try:
|
||||
with open('/tmp/tags_dump.txt', 'r') as f:
|
||||
for line in f:
|
||||
tag_id, name, slug, color = line.strip().split(',')
|
||||
tag_colors[int(tag_id)] = color
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
# Parse tags and create facets
|
||||
facets = {}
|
||||
old_tags = list(Tag.objects.all())
|
||||
for old_tag in old_tags:
|
||||
tag_id = old_tag.id
|
||||
name = old_tag.name
|
||||
color = tag_colors.get(tag_id, '#667eea')
|
||||
|
||||
# Check if tag uses "Facet:Description" format
|
||||
if ':' in name:
|
||||
facet_name, tag_description = name.split(':', 1)
|
||||
facet_name = facet_name.strip()
|
||||
tag_description = tag_description.strip()
|
||||
else:
|
||||
# Simple tags go to "General" facet
|
||||
facet_name = 'General'
|
||||
tag_description = name
|
||||
|
||||
# Get or create facet
|
||||
if facet_name not in facets:
|
||||
facet, created = Facet.objects.get_or_create(
|
||||
name=facet_name,
|
||||
defaults={'color': color, 'slug': facet_name.lower().replace(' ', '-')}
|
||||
)
|
||||
facets[facet_name] = facet
|
||||
|
||||
# Update existing tag with facet and new name
|
||||
old_tag.facet = facets[facet_name]
|
||||
old_tag.name = tag_description
|
||||
old_tag.save()
|
||||
|
||||
|
||||
def reverse_migrate_tags_to_facets(apps, schema_editor):
|
||||
"""Reverse migration: convert back to simple tags."""
|
||||
Tag = apps.get_model('boxes', 'Tag')
|
||||
|
||||
# Convert all tags back to simple format
|
||||
for tag in Tag.objects.all():
|
||||
if tag.facet and tag.facet.name != 'General':
|
||||
# Format as "Facet:Description"
|
||||
tag.name = f"{tag.facet.name}:{tag.name}"
|
||||
tag.facet = None
|
||||
tag.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('boxes', '0008_facet_alter_tag_options_alter_tag_name_tag_facet_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(migrate_tags_to_facets, reverse_migrate_tags_to_facets),
|
||||
]
|
||||
35
boxes/migrations/0010_remove_thingtype.py
Normal file
35
boxes/migrations/0010_remove_thingtype.py
Normal file
@@ -0,0 +1,35 @@
|
||||
# Migration to remove ThingType hierarchy
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('boxes', '0009_migrate_tags_to_facets'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
# Remove thing_type field from Thing
|
||||
migrations.AlterField(
|
||||
model_name='thing',
|
||||
name='thing_type',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='boxes.thingtype', related_name='things'),
|
||||
),
|
||||
# Remove thing_type field from Thing completely
|
||||
migrations.RemoveField(
|
||||
model_name='thing',
|
||||
name='thing_type',
|
||||
),
|
||||
# Make facet field non-nullable in Tag
|
||||
migrations.AlterField(
|
||||
model_name='tag',
|
||||
name='facet',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='boxes.facet', related_name='tags'),
|
||||
),
|
||||
# Delete ThingType model
|
||||
migrations.DeleteModel(
|
||||
name='ThingType',
|
||||
),
|
||||
]
|
||||
Reference in New Issue
Block a user