mirror of
https://github.com/inventree/InvenTree.git
synced 2026-09-28 14:45:57 +00:00
Squash migrations (#12830)
* Squash migrations for "users" app * Squash migrations for "common" app Note: we will come back again later to squash some more * Significant squashing * Update legacy migrations * Further squashing of migrations * Even more cleanup * Adjust CI workflow * Update CI Job * Update docs * Update CHANGELOG.md * Cleanup old comments * Throw error if pre 1.0.0 DB detected * Refactor incomplete migration check
This commit is contained in:
@@ -22,6 +22,7 @@ from InvenTree.ready import ignore_ready_warning
|
||||
|
||||
logger = structlog.get_logger('inventree')
|
||||
MIGRATIONS_CHECK_DONE = False
|
||||
PRE_1_0_0_CHECK_DONE = False
|
||||
|
||||
OIDC_CLIENT_CHECKED = False
|
||||
DEFAULT_OIDC_APP_ID = 'zDFnsiRheJIOKNx6aCQ0quBxECg1QBHtVFDPloJ6'
|
||||
@@ -55,6 +56,13 @@ class InvenTreeConfig(AppConfig):
|
||||
):
|
||||
return
|
||||
|
||||
# Check for a database stuck mid-way through the pre-1.0.0 migration squash.
|
||||
if (
|
||||
InvenTree.ready.canAppAccessDatabase(allow_plugins=True)
|
||||
or settings.TESTING_ENV
|
||||
):
|
||||
self.check_pre_1_0_0_upgrade()
|
||||
|
||||
# Skip if running migrations
|
||||
if InvenTree.ready.isRunningMigrations():
|
||||
return
|
||||
@@ -370,6 +378,31 @@ class InvenTreeConfig(AppConfig):
|
||||
logger.info('Default OIDC client created: %s', client)
|
||||
OIDC_CLIENT_CHECKED = True
|
||||
|
||||
def check_pre_1_0_0_upgrade(self=None):
|
||||
"""Check for a database stuck mid-way through the pre-1.0.0 migration squash."""
|
||||
global PRE_1_0_0_CHECK_DONE
|
||||
if PRE_1_0_0_CHECK_DONE:
|
||||
return
|
||||
|
||||
if not InvenTree.ready.canAppAccessDatabase(allow_plugins=True):
|
||||
return
|
||||
|
||||
if stuck_apps := InvenTree.tasks.get_stuck_pre_1_0_0_apps():
|
||||
docs = 'https://docs.inventree.org/en/stable/start/migrate/#updating-from-pre-100'
|
||||
logger.error(
|
||||
'INVE-E19: Database is only partially migrated through a pre-1.0.0 '
|
||||
'install, for app(s): %s\n'
|
||||
'This instance must first be fully upgraded to InvenTree 1.0.0 '
|
||||
'before upgrading further.\n'
|
||||
'- Refer to the InvenTree documentation for more information:\n'
|
||||
'- %s',
|
||||
', '.join(stuck_apps),
|
||||
docs,
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
PRE_1_0_0_CHECK_DONE = True
|
||||
|
||||
def ensure_migrations_done(self=None):
|
||||
"""Ensures there are no open migrations, stop if inconsistent state."""
|
||||
global MIGRATIONS_CHECK_DONE
|
||||
|
||||
@@ -995,6 +995,63 @@ def get_migration_count():
|
||||
return executor.loader.applied_migrations
|
||||
|
||||
|
||||
# The first and last individual migrations of each pre-1.0.0 squash range,
|
||||
# for every app squashed as part of the pre-1.0.0 migration-history cleanup.
|
||||
PRE_1_0_0_MIGRATION_BOUNDARIES = [
|
||||
('common', '0001_initial', '0007_colortheme'),
|
||||
(
|
||||
'common',
|
||||
'0008_remove_inventreesetting_description',
|
||||
'0039_emailthread_emailmessage',
|
||||
),
|
||||
('build', '0006_auto_20190913_1407', '0015_auto_20200425_1350'),
|
||||
('build', '0017_auto_20200426_0612', '0058_buildline_consumed'),
|
||||
('company', '0003_remove_supplierpart_minimum', '0047_supplierpart_pack_size'),
|
||||
('company', '0048_auto_20220913_0312', '0075_company_tax_id'),
|
||||
('order', '0001_initial', '0023_auto_20200420_2309'),
|
||||
('order', '0031_auto_20200426_0612', '0112_alter_salesorderlineitem_part'),
|
||||
('stock', '0002_auto_20190525_2226', '0030_auto_20200422_0015'),
|
||||
('stock', '0059_auto_20210404_2016', '0116_alter_stockitem_link'),
|
||||
('part', '0003_auto_20190525_2226', '0060_merge_20201112_1722'),
|
||||
(
|
||||
'part',
|
||||
'0061_auto_20210103_2313',
|
||||
'0142_remove_part_last_stocktake_remove_partstocktake_note_and_more',
|
||||
),
|
||||
('users', '0001_initial', '0015_alter_userprofile_type'),
|
||||
]
|
||||
|
||||
|
||||
def get_stuck_pre_1_0_0_apps() -> list:
|
||||
"""Detect apps stuck mid-way through the pre-1.0.0 migration squash.
|
||||
|
||||
A database which has applied the *first* migration of one of
|
||||
PRE_1_0_0_MIGRATION_BOUNDARIES's ranges but not the *last* is stuck
|
||||
between the old, granular history and the squashed one.
|
||||
|
||||
Returns a list of app labels which are in this "stuck" state. An empty
|
||||
list means it is safe to proceed with migrations.
|
||||
"""
|
||||
from django.db.migrations.recorder import MigrationRecorder
|
||||
|
||||
connection = connections[DEFAULT_DB_ALIAS]
|
||||
recorder = MigrationRecorder(connection)
|
||||
|
||||
if not recorder.has_table():
|
||||
# No migrations have ever been recorded - a genuinely fresh database
|
||||
return []
|
||||
|
||||
applied = recorder.applied_migrations()
|
||||
|
||||
stuck_apps = set()
|
||||
|
||||
for app_label, first, last in PRE_1_0_0_MIGRATION_BOUNDARIES:
|
||||
if (app_label, first) in applied and (app_label, last) not in applied:
|
||||
stuck_apps.add(app_label)
|
||||
|
||||
return sorted(stuck_apps)
|
||||
|
||||
|
||||
@tracer.start_as_current_span('check_for_migrations')
|
||||
@scheduled_task(ScheduledTask.DAILY)
|
||||
def check_for_migrations(force: bool = False, reload_registry: bool = True) -> bool:
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
# Generated by Django 2.2.5 on 2019-09-13 14:07
|
||||
|
||||
import InvenTree.fields
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('build', '0005_auto_20190604_2217'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='build',
|
||||
name='URL',
|
||||
field=InvenTree.fields.InvenTreeURLField(blank=True, help_text='Link to external URL'),
|
||||
),
|
||||
]
|
||||
+197
@@ -0,0 +1,197 @@
|
||||
# Generated by Django 5.2.17 on 2026-09-09 23:26
|
||||
|
||||
import InvenTree.fields
|
||||
import django.core.validators
|
||||
import django.db.models.deletion
|
||||
import mptt.fields
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
replaces = [
|
||||
("build", "0006_auto_20190913_1407"),
|
||||
("build", "0007_auto_20191118_2321"),
|
||||
("build", "0008_auto_20200201_1247"),
|
||||
("build", "0009_auto_20200210_1032"),
|
||||
("build", "0010_auto_20200318_1027"),
|
||||
("build", "0011_auto_20200406_0123"),
|
||||
("build", "0012_build_sales_order"),
|
||||
("build", "0013_auto_20200425_0507"),
|
||||
("build", "0014_auto_20200425_1243"),
|
||||
("build", "0015_auto_20200425_1350"),
|
||||
]
|
||||
|
||||
dependencies = [
|
||||
("build", "0005_auto_20190604_2217"),
|
||||
("order", "0029_auto_20200423_1042"),
|
||||
("part", "0035_auto_20200406_0045"),
|
||||
("stock", "0031_auto_20200422_0209"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="build",
|
||||
name="notes",
|
||||
field=models.TextField(
|
||||
blank=True, help_text="Extra build notes", verbose_name="Notes"
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="build",
|
||||
name="creation_date",
|
||||
field=models.DateField(auto_now_add=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="build",
|
||||
name="part",
|
||||
field=models.ForeignKey(
|
||||
help_text="Select part to build",
|
||||
limit_choices_to={
|
||||
"active": True,
|
||||
"assembly": True,
|
||||
"is_template": False,
|
||||
"virtual": False,
|
||||
},
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="builds",
|
||||
to="part.part",
|
||||
verbose_name="Part",
|
||||
),
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name="build",
|
||||
old_name="URL",
|
||||
new_name="link",
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="build",
|
||||
name="link",
|
||||
field=InvenTree.fields.InvenTreeURLField(
|
||||
blank=True,
|
||||
help_text="Link to external URL",
|
||||
max_length=2000,
|
||||
verbose_name="External Link",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="build",
|
||||
name="sales_order",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
help_text="Sales Order to which this build is allocated",
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="builds",
|
||||
to="order.salesorder",
|
||||
verbose_name="Sales Order Reference",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="build",
|
||||
name="level",
|
||||
field=models.PositiveIntegerField(default=0, editable=False),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="build",
|
||||
name="lft",
|
||||
field=models.PositiveIntegerField(default=0, editable=False),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="build",
|
||||
name="rght",
|
||||
field=models.PositiveIntegerField(default=0, editable=False),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="build",
|
||||
name="tree_id",
|
||||
field=models.PositiveIntegerField(db_index=True, default=0, editable=False),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="build",
|
||||
name="batch",
|
||||
field=models.CharField(
|
||||
blank=True,
|
||||
help_text="Batch code for this build output",
|
||||
max_length=100,
|
||||
null=True,
|
||||
verbose_name="Batch Code",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="build",
|
||||
name="quantity",
|
||||
field=models.PositiveIntegerField(
|
||||
default=1,
|
||||
help_text="Number of parts to build",
|
||||
validators=[django.core.validators.MinValueValidator(1)],
|
||||
verbose_name="Build Quantity",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="build",
|
||||
name="status",
|
||||
field=models.PositiveIntegerField(
|
||||
choices=[
|
||||
(10, "Pending"),
|
||||
(20, "Allocated"),
|
||||
(30, "Cancelled"),
|
||||
(40, "Complete"),
|
||||
],
|
||||
default=10,
|
||||
help_text="Build status code",
|
||||
validators=[django.core.validators.MinValueValidator(0)],
|
||||
verbose_name="Build Status",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="build",
|
||||
name="take_from",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
help_text="Select location to take stock from for this build (leave blank to take from any stock location)",
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="sourcing_builds",
|
||||
to="stock.stocklocation",
|
||||
verbose_name="Source Location",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="build",
|
||||
name="title",
|
||||
field=models.CharField(
|
||||
help_text="Brief description of the build",
|
||||
max_length=100,
|
||||
verbose_name="Build Title",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="build",
|
||||
name="parent",
|
||||
field=mptt.fields.TreeForeignKey(
|
||||
blank=True,
|
||||
help_text="Parent build to which this build is allocated",
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.DO_NOTHING,
|
||||
related_name="children",
|
||||
to="build.build",
|
||||
verbose_name="Parent Build",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="builditem",
|
||||
name="quantity",
|
||||
field=models.DecimalField(
|
||||
decimal_places=5,
|
||||
default=1,
|
||||
help_text="Stock quantity to allocate to build",
|
||||
max_digits=15,
|
||||
validators=[django.core.validators.MinValueValidator(0)],
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -1,19 +0,0 @@
|
||||
# Generated by Django 2.2.5 on 2019-11-18 23:21
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('build', '0006_auto_20190913_1407'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='builditem',
|
||||
name='quantity',
|
||||
field=models.DecimalField(decimal_places=5, default=1, help_text='Stock quantity to allocate to build', max_digits=15, validators=[django.core.validators.MinValueValidator(1)]),
|
||||
),
|
||||
]
|
||||
@@ -1,18 +0,0 @@
|
||||
# Generated by Django 2.2.9 on 2020-02-01 12:47
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('build', '0007_auto_20191118_2321'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='build',
|
||||
name='notes',
|
||||
field=models.TextField(blank=True, help_text='Extra build notes'),
|
||||
),
|
||||
]
|
||||
@@ -1,18 +0,0 @@
|
||||
# Generated by Django 2.2.9 on 2020-02-10 10:32
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('build', '0008_auto_20200201_1247'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='build',
|
||||
name='creation_date',
|
||||
field=models.DateField(auto_now_add=True),
|
||||
),
|
||||
]
|
||||
@@ -1,19 +0,0 @@
|
||||
# Generated by Django 2.2.9 on 2020-03-18 10:27
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('build', '0009_auto_20200210_1032'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='build',
|
||||
name='part',
|
||||
field=models.ForeignKey(help_text='Select part to build', limit_choices_to={'active': True, 'assembly': True, 'is_template': False, 'virtual': False}, on_delete=django.db.models.deletion.CASCADE, related_name='builds', to='part.Part'),
|
||||
),
|
||||
]
|
||||
@@ -1,18 +0,0 @@
|
||||
# Generated by Django 2.2.10 on 2020-04-06 01:23
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('build', '0010_auto_20200318_1027'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='build',
|
||||
old_name='URL',
|
||||
new_name='link',
|
||||
),
|
||||
]
|
||||
@@ -1,20 +0,0 @@
|
||||
# Generated by Django 3.0.5 on 2020-04-24 22:51
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('order', '0029_auto_20200423_1042'),
|
||||
('build', '0011_auto_20200406_0123'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='build',
|
||||
name='sales_order',
|
||||
field=models.ForeignKey(blank=True, help_text='Sales Order to which this build is allocated', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='builds', to='order.SalesOrder'),
|
||||
),
|
||||
]
|
||||
@@ -1,46 +0,0 @@
|
||||
# Generated by Django 3.0.5 on 2020-04-25 05:07
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import mptt.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
atomic = False
|
||||
|
||||
dependencies = [
|
||||
('build', '0012_build_sales_order'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='build',
|
||||
name='level',
|
||||
field=models.PositiveIntegerField(default=0, editable=False),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='build',
|
||||
name='lft',
|
||||
field=models.PositiveIntegerField(default=0, editable=False),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='build',
|
||||
name='parent',
|
||||
field=mptt.fields.TreeForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='children', to='build.Build'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='build',
|
||||
name='rght',
|
||||
field=models.PositiveIntegerField(default=0, editable=False),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='build',
|
||||
name='tree_id',
|
||||
field=models.PositiveIntegerField(db_index=True, default=0, editable=False),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
||||
@@ -1,70 +0,0 @@
|
||||
# Generated by Django 3.0.5 on 2020-04-25 12:43
|
||||
|
||||
import InvenTree.fields
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import mptt.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('part', '0035_auto_20200406_0045'),
|
||||
('stock', '0031_auto_20200422_0209'),
|
||||
('order', '0029_auto_20200423_1042'),
|
||||
('build', '0013_auto_20200425_0507'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='build',
|
||||
name='batch',
|
||||
field=models.CharField(blank=True, help_text='Batch code for this build output', max_length=100, null=True, verbose_name='Batch Code'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='build',
|
||||
name='link',
|
||||
field=InvenTree.fields.InvenTreeURLField(blank=True, help_text='Link to external URL', verbose_name='External Link'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='build',
|
||||
name='notes',
|
||||
field=models.TextField(blank=True, help_text='Extra build notes', verbose_name='Notes'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='build',
|
||||
name='parent',
|
||||
field=mptt.fields.TreeForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='children', to='build.Build', verbose_name='Parent Build'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='build',
|
||||
name='part',
|
||||
field=models.ForeignKey(help_text='Select part to build', limit_choices_to={'active': True, 'assembly': True, 'is_template': False, 'virtual': False}, on_delete=django.db.models.deletion.CASCADE, related_name='builds', to='part.Part', verbose_name='Part'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='build',
|
||||
name='quantity',
|
||||
field=models.PositiveIntegerField(default=1, help_text='Number of parts to build', validators=[django.core.validators.MinValueValidator(1)], verbose_name='Build Quantity'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='build',
|
||||
name='sales_order',
|
||||
field=models.ForeignKey(blank=True, help_text='Sales Order to which this build is allocated', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='builds', to='order.SalesOrder', verbose_name='Sales Order Reference'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='build',
|
||||
name='status',
|
||||
field=models.PositiveIntegerField(choices=[(10, 'Pending'), (20, 'Allocated'), (30, 'Cancelled'), (40, 'Complete')], default=10, help_text='Build status code', validators=[django.core.validators.MinValueValidator(0)], verbose_name='Build Status'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='build',
|
||||
name='take_from',
|
||||
field=models.ForeignKey(blank=True, help_text='Select location to take stock from for this build (leave blank to take from any stock location)', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='sourcing_builds', to='stock.StockLocation', verbose_name='Source Location'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='build',
|
||||
name='title',
|
||||
field=models.CharField(help_text='Brief description of the build', max_length=100, verbose_name='Build Title'),
|
||||
),
|
||||
]
|
||||
@@ -1,26 +0,0 @@
|
||||
# Generated by Django 3.0.5 on 2020-04-25 13:50
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import mptt.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('build', '0014_auto_20200425_1243'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='build',
|
||||
name='parent',
|
||||
field=mptt.fields.TreeForeignKey(blank=True, help_text='Parent build to which this build is allocated', null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='children', to='build.Build', verbose_name='Parent Build'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='builditem',
|
||||
name='quantity',
|
||||
field=models.DecimalField(decimal_places=5, default=1, help_text='Stock quantity to allocate to build', max_digits=15, validators=[django.core.validators.MinValueValidator(0)]),
|
||||
),
|
||||
]
|
||||
@@ -8,7 +8,7 @@ class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('stock', '0033_auto_20200426_0539'),
|
||||
('build', '0015_auto_20200425_1350'),
|
||||
('build', '0006_auto_20190913_1407_squashed_0015_auto_20200425_1350'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
# Generated by Django 3.0.5 on 2020-04-26 06:12
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('stock', '0034_auto_20200426_0602'),
|
||||
('build', '0016_auto_20200426_0551'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='builditem',
|
||||
name='stock_item',
|
||||
field=models.ForeignKey(help_text='Stock Item to allocate to build', limit_choices_to={'belongs_to': None, 'build_order': None, 'sales_order': None}, on_delete=django.db.models.deletion.CASCADE, related_name='allocations', to='stock.StockItem'),
|
||||
),
|
||||
]
|
||||
+323
@@ -0,0 +1,323 @@
|
||||
# Generated by Django 5.2.17 on 2026-09-09 00:01
|
||||
|
||||
import InvenTree.fields
|
||||
import build.status_codes
|
||||
import build.validators
|
||||
import django.core.validators
|
||||
import django.db.models.deletion
|
||||
import generic.states.fields
|
||||
import generic.states.validators
|
||||
import mptt.fields
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
def update_build_reference(apps, schema_editor):
|
||||
"""Update the build order reference.
|
||||
|
||||
Ref: https://github.com/inventree/InvenTree/pull/3267
|
||||
|
||||
Performs the following steps:
|
||||
- Extract existing 'prefix' value
|
||||
- Generate a build order pattern based on the prefix value
|
||||
- Update any existing build order references with the specified prefix
|
||||
"""
|
||||
InvenTreeSetting = apps.get_model('common', 'inventreesetting')
|
||||
|
||||
try:
|
||||
prefix = InvenTreeSetting.objects.get(key='BUILDORDER_REFERENCE_PREFIX').value
|
||||
except Exception:
|
||||
prefix = 'BO-'
|
||||
|
||||
pattern = prefix + '{ref:04d}'
|
||||
|
||||
try:
|
||||
setting = InvenTreeSetting.objects.get(key='BUILDORDER_REFERENCE_PATTERN')
|
||||
setting.value = pattern
|
||||
setting.save()
|
||||
except InvenTreeSetting.DoesNotExist:
|
||||
InvenTreeSetting.objects.create(key='BUILDORDER_REFERENCE_PATTERN', value=pattern)
|
||||
|
||||
Build = apps.get_model('build', 'build')
|
||||
|
||||
for build in Build.objects.all():
|
||||
if not build.reference.startswith(prefix):
|
||||
build.reference = prefix + build.reference
|
||||
build.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
atomic = False
|
||||
|
||||
replaces = [('build', '0017_auto_20200426_0612'), ('build', '0018_build_reference'), ('build', '0019_auto_20201019_1302'), ('build', '0020_auto_20201019_1325'), ('build', '0021_auto_20201020_0908_squashed_0026_auto_20201023_1228'), ('build', '0022_buildorderattachment'), ('build', '0023_auto_20201110_0911'), ('build', '0024_auto_20201201_1023'), ('build', '0025_build_target_date'), ('build', '0026_auto_20210216_1539'), ('build', '0027_auto_20210404_2016'), ('build', '0028_builditem_bom_item'), ('build', '0029_auto_20210601_1525'), ('build', '0030_alter_build_reference'), ('build', '0031_build_reference_int'), ('build', '0032_auto_20211014_0632'), ('build', '0033_auto_20211128_0151'), ('build', '0034_alter_build_reference_int'), ('build', '0035_alter_build_notes'), ('build', '0036_auto_20220707_1101'), ('build', '0037_build_priority'), ('build', '0038_alter_build_responsible'), ('build', '0039_auto_20230317_0816'), ('build', '0040_auto_20230404_1310'), ('build', '0041_alter_build_title'), ('build', '0042_alter_build_notes'), ('build', '0043_buildline'), ('build', '0044_auto_20230528_1410'), ('build', '0045_builditem_build_line'), ('build', '0046_auto_20230606_1033'), ('build', '0047_auto_20230606_1058'), ('build', '0048_build_project_code'), ('build', '0049_alter_builditem_build_line'), ('build', '0050_auto_20240508_0138'), ('build', '0051_delete_buildorderattachment'), ('build', '0052_build_status_custom_key_alter_build_status'), ('build', '0053_alter_build_part'), ('build', '0054_build_start_date'), ('build', '0055_auto_20250221_1230'), ('build', '0056_alter_build_link'), ('build', '0057_build_external'), ('build', '0058_buildline_consumed')]
|
||||
|
||||
dependencies = [
|
||||
('build', '0016_auto_20200426_0551'),
|
||||
('common', '0019_projectcode_metadata'),
|
||||
('part', '0051_bomitem_optional'),
|
||||
('part', '0066_bomitem_allow_variants'),
|
||||
('part', '0109_auto_20230517_1048'),
|
||||
('stock', '0034_auto_20200426_0602'),
|
||||
('stock', '0052_stockitem_is_building'),
|
||||
('stock', '0054_remove_stockitem_build_order'),
|
||||
('stock', '0058_stockitem_packaging'),
|
||||
('users', '0005_owner_model'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='builditem',
|
||||
name='stock_item',
|
||||
field=models.ForeignKey(help_text='Stock Item to allocate to build', limit_choices_to={'belongs_to': None, 'build_order': None, 'sales_order': None}, on_delete=django.db.models.deletion.CASCADE, related_name='allocations', to='stock.stockitem'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='build',
|
||||
name='reference',
|
||||
field=models.CharField(blank=True, help_text='Build Order Reference', max_length=64, verbose_name='Reference'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='build',
|
||||
name='reference',
|
||||
field=models.CharField(help_text='Build Order Reference', max_length=64, unique=True, verbose_name='Reference'),
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='build',
|
||||
options={'verbose_name': 'Build Order', 'verbose_name_plural': 'Build Orders'},
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='build',
|
||||
name='reference',
|
||||
field=models.CharField(default=build.validators.generate_next_build_reference, help_text='Build Order Reference', max_length=64, unique=True, validators=[build.validators.validate_build_order_reference], verbose_name='Reference'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='build',
|
||||
name='title',
|
||||
field=models.CharField(help_text='Brief description of the build', max_length=100, verbose_name='Description'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='build',
|
||||
name='destination',
|
||||
field=models.ForeignKey(blank=True, help_text='Select location where the completed items will be stored', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='incoming_builds', to='stock.stocklocation', verbose_name='Destination Location'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='build',
|
||||
name='status',
|
||||
field=models.PositiveIntegerField(choices=[(10, 'Pending'), (20, 'Production'), (25, 'On Hold'), (30, 'Cancelled'), (40, 'Complete')], default=10, help_text='Build status code', validators=[django.core.validators.MinValueValidator(0)], verbose_name='Build Status'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='build',
|
||||
name='part',
|
||||
field=models.ForeignKey(help_text='Select part to build', limit_choices_to={'active': True, 'assembly': True, 'virtual': False}, on_delete=django.db.models.deletion.CASCADE, related_name='builds', to='part.part', verbose_name='Part'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='build',
|
||||
name='completed',
|
||||
field=models.PositiveIntegerField(default=0, help_text='Number of stock items which have been completed', verbose_name='Completed items'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='build',
|
||||
name='quantity',
|
||||
field=models.PositiveIntegerField(default=1, help_text='Number of stock items to build', validators=[django.core.validators.MinValueValidator(1)], verbose_name='Build Quantity'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='build',
|
||||
name='parent',
|
||||
field=mptt.fields.TreeForeignKey(blank=True, help_text='Build Order to which this build is allocated', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='children', to='build.build', verbose_name='Parent Build'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='build',
|
||||
name='target_date',
|
||||
field=models.DateField(blank=True, help_text='Target date for build completion. Build will be overdue after this date.', null=True, verbose_name='Target completion date'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='build',
|
||||
name='completed_by',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='builds_completed', to=settings.AUTH_USER_MODEL, verbose_name='completed by'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='build',
|
||||
name='completion_date',
|
||||
field=models.DateField(blank=True, null=True, verbose_name='Completion Date'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='build',
|
||||
name='creation_date',
|
||||
field=models.DateField(auto_now_add=True, verbose_name='Creation Date'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='build',
|
||||
name='issued_by',
|
||||
field=models.ForeignKey(blank=True, help_text='User who issued this build order', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='builds_issued', to=settings.AUTH_USER_MODEL, verbose_name='Issued by'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='build',
|
||||
name='responsible',
|
||||
field=models.ForeignKey(blank=True, help_text='User responsible for this build order', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='builds_responsible', to='users.owner', verbose_name='Responsible'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='builditem',
|
||||
name='install_into',
|
||||
field=models.ForeignKey(blank=True, help_text='Destination stock item', limit_choices_to={'is_building': True}, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='items_to_install', to='stock.stockitem', verbose_name='Install into'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='builditem',
|
||||
name='quantity',
|
||||
field=models.DecimalField(decimal_places=5, default=1, help_text='Stock quantity to allocate to build', max_digits=15, validators=[django.core.validators.MinValueValidator(0)], verbose_name='Quantity'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='builditem',
|
||||
name='stock_item',
|
||||
field=models.ForeignKey(help_text='Source stock item', limit_choices_to={'belongs_to': None, 'sales_order': None}, on_delete=django.db.models.deletion.CASCADE, related_name='allocations', to='stock.stockitem', verbose_name='Stock Item'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='BuildOrderAttachment',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('attachment', models.FileField(help_text='Select file to attach', upload_to='attachments', verbose_name='Attachment')),
|
||||
('comment', models.CharField(blank=True, help_text='File comment', max_length=100, verbose_name='Comment')),
|
||||
('upload_date', models.DateField(auto_now_add=True, null=True, verbose_name='upload date')),
|
||||
('build', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='attachments', to='build.build')),
|
||||
('user', models.ForeignKey(blank=True, help_text='User', null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='User')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='build',
|
||||
name='reference_int',
|
||||
field=models.IntegerField(default=0),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='buildorderattachment',
|
||||
name='link',
|
||||
field=InvenTree.fields.InvenTreeURLField(blank=True, help_text='Link to external URL', max_length=2000, null=True, verbose_name='Link'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='buildorderattachment',
|
||||
name='attachment',
|
||||
field=models.FileField(blank=True, help_text='Select file to attach', null=True, upload_to='attachments', verbose_name='Attachment'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='build',
|
||||
name='reference_int',
|
||||
field=models.BigIntegerField(default=0),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='build',
|
||||
name='notes',
|
||||
field=InvenTree.fields.InvenTreeNotesField(blank=True, help_text='Extra build notes', max_length=50000, null=True, verbose_name='Notes'),
|
||||
),
|
||||
migrations.RunPython(code=update_build_reference, reverse_code=migrations.RunPython.noop),
|
||||
migrations.AddField(
|
||||
model_name='build',
|
||||
name='priority',
|
||||
field=models.PositiveIntegerField(default=0, help_text='Priority of this build order', validators=[django.core.validators.MinValueValidator(0)], verbose_name='Build Priority'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='build',
|
||||
name='responsible',
|
||||
field=models.ForeignKey(blank=True, help_text='User or group responsible for this build order', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='builds_responsible', to='users.owner', verbose_name='Responsible'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='build',
|
||||
name='metadata',
|
||||
field=models.JSONField(blank=True, help_text='JSON metadata field, for use by external plugins', null=True, verbose_name='Plugin Metadata'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='builditem',
|
||||
name='metadata',
|
||||
field=models.JSONField(blank=True, help_text='JSON metadata field, for use by external plugins', null=True, verbose_name='Plugin Metadata'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='build',
|
||||
name='barcode_data',
|
||||
field=models.CharField(blank=True, help_text='Third party barcode data', max_length=500, verbose_name='Barcode Data'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='build',
|
||||
name='barcode_hash',
|
||||
field=models.CharField(blank=True, help_text='Unique hash of barcode data', max_length=128, verbose_name='Barcode Hash'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='build',
|
||||
name='title',
|
||||
field=models.CharField(blank=True, help_text='Brief description of the build (optional)', max_length=100, verbose_name='Description'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='build',
|
||||
name='notes',
|
||||
field=InvenTree.fields.InvenTreeNotesField(blank=True, help_text='Markdown notes (optional)', max_length=50000, null=True, verbose_name='Notes'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='BuildLine',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('quantity', models.DecimalField(decimal_places=5, default=1, help_text='Required quantity for build order', max_digits=15, validators=[django.core.validators.MinValueValidator(0)], verbose_name='Quantity')),
|
||||
('bom_item', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='build_lines', to='part.bomitem')),
|
||||
('build', models.ForeignKey(help_text='Build object', on_delete=django.db.models.deletion.CASCADE, related_name='build_lines', to='build.build')),
|
||||
],
|
||||
options={
|
||||
'unique_together': {('build', 'bom_item')},
|
||||
'verbose_name': 'Build Order Line Item',
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='builditem',
|
||||
name='build_line',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='allocations', to='build.buildline'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='builditem',
|
||||
unique_together={('build_line', 'stock_item', 'install_into')},
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='builditem',
|
||||
name='build',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='build',
|
||||
name='project_code',
|
||||
field=models.ForeignKey(blank=True, help_text='Project code for this build order', null=True, on_delete=django.db.models.deletion.SET_NULL, to='common.projectcode', verbose_name='Project Code'),
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='BuildOrderAttachment',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='build',
|
||||
name='status_custom_key',
|
||||
field=generic.states.fields.ExtraInvenTreeCustomStatusModelField(blank=True, default=None, help_text='Additional status information for this item', null=True, validators=[generic.states.validators.CustomStatusCodeValidator(status_class=build.status_codes.BuildStatus)], verbose_name='Custom status key'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='build',
|
||||
name='status',
|
||||
field=generic.states.fields.InvenTreeCustomStatusModelField(choices=[(10, 'Pending'), (20, 'Production'), (25, 'On Hold'), (30, 'Cancelled'), (40, 'Complete')], default=10, help_text='Build status code', validators=[django.core.validators.MinValueValidator(0), generic.states.validators.CustomStatusCodeValidator(status_class=build.status_codes.BuildStatus)], verbose_name='Build Status'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='build',
|
||||
name='part',
|
||||
field=models.ForeignKey(help_text='Select part to build', limit_choices_to={'assembly': True}, on_delete=django.db.models.deletion.CASCADE, related_name='builds', to='part.part', verbose_name='Part'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='build',
|
||||
name='start_date',
|
||||
field=models.DateField(blank=True, help_text='Scheduled start date for this build order', null=True, verbose_name='Build start date'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='build',
|
||||
name='link',
|
||||
field=InvenTree.fields.InvenTreeURLField(blank=True, help_text='Link to external URL', max_length=2000, verbose_name='External Link'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='build',
|
||||
name='external',
|
||||
field=models.BooleanField(default=False, help_text='This build order is fulfilled externally', verbose_name='External Build'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='buildline',
|
||||
name='consumed',
|
||||
field=models.DecimalField(decimal_places=5, default=0, help_text='Quantity of consumed stock', max_digits=15, validators=[django.core.validators.MinValueValidator(0)], verbose_name='Consumed'),
|
||||
),
|
||||
]
|
||||
@@ -1,60 +0,0 @@
|
||||
# Generated by Django 3.0.7 on 2020-10-19 11:25
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
def add_default_reference(apps, schema_editor):
|
||||
"""
|
||||
Add a "default" build-order reference for any existing build orders.
|
||||
Best we can do is use the PK of the build order itself.
|
||||
"""
|
||||
|
||||
Build = apps.get_model('build', 'build')
|
||||
|
||||
count = 0
|
||||
|
||||
for build in Build.objects.all():
|
||||
|
||||
build.reference = str(build.pk)
|
||||
build.save()
|
||||
count += 1
|
||||
|
||||
if count > 0:
|
||||
print(f"\nUpdated build reference for {count} existing BuildOrder objects")
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
atomic = False
|
||||
|
||||
dependencies = [
|
||||
('build', '0017_auto_20200426_0612'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
# Initial operation - create a 'reference' field for the Build object:
|
||||
migrations.AddField(
|
||||
model_name='build',
|
||||
name='reference',
|
||||
field=models.CharField(help_text='Build Order Reference', blank=True, max_length=64, unique=False, verbose_name='Reference'),
|
||||
),
|
||||
|
||||
# Auto-populate the new reference field for any existing build order objects
|
||||
migrations.RunPython(
|
||||
add_default_reference,
|
||||
reverse_code=migrations.RunPython.noop
|
||||
),
|
||||
|
||||
# Now that each build has a non-empty, unique reference, update the field requirements!
|
||||
migrations.AlterField(
|
||||
model_name='build',
|
||||
name='reference',
|
||||
field=models.CharField(
|
||||
help_text='Build Order Reference',
|
||||
max_length=64,
|
||||
blank=False,
|
||||
unique=True,
|
||||
verbose_name='Reference'
|
||||
)
|
||||
)
|
||||
]
|
||||
@@ -1,23 +0,0 @@
|
||||
# Generated by Django 3.0.7 on 2020-10-19 13:02
|
||||
|
||||
import build.validators
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('build', '0018_build_reference'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='build',
|
||||
options={'verbose_name': 'Build Order', 'verbose_name_plural': 'Build Orders'},
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='build',
|
||||
name='reference',
|
||||
field=models.CharField(help_text='Build Order Reference', max_length=64, unique=True, validators=[build.validators.validate_build_order_reference], verbose_name='Reference'),
|
||||
),
|
||||
]
|
||||
@@ -1,18 +0,0 @@
|
||||
# Generated by Django 3.0.7 on 2020-10-19 13:25
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('build', '0019_auto_20201019_1302'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='build',
|
||||
name='title',
|
||||
field=models.CharField(help_text='Brief description of the build', max_length=100, verbose_name='Description'),
|
||||
),
|
||||
]
|
||||
-66
@@ -1,66 +0,0 @@
|
||||
# Generated by Django 3.0.7 on 2020-10-25 21:33
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import mptt.fields
|
||||
|
||||
from build.status_codes import BuildStatus
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
replaces = [('build', '0021_auto_20201020_0908'), ('build', '0022_auto_20201020_0953'), ('build', '0023_auto_20201020_1009'), ('build', '0024_auto_20201020_1144'), ('build', '0025_auto_20201020_1248'), ('build', '0026_auto_20201023_1228')]
|
||||
|
||||
dependencies = [
|
||||
('stock', '0052_stockitem_is_building'),
|
||||
('build', '0020_auto_20201019_1325'),
|
||||
('part', '0051_bomitem_optional'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='builditem',
|
||||
name='install_into',
|
||||
field=models.ForeignKey(blank=True, help_text='Destination stock item', limit_choices_to={'is_building': True}, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='items_to_install', to='stock.StockItem'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='builditem',
|
||||
name='stock_item',
|
||||
field=models.ForeignKey(help_text='Source stock item', limit_choices_to={'belongs_to': None, 'build_order': None, 'sales_order': None}, on_delete=django.db.models.deletion.CASCADE, related_name='allocations', to='stock.StockItem'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='build',
|
||||
name='destination',
|
||||
field=models.ForeignKey(blank=True, help_text='Select location where the completed items will be stored', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='incoming_builds', to='stock.StockLocation', verbose_name='Destination Location'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='build',
|
||||
name='parent',
|
||||
field=mptt.fields.TreeForeignKey(blank=True, help_text='Build Order to which this build is allocated', null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='children', to='build.Build', verbose_name='Parent Build'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='build',
|
||||
name='status',
|
||||
field=models.PositiveIntegerField(choices=BuildStatus.items(), default=BuildStatus.PENDING.value, help_text='Build status code', validators=[django.core.validators.MinValueValidator(0)], verbose_name='Build Status'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='build',
|
||||
name='part',
|
||||
field=models.ForeignKey(help_text='Select part to build', limit_choices_to={'active': True, 'assembly': True, 'virtual': False}, on_delete=django.db.models.deletion.CASCADE, related_name='builds', to='part.Part', verbose_name='Part'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='build',
|
||||
name='completed',
|
||||
field=models.PositiveIntegerField(default=0, help_text='Number of stock items which have been completed', verbose_name='Completed items'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='build',
|
||||
name='quantity',
|
||||
field=models.PositiveIntegerField(default=1, help_text='Number of stock items to build', validators=[django.core.validators.MinValueValidator(1)], verbose_name='Build Quantity'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='builditem',
|
||||
unique_together={('build', 'stock_item', 'install_into')},
|
||||
),
|
||||
]
|
||||
@@ -1,31 +0,0 @@
|
||||
# Generated by Django 3.0.7 on 2020-10-26 04:17
|
||||
|
||||
import InvenTree.models
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('build', '0021_auto_20201020_0908_squashed_0026_auto_20201023_1228'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='BuildOrderAttachment',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('attachment', models.FileField(help_text='Select file to attach', upload_to='attachments')),
|
||||
('comment', models.CharField(blank=True, help_text='File comment', max_length=100)),
|
||||
('upload_date', models.DateField(auto_now_add=True, null=True)),
|
||||
('build', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='attachments', to='build.Build')),
|
||||
('user', models.ForeignKey(blank=True, help_text='User', null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -1,20 +0,0 @@
|
||||
# Generated by Django 3.0.7 on 2020-11-10 09:11
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('stock', '0054_remove_stockitem_build_order'),
|
||||
('build', '0022_buildorderattachment'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='builditem',
|
||||
name='stock_item',
|
||||
field=models.ForeignKey(help_text='Source stock item', limit_choices_to={'belongs_to': None, 'sales_order': None}, on_delete=django.db.models.deletion.CASCADE, related_name='allocations', to='stock.StockItem'),
|
||||
),
|
||||
]
|
||||
@@ -1,20 +0,0 @@
|
||||
# Generated by Django 3.0.7 on 2020-11-30 23:23
|
||||
|
||||
from django.db import migrations
|
||||
import django.db.models.deletion
|
||||
import mptt.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('build', '0023_auto_20201110_0911'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='build',
|
||||
name='parent',
|
||||
field=mptt.fields.TreeForeignKey(blank=True, help_text='Build Order to which this build is allocated', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='children', to='build.Build', verbose_name='Parent Build'),
|
||||
),
|
||||
]
|
||||
@@ -1,18 +0,0 @@
|
||||
# Generated by Django 3.0.7 on 2020-12-15 12:13
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('build', '0024_auto_20201201_1023'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='build',
|
||||
name='target_date',
|
||||
field=models.DateField(blank=True, help_text='Target date for build completion. Build will be overdue after this date.', null=True, verbose_name='Target completion date'),
|
||||
),
|
||||
]
|
||||
@@ -1,27 +0,0 @@
|
||||
# Generated by Django 3.0.7 on 2021-02-16 04:39
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('users', '0005_owner_model'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('build', '0025_build_target_date'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='build',
|
||||
name='issued_by',
|
||||
field=models.ForeignKey(blank=True, help_text='User who issued this build order', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='builds_issued', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='build',
|
||||
name='responsible',
|
||||
field=models.ForeignKey(blank=True, help_text='User responsible for this build order', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='builds_responsible', to='users.Owner'),
|
||||
),
|
||||
]
|
||||
@@ -1,85 +0,0 @@
|
||||
# Generated by Django 3.0.7 on 2021-04-04 20:16
|
||||
|
||||
import InvenTree.models
|
||||
from django.conf import settings
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('stock', '0058_stockitem_packaging'),
|
||||
('users', '0005_owner_model'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('build', '0026_auto_20210216_1539'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='build',
|
||||
name='completed_by',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='builds_completed', to=settings.AUTH_USER_MODEL, verbose_name='completed by'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='build',
|
||||
name='completion_date',
|
||||
field=models.DateField(blank=True, null=True, verbose_name='Completion Date'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='build',
|
||||
name='creation_date',
|
||||
field=models.DateField(auto_now_add=True, verbose_name='Creation Date'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='build',
|
||||
name='issued_by',
|
||||
field=models.ForeignKey(blank=True, help_text='User who issued this build order', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='builds_issued', to=settings.AUTH_USER_MODEL, verbose_name='Issued by'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='build',
|
||||
name='responsible',
|
||||
field=models.ForeignKey(blank=True, help_text='User responsible for this build order', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='builds_responsible', to='users.Owner', verbose_name='Responsible'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='builditem',
|
||||
name='build',
|
||||
field=models.ForeignKey(help_text='Build to allocate parts', on_delete=django.db.models.deletion.CASCADE, related_name='allocated_stock', to='build.Build', verbose_name='Build'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='builditem',
|
||||
name='install_into',
|
||||
field=models.ForeignKey(blank=True, help_text='Destination stock item', limit_choices_to={'is_building': True}, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='items_to_install', to='stock.StockItem', verbose_name='Install into'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='builditem',
|
||||
name='quantity',
|
||||
field=models.DecimalField(decimal_places=5, default=1, help_text='Stock quantity to allocate to build', max_digits=15, validators=[django.core.validators.MinValueValidator(0)], verbose_name='Quantity'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='builditem',
|
||||
name='stock_item',
|
||||
field=models.ForeignKey(help_text='Source stock item', limit_choices_to={'belongs_to': None, 'sales_order': None}, on_delete=django.db.models.deletion.CASCADE, related_name='allocations', to='stock.StockItem', verbose_name='Stock Item'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='buildorderattachment',
|
||||
name='attachment',
|
||||
field=models.FileField(help_text='Select file to attach', upload_to='attachments', verbose_name='Attachment'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='buildorderattachment',
|
||||
name='comment',
|
||||
field=models.CharField(blank=True, help_text='File comment', max_length=100, verbose_name='Comment'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='buildorderattachment',
|
||||
name='upload_date',
|
||||
field=models.DateField(auto_now_add=True, null=True, verbose_name='upload date'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='buildorderattachment',
|
||||
name='user',
|
||||
field=models.ForeignKey(blank=True, help_text='User', null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='User'),
|
||||
),
|
||||
]
|
||||
@@ -1,20 +0,0 @@
|
||||
# Generated by Django 3.2 on 2021-06-01 05:23
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('part', '0066_bomitem_allow_variants'),
|
||||
('build', '0027_auto_20210404_2016'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='builditem',
|
||||
name='bom_item',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='allocate_build_items', to='part.bomitem'),
|
||||
),
|
||||
]
|
||||
@@ -1,60 +0,0 @@
|
||||
# Generated by Django 3.2 on 2021-06-01 05:25
|
||||
|
||||
import logging
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
logger = logging.getLogger('inventree')
|
||||
|
||||
|
||||
def assign_bom_items(apps, schema_editor):
|
||||
"""
|
||||
Run through existing BuildItem objects,
|
||||
and assign a matching BomItem
|
||||
"""
|
||||
|
||||
BuildItem = apps.get_model('build', 'builditem')
|
||||
BomItem = apps.get_model('part', 'bomitem')
|
||||
Part = apps.get_model('part', 'part')
|
||||
|
||||
count_valid = 0
|
||||
count_total = 0
|
||||
|
||||
for build_item in BuildItem.objects.all(): # pragma: no cover
|
||||
|
||||
# Try to find a BomItem which matches the BuildItem
|
||||
# Note: Before this migration, variant stock assignment was not allowed,
|
||||
# so BomItem lookup should be pretty easy
|
||||
|
||||
if count_total == 0:
|
||||
# First time around
|
||||
logger.info("Assigning BomItems to existing BuildItem objects")
|
||||
|
||||
count_total += 1
|
||||
|
||||
try:
|
||||
bom_item = BomItem.objects.get(
|
||||
part__id=build_item.build.part.pk,
|
||||
sub_part__id=build_item.stock_item.part.pk,
|
||||
)
|
||||
|
||||
build_item.bom_item = bom_item
|
||||
build_item.save()
|
||||
|
||||
count_valid += 1
|
||||
|
||||
except BomItem.DoesNotExist:
|
||||
pass
|
||||
|
||||
if count_total > 0: # pragma: no cover
|
||||
logger.info(f"Assigned BomItem for {count_valid}/{count_total} entries")
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('build', '0028_builditem_bom_item'),
|
||||
]
|
||||
|
||||
operations = []
|
||||
@@ -1,20 +0,0 @@
|
||||
# Generated by Django 3.2.4 on 2021-07-08 14:14
|
||||
|
||||
import build.validators
|
||||
import build.models
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('build', '0029_auto_20210601_1525'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='build',
|
||||
name='reference',
|
||||
field=models.CharField(default=build.validators.generate_next_build_reference, help_text='Build Order Reference', max_length=64, unique=True, validators=[build.validators.validate_build_order_reference], verbose_name='Reference'),
|
||||
),
|
||||
]
|
||||
@@ -1,18 +0,0 @@
|
||||
# Generated by Django 3.2.5 on 2021-10-14 06:23
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('build', '0030_alter_build_reference'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='build',
|
||||
name='reference_int',
|
||||
field=models.IntegerField(default=0),
|
||||
),
|
||||
]
|
||||
@@ -1,48 +0,0 @@
|
||||
# Generated by Django 3.2.5 on 2021-10-14 06:32
|
||||
|
||||
import re
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def build_refs(apps, schema_editor):
|
||||
"""
|
||||
Rebuild the integer "reference fields" for existing Build objects
|
||||
"""
|
||||
|
||||
BuildOrder = apps.get_model('build', 'build')
|
||||
|
||||
for build in BuildOrder.objects.all():
|
||||
|
||||
ref = 0
|
||||
|
||||
result = re.match(r"^(\d+)", build.reference)
|
||||
|
||||
if result and len(result.groups()) == 1:
|
||||
try:
|
||||
ref = int(result.groups()[0])
|
||||
except Exception: # pragma: no cover
|
||||
ref = 0
|
||||
|
||||
# Clip integer value to ensure it does not overflow database field
|
||||
if ref > 0x7fffffff:
|
||||
ref = 0x7fffffff
|
||||
|
||||
build.reference_int = ref
|
||||
build.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
atomic = False
|
||||
|
||||
dependencies = [
|
||||
('build', '0031_build_reference_int'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(
|
||||
build_refs,
|
||||
reverse_code=migrations.RunPython.noop
|
||||
)
|
||||
]
|
||||
@@ -1,25 +0,0 @@
|
||||
# Generated by Django 3.2.5 on 2021-11-28 01:51
|
||||
|
||||
import InvenTree.fields
|
||||
import InvenTree.models
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('build', '0032_auto_20211014_0632'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='buildorderattachment',
|
||||
name='link',
|
||||
field=InvenTree.fields.InvenTreeURLField(blank=True, help_text='Link to external URL', null=True, verbose_name='Link'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='buildorderattachment',
|
||||
name='attachment',
|
||||
field=models.FileField(blank=True, help_text='Select file to attach', null=True, upload_to='attachments', verbose_name='Attachment'),
|
||||
),
|
||||
]
|
||||
@@ -1,18 +0,0 @@
|
||||
# Generated by Django 3.2.5 on 2021-12-01 21:39
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('build', '0033_auto_20211128_0151'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='build',
|
||||
name='reference_int',
|
||||
field=models.BigIntegerField(default=0),
|
||||
),
|
||||
]
|
||||
@@ -1,19 +0,0 @@
|
||||
# Generated by Django 3.2.13 on 2022-06-20 07:28
|
||||
|
||||
import InvenTree.fields
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('build', '0034_alter_build_reference_int'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='build',
|
||||
name='notes',
|
||||
field=InvenTree.fields.InvenTreeNotesField(blank=True, help_text='Extra build notes', max_length=50000, null=True, verbose_name='Notes'),
|
||||
),
|
||||
]
|
||||
@@ -1,64 +0,0 @@
|
||||
# Generated by Django 3.2.14 on 2022-07-07 11:01
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def update_build_reference(apps, schema_editor):
|
||||
"""Update the build order reference.
|
||||
|
||||
Ref: https://github.com/inventree/InvenTree/pull/3267
|
||||
|
||||
Performs the following steps:
|
||||
- Extract existing 'prefix' value
|
||||
- Generate a build order pattern based on the prefix value
|
||||
- Update any existing build order references with the specified prefix
|
||||
"""
|
||||
|
||||
InvenTreeSetting = apps.get_model('common', 'inventreesetting')
|
||||
|
||||
try:
|
||||
prefix = InvenTreeSetting.objects.get(key='BUILDORDER_REFERENCE_PREFIX').value
|
||||
except Exception:
|
||||
prefix = 'BO-'
|
||||
|
||||
# Construct a reference pattern
|
||||
pattern = prefix + '{ref:04d}'
|
||||
|
||||
# Create or update the BuildOrder.reference pattern
|
||||
try:
|
||||
setting = InvenTreeSetting.objects.get(key='BUILDORDER_REFERENCE_PATTERN')
|
||||
setting.value = pattern
|
||||
setting.save()
|
||||
except InvenTreeSetting.DoesNotExist:
|
||||
setting = InvenTreeSetting.objects.create(
|
||||
key='BUILDORDER_REFERENCE_PATTERN',
|
||||
value=pattern,
|
||||
)
|
||||
|
||||
# Update any existing build order references with the prefix
|
||||
Build = apps.get_model('build', 'build')
|
||||
|
||||
n = 0
|
||||
|
||||
for build in Build.objects.all():
|
||||
if not build.reference.startswith(prefix):
|
||||
build.reference = prefix + build.reference
|
||||
build.save()
|
||||
n += 1
|
||||
|
||||
if n > 0:
|
||||
print(f"Updated reference field for {n} BuildOrder objects")
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('build', '0035_alter_build_notes'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(
|
||||
update_build_reference,
|
||||
reverse_code=migrations.RunPython.noop,
|
||||
)
|
||||
]
|
||||
@@ -1,19 +0,0 @@
|
||||
# Generated by Django 3.2.16 on 2023-01-17 20:37
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('build', '0036_auto_20220707_1101'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='build',
|
||||
name='priority',
|
||||
field=models.PositiveIntegerField(default=0, help_text='Priority of this build order', validators=[django.core.validators.MinValueValidator(0)], verbose_name='Build Priority'),
|
||||
),
|
||||
]
|
||||
@@ -1,20 +0,0 @@
|
||||
# Generated by Django 3.2.16 on 2023-02-09 23:52
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('users', '0005_owner_model'),
|
||||
('build', '0037_build_priority'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='build',
|
||||
name='responsible',
|
||||
field=models.ForeignKey(blank=True, help_text='User or group responsible for this build order', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='builds_responsible', to='users.owner', verbose_name='Responsible'),
|
||||
),
|
||||
]
|
||||
@@ -1,23 +0,0 @@
|
||||
# Generated by Django 3.2.18 on 2023-03-17 08:16
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('build', '0038_alter_build_responsible'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='build',
|
||||
name='metadata',
|
||||
field=models.JSONField(blank=True, help_text='JSON metadata field, for use by external plugins', null=True, verbose_name='Plugin Metadata'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='builditem',
|
||||
name='metadata',
|
||||
field=models.JSONField(blank=True, help_text='JSON metadata field, for use by external plugins', null=True, verbose_name='Plugin Metadata'),
|
||||
),
|
||||
]
|
||||
@@ -1,23 +0,0 @@
|
||||
# Generated by Django 3.2.18 on 2023-04-04 13:10
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('build', '0039_auto_20230317_0816'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='build',
|
||||
name='barcode_data',
|
||||
field=models.CharField(blank=True, help_text='Third party barcode data', max_length=500, verbose_name='Barcode Data'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='build',
|
||||
name='barcode_hash',
|
||||
field=models.CharField(blank=True, help_text='Unique hash of barcode data', max_length=128, verbose_name='Barcode Hash'),
|
||||
),
|
||||
]
|
||||
@@ -1,18 +0,0 @@
|
||||
# Generated by Django 3.2.18 on 2023-04-12 17:52
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('build', '0040_auto_20230404_1310'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='build',
|
||||
name='title',
|
||||
field=models.CharField(blank=True, help_text='Brief description of the build (optional)', max_length=100, verbose_name='Description'),
|
||||
),
|
||||
]
|
||||
@@ -1,19 +0,0 @@
|
||||
# Generated by Django 3.2.18 on 2023-04-19 00:37
|
||||
|
||||
import InvenTree.fields
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('build', '0041_alter_build_title'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='build',
|
||||
name='notes',
|
||||
field=InvenTree.fields.InvenTreeNotesField(blank=True, help_text='Markdown notes (optional)', max_length=50000, null=True, verbose_name='Notes'),
|
||||
),
|
||||
]
|
||||
@@ -1,29 +0,0 @@
|
||||
# Generated by Django 3.2.19 on 2023-05-19 06:04
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('part', '0109_auto_20230517_1048'),
|
||||
('build', '0042_alter_build_notes'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='BuildLine',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('quantity', models.DecimalField(decimal_places=5, default=1, help_text='Required quantity for build order', max_digits=15, validators=[django.core.validators.MinValueValidator(0)], verbose_name='Quantity')),
|
||||
('bom_item', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='build_lines', to='part.bomitem')),
|
||||
('build', models.ForeignKey(help_text='Build object', on_delete=django.db.models.deletion.CASCADE, related_name='build_lines', to='build.build')),
|
||||
],
|
||||
options={
|
||||
'unique_together': {('build', 'bom_item')},
|
||||
'verbose_name': 'Build Order Line Item',
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -1,97 +0,0 @@
|
||||
# Generated by Django 3.2.19 on 2023-05-28 14:10
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def get_bom_items_for_part(part, Part, BomItem):
|
||||
""" Return a list of all BOM items for a given part.
|
||||
|
||||
Note that we cannot use the ORM here (as we are inside a data migration),
|
||||
so we *copy* the logic from the Part class.
|
||||
|
||||
This is a snapshot of the Part.get_bom_items() method as of 2023-05-29
|
||||
"""
|
||||
|
||||
bom_items = set()
|
||||
|
||||
# Get all BOM items which directly reference the part
|
||||
for bom_item in BomItem.objects.filter(part=part):
|
||||
bom_items.add(bom_item)
|
||||
|
||||
# Get all BOM items which are inherited by the part
|
||||
parents = Part.objects.filter(
|
||||
tree_id=part.tree_id,
|
||||
level__lt=part.level,
|
||||
lft__lt=part.lft,
|
||||
rght__gt=part.rght
|
||||
)
|
||||
|
||||
for bom_item in BomItem.objects.filter(part__in=parents, inherited=True):
|
||||
bom_items.add(bom_item)
|
||||
|
||||
return list(bom_items)
|
||||
|
||||
|
||||
def add_lines_to_builds(apps, schema_editor):
|
||||
"""Create BuildOrderLine objects for existing build orders"""
|
||||
|
||||
# Get database models
|
||||
Build = apps.get_model("build", "Build")
|
||||
BuildLine = apps.get_model("build", "BuildLine")
|
||||
|
||||
Part = apps.get_model("part", "Part")
|
||||
BomItem = apps.get_model("part", "BomItem")
|
||||
|
||||
build_lines = []
|
||||
|
||||
builds = Build.objects.all()
|
||||
|
||||
if builds.count() > 0:
|
||||
print(f"Creating BuildOrderLine objects for {builds.count()} existing builds")
|
||||
|
||||
for build in builds:
|
||||
# Create a BuildOrderLine for each BuildItem
|
||||
|
||||
bom_items = get_bom_items_for_part(build.part, Part, BomItem)
|
||||
|
||||
for item in bom_items:
|
||||
build_lines.append(
|
||||
BuildLine(
|
||||
build=build,
|
||||
bom_item=item,
|
||||
quantity=item.quantity * build.quantity,
|
||||
)
|
||||
)
|
||||
|
||||
if len(build_lines) > 0:
|
||||
# Construct the new BuildLine objects
|
||||
BuildLine.objects.bulk_create(build_lines)
|
||||
print(f"Created {len(build_lines)} BuildOrderLine objects for existing builds")
|
||||
|
||||
|
||||
def remove_build_lines(apps, schema_editor):
|
||||
"""Remove BuildOrderLine objects from the database"""
|
||||
|
||||
# Get database models
|
||||
BuildLine = apps.get_model("build", "BuildLine")
|
||||
|
||||
n = BuildLine.objects.all().count()
|
||||
|
||||
BuildLine.objects.all().delete()
|
||||
|
||||
if n > 0:
|
||||
print(f"Removed {n} BuildOrderLine objects")
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('build', '0043_buildline'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(
|
||||
add_lines_to_builds,
|
||||
reverse_code=remove_build_lines,
|
||||
),
|
||||
]
|
||||
@@ -1,19 +0,0 @@
|
||||
# Generated by Django 3.2.19 on 2023-06-06 10:30
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('build', '0044_auto_20230528_1410'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='builditem',
|
||||
name='build_line',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='allocations', to='build.buildline'),
|
||||
),
|
||||
]
|
||||
@@ -1,95 +0,0 @@
|
||||
# Generated by Django 3.2.19 on 2023-06-06 10:33
|
||||
|
||||
import logging
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
logger = logging.getLogger('inventree')
|
||||
|
||||
|
||||
def add_build_line_links(apps, schema_editor):
|
||||
"""Data migration to add links between BuildLine and BuildItem objects.
|
||||
|
||||
Associated model types:
|
||||
Build: A "Build Order"
|
||||
BomItem: An individual line in the BOM for Build.part
|
||||
BuildItem: An individual stock allocation against the Build Order
|
||||
BuildLine: (new model) an individual line in the Build Order
|
||||
|
||||
Goals:
|
||||
- Find all BuildItem objects which are associated with a Build
|
||||
- Link them against the relevant BuildLine object
|
||||
- The BuildLine objects should have been created in 0044_auto_20230528_1410.py
|
||||
"""
|
||||
|
||||
BuildItem = apps.get_model("build", "BuildItem")
|
||||
BuildLine = apps.get_model("build", "BuildLine")
|
||||
|
||||
# Find any existing BuildItem objects
|
||||
build_items = BuildItem.objects.all()
|
||||
|
||||
n_missing = 0
|
||||
|
||||
for item in build_items:
|
||||
|
||||
# Find the relevant BuildLine object
|
||||
line = BuildLine.objects.filter(
|
||||
build=item.build,
|
||||
bom_item=item.bom_item
|
||||
).first()
|
||||
|
||||
if line is None:
|
||||
logger.warning(f"BuildLine does not exist for BuildItem {item.pk}")
|
||||
n_missing += 1
|
||||
|
||||
if item.build is None or item.bom_item is None:
|
||||
continue
|
||||
|
||||
# Create one!
|
||||
line = BuildLine.objects.create(
|
||||
build=item.build,
|
||||
bom_item=item.bom_item,
|
||||
quantity=item.bom_item.quantity * item.build.quantity
|
||||
)
|
||||
|
||||
# Link the BuildItem to the BuildLine
|
||||
# In the next data migration, we remove the 'build' and 'bom_item' fields from BuildItem
|
||||
item.build_line = line
|
||||
item.save()
|
||||
|
||||
if build_items.count() > 0:
|
||||
logger.info(f"add_build_line_links: Updated {build_items.count()} BuildItem objects (added {n_missing})")
|
||||
|
||||
|
||||
def reverse_build_links(apps, schema_editor):
|
||||
"""Reverse data migration from add_build_line_links
|
||||
|
||||
Basically, iterate through each BuildItem and update the links based on the BuildLine
|
||||
"""
|
||||
|
||||
BuildItem = apps.get_model("build", "BuildItem")
|
||||
|
||||
items = BuildItem.objects.all()
|
||||
|
||||
for item in items:
|
||||
item.build = item.build_line.build
|
||||
item.bom_item = item.build_line.bom_item
|
||||
item.save()
|
||||
|
||||
if items.count() > 0:
|
||||
logger.info(f"reverse_build_links: Updated {items.count()} BuildItem objects")
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('build', '0045_builditem_build_line'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(
|
||||
add_build_line_links,
|
||||
reverse_code=reverse_build_links,
|
||||
)
|
||||
]
|
||||
@@ -1,26 +0,0 @@
|
||||
# Generated by Django 3.2.19 on 2023-06-06 10:58
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('stock', '0101_stockitemtestresult_metadata'),
|
||||
('build', '0046_auto_20230606_1033'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterUniqueTogether(
|
||||
name='builditem',
|
||||
unique_together={('build_line', 'stock_item', 'install_into')},
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='builditem',
|
||||
name='bom_item',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='builditem',
|
||||
name='build',
|
||||
),
|
||||
]
|
||||
@@ -1,20 +0,0 @@
|
||||
# Generated by Django 3.2.19 on 2023-05-14 09:22
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('common', '0019_projectcode_metadata'),
|
||||
('build', '0047_auto_20230606_1058'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='build',
|
||||
name='project_code',
|
||||
field=models.ForeignKey(blank=True, help_text='Project code for this build order', null=True, on_delete=django.db.models.deletion.SET_NULL, to='common.projectcode', verbose_name='Project Code'),
|
||||
),
|
||||
]
|
||||
@@ -1,19 +0,0 @@
|
||||
# Generated by Django 4.2.12 on 2024-05-08 01:38
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('build', '0048_build_project_code'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='builditem',
|
||||
name='build_line',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='allocations', to='build.buildline'),
|
||||
),
|
||||
]
|
||||
@@ -1,26 +0,0 @@
|
||||
# Generated by Django 4.2.12 on 2024-05-08 01:38
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def forward(apps, schema_editor):
|
||||
"""Find and delete any BuildItem instances which have a null BuildLine field."""
|
||||
|
||||
BuildItem = apps.get_model('build', 'BuildItem')
|
||||
|
||||
items = BuildItem.objects.filter(build_line=None)
|
||||
|
||||
if items.count() > 0:
|
||||
print(f"Deleting {items.count()} BuildItem objects with null BuildLine field")
|
||||
items.delete()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('build', '0049_alter_builditem_build_line'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(forward, reverse_code=migrations.RunPython.noop),
|
||||
]
|
||||
@@ -1,21 +0,0 @@
|
||||
# Generated by Django 4.2.12 on 2024-06-09 09:02
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('build', '0050_auto_20240508_0138'),
|
||||
('common', '0026_auto_20240608_1238'),
|
||||
('company', '0069_company_active'),
|
||||
('order', '0099_alter_salesorder_status'),
|
||||
('part', '0123_parttesttemplate_choices'),
|
||||
('stock', '0110_alter_stockitemtestresult_finished_datetime_and_more')
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.DeleteModel(
|
||||
name='BuildOrderAttachment',
|
||||
),
|
||||
]
|
||||
-50
@@ -1,50 +0,0 @@
|
||||
# Generated by Django 4.2.14 on 2024-08-07 22:40
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations
|
||||
|
||||
import generic.states.fields
|
||||
import generic.states.validators
|
||||
import InvenTree.status_codes
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("build", "0051_delete_buildorderattachment"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="build",
|
||||
name="status_custom_key",
|
||||
field=generic.states.fields.ExtraInvenTreeCustomStatusModelField(
|
||||
blank=True,
|
||||
default=None,
|
||||
help_text="Additional status information for this item",
|
||||
null=True,
|
||||
verbose_name="Custom status key",
|
||||
validators=[
|
||||
generic.states.validators.CustomStatusCodeValidator(
|
||||
status_class=InvenTree.status_codes.BuildStatus
|
||||
),
|
||||
]
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="build",
|
||||
name="status",
|
||||
field=generic.states.fields.InvenTreeCustomStatusModelField(
|
||||
choices=InvenTree.status_codes.BuildStatus.items(),
|
||||
default=10,
|
||||
help_text="Build status code",
|
||||
validators=[
|
||||
django.core.validators.MinValueValidator(0),
|
||||
generic.states.validators.CustomStatusCodeValidator(
|
||||
status_class=InvenTree.status_codes.BuildStatus
|
||||
),
|
||||
],
|
||||
verbose_name="Build Status",
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -1,20 +0,0 @@
|
||||
# Generated by Django 4.2.16 on 2024-10-16 06:19
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('part', '0130_alter_parttesttemplate_part'),
|
||||
('build', '0052_build_status_custom_key_alter_build_status'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='build',
|
||||
name='part',
|
||||
field=models.ForeignKey(help_text='Select part to build', limit_choices_to={'assembly': True}, on_delete=django.db.models.deletion.CASCADE, related_name='builds', to='part.part', verbose_name='Part'),
|
||||
),
|
||||
]
|
||||
@@ -1,18 +0,0 @@
|
||||
# Generated by Django 4.2.18 on 2025-01-20 02:00
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('build', '0053_alter_build_part'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='build',
|
||||
name='start_date',
|
||||
field=models.DateField(blank=True, help_text='Scheduled start date for this build order', null=True, verbose_name='Build start date'),
|
||||
),
|
||||
]
|
||||
@@ -1,21 +0,0 @@
|
||||
# Generated by Django 4.2.19 on 2025-02-21 12:30
|
||||
|
||||
import InvenTree.fields
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("build", "0054_build_start_date"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='build',
|
||||
name='link',
|
||||
field=models.TextField(
|
||||
null=True, blank=True
|
||||
) # Temporary change to force new ALTER COLUMN operation in the next migration
|
||||
),
|
||||
]
|
||||
@@ -1,24 +0,0 @@
|
||||
# Generated by Django 4.2.19 on 2025-02-21 13:46
|
||||
|
||||
import InvenTree.fields
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("build", "0055_auto_20250221_1230"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="build",
|
||||
name="link",
|
||||
field=InvenTree.fields.InvenTreeURLField(
|
||||
blank=True,
|
||||
help_text="Link to external URL",
|
||||
max_length=2000,
|
||||
verbose_name="External Link",
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -1,22 +0,0 @@
|
||||
# Generated by Django 4.2.20 on 2025-03-13 22:34
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("build", "0056_alter_build_link"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="build",
|
||||
name="external",
|
||||
field=models.BooleanField(
|
||||
default=False,
|
||||
help_text="This build order is fulfilled externally",
|
||||
verbose_name="External Build",
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -1,19 +0,0 @@
|
||||
# Generated by Django 4.2.15 on 2024-09-26 10:11
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('build', '0057_build_external'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='buildline',
|
||||
name='consumed',
|
||||
field=models.DecimalField(decimal_places=5, default=0, help_text='Quantity of consumed stock', max_digits=15, validators=[django.core.validators.MinValueValidator(0)], verbose_name='Consumed'),
|
||||
),
|
||||
]
|
||||
@@ -16,7 +16,13 @@ class TestForwardMigrations(MigratorTestCase):
|
||||
Part = self.old_state.apps.get_model('part', 'part')
|
||||
|
||||
buildable_part = Part.objects.create(
|
||||
name='Widget', description='Buildable Part', active=True
|
||||
name='Widget',
|
||||
description='Buildable Part',
|
||||
active=True,
|
||||
level=0,
|
||||
tree_id=0,
|
||||
lft=0,
|
||||
rght=0,
|
||||
)
|
||||
|
||||
Build = self.old_state.apps.get_model('build', 'build')
|
||||
@@ -41,220 +47,3 @@ class TestForwardMigrations(MigratorTestCase):
|
||||
part.save()
|
||||
part.assembly = False
|
||||
part.save()
|
||||
|
||||
|
||||
class TestReferenceMigration(MigratorTestCase):
|
||||
"""Test custom migration which adds 'reference' field to Build model."""
|
||||
|
||||
migrate_from = ('build', unit_test.getOldestMigrationFile('build'))
|
||||
migrate_to = ('build', '0018_build_reference')
|
||||
|
||||
def prepare(self):
|
||||
"""Create some builds."""
|
||||
Part = self.old_state.apps.get_model('part', 'part')
|
||||
|
||||
part = Part.objects.create(name='Part', description='A test part')
|
||||
|
||||
Build = self.old_state.apps.get_model('build', 'build')
|
||||
|
||||
Build.objects.create(part=part, title='My very first build', quantity=10)
|
||||
|
||||
Build.objects.create(part=part, title='My very second build', quantity=10)
|
||||
|
||||
Build.objects.create(part=part, title='My very third build', quantity=10)
|
||||
|
||||
# Ensure that the builds *do not* have a 'reference' field
|
||||
for build in Build.objects.all():
|
||||
with self.assertRaises(AttributeError):
|
||||
print(build.reference)
|
||||
|
||||
def test_build_reference(self):
|
||||
"""Test that the build reference is correctly assigned to the PK of the Build."""
|
||||
Build = self.new_state.apps.get_model('build', 'build')
|
||||
|
||||
self.assertEqual(Build.objects.count(), 3)
|
||||
|
||||
# Check that the build reference is properly assigned
|
||||
for build in Build.objects.all():
|
||||
self.assertEqual(str(build.reference), str(build.pk))
|
||||
|
||||
|
||||
class TestReferencePatternMigration(MigratorTestCase):
|
||||
"""Unit test for data migration which converts reference to new format.
|
||||
|
||||
Ref: https://github.com/inventree/InvenTree/pull/3267
|
||||
"""
|
||||
|
||||
migrate_from = ('build', '0019_auto_20201019_1302')
|
||||
migrate_to = ('build', '0037_build_priority')
|
||||
|
||||
def prepare(self):
|
||||
"""Create some initial data prior to migration."""
|
||||
Setting = self.old_state.apps.get_model('common', 'inventreesetting')
|
||||
|
||||
# Create a custom existing prefix so we can confirm the operation is working
|
||||
Setting.objects.create(key='BUILDORDER_REFERENCE_PREFIX', value='BuildOrder-')
|
||||
|
||||
Part = self.old_state.apps.get_model('part', 'part')
|
||||
|
||||
assembly = Part.objects.create(
|
||||
name='Assy 1', description='An assembly', level=0, lft=0, rght=0, tree_id=0
|
||||
)
|
||||
|
||||
Build = self.old_state.apps.get_model('build', 'build')
|
||||
|
||||
for idx in range(1, 11):
|
||||
Build.objects.create(
|
||||
part=assembly,
|
||||
title=f'Build {idx}',
|
||||
quantity=idx,
|
||||
reference=f'{idx + 100}',
|
||||
level=0,
|
||||
lft=0,
|
||||
rght=0,
|
||||
tree_id=0,
|
||||
)
|
||||
|
||||
def test_reference_migration(self):
|
||||
"""Test that the reference fields have been correctly updated."""
|
||||
Build = self.new_state.apps.get_model('build', 'build')
|
||||
|
||||
for build in Build.objects.all():
|
||||
self.assertTrue(build.reference.startswith('BuildOrder-'))
|
||||
|
||||
Setting = self.new_state.apps.get_model('common', 'inventreesetting')
|
||||
|
||||
pattern = Setting.objects.get(key='BUILDORDER_REFERENCE_PATTERN')
|
||||
|
||||
self.assertEqual(pattern.value, 'BuildOrder-{ref:04d}')
|
||||
|
||||
|
||||
class TestBuildLineCreation(MigratorTestCase):
|
||||
"""Test that build lines are correctly created for existing builds.
|
||||
|
||||
Ref: https://github.com/inventree/InvenTree/pull/4855
|
||||
|
||||
This PR added the 'BuildLine' model, which acts as a link between a Build and a BomItem.
|
||||
|
||||
- Migration 0044 creates BuildLine objects for existing builds.
|
||||
- Migration 0046 links any existing BuildItem objects to corresponding BuildLine
|
||||
"""
|
||||
|
||||
migrate_from = ('build', '0041_alter_build_title')
|
||||
migrate_to = ('build', '0047_auto_20230606_1058')
|
||||
|
||||
def prepare(self):
|
||||
"""Create data to work with."""
|
||||
# Model references
|
||||
Part = self.old_state.apps.get_model('part', 'part')
|
||||
BomItem = self.old_state.apps.get_model('part', 'bomitem')
|
||||
Build = self.old_state.apps.get_model('build', 'build')
|
||||
BuildItem = self.old_state.apps.get_model('build', 'builditem')
|
||||
StockItem = self.old_state.apps.get_model('stock', 'stockitem')
|
||||
|
||||
# The "BuildLine" model does not exist yet
|
||||
with self.assertRaises(LookupError):
|
||||
self.old_state.apps.get_model('build', 'buildline')
|
||||
|
||||
# Create a part
|
||||
assembly = Part.objects.create(
|
||||
name='Assembly',
|
||||
description='An assembly',
|
||||
assembly=True,
|
||||
level=0,
|
||||
lft=0,
|
||||
rght=0,
|
||||
tree_id=0,
|
||||
)
|
||||
|
||||
# Create components
|
||||
for idx in range(1, 11):
|
||||
part = Part.objects.create(
|
||||
name=f'Part {idx}',
|
||||
description=f'Part {idx}',
|
||||
level=0,
|
||||
lft=0,
|
||||
rght=0,
|
||||
tree_id=0,
|
||||
)
|
||||
|
||||
# Create plentiful stock
|
||||
StockItem.objects.create(
|
||||
part=part, quantity=1000, level=0, lft=0, rght=0, tree_id=0
|
||||
)
|
||||
|
||||
# Create a BOM item
|
||||
BomItem.objects.create(
|
||||
part=assembly, sub_part=part, quantity=idx, reference=f'REF-{idx}'
|
||||
)
|
||||
|
||||
# Create some builds
|
||||
for idx in range(1, 4):
|
||||
build = Build.objects.create(
|
||||
part=assembly,
|
||||
title=f'Build {idx}',
|
||||
quantity=idx * 10,
|
||||
reference=f'REF-{idx}',
|
||||
level=0,
|
||||
lft=0,
|
||||
rght=0,
|
||||
tree_id=0,
|
||||
)
|
||||
|
||||
# Allocate stock to the build
|
||||
for bom_item in BomItem.objects.all():
|
||||
stock_item = StockItem.objects.get(part=bom_item.sub_part)
|
||||
BuildItem.objects.create(
|
||||
build=build,
|
||||
bom_item=bom_item,
|
||||
stock_item=stock_item,
|
||||
quantity=bom_item.quantity,
|
||||
)
|
||||
|
||||
def test_build_line_creation(self):
|
||||
"""Test that the BuildLine objects have been created correctly."""
|
||||
Build = self.new_state.apps.get_model('build', 'build')
|
||||
BomItem = self.new_state.apps.get_model('part', 'bomitem')
|
||||
BuildLine = self.new_state.apps.get_model('build', 'buildline')
|
||||
BuildItem = self.new_state.apps.get_model('build', 'builditem')
|
||||
StockItem = self.new_state.apps.get_model('stock', 'stockitem')
|
||||
|
||||
# There should be 3x builds
|
||||
self.assertEqual(Build.objects.count(), 3)
|
||||
|
||||
# 10x BOMItem objects
|
||||
self.assertEqual(BomItem.objects.count(), 10)
|
||||
|
||||
# 10x StockItem objects
|
||||
self.assertEqual(StockItem.objects.count(), 10)
|
||||
|
||||
# And 30x BuildLine items (1 for each BomItem for each Build)
|
||||
self.assertEqual(BuildLine.objects.count(), 30)
|
||||
|
||||
# And 30x BuildItem objects (1 for each BomItem for each Build)
|
||||
self.assertEqual(BuildItem.objects.count(), 30)
|
||||
|
||||
# Check that each BuildItem has been linked to a BuildLine
|
||||
for item in BuildItem.objects.all():
|
||||
self.assertIsNotNone(item.build_line)
|
||||
self.assertEqual(item.stock_item.part, item.build_line.bom_item.sub_part)
|
||||
|
||||
item = BuildItem.objects.first()
|
||||
|
||||
# Check that the "build" field has been removed
|
||||
with self.assertRaises(AttributeError):
|
||||
item.build
|
||||
|
||||
# Check that the "bom_item" field has been removed
|
||||
with self.assertRaises(AttributeError):
|
||||
item.bom_item
|
||||
|
||||
# Check that each BuildLine is correctly configured
|
||||
for line in BuildLine.objects.all():
|
||||
# Check that the quantity is correct
|
||||
self.assertEqual(
|
||||
line.quantity, line.build.quantity * line.bom_item.quantity
|
||||
)
|
||||
|
||||
# Check that the linked parts are correct
|
||||
self.assertEqual(line.build.part, line.bom_item.part)
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
# Generated by Django 2.2.4 on 2019-09-02 23:02
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class CreateModelOrSkip(migrations.CreateModel):
|
||||
"""Custom migration operation to create a model if it does not already exist.
|
||||
|
||||
- If the model already exists, the migration is skipped
|
||||
- This class has been added to deal with some errors being thrown in CI tests
|
||||
- The 'common_currency' table doesn't exist anymore anyway!
|
||||
- In the future, these migrations will be squashed
|
||||
"""
|
||||
|
||||
def database_forwards(self, app_label, schema_editor, from_state, to_state) -> None:
|
||||
"""Forwards migration *attempts* to create the model, but will fail gracefully if it already exists"""
|
||||
|
||||
try:
|
||||
super().database_forwards(app_label, schema_editor, from_state, to_state)
|
||||
except Exception: # pragma: no cover
|
||||
pass
|
||||
|
||||
def state_forwards(self, app_label, state) -> None:
|
||||
try:
|
||||
super().state_forwards(app_label, state)
|
||||
except Exception: # pragma: no cover
|
||||
pass
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
atomic = False
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
CreateModelOrSkip(
|
||||
name='Currency',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('symbol', models.CharField(help_text='Currency Symbol e.g. $', max_length=10)),
|
||||
('suffix', models.CharField(help_text='Currency Suffix e.g. AUD', max_length=10, unique=True)),
|
||||
('description', models.CharField(help_text='Currency Description', max_length=100)),
|
||||
('value', models.DecimalField(decimal_places=5, help_text='Currency Value', max_digits=10, validators=[django.core.validators.MinValueValidator(1e-05), django.core.validators.MaxValueValidator(100000)])),
|
||||
('base', models.BooleanField(default=False, help_text='Use this currency as the base currency')),
|
||||
],
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,127 @@
|
||||
# Generated by Django 5.2.17 on 2026-09-09 23:26
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
replaces = [
|
||||
("common", "0001_initial"),
|
||||
("common", "0002_auto_20190902_2304"),
|
||||
("common", "0003_auto_20190902_2310"),
|
||||
("common", "0004_inventreesetting"),
|
||||
("common", "0005_auto_20190915_1256"),
|
||||
("common", "0006_auto_20200203_0951"),
|
||||
("common", "0007_colortheme"),
|
||||
]
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = []
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="Currency",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"symbol",
|
||||
models.CharField(help_text="Currency Symbol e.g. $", max_length=10),
|
||||
),
|
||||
(
|
||||
"suffix",
|
||||
models.CharField(
|
||||
help_text="Currency Suffix e.g. AUD", max_length=10, unique=True
|
||||
),
|
||||
),
|
||||
(
|
||||
"description",
|
||||
models.CharField(help_text="Currency Description", max_length=100),
|
||||
),
|
||||
(
|
||||
"value",
|
||||
models.DecimalField(
|
||||
decimal_places=5,
|
||||
default=1.0,
|
||||
help_text="Currency Value",
|
||||
max_digits=10,
|
||||
validators=[
|
||||
django.core.validators.MinValueValidator(1e-05),
|
||||
django.core.validators.MaxValueValidator(100000),
|
||||
],
|
||||
),
|
||||
),
|
||||
(
|
||||
"base",
|
||||
models.BooleanField(
|
||||
default=False,
|
||||
help_text="Use this currency as the base currency",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name_plural": "Currencies",
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="InvenTreeSetting",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"key",
|
||||
models.CharField(
|
||||
help_text="Settings key", max_length=50, unique=True
|
||||
),
|
||||
),
|
||||
(
|
||||
"value",
|
||||
models.CharField(
|
||||
blank=True, help_text="Settings value", max_length=200
|
||||
),
|
||||
),
|
||||
(
|
||||
"description",
|
||||
models.CharField(
|
||||
blank=True, help_text="Settings description", max_length=200
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "InvenTree Setting",
|
||||
"verbose_name_plural": "InvenTree Settings",
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="ColorTheme",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("name", models.CharField(blank=True, default="", max_length=20)),
|
||||
("user", models.CharField(max_length=150, unique=True)),
|
||||
],
|
||||
),
|
||||
]
|
||||
@@ -1,17 +0,0 @@
|
||||
# Generated by Django 2.2.4 on 2019-09-02 23:04
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('common', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='currency',
|
||||
options={'verbose_name_plural': 'Currencies'},
|
||||
),
|
||||
]
|
||||
@@ -1,19 +0,0 @@
|
||||
# Generated by Django 2.2.4 on 2019-09-02 23:10
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('common', '0002_auto_20190902_2304'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='currency',
|
||||
name='value',
|
||||
field=models.DecimalField(decimal_places=5, default=1.0, help_text='Currency Value', max_digits=10, validators=[django.core.validators.MinValueValidator(1e-05), django.core.validators.MaxValueValidator(100000)]),
|
||||
),
|
||||
]
|
||||
@@ -1,21 +0,0 @@
|
||||
# Generated by Django 2.2.5 on 2019-09-15 12:44
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('common', '0003_auto_20190902_2310'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='InvenTreeSetting',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('key', models.CharField(help_text='Settings key', max_length=50, unique=True)),
|
||||
('value', models.CharField(blank=True, help_text='Settings value', max_length=200)),
|
||||
],
|
||||
),
|
||||
]
|
||||
@@ -1,23 +0,0 @@
|
||||
# Generated by Django 2.2.5 on 2019-09-15 12:56
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('common', '0004_inventreesetting'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='inventreesetting',
|
||||
name='description',
|
||||
field=models.CharField(blank=True, help_text='Settings description', max_length=200),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='inventreesetting',
|
||||
name='key',
|
||||
field=models.CharField(help_text='Settings key', max_length=50, unique=True),
|
||||
),
|
||||
]
|
||||
@@ -1,17 +0,0 @@
|
||||
# Generated by Django 2.2.9 on 2020-02-03 09:51
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('common', '0005_auto_20190915_1256'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='inventreesetting',
|
||||
options={'verbose_name': 'InvenTree Setting', 'verbose_name_plural': 'InvenTree Settings'},
|
||||
),
|
||||
]
|
||||
@@ -1,21 +0,0 @@
|
||||
# Generated by Django 3.0.7 on 2020-09-09 19:50
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('common', '0006_auto_20200203_0951'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='ColorTheme',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(blank=True, default='', max_length=20)),
|
||||
('user', models.CharField(max_length=150, unique=True)),
|
||||
],
|
||||
),
|
||||
]
|
||||
@@ -1,17 +0,0 @@
|
||||
# Generated by Django 3.0.7 on 2020-10-19 13:02
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('common', '0007_colortheme'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='inventreesetting',
|
||||
name='description',
|
||||
),
|
||||
]
|
||||
+404
@@ -0,0 +1,404 @@
|
||||
# Generated by Django 5.2.17 on 2026-09-08 23:18
|
||||
|
||||
import InvenTree.config
|
||||
import InvenTree.fields
|
||||
import InvenTree.models
|
||||
import common.models
|
||||
import common.validators
|
||||
import django.db.models.deletion
|
||||
import taggit.managers
|
||||
import uuid
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
from moneyed import CURRENCIES
|
||||
|
||||
|
||||
def set_default_currency(apps, schema_editor):
|
||||
"""Migrate the currency setting from config.yml to db."""
|
||||
from common.currency import currency_codes
|
||||
from common.models import InvenTreeSetting
|
||||
|
||||
base_currency = InvenTree.config.get_setting(
|
||||
'INVENTREE_BASE_CURRENCY', 'base_currency', 'USD'
|
||||
)
|
||||
|
||||
if base_currency not in currency_codes():
|
||||
base_currency = currency_codes()[0] if currency_codes() else 'USD'
|
||||
|
||||
InvenTreeSetting.set_setting(
|
||||
'INVENTREE_DEFAULT_CURRENCY', base_currency, None, create=True
|
||||
)
|
||||
|
||||
|
||||
def set_currencies(apps, schema_editor):
|
||||
"""Set the default currency codes.
|
||||
|
||||
Ref: https://github.com/inventree/InvenTree/pull/7390
|
||||
|
||||
Transfers any currency codes configured externally (config file /
|
||||
environment variable) into the database settings model.
|
||||
"""
|
||||
InvenTreeSetting = apps.get_model('common', 'InvenTreeSetting')
|
||||
|
||||
key = 'CURRENCY_CODES'
|
||||
|
||||
codes = InvenTree.config.get_setting('INVENTREE_CURRENCIES', 'currencies', None)
|
||||
|
||||
if codes is None:
|
||||
return
|
||||
|
||||
if isinstance(codes, str):
|
||||
codes = codes.split(',')
|
||||
|
||||
valid_codes = {code.strip().upper() for code in codes if code.strip().upper() in CURRENCIES}
|
||||
|
||||
if len(valid_codes) == 0:
|
||||
return
|
||||
|
||||
value = ','.join(valid_codes)
|
||||
|
||||
setting = InvenTreeSetting.objects.filter(key=key).first()
|
||||
|
||||
if setting:
|
||||
setting.value = value
|
||||
setting.save()
|
||||
else:
|
||||
InvenTreeSetting(key=key, value=value).save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
replaces = [('common', '0008_remove_inventreesetting_description'), ('common', '0009_delete_currency'), ('common', '0010_migrate_currency_setting'), ('common', '0011_auto_20210722_2114'), ('common', '0012_notificationentry'), ('common', '0013_webhookendpoint_webhookmessage'), ('common', '0014_notificationmessage'), ('common', '0015_newsfeedentry'), ('common', '0016_alter_notificationentry_updated'), ('common', '0017_notesimage'), ('common', '0018_projectcode'), ('common', '0019_projectcode_metadata'), ('common', '0020_customunit'), ('common', '0021_auto_20230805_1748'), ('common', '0022_projectcode_responsible'), ('common', '0023_auto_20240602_1332'), ('common', '0024_notesimage_model_id_notesimage_model_type'), ('common', '0025_attachment'), ('common', '0026_auto_20240608_1238'), ('common', '0027_alter_customunit_symbol'), ('common', '0028_colortheme_user_obj'), ('common', '0029_inventreecustomuserstatemodel'), ('common', '0030_barcodescanresult'), ('common', '0031_auto_20241026_0024'), ('common', '0032_selectionlist_selectionlistentry_and_more'), ('common', '0033_delete_colortheme'), ('common', '0034_alter_inventreecustomuserstatemodel_unique_together_and_more'), ('common', '0035_auto_20250221_1513'), ('common', '0036_alter_attachment_link'), ('common', '0037_dataoutput'), ('common', '0038_alter_attachment_model_type'), ('common', '0039_emailthread_emailmessage')]
|
||||
|
||||
dependencies = [
|
||||
('common', '0001_squashed_0007_colortheme'),
|
||||
('company', '0027_remove_supplierpricebreak_currency'),
|
||||
('contenttypes', '0002_remove_content_type_name'),
|
||||
('part', '0057_remove_partsellpricebreak_currency'),
|
||||
('plugin', '0009_alter_pluginconfig_key'),
|
||||
('users', '0010_alter_apitoken_key'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='inventreesetting',
|
||||
name='description',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='Currency',
|
||||
),
|
||||
migrations.RunPython(code=set_default_currency, reverse_code=migrations.RunPython.noop),
|
||||
migrations.CreateModel(
|
||||
name='WebhookEndpoint',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('endpoint_id', models.CharField(default=uuid.uuid4, editable=False, help_text='Endpoint at which this webhook is received', max_length=255, verbose_name='Endpoint')),
|
||||
('name', models.CharField(blank=True, help_text='Name for this webhook', max_length=255, null=True, verbose_name='Name')),
|
||||
('active', models.BooleanField(default=True, help_text='Is this webhook active', verbose_name='Active')),
|
||||
('token', models.CharField(blank=True, default=uuid.uuid4, help_text='Token for access', max_length=255, null=True, verbose_name='Token')),
|
||||
('secret', models.CharField(blank=True, help_text='Shared secret for HMAC', max_length=255, null=True, verbose_name='Secret')),
|
||||
('user', models.ForeignKey(blank=True, help_text='User', null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='User')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='WebhookMessage',
|
||||
fields=[
|
||||
('message_id', models.UUIDField(default=uuid.uuid4, editable=False, help_text='Unique identifier for this message', primary_key=True, serialize=False, verbose_name='Message ID')),
|
||||
('host', models.CharField(editable=False, help_text='Host from which this message was received', max_length=255, verbose_name='Host')),
|
||||
('header', models.CharField(blank=True, editable=False, help_text='Header of this message', max_length=255, null=True, verbose_name='Header')),
|
||||
('body', models.JSONField(blank=True, editable=False, help_text='Body of this message', null=True, verbose_name='Body')),
|
||||
('worked_on', models.BooleanField(default=False, help_text='Was the work on this message finished?', verbose_name='Worked on')),
|
||||
('endpoint', models.ForeignKey(blank=True, help_text='Endpoint on which this message was received', null=True, on_delete=django.db.models.deletion.SET_NULL, to='common.webhookendpoint', verbose_name='Endpoint')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='NotificationMessage',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('target_object_id', models.PositiveIntegerField()),
|
||||
('source_object_id', models.PositiveIntegerField(blank=True, null=True)),
|
||||
('category', models.CharField(max_length=250)),
|
||||
('name', models.CharField(max_length=250)),
|
||||
('message', models.CharField(blank=True, max_length=250, null=True)),
|
||||
('creation', models.DateTimeField(auto_now_add=True)),
|
||||
('read', models.BooleanField(default=False)),
|
||||
('source_content_type', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='notification_source', to='contenttypes.contenttype')),
|
||||
('target_content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notification_target', to='contenttypes.contenttype')),
|
||||
('user', models.ForeignKey(blank=True, help_text='User', null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='User')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='NewsFeedEntry',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('feed_id', models.CharField(max_length=250, unique=True, verbose_name='Id')),
|
||||
('title', models.CharField(max_length=250, verbose_name='Title')),
|
||||
('link', models.URLField(max_length=250, verbose_name='Link')),
|
||||
('published', models.DateTimeField(max_length=250, verbose_name='Published')),
|
||||
('author', models.CharField(max_length=250, verbose_name='Author')),
|
||||
('summary', models.CharField(max_length=250, verbose_name='Summary')),
|
||||
('read', models.BooleanField(default=False, help_text='Was this news item read?', verbose_name='Read')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='NotificationEntry',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('key', models.CharField(max_length=250)),
|
||||
('uid', models.IntegerField()),
|
||||
('updated', models.DateTimeField(auto_now=True, help_text='Timestamp of last update', null=True, verbose_name='Updated')),
|
||||
],
|
||||
options={
|
||||
'unique_together': {('key', 'uid')},
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='NotesImage',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('image', models.ImageField(help_text='Image file', upload_to=common.models.rename_notes_image, verbose_name='Image')),
|
||||
('date', models.DateTimeField(auto_now_add=True)),
|
||||
('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='CustomUnit',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(help_text='Unit name', max_length=50, unique=True, verbose_name='Name')),
|
||||
('symbol', models.CharField(blank=True, help_text='Optional unit symbol', max_length=10, unique=True, verbose_name='Symbol')),
|
||||
('definition', models.CharField(help_text='Unit definition', max_length=50, verbose_name='Definition')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Custom Unit',
|
||||
},
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='inventreesetting',
|
||||
name='value',
|
||||
field=models.CharField(blank=True, help_text='Settings value', max_length=2000),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='InvenTreeUserSetting',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('value', models.CharField(blank=True, help_text='Settings value', max_length=2000)),
|
||||
('key', models.CharField(help_text='Settings key', max_length=50)),
|
||||
('user', models.ForeignKey(blank=True, help_text='User', null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='User')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'InvenTree User Setting',
|
||||
'verbose_name_plural': 'InvenTree User Settings',
|
||||
'constraints': [models.UniqueConstraint(fields=('key', 'user'), name='unique key and user')],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ProjectCode',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('code', models.CharField(help_text='Unique project code', max_length=50, unique=True, verbose_name='Project Code')),
|
||||
('description', models.CharField(blank=True, help_text='Project description', max_length=200, verbose_name='Description')),
|
||||
('metadata', models.JSONField(blank=True, help_text='JSON metadata field, for use by external plugins', null=True, verbose_name='Plugin Metadata')),
|
||||
('responsible', models.ForeignKey(blank=True, help_text='User or group responsible for this project', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='project_codes', to='users.owner', verbose_name='Responsible')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Project Code',
|
||||
},
|
||||
),
|
||||
migrations.RunPython(code=set_currencies, reverse_code=migrations.RunPython.noop),
|
||||
migrations.AddField(
|
||||
model_name='notesimage',
|
||||
name='model_id',
|
||||
field=models.IntegerField(blank=True, default=None, help_text='Target model ID for this image', null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='notesimage',
|
||||
name='model_type',
|
||||
field=models.CharField(blank=True, help_text='Target model type for this image', max_length=100, null=True),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Attachment',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('model_id', models.PositiveIntegerField()),
|
||||
('attachment', models.FileField(blank=True, help_text='Select file to attach', null=True, upload_to=common.models.rename_attachment, verbose_name='Attachment')),
|
||||
('link', InvenTree.fields.InvenTreeURLField(blank=True, help_text='Link to external URL', max_length=2000, null=True, verbose_name='Link')),
|
||||
('comment', models.CharField(blank=True, help_text='Attachment comment', max_length=250, verbose_name='Comment')),
|
||||
('upload_date', models.DateField(auto_now_add=True, help_text='Date the file was uploaded', null=True, verbose_name='Upload date')),
|
||||
('file_size', models.PositiveIntegerField(default=0, help_text='File size in bytes', verbose_name='File size')),
|
||||
('model_type', models.CharField(help_text='Target model type for this image', max_length=100, validators=[common.validators.validate_attachment_model_type])),
|
||||
('upload_user', models.ForeignKey(blank=True, help_text='User', null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='User')),
|
||||
('metadata', models.JSONField(blank=True, help_text='JSON metadata field, for use by external plugins', null=True, verbose_name='Plugin Metadata')),
|
||||
('tags', taggit.managers.TaggableManager(blank=True, help_text='A comma-separated list of tags.', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='Tags')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Attachment',
|
||||
},
|
||||
bases=(InvenTree.models.PluginValidationMixin, models.Model),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='customunit',
|
||||
name='symbol',
|
||||
field=models.CharField(blank=True, help_text='Optional unit symbol', max_length=10, verbose_name='Symbol'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='colortheme',
|
||||
name='user_obj',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='InvenTreeCustomUserStateModel',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('key', models.IntegerField(help_text='Numerical value that will be saved in the models database', verbose_name='Value')),
|
||||
('name', models.CharField(help_text='Name of the state', max_length=250, validators=[common.validators.validate_uppercase, common.validators.validate_variable_string], verbose_name='Name')),
|
||||
('label', models.CharField(help_text='Label that will be displayed in the frontend', max_length=250, verbose_name='Label')),
|
||||
('color', models.CharField(choices=[('primary', 'primary'), ('secondary', 'secondary'), ('success', 'success'), ('danger', 'danger'), ('warning', 'warning'), ('info', 'info'), ('dark', 'dark')], default='secondary', help_text='Color that will be displayed in the frontend', max_length=10, verbose_name='Color')),
|
||||
('logical_key', models.IntegerField(help_text='State logical key that is equal to this custom state in business logic', verbose_name='Logical Key')),
|
||||
('reference_status', models.CharField(help_text='Status set that is extended with this custom state', max_length=250, verbose_name='Reference Status Set')),
|
||||
('model', models.ForeignKey(blank=True, help_text='Model this state is associated with', null=True, on_delete=django.db.models.deletion.SET_NULL, to='contenttypes.contenttype', verbose_name='Model')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Custom State',
|
||||
'verbose_name_plural': 'Custom States',
|
||||
'unique_together': {('reference_status', 'key'), ('reference_status', 'name')},
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='BarcodeScanResult',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('data', models.CharField(help_text='Barcode data', max_length=250, verbose_name='Data')),
|
||||
('timestamp', models.DateTimeField(auto_now_add=True, help_text='Date and time of the barcode scan', verbose_name='Timestamp')),
|
||||
('endpoint', models.CharField(blank=True, help_text='URL endpoint which processed the barcode', max_length=250, null=True, verbose_name='Path')),
|
||||
('context', models.JSONField(blank=True, help_text='Context data for the barcode scan', max_length=1000, null=True, verbose_name='Context')),
|
||||
('response', models.JSONField(blank=True, help_text='Response data from the barcode scan', max_length=1000, null=True, verbose_name='Response')),
|
||||
('result', models.BooleanField(default=False, help_text='Was the barcode scan successful?', verbose_name='Result')),
|
||||
('user', models.ForeignKey(blank=True, help_text='User who scanned the barcode', null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='User')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Barcode Scan',
|
||||
},
|
||||
bases=(InvenTree.models.PluginValidationMixin, models.Model),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='SelectionList',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('metadata', models.JSONField(blank=True, help_text='JSON metadata field, for use by external plugins', null=True, verbose_name='Plugin Metadata')),
|
||||
('name', models.CharField(help_text='Name of the selection list', max_length=100, unique=True, verbose_name='Name')),
|
||||
('description', models.CharField(blank=True, help_text='Description of the selection list', max_length=250, verbose_name='Description')),
|
||||
('locked', models.BooleanField(default=False, help_text='Is this selection list locked?', verbose_name='Locked')),
|
||||
('active', models.BooleanField(default=True, help_text='Can this selection list be used?', verbose_name='Active')),
|
||||
('source_string', models.CharField(blank=True, help_text='Optional string identifying the source used for this list', max_length=1000, verbose_name='Source String')),
|
||||
('created', models.DateTimeField(auto_now_add=True, help_text='Date and time that the selection list was created', verbose_name='Created')),
|
||||
('last_updated', models.DateTimeField(auto_now=True, help_text='Date and time that the selection list was last updated', verbose_name='Last Updated')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Selection List',
|
||||
'verbose_name_plural': 'Selection Lists',
|
||||
},
|
||||
bases=(InvenTree.models.PluginValidationMixin, models.Model),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='SelectionListEntry',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('value', models.CharField(help_text='Value of the selection list entry', max_length=255, verbose_name='Value')),
|
||||
('label', models.CharField(help_text='Label for the selection list entry', max_length=255, verbose_name='Label')),
|
||||
('description', models.CharField(blank=True, help_text='Description of the selection list entry', max_length=250, verbose_name='Description')),
|
||||
('active', models.BooleanField(default=True, help_text='Is this selection list entry active?', verbose_name='Active')),
|
||||
('list', models.ForeignKey(blank=True, help_text='Selection list to which this entry belongs', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='entries', to='common.selectionlist', verbose_name='Selection List')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Selection List Entry',
|
||||
'verbose_name_plural': 'Selection List Entries',
|
||||
'unique_together': {('list', 'value')},
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='selectionlist',
|
||||
name='default',
|
||||
field=models.ForeignKey(blank=True, help_text='Default entry for this selection list', null=True, on_delete=django.db.models.deletion.SET_NULL, to='common.selectionlistentry', verbose_name='Default Entry'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='selectionlist',
|
||||
name='source_plugin',
|
||||
field=models.ForeignKey(blank=True, help_text='Plugin which provides the selection list', null=True, on_delete=django.db.models.deletion.SET_NULL, to='plugin.pluginconfig', verbose_name='Source Plugin'),
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='ColorTheme',
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='attachment',
|
||||
name='link',
|
||||
field=InvenTree.fields.InvenTreeURLField(blank=True, help_text='Link to external URL', max_length=2000, null=True, verbose_name='Link'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='DataOutput',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created', models.DateField(auto_now_add=True)),
|
||||
('total', models.PositiveIntegerField(default=1)),
|
||||
('progress', models.PositiveIntegerField(default=0)),
|
||||
('complete', models.BooleanField(default=False)),
|
||||
('output_type', models.CharField(blank=True, max_length=100, null=True)),
|
||||
('template_name', models.CharField(blank=True, max_length=100, null=True)),
|
||||
('plugin', models.CharField(blank=True, max_length=100, null=True)),
|
||||
('output', models.FileField(blank=True, null=True, upload_to='data_output')),
|
||||
('errors', models.JSONField(blank=True, null=True)),
|
||||
('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='attachment',
|
||||
name='model_type',
|
||||
field=models.CharField(help_text='Target model type for image', max_length=100, validators=[common.validators.validate_attachment_model_type], verbose_name='Model type'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='EmailThread',
|
||||
fields=[
|
||||
('metadata', models.JSONField(blank=True, help_text='JSON metadata field, for use by external plugins', null=True, verbose_name='Plugin Metadata')),
|
||||
('key', models.CharField(blank=True, help_text='Unique key for this thread (used to identify the thread)', max_length=250, null=True, verbose_name='Key')),
|
||||
('global_id', models.UUIDField(default=uuid.uuid4, editable=False, help_text='Unique identifier for this thread', primary_key=True, serialize=False, verbose_name='Global ID')),
|
||||
('started_internal', models.BooleanField(default=False, help_text='Was this thread started internally?', verbose_name='Started Internal')),
|
||||
('created', models.DateTimeField(auto_now_add=True, help_text='Date and time that the thread was created', verbose_name='Created')),
|
||||
('updated', models.DateTimeField(auto_now=True, help_text='Date and time that the thread was last updated', verbose_name='Updated')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Email Thread',
|
||||
'verbose_name_plural': 'Email Threads',
|
||||
'ordering': ['-updated'],
|
||||
'unique_together': {('key', 'global_id')},
|
||||
},
|
||||
bases=(InvenTree.models.PluginValidationMixin, models.Model),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='EmailMessage',
|
||||
fields=[
|
||||
('global_id', models.UUIDField(default=uuid.uuid4, editable=False, help_text='Unique identifier for this message', primary_key=True, serialize=False, unique=True, verbose_name='Global ID')),
|
||||
('message_id_key', models.CharField(blank=True, help_text='Identifier for this message (might be supplied by external system)', max_length=250, null=True, verbose_name='Message ID')),
|
||||
('thread_id_key', models.CharField(blank=True, help_text='Identifier for this message thread (might be supplied by external system)', max_length=250, null=True, verbose_name='Thread ID')),
|
||||
('subject', models.CharField(max_length=250)),
|
||||
('body', models.TextField()),
|
||||
('to', models.EmailField(max_length=254)),
|
||||
('sender', models.EmailField(max_length=254)),
|
||||
('status', models.CharField(blank=True, choices=[('A', 'Announced'), ('S', 'Sent'), ('F', 'Failed'), ('D', 'Delivered'), ('R', 'Read'), ('C', 'Confirmed')], max_length=50, null=True)),
|
||||
('timestamp', models.DateTimeField(auto_now_add=True)),
|
||||
('headers', models.JSONField(blank=True, null=True)),
|
||||
('full_message', models.TextField(blank=True, null=True)),
|
||||
('direction', models.CharField(blank=True, choices=[('I', 'Inbound'), ('O', 'Outbound')], max_length=50, null=True)),
|
||||
('priority', models.IntegerField(choices=[(0, 'None'), (1, 'Very High'), (2, 'High'), (3, 'Normal'), (4, 'Low'), (5, 'Very Low')], verbose_name='Priority')),
|
||||
('delivery_options', models.JSONField(blank=True, null=True)),
|
||||
('error_code', models.CharField(blank=True, max_length=50, null=True)),
|
||||
('error_message', models.TextField(blank=True, null=True)),
|
||||
('error_timestamp', models.DateTimeField(blank=True, null=True)),
|
||||
('thread', models.ForeignKey(blank=True, help_text='Linked thread for this message', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='messages', to='common.emailthread', verbose_name='Thread')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Email Message',
|
||||
'verbose_name_plural': 'Email Messages',
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -1,18 +0,0 @@
|
||||
# Generated by Django 3.0.7 on 2020-11-10 11:40
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('company', '0027_remove_supplierpricebreak_currency'),
|
||||
('part', '0057_remove_partsellpricebreak_currency'),
|
||||
('common', '0008_remove_inventreesetting_description'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.DeleteModel(
|
||||
name='Currency',
|
||||
),
|
||||
]
|
||||
@@ -1,33 +0,0 @@
|
||||
# Generated by Django 3.2.4 on 2021-07-01 15:39
|
||||
|
||||
from django.db import migrations
|
||||
from common.models import InvenTreeSetting
|
||||
from InvenTree.config import get_setting
|
||||
|
||||
def set_default_currency(apps, schema_editor):
|
||||
""" migrate the currency setting from config.yml to db """
|
||||
# get value from settings-file
|
||||
base_currency = get_setting('INVENTREE_BASE_CURRENCY', 'base_currency', 'USD')
|
||||
|
||||
from common.currency import currency_codes
|
||||
|
||||
# check if value is valid
|
||||
if base_currency not in currency_codes():
|
||||
if len (currency_codes()) > 0:
|
||||
base_currency = currency_codes()[0]
|
||||
else:
|
||||
base_currency = 'USD'
|
||||
|
||||
# write to database
|
||||
InvenTreeSetting.set_setting('INVENTREE_DEFAULT_CURRENCY', base_currency, None, create=True)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('common', '0009_delete_currency'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(set_default_currency, reverse_code=migrations.RunPython.noop),
|
||||
]
|
||||
@@ -1,33 +0,0 @@
|
||||
# Generated by Django 3.2.4 on 2021-07-22 21:14
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('common', '0010_migrate_currency_setting'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='InvenTreeUserSetting',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('value', models.CharField(blank=True, help_text='Settings value', max_length=200)),
|
||||
('key', models.CharField(help_text='Settings key', max_length=50)),
|
||||
('user', models.ForeignKey(blank=True, help_text='User', null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='User')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'InvenTree User Setting',
|
||||
'verbose_name_plural': 'InvenTree User Settings',
|
||||
},
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name='inventreeusersetting',
|
||||
constraint=models.UniqueConstraint(fields=('key', 'user'), name='unique key and user'),
|
||||
),
|
||||
]
|
||||
@@ -1,25 +0,0 @@
|
||||
# Generated by Django 3.2.5 on 2021-11-03 13:54
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('common', '0011_auto_20210722_2114'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='NotificationEntry',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('key', models.CharField(max_length=250)),
|
||||
('uid', models.IntegerField()),
|
||||
('updated', models.DateTimeField(auto_now=True)),
|
||||
],
|
||||
options={
|
||||
'unique_together': {('key', 'uid')},
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -1,40 +0,0 @@
|
||||
# Generated by Django 3.2.5 on 2021-11-19 21:34
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('common', '0012_notificationentry'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='WebhookEndpoint',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('endpoint_id', models.CharField(default=uuid.uuid4, editable=False, help_text='Endpoint at which this webhook is received', max_length=255, verbose_name='Endpoint')),
|
||||
('name', models.CharField(blank=True, help_text='Name for this webhook', max_length=255, null=True, verbose_name='Name')),
|
||||
('active', models.BooleanField(default=True, help_text='Is this webhook active', verbose_name='Active')),
|
||||
('token', models.CharField(blank=True, default=uuid.uuid4, help_text='Token for access', max_length=255, null=True, verbose_name='Token')),
|
||||
('secret', models.CharField(blank=True, help_text='Shared secret for HMAC', max_length=255, null=True, verbose_name='Secret')),
|
||||
('user', models.ForeignKey(blank=True, help_text='User', null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='User')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='WebhookMessage',
|
||||
fields=[
|
||||
('message_id', models.UUIDField(default=uuid.uuid4, editable=False, help_text='Unique identifier for this message', primary_key=True, serialize=False, verbose_name='Message ID')),
|
||||
('host', models.CharField(editable=False, help_text='Host from which this message was received', max_length=255, verbose_name='Host')),
|
||||
('header', models.CharField(blank=True, editable=False, help_text='Header of this message', max_length=255, null=True, verbose_name='Header')),
|
||||
('body', models.JSONField(blank=True, editable=False, help_text='Body of this message', null=True, verbose_name='Body')),
|
||||
('worked_on', models.BooleanField(default=False, help_text='Was the work on this message finished?', verbose_name='Worked on')),
|
||||
('endpoint', models.ForeignKey(blank=True, help_text='Endpoint on which this message was received', null=True, on_delete=django.db.models.deletion.SET_NULL, to='common.webhookendpoint', verbose_name='Endpoint')),
|
||||
],
|
||||
),
|
||||
]
|
||||
@@ -1,33 +0,0 @@
|
||||
# Generated by Django 3.2.5 on 2022-02-13 03:09
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('contenttypes', '0002_remove_content_type_name'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('common', '0013_webhookendpoint_webhookmessage'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='NotificationMessage',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('target_object_id', models.PositiveIntegerField()),
|
||||
('source_object_id', models.PositiveIntegerField(blank=True, null=True)),
|
||||
('category', models.CharField(max_length=250)),
|
||||
('name', models.CharField(max_length=250)),
|
||||
('message', models.CharField(blank=True, max_length=250, null=True)),
|
||||
('creation', models.DateTimeField(auto_now_add=True)),
|
||||
('read', models.BooleanField(default=False)),
|
||||
('source_content_type', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='notification_source', to='contenttypes.contenttype')),
|
||||
('target_content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notification_target', to='contenttypes.contenttype')),
|
||||
('user', models.ForeignKey(blank=True, help_text='User', null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='User')),
|
||||
],
|
||||
),
|
||||
]
|
||||
@@ -1,26 +0,0 @@
|
||||
# Generated by Django 3.2.14 on 2022-07-31 19:13
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('common', '0014_notificationmessage'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='NewsFeedEntry',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('feed_id', models.CharField(max_length=250, unique=True, verbose_name='Id')),
|
||||
('title', models.CharField(max_length=250, verbose_name='Title')),
|
||||
('link', models.URLField(max_length=250, verbose_name='Link')),
|
||||
('published', models.DateTimeField(max_length=250, verbose_name='Published')),
|
||||
('author', models.CharField(max_length=250, verbose_name='Author')),
|
||||
('summary', models.CharField(max_length=250, verbose_name='Summary')),
|
||||
('read', models.BooleanField(default=False, help_text='Was this news item read?', verbose_name='Read')),
|
||||
],
|
||||
),
|
||||
]
|
||||
@@ -1,18 +0,0 @@
|
||||
# Generated by Django 3.2.16 on 2023-01-15 14:04
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('common', '0015_newsfeedentry'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='notificationentry',
|
||||
name='updated',
|
||||
field=models.DateTimeField(auto_now=True, help_text='Timestamp of last update', null=True, verbose_name='Updated'),
|
||||
),
|
||||
]
|
||||
@@ -1,27 +0,0 @@
|
||||
# Generated by Django 3.2.18 on 2023-04-17 05:55
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
import common.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('common', '0016_alter_notificationentry_updated'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='NotesImage',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('image', models.ImageField(help_text='Image file', upload_to=common.models.rename_notes_image, verbose_name='Image')),
|
||||
('date', models.DateTimeField(auto_now_add=True)),
|
||||
('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
]
|
||||
@@ -1,24 +0,0 @@
|
||||
# Generated by Django 3.2.18 on 2023-04-19 02:06
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('common', '0017_notesimage'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='ProjectCode',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('code', models.CharField(help_text='Unique project code', max_length=50, unique=True, verbose_name='Project Code')),
|
||||
('description', models.CharField(blank=True, help_text='Project description', max_length=200, verbose_name='Description')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Project Code',
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -1,18 +0,0 @@
|
||||
# Generated by Django 3.2.18 on 2023-04-19 13:38
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('common', '0018_projectcode'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='projectcode',
|
||||
name='metadata',
|
||||
field=models.JSONField(blank=True, help_text='JSON metadata field, for use by external plugins', null=True, verbose_name='Plugin Metadata'),
|
||||
),
|
||||
]
|
||||
@@ -1,25 +0,0 @@
|
||||
# Generated by Django 3.2.20 on 2023-07-18 11:38
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('common', '0019_projectcode_metadata'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='CustomUnit',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(help_text='Unit name', max_length=50, unique=True, verbose_name='Name')),
|
||||
('symbol', models.CharField(blank=True, help_text='Optional unit symbol', max_length=10, unique=True, verbose_name='Symbol')),
|
||||
('definition', models.CharField(help_text='Unit definition', max_length=50, verbose_name='Definition')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Custom Unit',
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -1,23 +0,0 @@
|
||||
# Generated by Django 3.2.20 on 2023-08-05 17:48
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('common', '0020_customunit'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='inventreesetting',
|
||||
name='value',
|
||||
field=models.CharField(blank=True, help_text='Settings value', max_length=2000),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='inventreeusersetting',
|
||||
name='value',
|
||||
field=models.CharField(blank=True, help_text='Settings value', max_length=2000),
|
||||
),
|
||||
]
|
||||
@@ -1,20 +0,0 @@
|
||||
# Generated by Django 3.2.23 on 2023-11-20 08:04
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('users', '0010_alter_apitoken_key'),
|
||||
('common', '0021_auto_20230805_1748'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='projectcode',
|
||||
name='responsible',
|
||||
field=models.ForeignKey(blank=True, help_text='User or group responsible for this project', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='project_codes', to='users.owner', verbose_name='Responsible'),
|
||||
),
|
||||
]
|
||||
@@ -1,78 +0,0 @@
|
||||
# Generated by Django 4.2.12 on 2024-06-02 13:32
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations
|
||||
|
||||
from moneyed import CURRENCIES
|
||||
|
||||
import InvenTree.config
|
||||
|
||||
|
||||
def set_currencies(apps, schema_editor):
|
||||
"""Set the default currency codes.
|
||||
|
||||
Ref: https://github.com/inventree/InvenTree/pull/7390
|
||||
|
||||
Previously, the allowed currency codes were set in the external configuration
|
||||
(e.g via the configuration file or environment variables).
|
||||
|
||||
Now, they are set in the database (via the InvenTreeSetting model).
|
||||
|
||||
So, this data migration exists to transfer any configured currency codes,
|
||||
from the external configuration, into the database settings model.
|
||||
"""
|
||||
|
||||
InvenTreeSetting = apps.get_model('common', 'InvenTreeSetting')
|
||||
|
||||
key = 'CURRENCY_CODES'
|
||||
|
||||
codes = InvenTree.config.get_setting('INVENTREE_CURRENCIES', 'currencies', None)
|
||||
|
||||
if codes is None:
|
||||
# No currency codes are defined in the configuration file
|
||||
return
|
||||
|
||||
if type(codes) == str:
|
||||
codes = codes.split(',')
|
||||
|
||||
valid_codes = set()
|
||||
|
||||
for code in codes:
|
||||
code = code.strip().upper()
|
||||
|
||||
if code in CURRENCIES:
|
||||
valid_codes.add(code)
|
||||
|
||||
if len(valid_codes) == 0:
|
||||
print(f"No currency codes found in configuration file - skipping migration")
|
||||
return
|
||||
|
||||
value = ','.join(valid_codes)
|
||||
|
||||
if not settings.TESTING: # pragma: no cover
|
||||
print(f"Found existing currency codes:", value)
|
||||
|
||||
setting = InvenTreeSetting.objects.filter(key=key).first()
|
||||
|
||||
if setting:
|
||||
if not settings.TESTING: # pragma: no cover
|
||||
print(f"- Updating existing setting for currency codes")
|
||||
setting.value = value
|
||||
setting.save()
|
||||
else:
|
||||
if not settings.TESTING: # pragma: no cover
|
||||
print(f"- Creating new setting for currency codes")
|
||||
setting = InvenTreeSetting(key=key, value=value)
|
||||
setting.save()
|
||||
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('common', '0022_projectcode_responsible'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(set_currencies, reverse_code=migrations.RunPython.noop)
|
||||
]
|
||||
-25
@@ -1,25 +0,0 @@
|
||||
# Generated by Django 4.2.12 on 2024-05-22 12:27
|
||||
|
||||
import common.validators
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('common', '0023_auto_20240602_1332'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='notesimage',
|
||||
name='model_id',
|
||||
field=models.IntegerField(blank=True, default=None, help_text='Target model ID for this image', null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='notesimage',
|
||||
name='model_type',
|
||||
field=models.CharField(blank=True, null=True, help_text='Target model type for this image', max_length=100),
|
||||
),
|
||||
]
|
||||
@@ -1,43 +0,0 @@
|
||||
# Generated by Django 4.2.12 on 2024-06-08 12:37
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import taggit.managers
|
||||
|
||||
import common.models
|
||||
import common.validators
|
||||
import InvenTree.fields
|
||||
import InvenTree.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('contenttypes', '0002_remove_content_type_name'),
|
||||
('common', '0024_notesimage_model_id_notesimage_model_type'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Attachment',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('model_id', models.PositiveIntegerField()),
|
||||
('attachment', models.FileField(blank=True, help_text='Select file to attach', null=True, upload_to=common.models.rename_attachment, verbose_name='Attachment')),
|
||||
('link', InvenTree.fields.InvenTreeURLField(blank=True, help_text='Link to external URL', null=True, verbose_name='Link')),
|
||||
('comment', models.CharField(blank=True, help_text='Attachment comment', max_length=250, verbose_name='Comment')),
|
||||
('upload_date', models.DateField(auto_now_add=True, help_text='Date the file was uploaded', null=True, verbose_name='Upload date')),
|
||||
('file_size', models.PositiveIntegerField(default=0, help_text='File size in bytes', verbose_name='File size')),
|
||||
('model_type', models.CharField(help_text='Target model type for this image', max_length=100, validators=[common.validators.validate_attachment_model_type])),
|
||||
('upload_user', models.ForeignKey(blank=True, help_text='User', null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='User')),
|
||||
('metadata', models.JSONField(blank=True, help_text='JSON metadata field, for use by external plugins', null=True, verbose_name='Plugin Metadata')),
|
||||
('tags', taggit.managers.TaggableManager(blank=True, help_text='A comma-separated list of tags.', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='Tags'))
|
||||
],
|
||||
bases=(InvenTree.models.PluginValidationMixin, models.Model),
|
||||
options={
|
||||
'verbose_name': 'Attachment',
|
||||
}
|
||||
),
|
||||
]
|
||||
@@ -1,122 +0,0 @@
|
||||
# Generated by Django 4.2.12 on 2024-06-08 12:38
|
||||
|
||||
from django.db import migrations
|
||||
from django.core.files.storage import default_storage
|
||||
|
||||
|
||||
def get_legacy_models():
|
||||
"""Return a set of legacy attachment models."""
|
||||
|
||||
# Legacy attachment types to convert:
|
||||
# app_label, table name, target model, model ref
|
||||
return [
|
||||
('build', 'BuildOrderAttachment', 'build', 'build'),
|
||||
('company', 'CompanyAttachment', 'company', 'company'),
|
||||
('company', 'ManufacturerPartAttachment', 'manufacturerpart', 'manufacturer_part'),
|
||||
('order', 'PurchaseOrderAttachment', 'purchaseorder', 'order'),
|
||||
('order', 'SalesOrderAttachment', 'salesorder', 'order'),
|
||||
('order', 'ReturnOrderAttachment', 'returnorder', 'order'),
|
||||
('part', 'PartAttachment', 'part', 'part'),
|
||||
('stock', 'StockItemAttachment', 'stockitem', 'stock_item')
|
||||
]
|
||||
|
||||
|
||||
def update_attachments(apps, schema_editor):
|
||||
"""Migrate any existing attachment models to the new attachment table."""
|
||||
|
||||
Attachment = apps.get_model('common', 'attachment')
|
||||
|
||||
N = 0
|
||||
|
||||
for app, model, target_model, model_ref in get_legacy_models():
|
||||
LegacyAttachmentModel = apps.get_model(app, model)
|
||||
|
||||
if LegacyAttachmentModel.objects.count() == 0:
|
||||
continue
|
||||
|
||||
to_create = []
|
||||
|
||||
for attachment in LegacyAttachmentModel.objects.all():
|
||||
|
||||
# Find the size of the file (if exists)
|
||||
if attachment.attachment and default_storage.exists(attachment.attachment.name):
|
||||
try:
|
||||
file_size = default_storage.size(attachment.attachment.name)
|
||||
except NotImplementedError:
|
||||
file_size = 0
|
||||
else:
|
||||
file_size = 0
|
||||
|
||||
to_create.append(
|
||||
Attachment(
|
||||
model_type=target_model,
|
||||
model_id=getattr(attachment, model_ref).pk,
|
||||
attachment=attachment.attachment,
|
||||
link=attachment.link,
|
||||
comment=attachment.comment,
|
||||
upload_date=attachment.upload_date,
|
||||
upload_user=attachment.user,
|
||||
file_size=file_size
|
||||
)
|
||||
)
|
||||
|
||||
if len(to_create) > 0:
|
||||
print(f"Migrating {len(to_create)} attachments for the legacy '{model}' model.")
|
||||
Attachment.objects.bulk_create(to_create)
|
||||
|
||||
N += len(to_create)
|
||||
|
||||
# Check the correct number of Attachment objects has been created
|
||||
assert(N == Attachment.objects.count())
|
||||
|
||||
|
||||
def reverse_attachments(apps, schema_editor):
|
||||
"""Reverse data migration, and map new Attachment model back to legacy models."""
|
||||
|
||||
Attachment = apps.get_model('common', 'attachment')
|
||||
|
||||
N = 0
|
||||
|
||||
for app, model, target_model, model_ref in get_legacy_models():
|
||||
LegacyAttachmentModel = apps.get_model(app, model)
|
||||
|
||||
to_create = []
|
||||
|
||||
for attachment in Attachment.objects.filter(model_type=target_model):
|
||||
|
||||
TargetModel = apps.get_model(app, target_model)
|
||||
|
||||
data = {
|
||||
'attachment': attachment.attachment,
|
||||
'link': attachment.link,
|
||||
'comment': attachment.comment,
|
||||
'upload_date': attachment.upload_date,
|
||||
'user': attachment.upload_user,
|
||||
model_ref: TargetModel.objects.get(pk=attachment.model_id)
|
||||
}
|
||||
|
||||
to_create.append(LegacyAttachmentModel(**data))
|
||||
|
||||
if len(to_create) > 0:
|
||||
print(f"Reversing {len(to_create)} attachments for the legacy '{model}' model.")
|
||||
LegacyAttachmentModel.objects.bulk_create(to_create)
|
||||
|
||||
N += len(to_create)
|
||||
|
||||
# Check the correct number of LegacyAttachmentModel objects has been created
|
||||
assert(N == Attachment.objects.count())
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('build', '0050_auto_20240508_0138'),
|
||||
('common', '0025_attachment'),
|
||||
('company', '0069_company_active'),
|
||||
('order', '0099_alter_salesorder_status'),
|
||||
('part', '0123_parttesttemplate_choices'),
|
||||
('stock', '0110_alter_stockitemtestresult_finished_datetime_and_more')
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(update_attachments, reverse_code=reverse_attachments),
|
||||
]
|
||||
@@ -1,18 +0,0 @@
|
||||
# Generated by Django 4.2.12 on 2024-07-04 10:32
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('common', '0026_auto_20240608_1238'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='customunit',
|
||||
name='symbol',
|
||||
field=models.CharField(blank=True, help_text='Optional unit symbol', max_length=10, verbose_name='Symbol'),
|
||||
),
|
||||
]
|
||||
@@ -1,39 +0,0 @@
|
||||
# Generated by Django 4.2.12 on 2024-07-04 10:23
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
def migrate_userthemes(apps, schema_editor):
|
||||
"""Mgrate text-based user references to ForeignKey references."""
|
||||
ColorTheme = apps.get_model("common", "ColorTheme")
|
||||
User = apps.get_model(settings.AUTH_USER_MODEL)
|
||||
|
||||
for theme in ColorTheme.objects.all():
|
||||
try:
|
||||
theme.user_obj = User.objects.get(username=theme.user)
|
||||
theme.save()
|
||||
except User.DoesNotExist:
|
||||
pass
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
("common", "0027_alter_customunit_symbol"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="colortheme",
|
||||
name="user_obj",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
migrations.RunPython(migrate_userthemes, migrations.RunPython.noop),
|
||||
]
|
||||
@@ -1,97 +0,0 @@
|
||||
# Generated by Django 4.2.14 on 2024-08-07 22:40
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
from common.models import state_color_mappings
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("contenttypes", "0002_remove_content_type_name"),
|
||||
("common", "0028_colortheme_user_obj"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="InvenTreeCustomUserStateModel",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"key",
|
||||
models.IntegerField(
|
||||
help_text="Value that will be saved in the models database",
|
||||
verbose_name="Key",
|
||||
),
|
||||
),
|
||||
(
|
||||
"name",
|
||||
models.CharField(
|
||||
help_text="Name of the state",
|
||||
max_length=250,
|
||||
verbose_name="Name",
|
||||
),
|
||||
),
|
||||
(
|
||||
"label",
|
||||
models.CharField(
|
||||
help_text="Label that will be displayed in the frontend",
|
||||
max_length=250,
|
||||
verbose_name="Label",
|
||||
),
|
||||
),
|
||||
(
|
||||
"color",
|
||||
models.CharField(
|
||||
choices=state_color_mappings(),
|
||||
default="secondary",
|
||||
help_text="Color that will be displayed in the frontend",
|
||||
max_length=10,
|
||||
verbose_name="Color",
|
||||
),
|
||||
),
|
||||
(
|
||||
"logical_key",
|
||||
models.IntegerField(
|
||||
help_text="State logical key that is equal to this custom state in business logic",
|
||||
verbose_name="Logical Key",
|
||||
),
|
||||
),
|
||||
(
|
||||
"reference_status",
|
||||
models.CharField(
|
||||
help_text="Status set that is extended with this custom state",
|
||||
max_length=250,
|
||||
verbose_name="Reference Status Set",
|
||||
),
|
||||
),
|
||||
(
|
||||
"model",
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
help_text="Model this state is associated with",
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
to="contenttypes.contenttype",
|
||||
verbose_name="Model",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "Custom State",
|
||||
"verbose_name_plural": "Custom States",
|
||||
"unique_together": {
|
||||
("model", "reference_status", "key", "logical_key")
|
||||
},
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -1,34 +0,0 @@
|
||||
# Generated by Django 4.2.15 on 2024-09-21 06:05
|
||||
|
||||
import InvenTree.models
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('common', '0029_inventreecustomuserstatemodel'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='BarcodeScanResult',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('data', models.CharField(help_text='Barcode data', max_length=250, verbose_name='Data')),
|
||||
('timestamp', models.DateTimeField(auto_now_add=True, help_text='Date and time of the barcode scan', verbose_name='Timestamp')),
|
||||
('endpoint', models.CharField(blank=True, help_text='URL endpoint which processed the barcode', max_length=250, null=True, verbose_name='Path')),
|
||||
('context', models.JSONField(blank=True, help_text='Context data for the barcode scan', max_length=1000, null=True, verbose_name='Context')),
|
||||
('response', models.JSONField(blank=True, help_text='Response data from the barcode scan', max_length=1000, null=True, verbose_name='Response')),
|
||||
('result', models.BooleanField(default=False, help_text='Was the barcode scan successful?', verbose_name='Result')),
|
||||
('user', models.ForeignKey(blank=True, help_text='User who scanned the barcode', null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='User')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Barcode Scan',
|
||||
},
|
||||
bases=(InvenTree.models.PluginValidationMixin, models.Model),
|
||||
),
|
||||
]
|
||||
@@ -1,39 +0,0 @@
|
||||
# Generated by Django 4.2.16 on 2024-10-26 00:24
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations
|
||||
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger('inventree')
|
||||
|
||||
|
||||
def update_news_feed_urls(apps, schema_editor):
|
||||
"""Update and validate the news feed URLs."""
|
||||
|
||||
from common.models import NewsFeedEntry
|
||||
|
||||
n = 0
|
||||
|
||||
for entry in NewsFeedEntry.objects.all():
|
||||
if entry.link and entry.link.startswith('/'):
|
||||
entry.link = settings.INVENTREE_BASE_URL + entry.link
|
||||
entry.save()
|
||||
n += 1
|
||||
|
||||
if n > 0:
|
||||
logger.info("Updated link for %s NewsFeedEntry objects", n)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('common', '0030_barcodescanresult'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(
|
||||
update_news_feed_urls,
|
||||
reverse_code=migrations.RunPython.noop
|
||||
)
|
||||
]
|
||||
-191
@@ -1,191 +0,0 @@
|
||||
# Generated by Django 4.2.16 on 2024-11-24 12:41
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
import InvenTree.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('plugin', '0009_alter_pluginconfig_key'),
|
||||
('common', '0031_auto_20241026_0024'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='SelectionList',
|
||||
fields=[
|
||||
(
|
||||
'id',
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name='ID',
|
||||
),
|
||||
),
|
||||
(
|
||||
'metadata',
|
||||
models.JSONField(
|
||||
blank=True,
|
||||
help_text='JSON metadata field, for use by external plugins',
|
||||
null=True,
|
||||
verbose_name='Plugin Metadata',
|
||||
),
|
||||
),
|
||||
(
|
||||
'name',
|
||||
models.CharField(
|
||||
help_text='Name of the selection list',
|
||||
max_length=100,
|
||||
unique=True,
|
||||
verbose_name='Name',
|
||||
),
|
||||
),
|
||||
(
|
||||
'description',
|
||||
models.CharField(
|
||||
blank=True,
|
||||
help_text='Description of the selection list',
|
||||
max_length=250,
|
||||
verbose_name='Description',
|
||||
),
|
||||
),
|
||||
(
|
||||
'locked',
|
||||
models.BooleanField(
|
||||
default=False,
|
||||
help_text='Is this selection list locked?',
|
||||
verbose_name='Locked',
|
||||
),
|
||||
),
|
||||
(
|
||||
'active',
|
||||
models.BooleanField(
|
||||
default=True,
|
||||
help_text='Can this selection list be used?',
|
||||
verbose_name='Active',
|
||||
),
|
||||
),
|
||||
(
|
||||
'source_string',
|
||||
models.CharField(
|
||||
blank=True,
|
||||
help_text='Optional string identifying the source used for this list',
|
||||
max_length=1000,
|
||||
verbose_name='Source String',
|
||||
),
|
||||
),
|
||||
(
|
||||
'created',
|
||||
models.DateTimeField(
|
||||
auto_now_add=True,
|
||||
help_text='Date and time that the selection list was created',
|
||||
verbose_name='Created',
|
||||
),
|
||||
),
|
||||
(
|
||||
'last_updated',
|
||||
models.DateTimeField(
|
||||
auto_now=True,
|
||||
help_text='Date and time that the selection list was last updated',
|
||||
verbose_name='Last Updated',
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Selection List',
|
||||
'verbose_name_plural': 'Selection Lists',
|
||||
},
|
||||
bases=(InvenTree.models.PluginValidationMixin, models.Model),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='SelectionListEntry',
|
||||
fields=[
|
||||
(
|
||||
'id',
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name='ID',
|
||||
),
|
||||
),
|
||||
(
|
||||
'value',
|
||||
models.CharField(
|
||||
help_text='Value of the selection list entry',
|
||||
max_length=255,
|
||||
verbose_name='Value',
|
||||
),
|
||||
),
|
||||
(
|
||||
'label',
|
||||
models.CharField(
|
||||
help_text='Label for the selection list entry',
|
||||
max_length=255,
|
||||
verbose_name='Label',
|
||||
),
|
||||
),
|
||||
(
|
||||
'description',
|
||||
models.CharField(
|
||||
blank=True,
|
||||
help_text='Description of the selection list entry',
|
||||
max_length=250,
|
||||
verbose_name='Description',
|
||||
),
|
||||
),
|
||||
(
|
||||
'active',
|
||||
models.BooleanField(
|
||||
default=True,
|
||||
help_text='Is this selection list entry active?',
|
||||
verbose_name='Active',
|
||||
),
|
||||
),
|
||||
(
|
||||
'list',
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
help_text='Selection list to which this entry belongs',
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name='entries',
|
||||
to='common.selectionlist',
|
||||
verbose_name='Selection List',
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Selection List Entry',
|
||||
'verbose_name_plural': 'Selection List Entries',
|
||||
'unique_together': {('list', 'value')},
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='selectionlist',
|
||||
name='default',
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
help_text='Default entry for this selection list',
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
to='common.selectionlistentry',
|
||||
verbose_name='Default Entry',
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='selectionlist',
|
||||
name='source_plugin',
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
help_text='Plugin which provides the selection list',
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
to='plugin.pluginconfig',
|
||||
verbose_name='Source Plugin',
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -1,16 +0,0 @@
|
||||
# Generated by Django 4.2.17 on 2024-12-08 23:47
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("common", "0032_selectionlist_selectionlistentry_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.DeleteModel(
|
||||
name="ColorTheme",
|
||||
),
|
||||
]
|
||||
-32
@@ -1,32 +0,0 @@
|
||||
# Generated by Django 4.2.17 on 2024-12-27 09:15
|
||||
|
||||
import common.validators
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('common', '0033_delete_colortheme'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterUniqueTogether(
|
||||
name='inventreecustomuserstatemodel',
|
||||
unique_together=set(),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='inventreecustomuserstatemodel',
|
||||
name='key',
|
||||
field=models.IntegerField(help_text='Numerical value that will be saved in the models database', verbose_name='Value'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='inventreecustomuserstatemodel',
|
||||
name='name',
|
||||
field=models.CharField(help_text='Name of the state', max_length=250, validators=[common.validators.validate_uppercase, common.validators.validate_variable_string], verbose_name='Name'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='inventreecustomuserstatemodel',
|
||||
unique_together={('reference_status', 'name'), ('reference_status', 'key')},
|
||||
),
|
||||
]
|
||||
@@ -1,20 +0,0 @@
|
||||
# Generated by Django 4.2.19 on 2025-02-21 15:13
|
||||
|
||||
import InvenTree.fields
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("common", "0034_alter_inventreecustomuserstatemodel_unique_together_and_more"),
|
||||
]
|
||||
|
||||
operations = [migrations.AlterField(
|
||||
model_name='attachment',
|
||||
name='link',
|
||||
field=models.TextField(
|
||||
null=True, blank=True
|
||||
) # Temporary change to force new ALTER COLUMN operation in the next migration
|
||||
),
|
||||
]
|
||||
@@ -1,25 +0,0 @@
|
||||
# Generated by Django 4.2.19 on 2025-02-21 15:15
|
||||
|
||||
import InvenTree.fields
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("common", "0035_auto_20250221_1513"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="attachment",
|
||||
name="link",
|
||||
field=InvenTree.fields.InvenTreeURLField(
|
||||
blank=True,
|
||||
help_text="Link to external URL",
|
||||
max_length=2000,
|
||||
null=True,
|
||||
verbose_name="Link",
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -1,58 +0,0 @@
|
||||
# Generated by Django 4.2.19 on 2025-03-11 08:22
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
("common", "0036_alter_attachment_link"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="DataOutput",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("created", models.DateField(auto_now_add=True)),
|
||||
("total", models.PositiveIntegerField(default=1)),
|
||||
("progress", models.PositiveIntegerField(default=0)),
|
||||
("complete", models.BooleanField(default=False)),
|
||||
(
|
||||
"output_type",
|
||||
models.CharField(blank=True, max_length=100, null=True),
|
||||
),
|
||||
(
|
||||
"template_name",
|
||||
models.CharField(blank=True, max_length=100, null=True),
|
||||
),
|
||||
("plugin", models.CharField(blank=True, max_length=100, null=True)),
|
||||
(
|
||||
"output",
|
||||
models.FileField(blank=True, null=True, upload_to="data_output"),
|
||||
),
|
||||
("errors", models.JSONField(blank=True, null=True)),
|
||||
(
|
||||
"user",
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="+",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
||||
@@ -1,24 +0,0 @@
|
||||
# Generated by Django 4.2.20 on 2025-04-07 20:53
|
||||
|
||||
import common.validators
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("common", "0037_dataoutput"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="attachment",
|
||||
name="model_type",
|
||||
field=models.CharField(
|
||||
help_text="Target model type for image",
|
||||
max_length=100,
|
||||
validators=[common.validators.validate_attachment_model_type],
|
||||
verbose_name="Model type",
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -1,61 +0,0 @@
|
||||
# Generated by Django 4.2.20 on 2025-05-07 00:17
|
||||
|
||||
import InvenTree.models
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('common', '0038_alter_attachment_model_type'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='EmailThread',
|
||||
fields=[
|
||||
('metadata', models.JSONField(blank=True, help_text='JSON metadata field, for use by external plugins', null=True, verbose_name='Plugin Metadata')),
|
||||
('key', models.CharField(blank=True, help_text='Unique key for this thread (used to identify the thread)', max_length=250, null=True, verbose_name='Key')),
|
||||
('global_id', models.UUIDField(default=uuid.uuid4, editable=False, help_text='Unique identifier for this thread', primary_key=True, serialize=False, verbose_name='Global ID')),
|
||||
('started_internal', models.BooleanField(default=False, help_text='Was this thread started internally?', verbose_name='Started Internal')),
|
||||
('created', models.DateTimeField(auto_now_add=True, help_text='Date and time that the thread was created', verbose_name='Created')),
|
||||
('updated', models.DateTimeField(auto_now=True, help_text='Date and time that the thread was last updated', verbose_name='Updated')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Email Thread',
|
||||
'verbose_name_plural': 'Email Threads',
|
||||
'ordering': ['-updated'],
|
||||
'unique_together': {('key', 'global_id')},
|
||||
},
|
||||
bases=(InvenTree.models.PluginValidationMixin, models.Model),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='EmailMessage',
|
||||
fields=[
|
||||
('global_id', models.UUIDField(default=uuid.uuid4, editable=False, help_text='Unique identifier for this message', primary_key=True, serialize=False, unique=True, verbose_name='Global ID')),
|
||||
('message_id_key', models.CharField(blank=True, help_text='Identifier for this message (might be supplied by external system)', max_length=250, null=True, verbose_name='Message ID')),
|
||||
('thread_id_key', models.CharField(blank=True, help_text='Identifier for this message thread (might be supplied by external system)', max_length=250, null=True, verbose_name='Thread ID')),
|
||||
('subject', models.CharField(max_length=250)),
|
||||
('body', models.TextField()),
|
||||
('to', models.EmailField(max_length=254)),
|
||||
('sender', models.EmailField(max_length=254)),
|
||||
('status', models.CharField(blank=True, choices=[('A', 'Announced'), ('S', 'Sent'), ('F', 'Failed'), ('D', 'Delivered'), ('R', 'Read'), ('C', 'Confirmed')], max_length=50, null=True)),
|
||||
('timestamp', models.DateTimeField(auto_now_add=True)),
|
||||
('headers', models.JSONField(blank=True, null=True)),
|
||||
('full_message', models.TextField(blank=True, null=True)),
|
||||
('direction', models.CharField(blank=True, choices=[('I', 'Inbound'), ('O', 'Outbound')], max_length=50, null=True)),
|
||||
('priority', models.IntegerField(choices=[(0, 'None'), (1, 'Very High'), (2, 'High'), (3, 'Normal'), (4, 'Low'), (5, 'Very Low')], verbose_name='Priority')),
|
||||
('delivery_options', models.JSONField(blank=True, null=True)),
|
||||
('error_code', models.CharField(blank=True, max_length=50, null=True)),
|
||||
('error_message', models.TextField(blank=True, null=True)),
|
||||
('error_timestamp', models.DateTimeField(blank=True, null=True)),
|
||||
('thread', models.ForeignKey(blank=True, help_text='Linked thread for this message', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='messages', to='common.emailthread', verbose_name='Thread')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Email Message',
|
||||
'verbose_name_plural': 'Email Messages',
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import importlib
|
||||
import io
|
||||
import os
|
||||
from unittest import mock
|
||||
|
||||
from django.core.files.base import ContentFile
|
||||
@@ -42,207 +41,6 @@ def get_historical_model(state, app: str, model: str):
|
||||
return model_class
|
||||
|
||||
|
||||
def get_legacy_models():
|
||||
"""Return a set of legacy attachment models."""
|
||||
# Legacy attachment types to convert:
|
||||
# app_label, table name, target model, model ref
|
||||
return [
|
||||
('build', 'BuildOrderAttachment', 'build', 'build'),
|
||||
('company', 'CompanyAttachment', 'company', 'company'),
|
||||
(
|
||||
'company',
|
||||
'ManufacturerPartAttachment',
|
||||
'manufacturerpart',
|
||||
'manufacturer_part',
|
||||
),
|
||||
('order', 'PurchaseOrderAttachment', 'purchaseorder', 'order'),
|
||||
('order', 'SalesOrderAttachment', 'salesorder', 'order'),
|
||||
('order', 'ReturnOrderAttachment', 'returnorder', 'order'),
|
||||
('part', 'PartAttachment', 'part', 'part'),
|
||||
('stock', 'StockItemAttachment', 'stockitem', 'stock_item'),
|
||||
]
|
||||
|
||||
|
||||
def generate_attachment():
|
||||
"""Generate a file attachment object for test upload."""
|
||||
file_object = io.StringIO('Some dummy data')
|
||||
file_object.seek(0)
|
||||
|
||||
return ContentFile(file_object.getvalue(), 'test.txt')
|
||||
|
||||
|
||||
class TestForwardMigrations(MigratorTestCase):
|
||||
"""Test entire schema migration sequence for the common app."""
|
||||
|
||||
migrate_from = ('common', '0024_notesimage_model_id_notesimage_model_type')
|
||||
migrate_to = ('common', '0039_emailthread_emailmessage')
|
||||
|
||||
def prepare(self):
|
||||
"""Create initial data.
|
||||
|
||||
Legacy attachment model types are:
|
||||
- BuildOrderAttachment
|
||||
- CompanyAttachment
|
||||
- ManufacturerPartAttachment
|
||||
- PurchaseOrderAttachment
|
||||
- SalesOrderAttachment
|
||||
- ReturnOrderAttachment
|
||||
- PartAttachment
|
||||
- StockItemAttachment
|
||||
"""
|
||||
# Dummy MPPT data
|
||||
tree = {'tree_id': 0, 'level': 0, 'lft': 0, 'rght': 0}
|
||||
|
||||
# BuildOrderAttachment
|
||||
Part = self.old_state.apps.get_model('part', 'Part')
|
||||
Build = self.old_state.apps.get_model('build', 'Build')
|
||||
|
||||
part = Part.objects.create(
|
||||
name='Test Part',
|
||||
description='Test Part Description',
|
||||
active=True,
|
||||
assembly=True,
|
||||
purchaseable=True,
|
||||
**tree,
|
||||
)
|
||||
|
||||
build = Build.objects.create(part=part, title='Test Build', quantity=10, **tree)
|
||||
|
||||
PartAttachment = self.old_state.apps.get_model('part', 'PartAttachment')
|
||||
PartAttachment.objects.create(
|
||||
part=part, attachment=generate_attachment(), comment='Test file attachment'
|
||||
)
|
||||
PartAttachment.objects.create(
|
||||
part=part, link='http://example.com', comment='Test link attachment'
|
||||
)
|
||||
self.assertEqual(PartAttachment.objects.count(), 2)
|
||||
|
||||
BuildOrderAttachment = self.old_state.apps.get_model(
|
||||
'build', 'BuildOrderAttachment'
|
||||
)
|
||||
BuildOrderAttachment.objects.create(
|
||||
build=build, link='http://example.com', comment='Test comment'
|
||||
)
|
||||
BuildOrderAttachment.objects.create(
|
||||
build=build, attachment=generate_attachment(), comment='a test file'
|
||||
)
|
||||
self.assertEqual(BuildOrderAttachment.objects.count(), 2)
|
||||
|
||||
StockItem = self.old_state.apps.get_model('stock', 'StockItem')
|
||||
StockItemAttachment = self.old_state.apps.get_model(
|
||||
'stock', 'StockItemAttachment'
|
||||
)
|
||||
|
||||
item = StockItem.objects.create(part=part, quantity=10, **tree)
|
||||
|
||||
StockItemAttachment.objects.create(
|
||||
stock_item=item,
|
||||
attachment=generate_attachment(),
|
||||
comment='Test file attachment',
|
||||
)
|
||||
StockItemAttachment.objects.create(
|
||||
stock_item=item, link='http://example.com', comment='Test link attachment'
|
||||
)
|
||||
self.assertEqual(StockItemAttachment.objects.count(), 2)
|
||||
|
||||
Company = self.old_state.apps.get_model('company', 'Company')
|
||||
CompanyAttachment = self.old_state.apps.get_model(
|
||||
'company', 'CompanyAttachment'
|
||||
)
|
||||
|
||||
company = Company.objects.create(
|
||||
name='Test Company',
|
||||
description='Test Company Description',
|
||||
is_customer=True,
|
||||
is_manufacturer=True,
|
||||
is_supplier=True,
|
||||
)
|
||||
|
||||
CompanyAttachment.objects.create(
|
||||
company=company,
|
||||
attachment=generate_attachment(),
|
||||
comment='Test file attachment',
|
||||
)
|
||||
CompanyAttachment.objects.create(
|
||||
company=company, link='http://example.com', comment='Test link attachment'
|
||||
)
|
||||
self.assertEqual(CompanyAttachment.objects.count(), 2)
|
||||
|
||||
PurchaseOrder = self.old_state.apps.get_model('order', 'PurchaseOrder')
|
||||
PurchaseOrderAttachment = self.old_state.apps.get_model(
|
||||
'order', 'PurchaseOrderAttachment'
|
||||
)
|
||||
|
||||
po = PurchaseOrder.objects.create(
|
||||
reference='PO-12345',
|
||||
supplier=company,
|
||||
description='Test Purchase Order Description',
|
||||
)
|
||||
|
||||
PurchaseOrderAttachment.objects.create(
|
||||
order=po, attachment=generate_attachment(), comment='Test file attachment'
|
||||
)
|
||||
PurchaseOrderAttachment.objects.create(
|
||||
order=po, link='http://example.com', comment='Test link attachment'
|
||||
)
|
||||
self.assertEqual(PurchaseOrderAttachment.objects.count(), 2)
|
||||
|
||||
SalesOrder = self.old_state.apps.get_model('order', 'SalesOrder')
|
||||
SalesOrderAttachment = self.old_state.apps.get_model(
|
||||
'order', 'SalesOrderAttachment'
|
||||
)
|
||||
|
||||
so = SalesOrder.objects.create(
|
||||
reference='SO-12345',
|
||||
customer=company,
|
||||
description='Test Sales Order Description',
|
||||
)
|
||||
|
||||
SalesOrderAttachment.objects.create(
|
||||
order=so, attachment=generate_attachment(), comment='Test file attachment'
|
||||
)
|
||||
SalesOrderAttachment.objects.create(
|
||||
order=so, link='http://example.com', comment='Test link attachment'
|
||||
)
|
||||
self.assertEqual(SalesOrderAttachment.objects.count(), 2)
|
||||
|
||||
ReturnOrder = self.old_state.apps.get_model('order', 'ReturnOrder')
|
||||
ReturnOrderAttachment = self.old_state.apps.get_model(
|
||||
'order', 'ReturnOrderAttachment'
|
||||
)
|
||||
|
||||
ro = ReturnOrder.objects.create(
|
||||
reference='RO-12345',
|
||||
customer=company,
|
||||
description='Test Return Order Description',
|
||||
)
|
||||
|
||||
ReturnOrderAttachment.objects.create(
|
||||
order=ro, attachment=generate_attachment(), comment='Test file attachment'
|
||||
)
|
||||
ReturnOrderAttachment.objects.create(
|
||||
order=ro, link='http://example.com', comment='Test link attachment'
|
||||
)
|
||||
self.assertEqual(ReturnOrderAttachment.objects.count(), 2)
|
||||
|
||||
def test_items_exist(self):
|
||||
"""Test to ensure that the attachments are correctly migrated."""
|
||||
Attachment = self.new_state.apps.get_model('common', 'Attachment')
|
||||
|
||||
self.assertEqual(Attachment.objects.count(), 14)
|
||||
|
||||
for model in [
|
||||
'build',
|
||||
'company',
|
||||
'purchaseorder',
|
||||
'returnorder',
|
||||
'salesorder',
|
||||
'part',
|
||||
'stockitem',
|
||||
]:
|
||||
self.assertEqual(Attachment.objects.filter(model_type=model).count(), 2)
|
||||
|
||||
|
||||
class TestAttachmentThumbnailMigration(MigratorTestCase):
|
||||
"""Test that migration 0043 correctly populates is_image and generates thumbnails for existing attachments."""
|
||||
|
||||
@@ -684,55 +482,3 @@ class TestNoteMigrationBatching(MigratorTestCase):
|
||||
)
|
||||
image = NotesImage.objects.get(pk=self.linked_image.pk)
|
||||
self.assertEqual(image.note.pk, expected_note.pk)
|
||||
|
||||
|
||||
def prep_currency_migration(self, vals: str):
|
||||
"""Prepare the environment for the currency migration tests."""
|
||||
# Set keys
|
||||
os.environ['INVENTREE_CURRENCIES'] = vals
|
||||
|
||||
# And setting
|
||||
InvenTreeSetting = self.old_state.apps.get_model('common', 'InvenTreeSetting')
|
||||
|
||||
setting = InvenTreeSetting(key='CURRENCY_CODES', value='123')
|
||||
setting.save()
|
||||
|
||||
|
||||
class TestCurrencyMigration(MigratorTestCase):
|
||||
"""Test currency migration."""
|
||||
|
||||
migrate_from = ('common', '0022_projectcode_responsible')
|
||||
migrate_to = ('common', '0023_auto_20240602_1332')
|
||||
|
||||
def prepare(self):
|
||||
"""Prepare the environment for the migration test."""
|
||||
prep_currency_migration(self, 'USD,EUR,GBP')
|
||||
|
||||
def test_currency_migration(self):
|
||||
"""Test that the currency migration works."""
|
||||
InvenTreeSetting = self.old_state.apps.get_model('common', 'InvenTreeSetting')
|
||||
setting = InvenTreeSetting.objects.filter(key='CURRENCY_CODES').first()
|
||||
|
||||
codes = str(setting.value).split(',')
|
||||
|
||||
self.assertEqual(len(codes), 3)
|
||||
|
||||
for code in ['USD', 'EUR', 'GBP']:
|
||||
self.assertIn(code, codes)
|
||||
|
||||
|
||||
class TestCurrencyMigrationNo(MigratorTestCase):
|
||||
"""Test currency migration."""
|
||||
|
||||
migrate_from = ('common', '0022_projectcode_responsible')
|
||||
migrate_to = ('common', '0023_auto_20240602_1332')
|
||||
|
||||
def prepare(self):
|
||||
"""Prepare the environment for the migration test."""
|
||||
prep_currency_migration(self, 'YYY,ZZZ')
|
||||
|
||||
def test_currency_migration(self):
|
||||
"""Test that no currency migration occurs if wrong currencies are set."""
|
||||
InvenTreeSetting = self.old_state.apps.get_model('common', 'InvenTreeSetting')
|
||||
setting = InvenTreeSetting.objects.filter(key='CURRENCY_CODES').first()
|
||||
self.assertEqual(setting.value, '123')
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user