mirror of
https://github.com/inventree/InvenTree.git
synced 2026-09-11 06:59:04 +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:
@@ -50,7 +50,7 @@ jobs:
|
||||
contains(github.event.pull_request.labels.*.name, 'full-run')
|
||||
|
||||
sqlite:
|
||||
name: Tests - Migrations [SQLite]
|
||||
name: Migrations [SQLite]
|
||||
runs-on: ubuntu-latest
|
||||
needs: paths-filter
|
||||
if: ${{ (needs.paths-filter.outputs.force == 'true') || (github.ref == 'refs/heads/master' && needs.paths-filter.outputs.migrations == 'true') }}
|
||||
@@ -83,7 +83,7 @@ jobs:
|
||||
flags: migrations-sqlite
|
||||
|
||||
mysql:
|
||||
name: Tests - Migrations [MySQL]
|
||||
name: Migrations [MySQL]
|
||||
runs-on: ubuntu-24.04
|
||||
needs: paths-filter
|
||||
if: ${{ (needs.paths-filter.outputs.force == 'true') || (github.ref == 'refs/heads/master' && needs.paths-filter.outputs.migrations == 'true') }}
|
||||
@@ -134,7 +134,7 @@ jobs:
|
||||
flags: migrations-mysql
|
||||
|
||||
postgresql:
|
||||
name: Tests - Migrations [PostgreSQL]
|
||||
name: Migrations [PostgreSQL]
|
||||
runs-on: ubuntu-latest
|
||||
needs: paths-filter
|
||||
if: ${{ (needs.paths-filter.outputs.force == 'true') || (github.ref == 'refs/heads/master' && needs.paths-filter.outputs.migrations == 'true') }}
|
||||
@@ -181,7 +181,7 @@ jobs:
|
||||
flags: migrations-postgresql
|
||||
|
||||
migrations-checks:
|
||||
name: Tests - Database Migrations
|
||||
name: Database Migrations
|
||||
runs-on: ubuntu-latest
|
||||
needs: paths-filter
|
||||
if: ${{ (needs.paths-filter.outputs.force == 'true') || (github.ref == 'refs/heads/master' && needs.paths-filter.outputs.migrations == 'true') }}
|
||||
@@ -205,37 +205,45 @@ jobs:
|
||||
- name: Fetch Database
|
||||
run: git clone --depth 1 https://github.com/inventree/test-db ./test-db
|
||||
|
||||
- name: 0.10.0 Database
|
||||
- name: 1.0.0 Database
|
||||
run: |
|
||||
rm /home/runner/work/InvenTree/db.sqlite3
|
||||
cp test-db/stable_1.0.0.sqlite3 /home/runner/work/InvenTree/db.sqlite3
|
||||
chmod +rw /home/runner/work/InvenTree/db.sqlite3
|
||||
invoke migrate
|
||||
|
||||
- name: 1.5.0 Database
|
||||
run: |
|
||||
rm /home/runner/work/InvenTree/db.sqlite3
|
||||
cp test-db/stable_1.5.0.sqlite3 /home/runner/work/InvenTree/db.sqlite3
|
||||
chmod +rw /home/runner/work/InvenTree/db.sqlite3
|
||||
invoke migrate
|
||||
|
||||
- name: 0.10.0 Database (SHOULD FAIL)
|
||||
run: |
|
||||
rm /home/runner/work/InvenTree/db.sqlite3
|
||||
cp test-db/stable_0.10.0.sqlite3 /home/runner/work/InvenTree/db.sqlite3
|
||||
chmod +rw /home/runner/work/InvenTree/db.sqlite3
|
||||
invoke migrate
|
||||
|
||||
- name: 0.11.0 Database
|
||||
run: |
|
||||
rm /home/runner/work/InvenTree/db.sqlite3
|
||||
cp test-db/stable_0.11.0.sqlite3 /home/runner/work/InvenTree/db.sqlite3
|
||||
chmod +rw /home/runner/work/InvenTree/db.sqlite3
|
||||
invoke migrate
|
||||
set +e
|
||||
output=$(invoke migrate 2>&1)
|
||||
exit_code=$?
|
||||
set -e
|
||||
|
||||
- name: 0.13.5 Database
|
||||
run: |
|
||||
rm /home/runner/work/InvenTree/db.sqlite3
|
||||
cp test-db/stable_0.13.5.sqlite3 /home/runner/work/InvenTree/db.sqlite3
|
||||
chmod +rw /home/runner/work/InvenTree/db.sqlite3
|
||||
invoke migrate
|
||||
# Always show the actual output, so the reason for pass/fail is visible
|
||||
# in the job log rather than just a generic summary line
|
||||
echo "$output"
|
||||
|
||||
- name: 0.16.0 Database
|
||||
run: |
|
||||
rm /home/runner/work/InvenTree/db.sqlite3
|
||||
cp test-db/stable_0.16.0.sqlite3 /home/runner/work/InvenTree/db.sqlite3
|
||||
chmod +rw /home/runner/work/InvenTree/db.sqlite3
|
||||
invoke migrate
|
||||
if [ "$exit_code" -eq 0 ]; then
|
||||
echo "::error::Migration succeeded for 0.10.0 database, but this is expected to fail (squashed migrations no longer support this old baseline)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: 0.17.0 Database
|
||||
run: |
|
||||
rm /home/runner/work/InvenTree/db.sqlite3
|
||||
cp test-db/stable_0.17.0.sqlite3 /home/runner/work/InvenTree/db.sqlite3
|
||||
chmod +rw /home/runner/work/InvenTree/db.sqlite3
|
||||
invoke migrate
|
||||
# Confirm it failed for the *expected* reason (INVE-E19: stuck mid-way
|
||||
# through the pre-1.0.0 migration squash), not some unrelated crash
|
||||
if echo "$output" | grep -q "INVE-E19"; then
|
||||
echo "Migration failed as expected for 0.10.0 database (INVE-E19)"
|
||||
else
|
||||
echo "::error::Migration failed for 0.10.0 database, but not with the expected INVE-E19 error - this looks like an unrelated failure"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
- [#12830](https://github.com/inventree/InvenTree/pull/12830) squashes all database migrations prior to the 1.0.0 release. This means that any users who are updating from a version older than 1.0.0 must first update to the 1.0.0 release before updating to the current release.
|
||||
- [#11971](https://github.com/inventree/InvenTree/pull/11971) is a major refactor of how notes are handled. Notes are now stored in a separate database table (in line with how attachments are handled), and each model instance can have multiple notes associated with it. The `notes` field has been removed from the individual models (and their associated API endpoints), and notes are now accessed via the new `/api/note/` endpoint. Existing notes data (and any embedded images) are automatically migrated to the new notes table, with the markdown content converted to HTML. Any external client applications which read or write the `notes` field via the API will need to be updated to use the new endpoint.
|
||||
- [#12507](https://github.com/inventree/InvenTree/pull/12507) calling an invalid or repeated state transition now raises a ValidationError. Plugins implementing state transitions should evaluate the PR and adapt their usage of transitions to gain the new safeguards.
|
||||
- [#12672](https://github.com/inventree/InvenTree/pull/12672) renames the newly added `tags` filter from 1.4.0 (https://github.com/inventree/InvenTree/pull/12077) to `tag_name` to remove a nameclash.
|
||||
|
||||
@@ -107,10 +107,14 @@ For more information, refer to the installation guides:
|
||||
|
||||
- [Docker Installation](./start/docker_install.md#updating-inventree)
|
||||
- [Bare Metal Installation](./start/install.md#updating-inventree)
|
||||
- [Package Installer](./start/installer.md#updating-inventree)
|
||||
|
||||
!!! warning "Invoke Update"
|
||||
You must ensure that the `invoke update` command is performed *every time* you update InvenTree
|
||||
|
||||
!!! danger "Updating from Pre 1.0.0"
|
||||
If your installation is running a version of InvenTree older than `1.0.0`, you cannot update directly to the current release - see [Updating from Pre 1.0.0](./start/migrate.md#updating-from-pre-100) for the required intermediate step.
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
Before performing an update, check the release notes! Any *breaking changes* (changes which require user intervention) will be clearly noted.
|
||||
|
||||
@@ -116,6 +116,11 @@ An error occurred while rendering a component in the frontend. Typically this is
|
||||
|
||||
The InvenTree server failed to start, because the configured server URL does not include a top-level domain (TLD). A valid TLD is required for proper operation.
|
||||
|
||||
#### INVE-E19
|
||||
**Database stuck mid-way through pre-1.0.0 migration squash - Backend**
|
||||
|
||||
If your installation is currently running a version of InvenTree older than `1.0.0`, you must first update to the `1.0.0` release before updating further. See [Updating from Pre 1.0.0](../start/migrate.md#updating-from-pre-100) for the full upgrade procedure.
|
||||
|
||||
## Warning Codes
|
||||
|
||||
### INVE-W (InvenTree Warning)
|
||||
|
||||
@@ -118,6 +118,9 @@ This command launches the following containers:
|
||||
|
||||
To update your InvenTree installation to the latest version, follow these steps:
|
||||
|
||||
!!! danger "Updating from Pre 1.0.0"
|
||||
If your installation is running a version of InvenTree older than `1.0.0`, you cannot update directly to the current release. See [Updating from Pre 1.0.0](./migrate.md#updating-from-pre-100) for the required intermediate step.
|
||||
|
||||
### Stop Containers
|
||||
|
||||
Stop all running containers as below:
|
||||
|
||||
@@ -289,6 +289,9 @@ Administrators wishing to update InvenTree to the latest version should follow t
|
||||
!!! info "Update Database"
|
||||
It is advisable to [backup the InvenTree database](./backup.md) before performing these steps. The particular backup procedure may depend on your installation details.
|
||||
|
||||
!!! danger "Updating from Pre 1.0.0"
|
||||
If your installation is running a version of InvenTree older than `1.0.0`, you cannot update directly to the current release. See [Updating from Pre 1.0.0](./migrate.md#updating-from-pre-100) for the required intermediate step.
|
||||
|
||||
### Stop InvenTree Server
|
||||
|
||||
Ensure the InvenTree server is stopped. This will depend on the particulars of your database installation.
|
||||
|
||||
@@ -140,6 +140,9 @@ To change the data storage location, link the new location to `/opt/inventree/da
|
||||
|
||||
## Updating InvenTree
|
||||
|
||||
!!! danger "Updating from Pre 1.0.0"
|
||||
If your installation is running a version of InvenTree older than `1.0.0`, you cannot update directly to the current release. See [Updating from Pre 1.0.0](./migrate.md#updating-from-pre-100) for the required intermediate step.
|
||||
|
||||
To update InvenTree run the following command, which updates the InvenTree package to the latest version:
|
||||
|
||||
```bash
|
||||
|
||||
@@ -2,6 +2,24 @@
|
||||
title: Migrating Data
|
||||
---
|
||||
|
||||
## Updating from Pre 1.0.0
|
||||
|
||||
!!! danger "Required Stopover"
|
||||
As part of the 1.0.0 release cycle, InvenTree's database migration history was *squashed* - many individual migration files were consolidated into a smaller number of squashed migrations, and the original (now-redundant) migration files were subsequently removed from the codebase.
|
||||
|
||||
As a result, InvenTree cannot migrate a database directly from a version **older than 1.0.0** to the current release. Attempting to do so will cause `invoke update` (or `invoke migrate`) to fail.
|
||||
|
||||
If your installation is currently running a version of InvenTree older than `1.0.0`, you must first update to the `1.0.0` release, before updating to the current release.
|
||||
|
||||
### How to Update
|
||||
|
||||
1. Determine your current InvenTree version. If it is older than `1.0.0`, do not skip directly to the latest release.
|
||||
2. Follow the normal update procedure for your installation method - [Bare Metal](./install.md#updating-inventree), [Docker](./docker_install.md#updating-inventree), or [Package Installer](./installer.md#updating-inventree) - targeting a `1.0.0` release.
|
||||
3. Once the database has been successfully updated to `1.0.0`, repeat the update procedure again to bring the installation up to the current release.
|
||||
|
||||
!!! danger "Skipping Directly to Latest"
|
||||
Attempting to update directly from a pre-1.0.0 database to the current release, skipping the `1.0.0` stopover, is not supported and will fail.
|
||||
|
||||
## Migrating Data to a Different Database
|
||||
|
||||
In the case that data needs to be migrated from one database installation to another, the following procedure can be used to export data, initialize the new database, and re-import the data. The following instructions apply to bare-metal and docker installations, although the particular commands required may vary slightly in each case.
|
||||
@@ -100,6 +118,9 @@ Copy the entire directory tree from the original InvenTree installation to the n
|
||||
|
||||
If you are updating from an older version of InvenTree to a newer version, the migration steps outlined above *do not apply*.
|
||||
|
||||
!!! danger "Updating from Pre 1.0.0"
|
||||
If your existing installation is running a version older than `1.0.0`, you cannot update directly to the current release. See [Updating from Pre 1.0.0](#updating-from-pre-100) above for the required intermediate step.
|
||||
|
||||
An update from an old version to a new one requires not only that the database *schema* are updated, but the *data* held within the database must be updated in the correct sequence.
|
||||
|
||||
Follow the sequence of steps below to ensure that the database records are updated correctly.
|
||||
|
||||
@@ -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',
|
||||
),
|
||||
),
|
||||
]
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user